你无法看到我

UE5和蓝图节点入门PART1

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 被创建、移动或属性被修改时 由游戏事件触发(如 BeginPlayTick、输入、碰撞)
典型应用 自动生成一排柱子、根据变量切换材质 角色移动、开关门、计算伤害
预览 在编辑器中直接预览结果 必须运行游戏才能看到效果

构造脚本

构造图表 – 在游戏开始之前设置和构建对象,会在编辑器中运行,当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类

 

留下影子