
Actor 这个词我在带新人入行游戏开发时几乎每次都要解释一遍。它看起来像什么高深的底层概念实际上你打开任何一款游戏里面那个能动的角色、你踩的地板、头顶的光源、触发剧情的气团全都是 Actor。说白了Actor 就是放进游戏世界里的一切东西的统称。今天这篇是第 3 天系列里我非常想好好写的一篇因为很多新手在学 Unreal EngineUE时第一个被卡住的名词就是它而一旦理解清楚 Actor 和它周围那一圈概念的关系后面看任何教程都会顺很多。这篇内容适合完全没接触过 UE 的人也适合从 Unity 等引擎转过来的朋友。文章里我会从最底层的概念讲起再带你走一遍在编辑器中创建第一个 Actor 的完整流程最后补几个我这些年见过最多人踩的坑。只讲理论不给操作的教学没什么用我尽量让每一步你都能自己复现出来。1. Actor 的本质场景里一切看得见摸得着的东西1.1 从层级关系看 Actor你的游戏世界不是一张平面图你在 UE 编辑器里打开一个关卡看到的是一片世界。这片世界不是一张静态图片它是由很多独立的物件叠加组成的。这些物件在引擎里统一叫 Actor。举个例子你在场景里放了一个方块引擎里其实发生了几件事场景里多了一个 Actor这个 Actor 内部挂着一个方块形状的静态网格体组件Static Mesh Component组件里塞了一份方块模型资源模型上还贴着一份材质。对玩家来说他看到的是一个方块但对引擎来说这个方块是一个带组件的 Actor。这就是 UE 最核心的架构思路Actor 是存在的单位Component 是能力的单位。一个 Actor 默认只是个空壳你给它加一个 Static Mesh Component它就能被看见你给它加一个 Physics Constraint它就能被物理引擎接管你给它加一段音频组件它就能发声。Actor 本身不负责具体功能它负责把这些组件组织起来并让自己被引擎的生成、销毁、Tick 流程管理。1.2 Actor 和 GameObject不同引擎里相同的物件哲学很多从 Unity 转过来的朋友第一反应是这不就是 GameObject 吗对它们本质上是同一个东西都是场景中所有实体的基类但用法上有不少差异。Unity 的 GameObject 本身的能力更弱几乎所有行为都靠 AddComponent 挂脚本。而 UE 的 Actor 天生带了一套生命周期管理BeginPlay、Tick、EndPlay 这些函数是 Actor 自带的你不用额外挂脚本就拥有这些事件。另一个重要区别是 UE 里 Actor 可以由蓝图实现也可以由 C 实现甚至两者混合而 Unity 的 GameObject 本身不是脚本脚本需要单独挂载。从设计哲學上说Unity 更偏向组合优于继承什么都往 GameObject 上堆组件UE 则允许你用继承来派生新的 Actor 类型。你完全可以创建一个 BP_EnemyAnimal 继承自 BP_Animal再继承自 AnimalBase每一层添加自己的逻辑和组件。这种方式在大型项目中非常实用因为同类型的 Actor 可以直接共享父类里写好的逻辑。1.3 Actor 的类型为什么会有这么多 Actor 子类UE 里你几乎不会直接用一个裸的 Actor而是用它的子类因为每个子类针对不同场景预装好了能力。搞清楚几个最常用的子类你读文档、看教程的时候会轻松很多Actor 类型主要用途关键特性AActor最基类通用物件自带 Transform、生命周期啥都可以做APawn可以被控制器控制适合做角色、载具、可以接收输入ACharacter带骨骼和移动能力的 Pawn内置 CharacterMovementComponent适合做人类角色APlayerController接收玩家输入负责控制 Pawn处理 HUD 和输入AStaticMeshActor场景里的静态物体只挂了一个 StaticMeshComponent用来放方块、椅子、建筑ACameraActor摄像机用于拍摄视角AActor 的各种蓝图子类你自己定义的东西比如 BP_DoorBP_EnemyBP_Item我第一次学的时候以为必须记住所有这些类的继承关系才能动手其实根本不需要。你只需要记住绝大部分时候你从蓝图类里创建选择 Actor 作为父类然后往里面加组件就行。后面用到 Pawn 和 Character 时再回头区分它们的差别完全来得及。2. 创建第一个 Actor 前的准备理解坐标与场景结构2.1 为什么 Unity 转过来的新手容易在坐标上翻车创建 Actor 之前你需要先搞懂 UE 的坐标体系。UE 是左手坐标系X 轴朝前Y 轴朝右Z 轴朝上。Unity 也是左手坐标系但有一个非常容易让人混淆的点Unity 的 Forward 在旋转为 0 时指向 Z 正方向而 UE 的 Forward 是 X 正方向。这个差异会导致一个很经典的翻车现场你在 Unity 里习惯把一个物体放在 (0, 1, 0) 让它往前移动一段在 UE 里你想让物体沿自身前方移动写的是GetActorForwardVector()乘以速度而这个 ForwardVector 在世界坐标里是 X 方向。如果你不知道这个区别写移动逻辑时候很容易出现我想让它往前走结果它往侧面飞了的情况。坐标的另一个要点是 UE 的世界中心不是 (0,0,0) 本身而是你关卡里的原点。每次新建一个关卡原点都在地图中央地面上缩略图里那个小红圈的位置。你创建的第一个 Actor 如果不设置坐标它会出生在 (0,0,0)也就是世界里那个原点上。2.2 视野里的 Level、World、Actor 三者关系创建 Actor 前还要把关卡Level和世界World这两个词的关系理清楚。World 是整个游戏运行时的最高容器一个世界可以有多个关卡常见的是持久关卡Persistent Level加上若干子关卡Sub-Level。你平时编辑的那个场景其实是 World 中的一个 Level 里的内容。Actor 必须存在于某个关卡里不存在游离在世界之外的 Actor。这带来一个非常重要的实操认知当你要在运行时动态生成一个 Actor 时必须告诉引擎生成到哪个关卡。大部分时候你直接用GetWorld()拿到当前世界引擎会自动帮你决定放到当前关卡但你自己写代码时要注意如果你在某个子关卡里调用生成而当前可见关卡不是它可能会出现物体在但你看不到的情况。知道这三个词的关系之后创建 Actor 前的基础知识就齐了。我强烈建议你不要直接跳到编辑器里乱点先在心里建立这个模型你在编辑器里拖进场景的每一个东西都会被引擎打包成一个 Actor 的实例存进当前 Level 的文件里游戏运行时由 World 统一管理他们。3. 手把手创建第一个 Actor从空 Actor 到看得见的方块3.1 创建 Blueprint Actor 的完整流程现在进入实际操作。UE 项目里创建 Actor 有两种主流方式蓝图和 C。不管你是哪种开发者我都建议从蓝图开始走一遍流程因为蓝图能让你直观看到 Actor 的组成结构不会被编译流程干扰。具体步骤如下打开你的项目在 Content Browser内容浏览器里找一个你喜欢的位置右键选择蓝图类。弹出的窗口里选择父类这里直接选最顶部的 Actor 然后命名为BP_FirstActor。双击打开 BP_FirstActor进入蓝图编辑器。这时候左侧面板叫Components里面默认只有一个 DefaultSceneRoot 组件。这个组件的角色是给整个 Actor 提供一个原点它本身不可见。点击左上角的添加组件按钮搜索并添加一个 Static Mesh 组件。你会发现它自动成了 DefaultSceneRoot 的子级。在右侧详情面板的 Static Mesh 下拉框里搜索Cube选中它。你的方块此时已经在预览窗口里出现了。到这一步你已经创建了人生中第一个看得见的 Actor。接下来编译Compile并保存Save这个蓝图把它从内容浏览器里拖到游戏场景中点击运行Play你就能在游戏视口里看到这个方块稳稳地站在原点附近。这个流程有一个细节很重要每个 Actor 都需要一个 Root Component根组件。你在第 1 步创建后默认就有的 DefaultSceneRoot 就是这个根。我们之所以添加 Static Mesh 组件到它下面是因为一个 Actor 只能有一个根根负责定义 Actor 的 Transform位置、旋转、缩放子组件全都跟着根走。如果你添加第一个组件时选择了替换根组件选项那 Static Mesh 就会成为新的根。更复杂的情况比如你想让一个角色身体下有多个零件你会希望它们统一挂在同一个根下面这样移动角色时所有零件一起动。3.2 给 Actor 添加可视化组件Static Mesh 与材质方块在预览窗口里显示成灰色是因为默认材质只是一个基础材质。为了让你的 Actor 更接近真实游戏里的表现你需要给它指定一个材质。在 Content Browser 里右键选择材质命名M_TestBlock。双击打开材质编辑器左侧是输出节点右侧可以添加各种表达式节点。一个最简单的选择选中输出节点在右侧细节面板里把 Base Color 改成你喜欢的颜色。顺便把 Metallic 调到 0Roughness 调到 0.8 左右这样它会呈现一种哑光质感看起来没那么塑料。编辑完点击 Apply 保存回到 BP_FirstActor把 Static Mesh 组件上的 Material Element 0 换成 M_TestBlock。大多数初学者在这一步会看到一个现象预览窗口里方块已经变色了但拖到场景里看还是灰色的。这并不是你操作错了而是编辑器视口的实时刷新层级问题。你只需要重新拖入一个新的 BP_FirstActor 到场景或者把场景里已有的那个删掉再复制一份就会看到材质正常显示了。材质这个概念现在你只需要理解为它决定了 Actor 表面被光照之后反射成什么颜色和质感。后面学到 PBR 材质系统时你会接触更多参数但今天知道怎么换颜色就足够了。3.3 用代码动态生成 ActorSpawnActor 的两种写法上面我们通过拖拽把 Actor 放进了关卡这叫静态放置。但实际游戏里大量 Actor 是运行时才出现的敌人从刷怪点刷新、子弹击中目标后产生爆炸、掉落物从宝箱里弹出。这些都要靠动态生成UE 里叫 Spawn。在蓝图中你只需要使用Spawn Actor from Class节点。它的几个关键输入Class你要生成哪种 Actor 类型选 BP_FirstActor。Spawn Transform生成位置和旋转。你可以先用Make Transform节点连一个位置 (0, 0, 150)表示在原点上方 150 个单位生成。Owner生成它的宿主 Actor一般可以留空。接好之后运行游戏你会看到方块凭空出现在场景中指定位置。这就是运行时生成一个 Actor 的完整流程。如果使用 C对应代码是这样UWorld* World GetWorld(); if (World) { FVector SpawnLocation(0.f, 0.f, 150.f); FRotator SpawnRotation(0.f, 0.f, 0.f); FActorSpawnParameters SpawnParams; SpawnParams.Owner this; SpawnParams.SpawnCollisionHandlingOverride ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn; AActor* SpawnedActor World-SpawnActorAActor(BP_FirstActorClass, SpawnLocation, SpawnRotation, SpawnParams); }注意这里使用了SpawnCollisionHandlingOverride参数。它的意思是如果要生成的位置被其他物体占用了引擎会自动调整位置以避免重叠但如果始终找不到合适位置仍然强制生成。这个参数是初学者最容易忽略的因为默认情况下如果生成点有碰撞阻挡Actor 会生成失败但你不一定立刻发现直到发现敌人没有刷出来。所以我自己写 Spawn 时都会显式指定这个处理方式避免为什么我的敌人神秘消失了这类问题。4. 生命周期与 TickActor 的出生、心跳与死亡4.1 BeginPlay 与 Tick 的实际应用场景Actor 被放进场景或者运行时通过 Spawn 生成之后它并不是死的。引擎会按照固定的频率对它进行更新。UE 里它表现为三个事件分别在 Actor 生命周期的不同阶段被调用事件调用时机典型用途Construction Script蓝图被编译、Actor 被放置到场景、属性被修改时根据参数动态搭建组件结构BeginPlay游戏运行时Actor 进入世界的瞬间初始化血量、绑定事件、查询其他 ActorTick每一帧调用持续更新位置、检测条件、做插值动画新手最容易困惑的点是 Construction Script 和 BeginPlay 的区别。Construction Script 在编辑器里调整参数时就会立刻执行所以适合做编辑器里马上能看到变化的事情比如根据你设置的楼层高度动态在预览里生成对应的楼层模型。而 BeginPlay 只在真正运行游戏时才执行适合做运行时初始化。Tick 的理解更直接。假设你想让方块在游戏运行时以每秒 90 度的速度绕 Z 轴旋转在蓝图里就给 Actor 的 Tick 节点加一段逻辑或者更简单你直接在 Static Mesh 组件上调用AddLocalRotation并且把旋转增量乘以Delta SecondsTick 自带的参数这样旋转就和帧率无关了不管游戏跑 30 帧还是 120 帧转速都一样。这就是 Tick 里最经典的一个示范。但这里我要强调一个容易忽略的地方引擎里每个 Actor 默认都会开 Tick哪怕它根本不需要每帧更新。当一个关卡里有上千个静态 Actor每个都开着 Tick光是空跑一遍 Tick 就会造成不小的 CPU 开销。所以我的习惯是不在 Tick 里做事的时候把 Actor 的Start Tick Enabled和Can Ever Tick属性关掉或者把 Tick 间隔设置成 0.1 秒甚至更长。这种优化在项目规模变大之后会非常明显。4.2 Actor 的销毁与延迟销毁谁负责清理垃圾有出生就有销毁。当你需要移除一个 Actor 时蓝图中可以直接对目标 Actor 调用Destroy Actor节点。C 里对应的是Actor-Destroy()。但需要注意Destroy 并不是立刻把内存收回。它只是标记这个 Actor请求被销毁引擎会在合适的时机把它从世界中移除并释放其持有的资源。如果你循环遍历所有 Actor发现要销毁当前遍历到的 Actor直接 Destroy 就好但要避免在遍历的同时还去读取它的属性因为它可能在下一次 GC 时被回收继续引用就会崩溃。另一个非常实用的函数是Set LifeSpanC 里是SetLifeSpan。它可以告诉引擎这个 Actor 在存活多少秒后自动销毁。这对爆炸特效这类临时生成的对象非常合适。你生成一个爆炸特效 Actor设置 LifeSpan 为 2 秒它会在 2 秒后自己消失完全不用手动管理。我自己写技能系统时很多飞弹和弹壳都是用这种方式处理的省心。生命周期相关的坑我后面会在第 5 节集中讲这里先记住一个核心认知Actor 的构建、初始化、更新、销毁这四个阶段都有对应的事件操作 Actor 时先想清楚现在是哪个阶段能避免大量 bug。5. 新手最容易踩的 Actor 相关坑5.1 Transform 属性与坐标轴混乱我见过太多新手在创建完 Actor 后一脸疑惑地问我为什么我把一个方块放在了场景里某个位置运行游戏后它却在我的脚下或者干脆消失了这背后往往就是 Transform 弄混了。UE 里的 Transform 包含三个内容Location位置、Rotation旋转、Scale缩放。你拖进场景的 Actor它的位置显示的是在世界坐标系的绝对位置但你在蓝图里修改位置时要分清楚你是想改相对位置相对父组件的本地空间还是世界位置关卡坐标系。举个例子你在 BP_FirstActor 的 Static Mesh 组件上把 Relative Location 改成 (0, 0, 100)意思是方块相对 Actor 的根组件往上偏移 100 个单位。你把它拖进场景时这个偏移依然保留。此时你在世界中看到的方块位置是 Actor 世界位置 组件相对位置叠加的结果。如果你在蓝图里通过 Tick 移动的是组件相对位置而你还想让它以世界坐标为准就会产生奇怪的漂移。5.2 旋转单位的坑Degrees 还是 Radians这是所有从代码写起、尤其是学过一点点数学的开发者最容易纠结的问题。UE 蓝图里的旋转单位是度Degrees比如Set Actor Rotation传 (0, 0, 90)表示绕 Z 轴转 90 度。但如果你在 C 或者材质蓝图里用三角函数、插值很多节点用的是弧度Radians。你做一个原地转身功能按直觉写了一个旋转角度值结果发现角色转得飞快或者几乎不动多半就是单位写错了。我自己写逻辑时有一个默认习惯C 里如果涉及旋转参数先看接口注释里写的是 Degrees 还是 Radians。UE 的FRotator结构体所有分量都是度而FMath::DegreesToRadians这个函数存在的意义就是让你在两种单位之间转换。蓝图里也是一样看到一个数值节点连到旋转节点之前先想清楚它是度还是弧度。新手阶段这个坑会反复出现多踩几次自然就记住了。5.3 在构造函数里创建组件的陷阱最后一个坑必须单独拿出来讲因为它牵扯到 Actor 和组件生命周期中非常隐蔽的一段。有些初学者尝试在 C 构造函数里直接NewObject创建组件或者在某些特殊时机动态添加组件发现运行时组件有时存在有时不存在属性怎么调都对不上。正确做法是C 里的组件创建应该放在构造函数里但必须使用CreateDefaultSubobject这个专用函数而不是NewObject。因为CreateDefaultSubobject会把组件注册进 Actor 的组件列表参与后续的初始化、序列化和属性同步。如果你用NewObject创建组件相当于创建了一个跟 Actor 没有隶属关系的普通对象它不会被引擎当作 Actor 的组件来管理。Blueprint 场景中同样有对应的规则在蓝图编辑器的组件列表里添加组件是设计时静态添加的方式运行时动态添加组件需要调用Add Component类节点比如Add Static Mesh Component。在蓝图里动态添加组件和设计时添加组件有一个重要差异动态添加的组件不会自动拥有一个默认的根组件或者附加位置你需要自己用Attach To Component把它挂到某个组件下否则它的位置可能完全不受控制。这个坑在教程里很少被提到但实际项目里几乎每个人都会遇到。有一次我在做求生类 Demo 时想给角色动态挂载一个武器模型结果忘了设置附加规则武器模型飞到了地图外面查了半天才发现是根组件和相对位置没有设置。6. 实操一遍后我建议你做的几个小练习到目前为止你理解了 Actor 是什么、怎么创建、怎么动态生成、怎么管理生命周期也知道了几个常见的坑。但光看不练很快会忘。我会带几个新人走这段路每次都要他们完成三件小事今天也留给各位第一件事创建一个 BP_RotatingCube 的蓝图 Actor让它包含一个 Cube 静态网格并且在 Tick 里绕 Z 轴匀速旋转。做完这一步你会体会到组件跟着根一起动和组件自身旋转的区别。第二件事写一段 Spawn 逻辑每按一次键盘上的 1 键就在角色面前 200 个单位的位置生成一个 BP_RotatingCube并设置 LifeSpan 为 3 秒。这个练习能让你真正掌握运行时生成 Actor 的流程同时理解 LifeSpan 的作用。第三件事给 BP_RotatingCube 增加一个Hit事件Static Mesh 组件上有 On Component Hit当它碰到任何东西时输出一条日志或者改变材质颜色。这个练习是为了让你理解事件在 Actor 中扮演的角色——Actor 里不只有 Tick 在不断运行它还时刻监听其他系统发来的事件并做出响应。这三个练习覆盖了 Actor 的核心能力组件、生命周期、Tick、动态生成、事件监听。做完后你再回头看 Actor 这个概念会发现自己已经从它是名词解释升级成我知道怎么用它了。以前我刚开始学的时候总想着先把文档里所有 Actor 相关的类和函数背下来再动手结果越看越记不住。后来改变策略直接创建一个小项目在里面犯各种错反而成长飞快。很多知识点光靠文字是不够的亲手把一个方块放在场景里并让它转起来比读十遍文档都有用。希望今天的这篇内容能帮你迈出这一步。