蓝图与 C++ 互调

自动关联目录:蓝图与 C++ 互调

官方依据:Blueprints Visual Scripting 已明确——C++ 实现中的 Blueprint 专用标记让程序员能创建可被设计者扩展的基线系统。本篇就是这套标记的用法与代价。

1. 定位

互调的纽带是反射(见 反射与 UHT):C++ 用宏把类型信息暴露出去,蓝图才能看见并调用。

一句话定位:两个方向的调用机制完全不同——C++ 调用蓝图靠"事件钩子",蓝图调用 C++ 靠"反射可见"。

2. 标记速查

让蓝图能看见 C++

标记作用
UCLASS(Blueprintable)可被蓝图继承
UPROPERTY(BlueprintReadWrite)蓝图可读写
UPROPERTY(BlueprintReadOnly)蓝图只读
UFUNCTION(BlueprintCallable)蓝图可调用
UFUNCTION(BlueprintPure)无副作用的纯函数
UFUNCTION(BlueprintImplementableEvent)C++ 声明、蓝图实现
UFUNCTION(BlueprintNativeEvent)C++ 有默认实现、蓝图可覆写
USTRUCT(BlueprintType)结构体可用于蓝图
UENUM(BlueprintType)枚举可用于蓝图

忘标 UPROPERTY 的字段对蓝图不存在——这是"蓝图里看不到这个变量"的唯一原因(见 反射与 UHT)。

让 C++ 能触发蓝图

// 声明:蓝图里实现
UFUNCTION(BlueprintImplementableEvent)
void OnDamaged(float Amount);

// C++ 里触发(蓝图的实现会被调用)
OnDamaged(50.f);

// 带默认实现版本
UFUNCTION(BlueprintNativeEvent)
void OnDeath();
void AMyActor::OnDeath_Implementation() { /* C++ 默认实现 */ }

BlueprintNativeEvent 的实现必须写在 _Implementation 后缀的函数里,直接写原函数名会链接失败。

3. 反向:蓝图调 C++

UFUNCTION(BlueprintCallable, Category = "Combat")
void ApplyDamage(AActor* Target, float Amount);

蓝图中直接作为节点出现。代价:走一次反射调用(比直接 C++ 调用慢,见下)。

4. 代价与权衡

设计收益代价什么时候不该用
BlueprintCallable蓝图能用反射调用开销每帧高频调用不应走蓝图
BlueprintImplementableEvent解耦,C++ 不关心实现C++ 侧无法保证有实现必须有默认行为时用 NativeEvent
BlueprintNativeEvent有默认实现调用多一层间接纯 C++ 逻辑不必用它
BlueprintReadWrite蓝图可调参外部可随意改,破坏封装内部状态应只读或私有
BlueprintPure无副作用,蓝图里清爽被多次调用时可能重复计算昂贵计算不要标 Pure

5. 踩坑与排查

坑现象怎么验证
蓝图看不到变量/函数不存在检查是否标了 UPROPERTY / UFUNCTION
BlueprintNativeEvent 链接失败编译错误实现是否写成 _Implementation
热路径走 BlueprintCallable慢改为 C++ 内部调用
BlueprintPure 里做重计算被重复调用去掉 Pure,或缓存
蓝图改了不该改的字段状态混乱改 BlueprintReadOnly,或加校验
忘记 Blueprintable无法继承检查 UCLASS 标记
结构体/枚举在蓝图不可用选不到类型加 BlueprintType

6. 排查顺序

1. 蓝图看不到?→ 反射标记
2. C++ 调不动蓝图?→ 是否用 ImplementableEvent/NativeEvent 触发
3. NativeEvent 报错?→ _Implementation 后缀
4. 慢?→ 热路径是否走了反射调用
5. 状态被乱改?→ 收紧读写权限

参考