当前位置: 首页 > news >正文

Unreal对C++做了什么 · 第 17 章 · C++ ↔ Blueprint:反射的第一回报

第 17 章 · C++ ↔ Blueprint:反射的第一回报

第 2 章的全景图里,反射系统向下分出多条支路:GC、序列化、Blueprint 桥接、网络同步、编辑器集成。其中"让蓝图看见 C++"是最直观的一条——设计师在蓝图里拖节点、连线,节点背后是 C++ 的函数和属性。没有反射,蓝图无法知道一个 C++ 类有哪些可调用的方法、有哪些可读写的变量。

本章讲反射如何为蓝图打开这扇门,以及 C++ 与蓝图之间的协作边界。


17.1 反射为蓝图打开的门

蓝图是解释执行的。它不直接持有 C++ 函数指针——它持有的是"在某个对象上调用名为 X 的函数"这样的描述。执行时,由引擎通过反射把描述变成真正的调用。

调用函数:蓝图节点"Call function MaxHealth" 在运行时等价于:

  1. 拿到目标对象(例如AMyCharacter*);
  2. 通过UClass::FindFunctionByName("GetMaxHealth")拿到UFunction*
  3. 准备参数块(若有参数);
  4. 调用Target->ProcessEvent(Func, &Params)
  5. ProcessEvent 内部通过 UFunction 的 thunk 转调你的 C++ 实现。

读写属性:蓝图节点 “Get MaxHealth” 等价于:

  1. 通过UClass::FindPropertyByName("MaxHealth")拿到FProperty*
  2. FProperty::ContainerPtrToValuePtr<float>(Target)得到内存地址;
  3. 读取或写入该地址的值。

也就是说,蓝图不"认识"C++,它只认识UClass / UFunction / FProperty。反射是两者之间的唯一桥梁。你在 C++ 里写的UFUNCTION(BlueprintCallable)UPROPERTY(BlueprintReadWrite),本质上是告诉 UHT 和反射系统:把这些成员登记到元数据里,并打上"对蓝图可见"的标记,蓝图编辑器查询元数据时就能列出它们并生成对应节点。


17.2 C++ 暴露给蓝图

函数:BlueprintCallable / BlueprintPure

UFUNCTION(BlueprintCallable,Category="Combat")voidTakeDamage(floatAmount);UFUNCTION(BlueprintPure,Category="Combat")floatGetCurrentHealth()const;
  • BlueprintCallable:蓝图可以调用,可以有副作用(改状态、产生效果)。在蓝图中是"执行"类节点(有执行线)。
  • BlueprintPure:无副作用,只做计算。在蓝图中是纯数据节点(无执行线,只有数据线),可被优化掉重复计算。

Category决定在蓝图节点菜单里的分组,方便设计师查找。

参数与返回值:蓝图支持的参数/返回值类型是有限的——基本类型、FVector、FRotator、FString、FName、FText、UObject*、TSubclassOf、TArray、部分 USTRUCT 等。复杂模板、裸指针、自定义非 USTRUCT 类型不能直接暴露,需要包成 USTRUCT 或拆成多个简单参数。下表是常见类型的暴露情况:

C++ 类型蓝图是否支持备注
int32、float、bool、FName、FString、FText直接对应
FVector、FRotator、FTransform、FLinearColor引擎内置结构体
UObject*、AActor*、TSubclassOf<UClass>引用需注意 GC
TArray<T>T 需为蓝图支持类型
USTRUCT() 自定义结构体需 BlueprintType,且成员类型受限
裸指针、TSharedPtr、复杂模板需用 UObject* 或 USTRUCT 包装

属性:BlueprintReadWrite / BlueprintReadOnly

UPROPERTY(EditAnywhere,BlueprintReadWrite,Category="Combat")floatMaxHealth;UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category="Combat")floatCurrentHealth;
  • BlueprintReadWrite:蓝图可读可写,常用于配置或运行时修改。
  • BlueprintReadOnly:蓝图只能读,适合派生值或由 C++ 权威维护的状态。

