UE5文件结构
通常的树状结构如下:
MyProject/
├── Binaries/
├── Build/
├── Config/
├── Content/
├── DerivedDataCache/
├── Intermediate/
├── Saved/
├── Source/ (仅C++项目)
└── MyProject.uproject
*.uproject 是UE5项目工程的扩展名,在编辑器的 内容浏览器 中的文件结构是 Content文件夹内的
下图更详细的描述了文件目录结构的情况
graph TD A["MyProject/ (项目根目录)"] A --> B["MyProject.uproject"] A --> C["Content (所有游戏资产)"] A --> D["Config (配置文件 .ini)"] A --> E["Source (仅C++项目)"] A --> F["Binaries (已编译文件)"] A --> G["Intermediate (临时文件)"] A --> H["Saved (自动保存和日志)"] C --> C1["Blueprints (蓝图)"] C --> C2["Maps (关卡)"] C --> C3["Materials (材质)"] C --> C4["..."] D --> D1["DefaultEngine.ini"] D --> D2["DefaultGame.ini"] D --> D3["..."]
UE5中的关卡MAP
关卡MAP是游戏世界的全部,或者游戏世界的一部分,可以将游戏整个放置在一个关卡地图中,也可以在多个地图中进行切换
在UE5进行游戏设计时,一切都将从一个关卡MAP开始,可以在关卡中放置Actor等组件来实现游戏需要的环境和功能
虚幻引擎将每个关卡保存为一个单独的 .umap 文件
UE5各个组件的关系
UE5中各个组件都是基于C++的逻辑关系来构建的 (其本身也是基于C++编写的)
graph TD A["Object (对象) (所有对象的基类 通常在蓝图中不直接接触)"] subgraph "核心概念" A -- 是...的父类 --> B["Actor (演员) (能放置在关卡中的任何对象)"] A -- 是...的父类 --> C["Actor Component (组件) (附加到Actor的功能模块)"] end subgraph "常见组件" C -- 派生出 --> D["Scene Component (场景组件) (带有位置/旋转/缩放属性)"] D -- 派生出 --> E["Primitive Component (图元组件) (可被渲染或用于碰撞的组件)"] E -- 派生出 --> F["Static Mesh Component (静态网格体组件)"] E -- 派生出 --> G["Skeletal Mesh Component (骨骼网格体组件)"] end B -- "可以添加 (Can Have)" --> C
最常接触到的是
Actor
Actor – 场景中编辑器创建的大部分东西都是一个Actor
Actor 是一个容器,他实现什么功能,表现出怎样的形式 与其内部的子类有关系(即 Actor里面包含了怎样的组件)
Scene Component
Scene Component – 场景组件,这个组件是专门存放场景中对象的位置与变换数据的
变换数据并不直接存放在Actor 或者 静态网格体上面,而是存放在场景组件上(默认的是 DefaultSceneRoot 场景根组件)
DefaultSceneRoot 是一个占位符组件,当创建一个新的Actor时,它会自动生成,以免新建的空Actor在场景中无法被定位
Primitive Component
图元组件是默认场景组件的一个子类,其又可以派生出例如 静态网格体 或者 粒子系统 这样的子类。
Static Mesh Component
它的主要职责是引用一个静态网格体资产(Static Mesh Asset),并在游戏世界中将其渲染出来。它还负责处理该网格体的物理、碰撞、材质等属性
这里静态的含义是指 – 关联的几何物体的形状是静态的
主要作用 – 构建几何体(比如地面,墙壁,建筑,可交互物品等 3D模型)
Skeletal Mesh Component
所有通过骨骼动画驱动的物体都是这一类型,其与静态网格体的区别如下图:
graph TD A["我需要一个游戏对象"] --> B{"它是否需要 通过骨骼驱动的 复杂形变或动画?"}; B -- "是" --> C["使用 Skeletal Mesh Component"]; C --> D["例如: - 角色 - 动物 - 复杂的机械臂"]; B -- "否" --> E["使用 Static Mesh Component"]; E --> F{"是否需要渲染 成百上千个 完全相同的对象?"}; F -- "是" --> G["可以进一步优化为 Instanced Static Mesh Component 或 Hierarchical Instanced Static Mesh Component"]; F -- "否" --> H["直接使用 Static Mesh Component即可"]; H --> I["例如: - 建筑 - 道具 - 简单的门"]; G --> J["例如: - 森林里的树 - 草地 - 一堆石块"];
蓝图编辑器
在蓝图编辑器中存在 事件图表 和 构造脚本 两个区域,他们之间最大的区别是 其中的程序执行的 时间和目的
特性 | 构造脚本 (Construction Script) | 事件图表 (Event Graph) |
---|---|---|
执行时间 | 编辑器中(游戏开始前) | 游戏中(游戏运行时) |
主要目的 | 对象的初始化和程序化搭建 | 游戏逻辑和玩家交互 |
触发方式 | Actor 被创建、移动或属性被修改时 | 由游戏事件触发(如 BeginPlay 、Tick 、输入、碰撞) |
典型应用 | 自动生成一排柱子、根据变量切换材质 | 角色移动、开关门、计算伤害 |
预览 | 在编辑器中直接预览结果 | 必须运行游戏才能看到效果 |
构造脚本
构造图表 – 在游戏开始之前设置和构建对象,会在编辑器中运行,当Actor被放置,编辑,变量被修改时会执行其中的程序
flowchart TD A["在编辑器中 将墙Actor放入关卡"] --> B["构造脚本执行"] B --> C["For 循环: 根据砖块数量变量进行迭代"] C --> D["在每次循环中 计算每块砖的位置"] D --> E["Add Static Mesh Component (添加砖块网格组件)"] E --> C C -- 循环结束后 --> F["在编辑器中 看到一堵完整的墙"]
事件图表
事件图表 – 在游戏运行时处理逻辑事件,不会在编辑器中运行
flowchart TD A["游戏开始运行"] --> B["Event BeginPlay (初始化门的状态为关闭)"] C["玩家走到门前 触发碰撞事件 (OnComponentBeginOverlap)"] --> D["在屏幕上显示 按 E 开门 的提示"] D --> E{玩家是否按下了 E 键?} E -- 是 --> F["InputAction E 事件被触发"] F --> G["播放开门动画和音效 (使用时间轴 Timeline)"] E -- 否 --> C
事件图表中的三种事件
1.Event BeginPlay
这是Actor(可以理解为游戏世界中的任何对象,如角色、道具、光源等)生命周期中非常重要的一个事件。它会在该Actor被加载到游戏世界中、游戏正式开始后 执行且仅执行一次
2. Event Actor Begin Overlap
当这个Actor的碰撞体(Collision Component)与其他Actor的碰撞体开始发生重叠(或接触)时,这个事件会被触发
常见用途:
- 触发区域: 制作玩家走进某个区域就开门、触发陷阱或播放剧情的效果。
- 拾取物品: 当玩家角色碰到(重叠)一个金币或道具时,执行拾取逻辑。
- 伤害判定: 当武器的碰撞体挥动并接触到敌人时,计算伤害。
- 检测: 检测特定类型的Actor是否进入了某个感应范围。
3. Event Tick
它会在 游戏的每一帧 都执行一次。它的执行频率与游戏的帧率(FPS)直接相关
它有一个重要的输出引脚 Delta Seconds,表示“距离上一帧所经过的时间(秒)”。使用这个值可以使你的逻辑不受帧率变化的影响(例如,移动速度不会因为帧率高而变快)。
flowchart TD A["游戏开始"] --> B["Event BeginPlay (当Actor进入游戏时执行一次 用于初始化设置)"] B --> C{"游戏主循环 (每帧重复)"} C --> D["Event Tick (在每一帧都执行 用于持续更新)"] D --> C C --> E{"两个Actor的 碰撞体是否发生重叠?"} E -- 是 --> F["Event Actor Begin Overlap (在重叠发生的瞬间执行一次 用于触发特定逻辑)"] E -- 否 --> C
蓝图节点的执行顺序
从红色事件节点开始
沿着白色执行连线前进
从左到右,从输入到输出
使用流程控制节点(如 Sequence、Branch)来管理和引导执行路径
没有执行引脚的纯节点(绿色),只在需要其数据时才会被动执行
graph TD subgraph " " direction LR A["1. 从红色事件节点开始 例如 Event BeginPlay"] -- "2. 沿着白色执行连线前进" --> B["4. 使用流程控制节点 Branch 一个IF判断"] C["5. 没有执行引脚的纯节点 绿色 例如 Random Bool 被动提供数据"]-.- |"3. 当执行到Branch节点时 才通过数据连线获取布尔值"| B B -- "True路径" --> D["执行打印字符串 打印 随机值为True"] B -- "False路径" --> E["执行另一个操作 例如播放声音"] end
变量的数据类型
1.整数
整数就是没有小数部分的数字,一般用来存储 物品数量,循环次数等
整数 – 32位 – 最大21亿数值(可以表示负数)
整数64位 – 64位 – 就是64位的整数,可以表示更大的数字
字节 – 8位 – 范围是 0~255,不能表示负数
2. 浮点数
是带小数的数字,比整数更精确,一般用来存储 位置,变换等
UE5的浮点数可以自动在 单精度 和 双精度 之间切换
3.布尔值
用来存储 True 和 False 两种状态,主要用于逻辑判断
4.字符串
标准的文字字符串,用来保存文本信息,比如消息,名称等
5.文本
特殊的字符串,为本地化做了优化,可以便利的将UI文本翻译成其他语言
6.命名
经过优化的字符串,速度很快(指计算速度),用来标识内部组件的名称,比如Actor的名称
7.向量
由三个浮点数组成的坐标(X,Y,Z),表示3D空间中的单一属性(比如位置 或者 缩放)
8.旋转体
由三个浮点数组成,用来表示物体的 旋转状态
9.变换
复合结构体,包含了 位置(是一个向量),旋转(是一个旋转体) 和 缩放(是一个向量),用于描述物体在世界中的空间属性
世界和局部坐标系
特性 | 世界坐标系 (World Space) | 局部坐标系 (Local Space) |
---|---|---|
参考系 | 整个关卡,是绝对的、全局的 | 物体自身,是相对的、独立的 |
原点 | 关卡中心 (0,0,0) | 物体的轴心点 (Pivot) |
坐标轴 | 固定不变 | 随物体自身的旋转而改变 |
应用 | 精确定位场景中的物体 | 使物体相对于自身方向移动(如汽车前进) |
编辑器Gizmo | 无论物体如何旋转,Gizmo方向始终与世界坐标轴对齐。 | Gizmo方向始终与物体自身的坐标轴对齐。 |
这一点和大多数3D编辑软件(比如MAYA ,Blender的行为相同)
实例和自身的区别
1.实例
实例是一种基于 模板 创建出来的对象,在程序中是基于父类创建出来的对象
父类定义了某个对象可以拥有的属性和能力,比如一辆汽车的颜色,速度,移动等
所有被放置在世界中的蓝图对象都是这个蓝图的一个实例(包括所有的Actor)
2.自身
这是一个特殊引用,明确的指向一个实例自身
简单比喻:你是一个人类的实例。当你说“我要举起我的手”时,这里的“我”就相当于Self。你用它来指代你自己这个个体,以便对自己执行某个动作。
内部引用:在蓝图内部的 Self 引脚指向蓝图实例自身
graph TD subgraph "蓝图 (Class): BP_Car" direction LR A[" 变量: - Color - Speed 函数: - Accelerate() - DestroySelf()"] end subgraph "游戏世界 (Level)" B["Instance 1: Car_A Color = Red Speed = 50"] C["Instance 2: Car_B Color = Blue Speed = 0"] end subgraph "Car_A 的内部逻辑" D["Event BeginPlay"] --> E{"调用 Accelerate()"} F["当生命值耗尽"] --> G["调用 DestroySelf() Target = Self"] E --> H["Self.Speed = 10"] G --> I["销毁 Car_A 这个实例"] subgraph "在逻辑中, 'Self' 指向 Car_A" direction LR J[Self] --> B end end A -- "创建" --> B A -- "创建" --> C
UE5视口导航和快捷键
视口操作与导航
快捷键 | 功能 |
---|---|
按住右键 + W/A/S/D | 在场景中像玩游戏一样前后左右移动 |
按住右键 + Q/E | 垂直下降/上升 |
按住左键 + 拖动 | 根据鼠标移动方向,在水平面上前后移动或左右旋转相机 |
按住中键 + 拖动 | 在屏幕空间中上下左右平移相机 |
F | 聚焦于选中的Actor |
Alt + 按住左键 + 拖动 | 以选中的Actor为中心旋转相机 |
Alt + 按住右键 + 拖动 | 以选中的Actor为中心拉近或推远相机 |
G | 游戏视图(隐藏所有编辑器图标,如摄像机、灯光图标等) |
Actor操作
快捷键 | 功能 |
---|---|
W | 切换到移动(Translate)工具 |
E | 切换到旋转(Rotate)工具 |
R | 切换到缩放(Scale)工具 |
空格键 | 在移动、旋转、缩放工具之间循环切换 |
Ctrl + D 或 Alt + 拖动 | 复制选中的Actor |
Delete | 删除选中的Actor |
End | 将选中的Actor对齐到其下方的地面或物体上 |
F2 | 重命名选中的Actor(在世界大纲视图中) |
游戏测试
快捷键 | 功能 |
---|---|
Alt + P | 在编辑器中播放(Play) |
Alt + S | 在编辑器中模拟(Simulate) |
Esc | 停止播放或模拟 |
F8 | 在“播放”模式下,从玩家控制器中“弹出”,恢复为自由相机 |
F11 | 全屏显示当前视口 |
创建简单的子弹打砖墙DEMO
该DEMO的核心在于,创建可以程序化生成的墙体(由多个立方体构成的墙),并为墙体启用物理碰撞
创建可以发射出去的球体,并调整球体的发射速度,以及为球体启用物理碰撞
1.创建程序化生成的墙体
思路 – 通过行与列这两个变量来控制墙的高度和宽度,使用实例化网格体构造墙体来提高性能,通过一个内外双循环来依据变量传递的参数放置方块
flowchart TD A[Construction Script] --> B["Clear Instances Target: Bricks"] B --> C["Set Static Mesh Target: Bricks New Mesh: Brick Mesh"] C --> D["For Loop -- 外层 循环次数由 Wall Colums 决定"] D -- Loop Body --> E["For Loop -- 内层 循环次数由 Wall Rows 决定"] E -- Loop Body --> F["计算砖块位置 X = 列索引 * 砖块宽度 Y = 0 Z = 行索引 * 砖块高度"] F --> G["Make Transform 使用计算出的位置和Scale生成变换"] G --> H["Add Instance Target: Bricks"] H -- " " --> E E -- "内层循环完成" --> D
变量 – Wall Colums = 整数 – 墙的宽度,变量 – Wall Rows = 整数 – 墙的高度,Bricks – 实例化静态网格体组件,变量- BrickMesh – 静态网格体
Clear Instances – 清理实例节点,将目标指向要清理的实例类型(需要传入 静态网格体)
Set Static Mesh – 设置网格体,将目标指向要使用的静态网格体变量(此处是 BrickMesh)
For Loop 节点的主要输入输出
引脚(Pin) | 说明 |
---|---|
Exec(白色引脚) | 触发循环开始 |
First Index – 输入 | 起始索引(通常是 0) |
Last Index – 输入 | 结束索引(包含这个值) |
Loop Body – 输出 | 每次循环执行这里连接的逻辑 |
Index – 输出 | 当前循环的索引值(输出) |
Completed – 输出 | 循环完成后触发的执行路径 |
First Index 和 Last Index的差值决定这个循环要执行多少次,而 Index 输出引脚则会输出当前正处在哪一个循环(如果First Index是0,则也可以认为此处输出的是目前是第几个循环)
For Loop 节点是 同步执行的,也就是说它会 立即执行。这意味着当触发 For Loop 时,循环内的每个步骤会在当前帧内按顺序执行,直到完成整个循环
此处的内外层循环,是指,将一个循环嵌套在另外一个循环里,由先执行的循环 – 外循环 触发 内循环,外循环每循环一次,内循环都要完整的循环结束
graph LR A["开始设置墙壁 (Rows=3, Columns=4)"] --> B["列0"] subgraph "列0" direction TB B1["Row0 --> (0,0)"] B2["Row1 --> (0,1)"] B3["Row2 --> (0,2)"] B4["完成列0"] end B --> B1 B3 --> C["列1"] subgraph "列1" direction TB C1["Row0 --> (1,0)"] C2["Row1 --> (1,1)"] C3["Row2 --> (1,2)"] C4["完成列1"] end C --> C1 C3 --> D["列2"] subgraph "列2" direction TB D1["Row0 --> (2,0)"] D2["Row1 --> (2,1)"] D3["Row2 --> (2,2)"] D4["完成列2"] end D --> D1 D3 --> E["列3"] subgraph "列3" direction TB E1["Row0 --> (3,0)"] E2["Row1 --> (3,1)"] E3["Row2 --> (3,2)"] E4["完成列3"] end E --> E1 E3 --> F["流程结束"]
Get Bounds – 获取静态网格体的边界框尺寸 – Event引脚 – 返回 物体中心到边界的距离(也就是长宽高的一半)
这里利用了这个返回数值以及循环的Index计数来计算放置方块的坐标变换 (逻辑是:每下一次放置的方块应该偏移一整个方块大小的距离)
Add Instance – 添加实例,将实例化静态网格体组件添加到场景中 – 实例变换 引脚 – 确定实例的位置变换
需要特别注意:实例化网格体虽然可以提高渲染性能,但是所有实例共用一个碰撞体,无法直接实现每个实例的物理模拟(比如,创建的砖墙会变成一个整体进行物理模拟,无法按照每一个砖块来进行物理碰撞)
2.给砖墙设置正确的物理
思路:在游戏开始的时候,将刚才创建的实例化网格体逐个转换为普通的静态网格体,并为其设置物理选项,然后移除实例化网格体
flowchart TD A["事件 BeginPlay (Event BeginPlay)"] --> B["获取实例数量 (Get Instance Count)"] B --> C["For Loop (循环)"] C -- "循环体 (Loop Body)" --> D["获取实例变换 (Get Instance Transform)"] D --> E["生成静态网格体 Actor (SpawnActor Static Mesh Actor)"] E --> F["设置静态网格 (Set Static Mesh)"] F --> G["设置物体可移动性 (Set Mobility)"] G --> H["设置碰撞启用 (Set Collision Enabled)"] H --> I["设置模拟物理 (Set Simulate Physics)"] I --> J["设置质量覆盖值(千克) (Set Mass Override in Kg)"] J --> C C -- "完成时 (Completed)" --> K["销毁组件 (Destroy Component)"]
Spawn Actor From Class
根据指定的类,动态的生成Actor – 在此处可以选择默认的静态网格体类,或者自己新建一个Actor类。Instigator引脚 – 用于追踪导致生成该Actor的来源(是谁或者什么导致了这个Actor的生成,可以用来追踪伤害归属)
Return Value 引脚 – 输出对创建的Actor的引用
Get Static Mesh Component – 获取Actor里面的静态网格体组件
Set Static Mesh – 将静态网格体组件设置成新的静态网格体 – 此处可以从变量 BrickMesh 传递值
3.创建子弹Actor类
子弹Actor类只需要处理自身的速度,即,球体被发射出去的时候以多快的速度飞出去
flowchart TD A["事件开始运行 Event BeginPlay"] -- 执行 --> B["设置生命周期 Set Life Span 将自身生命周期设为6秒"] B -- 执行 --> C["添加冲量 Add Impulse"] subgraph "为添加冲量节点提供数据" D["Fireball组件 Primitive Component"] -- "目标 Target" --> C E["获取Actor的前向向量 Get Actor Forward Vector"] --> F["向量乘以浮点数 Vector * Float 将前向向量乘以3000.0"] F -- "冲量 Impulse" --> C end
添加冲量 Add Impulse
这是一个向量 (Vector) 值,它同时定义了冲量的 方向 和 大小。向量的指向就是力的方向,向量的长度就是力的大小
该节点添加的是一个,一次性的 / 瞬间的力,只在调用的一瞬间产生效果。和 Add Force 不同的是,Add Force 会给予物体持续性的力。
提示:这里可以勾选 Vel change ,这将使得输入的数值作为直接的速度(也就是忽略球体的质量),可以使得冲量不用设置的太大就可以使球有很高的速度
Get Actor Forward Vector – 获取一个指向 Actor 正前方 的方向向量,返回值是一个 单位向量
什么是单位向量 (Unit Vector)?
单位向量是指长度(或称为“模”)恒等于 1 的向量。
这非常重要,因为它本身不代表任何“速度”或“距离”,它 只代表方向。你可以把它想象成一个指向某个方向的箭头,但这个箭头的长度被标准化为1。
- X 轴正方向 的单位向量是
(1, 0, 0)
- Y 轴正方向 的单位向量是
(0, 1, 0)
- Z 轴正方向 的单位向量是
(0, 0, 1)
Get Actor Forward Vector
返回的向量也是如此,它的长度总是1,但其X, Y, Z
的分量会根据Actor在世界中的旋转而改变。它与Actor的“本地坐标系”有关
每个Actor都有自己的坐标系,我们称之为“本地空间”(Local Space)。
- 本地 X 轴 通常被认为是Actor的 “前” 方向。
- 本地 Y 轴 通常是 “右” 方向。
- 本地 Z 轴 通常是 “上” 方向。
当你旋转一个Actor时,它的整个本地坐标系都随之旋转了。
Get Actor Forward Vector
所做的,就是告诉你这个Actor的本地X轴(也就是它的“前方”)现在正指向世界坐标系中的哪个方向。举个例子:
- 如果一个角色面朝世界坐标的X轴正方向,
Get Actor Forward Vector
会返回(1, 0, 0)
。- 如果这个角色向右转了90度,现在面朝世界坐标的Y轴正方向,
Get Actor Forward Vector
会返回(0, 1, 0)
。- 如果他面朝X和Y轴之间的45度角方向,
Get Actor Forward Vector
会返回类似(0.707, 0.707, 0)
的值。常见用途
由于它只提供方向,所以它通常需要和别的节点配合使用,最常见的就是与乘法节点
(Vector * Float)
结合。方向向量 × 浮点数 = 带有大小的向量 (例如速度、位移)
4.设置角色和按键绑定
此处直接使用UE5自带的那个第三人称角色模板,将关卡地图的游戏模式选择为第三人称视角,然后编辑第三人称视角角色的蓝图
思路:绑定鼠标左键,使得按下鼠标左键的时候就发射球体,同时在发射的时候需要根据角色的朝向放置球体(也可以将此处的逻辑移动到球体的蓝图里)
增强输入(Enhanced Input):输入映射上下文(Input Mapping Context)和按键绑定
想象一下传统的输入系统,你通常会直接在项目设置里把 “Jump” 这个动作直接绑定到 “Spacebar” 按键上。这种方式简单,但不灵活。比如,当玩家在驾驶汽车时,你可能不希望空格键还执行“跳跃”功能,而是希望它执行“手刹”
flowchart TD subgraph "1. 创建输入资产" A["创建输入操作 Input Action 例如: IA_Jump, IA_Move"] end subgraph "2. 创建并配置输入映射上下文 IMC" B["创建IMC资产, 例如: IMC_OnFoot_Controls"] C{"在IMC内部添加映射 Bindings"} D["将 空格键 绑定到 IA_Jump"] E["将 W/A/S/D 键 绑定到 IA_Move, 并使用修饰符"] B --> C --> D & E end subgraph "3. 在蓝图中应用" F["在玩家控制器 PlayerController 的BeginPlay事件中"] G["获取增强输入子系统"] H["调用 Add Mapping Context - Target: 增强输入子系统 - Context: IMC_OnFoot_Controls - Priority: 0"] F --> G --> H end subgraph "4. 在角色中响应事件" L["在玩家角色 Pawn或Character 的蓝图事件图表中"] M["创建输入操作事件节点 IA_Jump"] N["将事件连接到 Jump 函数"] L --> M --> N end A -- "用于绑定" --> D A -- "用于创建事件" --> M B -- "在节点中使用" --> H
在内容浏览器里添加一个 输入操作(数据资产) – Fire,在第三人称角色使用的 输入输出上下文映射 里,添加该输入操作
然后在角色的事件蓝图里,通过搜索 Fire ,添加 EnhancedInputAction Fire 来处理按键输入
flowchart TD A["事件触发 当输入 Fire 开始时"] -- "执行" --> E["生成Actor: BP_Ball"]; subgraph "为生成Actor准备 Spawn Transform 参数" B{"获取当前Actor的 位置 Location 旋转 Rotation 前向向量 Forward Vector"} --> C; C{"1. 计算生成点的位置 最终位置 = Actor位置 + Actor前向向量 * 50 + 0, 0, -50 的向量"} --> D; D{"2. 组合最终的Transform - Location: 使用上一步计算的位置 - Rotation: 使用Actor的当前旋转 - Scale: 固定为 0.5, 0.5, 0.5"} end D -- "作为参数传入" --> E;
这里最关键的地方在于,先获取Actor(也就是角色)所在的位置和朝向,再基于此为生成的球体(子弹)设置一个偏移,防止球体生成在角色模型的内部,或者和角色模型产生碰撞
以Actor的当前位置为基准,将Actor的前向向量乘以50,得到一个“前方50个单位”的向量
再加上一个(0, 0, -50)的向量,即“下方50个单位”
最终位置 = 当前位置 + 前方50单位 + 下方50单位
这里的 Spawn Actor 节点的Class要选择刚才创建的子弹Actor类