概念页
蓝图与 C++ 协作
Blueprint & C++ Interop
Epic 官方建议:多数项目混用蓝图 (Blueprint) 与 C++,以 C++ 作为基础、在其上构建蓝图类。C++ 适合“编程”——定义系统;蓝图适合“脚本”——在已有系统上定义具体行为。两者通过反射说明符互相调用:C++ 用 BlueprintCallable/BlueprintReadWrite 等暴露给蓝图,用 BlueprintImplementableEvent/BlueprintNativeEvent 让蓝图实现或覆盖 C++ 声明的函数。
编程与脚本的划分
- 编程:定义系统的指令。例:载具系统的加速、转向等基础功能。
- 脚本:通过与现有系统交互来定义行为的指令。例:汽车、船等具体载具类型。
C++ 是编程语言,蓝图是脚本语言,但界线并不绝对:C++ 也能写行为,蓝图也能搭系统,最佳拆分取决于场景、项目与团队。
优势对比
| 维度 | 蓝图 | C++ |
|---|---|---|
| 擅长 | 定义行为 | 构建新系统 |
| 迭代速度 | 创建、修改、编译、测试更快,适合原型 | 较慢 |
| 可及性 | 可视化,策划、美术也能用;API 与资产引用更好发现 | 需要程序员 |
| 安全性 | 内存模型设计成避免崩溃 | 可直接接触底层,风险更高 |
| 性能 | 编译为字节码在虚拟机执行,有额外开销 | 编译为机器码直接在 CPU 执行 |
| 能力范围 | 只能用已暴露的功能;不支持多线程 | 可访问底层功能,接入外部系统和库 |
| 协作 | 二进制资产,可用 Unreal Diff Tool 对比 | 文本,易于 diff、合并、跨项目共享 |
| 规模与调试 | 大型图表难维护;蓝图调试器 | 大文件更易修改;调试工具更强 |
性能:何时真正有影响
二者差异通常不显著,主要在以下场景才明显:
- 核心、底层基础设施;
- 大量使用 I/O 或计算的紧密循环;
- 处理大数据集的系统;
- 实例很多且依赖 Tick 的类;
- 能从多线程获益的场景。
官方建议:蓝图中用定时器或委托安排工作,代替 Tick(见 委托与 Tick 机制);遇到性能问题先用 Unreal Insights 分析,优化最大瓶颈,再考虑把蓝图改写为 C++。
互相调用的方式
下图展示两个方向的调用通道。
flowchart LR
subgraph CPP["C++ 基类"]
P1["UPROPERTY(BlueprintReadWrite / ReadOnly)"]
F1["UFUNCTION(BlueprintCallable / Pure)"]
E1["UFUNCTION(BlueprintImplementableEvent)"]
E2["UFUNCTION(BlueprintNativeEvent)<br/>+ _Implementation 默认实现"]
L["UBlueprintFunctionLibrary 静态函数"]
end
subgraph BP["蓝图子类"]
G["事件图 / 构造脚本"]
end
P1 -->|"读写变量"| G
F1 -->|"调用节点"| G
L -->|"全局节点"| G
G -->|"必须实现"| E1
G -->|"可选覆盖"| E2| 方向 | 方式 | 说明 |
|---|---|---|
| C++ → 蓝图 | 蓝图继承 C++ 类 + UPROPERTY/UFUNCTION 说明符 | 未加说明符的组件、变量、函数在蓝图图表中不可访问 |
| C++ → 蓝图 | 继承 UBlueprintFunctionLibrary | 暴露静态函数 |
| 蓝图 → C++ | BlueprintImplementableEvent | 相当于在蓝图中实现的纯虚函数 |
| 蓝图 → C++ | BlueprintNativeEvent | 虚函数,C++ 默认实现写在 函数名_Implementation;蓝图未覆盖时执行 C++ 版本 |
| 蓝图 → C++(UI) | UPROPERTY(meta=(BindWidget)) | 访问蓝图中创建的 UserWidget 控件 |
官方示例:LightSwitchBoth
UCLASS()
class MYPROJECT_API ALightSwitchBoth : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Switch Components")
class UPointLightComponent* PointLight1;
UFUNCTION(BlueprintNativeEvent, Category="Switch Functions")
void OnOverlapBegin(/* 重叠参数 */);
void OnOverlapBegin_Implementation(/* 同上 */); // C++ 默认:切换灯的可见性
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Switch Variables")
float DesiredIntensity;
};
// 构造函数中:Sphere1->OnComponentBeginOverlap.AddDynamic(this, &ALightSwitchBoth::OnOverlapBegin);
- 派生蓝图
LightSwitchBoth_BP中,C++ 添加的组件显示为深蓝色图标(原生继承),蓝图新增的为浅蓝色。 - 蓝图的 OnOverlap 事件覆盖了
_Implementation,改为随机设置灯光颜色。 - 构造脚本 (Construction Script) 在 Actor 被移动或暴露的变量改变时重新执行,能即时预览;单靠 C++ 构造函数做不到。
EditAnywhere让每个实例可以有自己的DesiredIntensity。Blueprintable类说明符会被继承,AActor已是 Blueprintable。
把蓝图转为 C++
用蓝图头文件视图 (Blueprint Header View) 生成包含变量与函数声明的 .h,函数实现需要手动迁移到 .cpp;引用大量需要更新时,用核心重定向 (Core Redirects) 自动重映射。
实际项目中的分工
- 程序员在 C++ 中写基类、核心系统、网络与数据结构,暴露少量清晰的属性、函数和事件。
- 策划/美术在蓝图子类中配置数值、资产引用,编写具体关卡和物件的行为。
- 官方的 Lyra 示例项目包含上述所有暴露方式的例子。
常见坑
BlueprintPure节点不缓存结果,每次连线取值都会重新执行,重计算会被悄悄放大。- 蓝图里大量 Tick 是常见性能问题;用事件、定时器、委托替代。
- 首次在纯蓝图项目添加 C++ 类时,需关闭编辑器、在 IDE 中编译后重开;构建配置要与编辑器可执行文件一致。
GEA 的视角:蓝图在脚本谱系中的位置(卷 II 17.8.11、17.9)
GEA 第 17 章两次把 UE 蓝图当作例子:
- 数据通路通信系统:对象有输入/输出端口,策划用 GUI 连线,数据流(布尔、单位浮点、向量、颜色……)在端口间传递,类型不兼容时禁止连接或自动转换;GEA 说这就是"基于 GUI 的事件系统(如 UE 蓝图)的本质"。它是"简单可配置事件"与"完全自由的脚本语言"之间的中间路线,可以限定一组受控的原子操作并做错误检查。详见 事件与消息传递。
- GUI 编程的利弊:易用、学习曲线平缓、工具内帮助和大量错误检查;但开发调试维护成本高、额外复杂度可能带来恼人的 bug、策划受工具能力限制。文本脚本则更简单、可搜索替换、能用任何编辑器。这与本页"蓝图适合脚本、C++ 适合编程"的分工说法互补。
- UnrealScript 的退役:GEA 第 4 版说明 Epic 已不再支持 UnrealScript,开发者改用蓝图或 C++ 定制游戏行为;GEA 还说 UnrealScript 是他见过唯一能让脚本类无缝派生自原生类的脚本语言,而在 UE5 中这一能力由"以 C++ 类为基础构建蓝图类"承担。
- 解释执行的代价:GEA 指出虚拟机执行字节码通常比原生机器码慢得多,与本页"蓝图编译为字节码在虚拟机执行,有额外开销"一致。脚本架构的完整谱系见 游戏脚本系统。
出处
- Coding in UE: Blueprint vs. C++ UE 5.8 文档
- C++ and Blueprints (LightSwitchBoth example) UE 5.8 文档
- UFunctions UE 5.8 文档
- Properties (UPROPERTY) UE 5.8 文档
- Game_Engine_Architecture_4th_Edtion_Volume_2_-_Jason_Gregory.pdf
- Jason Gregory《Game Engine Architecture》第 4 版(2026)卷 II 第 17 章 17.8.11 节
- Jason Gregory《Game Engine Architecture》第 4 版(2026)卷 II 第 17 章 17.9 节
相关页面
游戏开发 · UE5UObject 与反射系统UObject & Reflection System虚幻引擎 (Unreal Engine) 的对象基类是 UObject。在 C++ 类、属性、函数上加 UCLASS/UPROPERTY/UFUNCTION 等宏,再由构建前的预处理工具虚幻头文件工具 (Unreal Header Tool, UHT) 扫描头文件生成代码,引擎就能在运行时“看见”这些类型——这就是反射系统 (Reflection System)。垃圾回收、序列化、编辑器面板、蓝图、网络复制都建立在它之上。游戏开发 · UE5Gameplay 框架Gameplay FrameworkGameplay 框架 (Gameplay Framework) 是 UE 提供的一组相互配合的类,用来搭建游戏的基本结构:Actor 与组件 (Component) 构成场景中的一切;GameMode 在服务器上定规则并生成玩家;玩家由“控制器 (Controller) + 棋子 (Pawn)”组成;GameState 与 PlayerState 负责把全局与每个玩家的状态复制给所有客户端;GameInstance 跨关卡存活。游戏开发 · UE5委托与 Tick 机制Delegates & TickUE 有两种驱动游戏逻辑的方式:事件驱动——委托 (Delegate) 在某事发生时回调绑定的函数;轮询驱动——Tick 每帧(或按最小间隔)调用 Actor/组件的更新函数,并按 Tick 组 (Tick Group) 与物理模拟排序。官方建议能用定时器、委托就不用 Tick,必须 Tick 时用 Tick 组和 Tick 依赖保证顺序,避免“差一帧”问题。游戏开发 · UE5模块、插件与构建系统Modules, Plugins & Build SystemUE 的代码由模块 (Module) 组成:每个模块是一个独立编译单元,用 [模块名].Build.cs 声明依赖。插件 (Plugin) 是可按项目启用/禁用的代码与内容集合,用 .uplugin 描述,可包含多个模块。虚幻构建工具 (UnrealBuildTool, UBT) 根据 .Target.cs 和 .Build.cs(而不是 IDE 的 .sln)编译,默认把模块编成 DLL 由一个可执行文件加载,也可整体链接成单体 (Monolithic) 可执行文件。游戏开发 · UE5动画蓝图与动画系统Animation Blueprint & Animation SystemUE5 角色动画以动画蓝图 (Animation Blueprint) 为核心:它每帧为骨骼网格体 (Skeletal Mesh) 计算最终姿势。动画蓝图内部用状态机 (State Machine) 组织“何时播什么”,用混合空间 (Blend Space) 按速度、方向等参数连续混合,用蒙太奇 (Animation Montage) 在插槽 (Slot) 中插播攻击、交互等一次性动作,用 Control Rig 做程序化骨骼控制(如贴地)。算出的骨骼姿势最终交给蒙皮 (Skinning) 驱动顶点。游戏开发 · UE5增强输入Enhanced Input增强输入 (Enhanced Input) 是 UE5 默认启用的输入插件,把输入拆成四层:输入动作 (Input Action) 表示“玩家想做什么”,输入映射上下文 (Input Mapping Context) 规定“哪些按键在什么情境下触发它”,输入修饰器 (Input Modifier) 预处理原始值,输入触发器 (Input Trigger) 判定是否触发。上下文可在运行时按优先级增删,从而用数据资产而非代码处理情境切换、按键冲突与改键。游戏开发 · UE5UMG、Slate 与 Common UIUMG, Slate & Common UISlate 是 UE 平台无关的 C++ 声明式 UI 框架(编辑器即用它构建),UMG 是在其之上的可视化控件蓝图层(UE 5.8 API:UWidget 是所有暴露给 UObject 的被包装 Slate 控件的基类),Common UI 插件再在 UMG 之上补充多层界面、输入路由、手柄导航、平台按键图标和样式资产。官方 UMG 最佳实践强调事件驱动更新而非绑定/Tick、固定分辨率与 DPI 制作、用 Scale Box 而非 Render Transform 做永久缩放。游戏开发 · UE5子系统Subsystems子系统 (Subsystem) 是自动实例化、生命周期绑定在宿主对象上的类:Engine、Editor、GameInstance、World、LocalPlayer 五种。宿主创建时自动创建并 Initialize(),宿主关闭时 Deinitialize(),之后无引用即被 GC。它替代了「继承 GameInstance 塞管理器」的老做法,自带按类型的蓝图节点和 Python 访问,特别适合插件。官方编程页只列四种,World 子系统依据 5.8 Python API 补充。游戏开发 · UE5行为树与 StateTreeBehavior Tree & StateTreeUE 的 AI 决策有两套工具:行为树 (Behavior Tree) + 黑板 (Blackboard) 是事件驱动的,条件写成装饰器 (Decorator),用 Simple Parallel、Services 和 Observer Aborts 代替传统并行节点;StateTree 是结合了行为树选择器和状态机转换的通用分层状态机,只在转换触发时按需选择状态,激活路径上的任务并发运行,通过属性绑定传递数据,也是 Mass 的行为层。优先级抢占多的战斗 AI 适合行为树,状态显式、分层或大规模的场景适合 StateTree。游戏开发 · UE5编辑器扩展与 Python 脚本Editor Extension & Python Scripting扩展 UE 编辑器有三条主要路线,门槛由低到高:Python 脚本(只在编辑器中可用,适合批量处理资产和串联外部 DCC 管线)、Editor Utility Widget(基于 UMG 的蓝图编辑器界面,做成自定义标签页),以及 C++ 编辑器插件(如 Editor Standalone Window 模板,用 Slate 的声明式语法构建窗口)。游戏开发字节码模式Bytecode Pattern字节码模式 (Bytecode Pattern) 把行为编码成虚拟机 (VM) 能执行的指令序列,让行为拥有数据一样的灵活性:可以热加载、与引擎核心隔离(沙盒化),还可以由非程序员编写。它是全书最复杂的模式,代价是要写编译器或创作工具,调试器也用不上了,而且这类小语言容易无节制地膨胀。游戏开发游戏脚本系统Game Scripting脚本语言是主要用来让用户控制和定制软件行为的编程语言;在游戏引擎中,它是高层、相对易用、能方便访问引擎大多数常用功能的语言,程序员和非程序员都能用它开发新游戏或给现有游戏做 mod。GEA 先区分数据定义语言和运行时脚本语言,梳理语言分类与游戏脚本的典型特征(解释执行、轻量、快速迭代、方便),介绍 QuakeC、UnrealScript、Lua、Python、Pawn,列出从"脚本回调"到"脚本驱动整个游戏"的六种架构,再以 Naughty Dog 的 DCScript 为例讲虚拟机与原生函数接口,最后是对象引用、事件收发、面向对象脚本、脚本状态机和协作式多线程脚本(GEA4 卷 II 17.游戏开发事件与消息传递Events & Message Passing游戏天然是事件驱动的:爆炸、玩家被敌人发现、血包被捡起都是事件,系统要通知感兴趣的对象并让它们以各自的方式处理。用虚函数 OnExplosion() 通知属于静态类型的后期绑定,迫使基类为所有可能的事件声明虚函数;GEA 主张把事件封装成对象(类型 + 参数),实现动态类型的后期绑定,从而获得单一处理函数、持久性和盲转发。GEA 依次讨论事件类型(枚举 vs 字符串/字符串 id)、参数(派生类、Variant 数组、键值对)、处理器、职责链、兴趣注册、排队与否的利弊,最后是数据驱动的事件系统和以数据通路连接端口的图形化系统(如 UE 蓝图)(GEA4 卷 II 17.8)。游戏开发子类沙盒Subclass Sandbox子类沙盒 (Subclass Sandbox) 让基类提供一组受保护的“提供的操作”和一个抽象的沙盒方法,派生类只用这些操作来实现沙盒方法。这样大量派生类(例如几十种超能力)只耦合到基类,不直接接触引擎的各个系统;代价是基类会越来越臃肿,变成脆弱基类。综合游戏编程模式选型:解耦与性能Choosing Game Programming Patterns本页把《Game Programming Patterns》里的 19 个模式放到同一张取舍图上:大多数模式要么为了解耦(降低改代码时需要装进脑子里的知识量),要么为了性能(利用对数据和硬件的具体假设),而这两个目标经常互相拉扯。下面先讲全书的定位和 Nystrom 的架构观,再给出按问题选模式的路线和与 UE 的对照。