属性类型同样受蓝图类型系统限制,需在引擎支持的属性类型范围内。


17.3 蓝图覆盖 C++

有时你希望"框架在 C++,行为在蓝图"——C++ 定义接口和流程,具体逻辑由蓝图实现。

BlueprintImplementableEvent

UFUNCTION(BlueprintImplementableEvent)voidOnWeaponFired();

C++ 里只有声明,没有实现。实现完全在蓝图中:设计师在蓝图里重写该事件并编写逻辑。调用时,若当前对象是蓝图子类且实现了该事件,就执行蓝图实现;否则什么都不执行。

适合"可选扩展点"——C++ 在合适时机调用,有没有蓝图实现都可以。

BlueprintNativeEvent

UFUNCTION(BlueprintNativeEvent)voidOnDeath();virtualvoidOnDeath_Implementation();// .cppvoidAMyCharacter::OnDeath_Implementation(){// C++ 默认实现PlayDeathAnimation();}

C++ 提供默认实现(_Implementation后缀是 UHT 的约定),蓝图可以覆盖。运行时:若蓝图有覆盖则执行蓝图的,否则执行 C++ 的_Implementation。这样既能在 C++ 里写通用逻辑,又能在蓝图中按需定制。


17.4 动态委托与蓝图事件

要让蓝图"订阅"C++ 里的事件,必须用动态多播委托(第 11 章):普通委托存的是函数指针,蓝图无法绑定;动态委托存的是函数名,通过反射调用,蓝图才能识别。

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthChanged,float,NewHealth);UPROPERTY(BlueprintAssignable,Category="Combat")FOnHealthChanged OnHealthChanged;

BlueprintAssignable让该委托在蓝图中显示为"事件分发器",设计师可以在蓝图中绑定"当 Health 改变时"的逻辑。C++ 里在适当时机OnHealthChanged.Broadcast(CurrentHealth)即可。


17.5 底层一瞥:ProcessEvent 与 thunk

蓝图"Call function" 最终会走到UObject::ProcessEvent(UFunction* Function, void* Params)。这里Params是一块由引擎按 UFunction 的反射信息分配好的参数块,按偏移存放各参数;Function指向的 UFunction 里有一个函数指针,指向一段由 UHT 生成的thunk代码。

thunk 的作用是:从 Params 里按布局取出参数,转成 C++ 调用约定,再调用你写的 C++ 实现(例如AMyCharacter::TakeDamage(float))。也就是说,蓝图 → 反射 → thunk → 你的函数,中间没有"解释执行你的 C++",而是真正的原生调用。所以 C++ 实现的函数性能与直接调用一致;蓝图侧的消耗主要在"查 UFunction、组参数块、走 ProcessEvent"这几步。


17.6 C++ 与蓝图的协作边界

性能:蓝图是解释执行,每帧大量 Tick、物理、碰撞等仍适合放在 C++。复杂算法、密集循环也应用 C++。

迭代速度:玩法调整、数值调试、关卡逻辑用蓝图更快,无需重编。

团队分工:常见模式是 C++ 提供基类和接口(Actor、Component、事件和可覆盖函数),策划和关卡在蓝图子类里组装行为、调参数、连事件。

架构建议:核心框架、网络权威逻辑、引擎交互用 C++;表现层、关卡逻辑、一次性脚本用蓝图。避免在蓝图中实现复杂状态机或深层继承,必要时用 C++ 抽成子系统再在蓝图中调用。

维度更适合作业原因
性能敏感路径C++蓝图解释 + 反射调用有固定开销
数值/公式/算法C++类型与调试更可控
玩法原型、关卡逻辑蓝图无需编译,策划可改
网络权威、复制逻辑C++避免蓝图侧误改权威状态
UI 表现、一次性脚本蓝图迭代快,与设计师协作方便

实验:在蓝图中验证反射暴露

  1. 暴露一个 BlueprintCallable。在任意 C++ Actor 中加一句:UFUNCTION(BlueprintCallable, Category = "Test") void MyBlueprintCall() { UE_LOG(LogTemp, Warning, TEXT("Called from Blueprint")); },编译后创建该 Actor 的蓝图子类,在事件图表里搜索 “My Blueprint Call”,拖出节点并连接到 Event BeginPlay,运行后查看 Output Log 是否打印。
  2. 暴露一个 BlueprintImplementableEvent。声明UFUNCTION(BlueprintImplementableEvent) void OnCustomEvent();,在蓝图子类里重写 “On Custom Event”,在 C++ 的BeginPlay里调用OnCustomEvent();若在蓝图里实现了该事件,应执行蓝图逻辑,否则无效果。
  3. 观察 Pure 与 Callable 的差异。在蓝图中分别使用一个 BlueprintPure 和一个 BlueprintCallable 节点,注意 Pure 没有执行引脚、可被优化;Callable 有执行引脚,适合有副作用的调用。

一句话总结

蓝图通过反射查询 UFunction 和 FProperty 来调用 C++ 和读写属性;C++ 用 BlueprintCallable/BlueprintReadWrite 暴露接口,用 BlueprintImplementableEvent/BlueprintNativeEvent 提供扩展点,用动态多播委托暴露事件,二者在反射之上形成清晰分工。

http://www.jsqmd.com/news/508938/

相关文章:

  • AudioSeal入门必看:水印密钥管理、私钥保护与多租户隔离实践建议
  • 2026年席梦思床垫厂家推荐排行榜:席梦思弹簧床垫/席梦思乳胶床垫/席梦思独立袋装弹簧床垫,护脊深睡科技之选 - 品牌企业推荐师(官方)
  • 如何提升React Error Boundary单元测试覆盖率:7个实用测试策略
  • Nanbeige 4.1-3B企业实操:SaaS平台嵌入像素终端提升用户停留时长
  • 《OpenClaw架构与源码解读》· 第 16 章 运维日常:升级、排障、模型 Failover
  • OpenAI收购Python工具开发商Astral以增强编程实力
  • Meixiong Niannian画图引擎参数详解:随机种子-1的多样性熵值与采样分布
  • 别让第三方 Logo 毁了你的百万合同!Wyn BI 深度白标“伪装”指南
  • React Error Boundary 终极升级指南:6.0版本平滑迁移完整清单
  • 如何为JTAppleCalendar构建完整的持续集成监控体系:提升iOS日历库的构建健康度与告警机制
  • 丹青幻境效果惊艳!实测4090优化下的国风AI绘画作品集
  • HY-Motion 1.0与Vue3前端框架集成:实时动作预览系统
  • 掌握spy-debugger快捷键:提升移动端Web调试效率的10个必备技巧
  • Maestro与GitLab CI集成:构建完整DevOps测试流程的终极指南
  • 如何利用sebastian/diff实现PHP代码差异对比:完整的文档注释实践指南
  • 通义千问3-4B工具调用踩坑记:Python调用API部署教程
  • Python 利用 SeleniumWire 高效解析动态网页的请求与响应数据
  • Napa.js开源贡献完全指南:从发现Issue到提交PR的终极流程
  • 终极指南:如何为grex命令行工具构建自动化测试框架
  • 终极指南:如何用Squirrel快速实现Go语言CRUD操作
  • Maestro与Linkerd集成:微服务UI测试策略
  • IndexTTS 2.0实战:快速为短视频生成带情绪的AI配音,效果惊艳
  • TCT亚洲展现场,金石三维签约超3000万元3D打印机大单!
  • ni终极指南:10个技巧让你成为JavaScript包管理专家
  • 掌握asyncpg连接池事件:监控与回调的终极指南
  • 如何实现Prometheus与BigQuery集成:数据库监控的终极指南
  • Qwen-Image入门必看:通义千问视觉模型在RTX4090D上的加载速度与响应优化
  • 10个RAP2-delos接口文档批量操作技巧:让你的API管理效率提升300%
  • 电话号码字母组合回溯解法详解(Python,Java解法)
  • 为什么92%的低轨终端在-40℃下功耗暴增?揭秘C语言浮点运算、内存对齐与时钟门控的隐性耗电黑洞