关于 ue-blueprint-cpp-interop
面向 UE 5.8 开发者的 C++ 与 Blueprint 互操作技能,说明反射说明符如何塑造节点,以及原生代码如何向 Blueprint 暴露数据、操作和事件。
主要功能
- 01UFUNCTION 节点与事件配置
- 02UPROPERTY 数据暴露规则
- 03函数库与异步节点模式
- 04Blueprint 接口和委托处理
- 05原生代码调用 Blueprint
适用场景
- 01让 C++ 函数可在 Blueprint 中调用或重写
- 02为属性配置 Blueprint 读写方式
- 03实现延迟或异步 Blueprint 节点
- 04从原生代码触发 Blueprint 逻辑
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ quodsoler/ unreal-engine-skills --skill ue-blueprint-cpp-interop安装后在 Claude Code 中输入/ue-blueprint-cpp-interop确认已加载。
附带文件references/blueprint-meta-specifiers.mdreferences/blueprint-node-patterns.md
详细介绍
UE Blueprint / C++ 互操作
目标引擎:UE 5.8。下文中的 API 均已根据 5.8 头文件验证;旧版形式列于“已弃用 — 请勿使用”部分。
本技能涵盖原生代码与 Blueprint VM 之间的边界:哪些 UFUNCTION/UPROPERTY 限定符和 meta= 键会生成哪些节点形态、函数库、潜伏节点和异步节点、Blueprint 可实现的接口,以及 C++ 如何回调 Blueprint 图表。这里涉及的所有内容都属于 Core、CoreUObject 和 Engine(PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine" }););Project Settings 页面还需要 DeveloperSettings。每个限定符的权威来源都是 Runtime/CoreUObject/Public/UObject/ObjectMacros.h——任何未在其 UC、UI、UF、UP、US 或 UM 枚举中声明的内容都不是限定符。
上下文
如果 .agents/ue-project-context.md 存在,请阅读它(模块名称、约定、已启用的插件、GAS/网络设置)。如果文件不存在,也请继续操作。
根据请求和代码库确定相关领域。只有在两种合理解读会产生不同代码时才提问。
| 请求内容…… | 前往 |
|---|---|
| 使 C++ 函数可在 Blueprint 中调用或重写 | UFUNCTION 暴露 |
| 使数据在 Blueprint 中可见或可编辑,或使用 getter/setter | UPROPERTY 暴露 |
静态辅助函数、WorldContext、DefaultToSelf | 函数库 |
| 带有执行引脚、稍后才完成的节点(Delay 风格) | 潜伏节点 |
| 具有多个输出执行引脚、由事件触发的节点 | 异步操作节点 |
Blueprint 可实现的接口、Execute_、TScriptInterface | Blueprint 接口 |
UENUM、USTRUCT、作为 Blueprint 类型的容器 | Blueprint 中的枚举和结构体 |
BlueprintAssignable 事件、将 Blueprint 绑定到 C++ 委托 | 委托和事件 |
| 从原生代码运行 Blueprint 逻辑、Blueprint 类 | 从原生代码调用 Blueprint |
子系统、UDeveloperSettings、Exec 命令 | Blueprint 中的子系统和设置 |
| Details 面板中的按钮、仅限编辑器的操作 | 可在编辑器中调用的函数 |
UFUNCTION 暴露方式
| 说明符 | 节点形状 | C++ 函数体 |
|---|---|---|
BlueprintCallable | 带执行引脚输入和输出 | 由你实现 |
BlueprintPure | 不带执行引脚;隐含 BlueprintCallable | 由你实现,且无副作用 |
BlueprintImplementableEvent | 由 Blueprint 实现的事件 | 无 — UHT 会生成该跳板函数 |
BlueprintNativeEvent | Blueprint 可以重写的事件 | 仅实现 Name_Implementation |
BlueprintGetter / BlueprintSetter | 隐藏;通过属性访问 | 由你实现 |
BlueprintAuthorityOnly | 缺少网络权限时会跳过 Blueprint 调用(直接调用 C++ 仍会执行) | 由你实现 |
BlueprintCosmetic | 专用服务器上会跳过 Blueprint 调用(直接调用 C++ 仍会执行) | 由你实现 |
BlueprintInternalUseOnly | 不会显示在面板中;只有生成的节点会调用它 | 由你实现 |
CallInEditor | Details 面板上的按钮 | 由你实现 |
Exec | 控制台命令 | 由你实现 |
Category="A\|B" 会构建嵌套的面板类别。用于调整节点形状的 meta= 键——DisplayName、ToolTip、Keywords、CompactNodeTitle、AdvancedDisplay、ReturnDisplayName、HidePin、DeterminesOutputType、DynamicOutputParam、ArrayParm、ExpandEnumAsExecs、WorldContext、UnsafeDuringActorConstruction、DevelopmentOnly、BlueprintThreadSafe 等——均在 references/blueprint-meta-specifiers.md 中列出,并附有标头引用。
// MyActor.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"
UCLASS(Blueprintable)
class MYGAME_API AMyActor : public AActor
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category="MyGame|Combat", meta=(Keywords="damage hurt"))
void ApplyDamage(float Amount);
UFUNCTION(BlueprintPure, Category="MyGame|Combat", meta=(CompactNodeTitle="HP", ReturnDisplayName="Health"))
float GetHealth() const;
/** Blueprint supplies the whole body; C++ never defines this. */
UFUNCTION(BlueprintImplementableEvent, Category="MyGame|Combat")
void OnDamaged(float Amount);
/** C++ supplies the default in OnDied_Implementation; Blueprint may override it. */
UFUNCTION(BlueprintNativeEvent, Category="MyGame|Combat")
void OnDied();
virtual void OnDied_Implementation();
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="MyGame|Combat")
void ResetOnServer() { Health = MaxHealth; } // Blueprint calls skipped on clients
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="MyGame|FX")
void PlayHitFlash() {} // Blueprint calls skipped on a dedicated server
UFUNCTION(CallInEditor, Category="MyGame|Tools")
void SnapToGround() {} // button in the Details panel
private:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="MyGame|Combat", meta=(AllowPrivateAccess="true", ClampMin="0.0"))
float Health = 100.f;
float MaxHealth = 100.f;
};
// MyActor.cpp
#include "MyActor.h"
void AMyActor::ApplyDamage(float Amount)
{
Health = FMath::Max(0.f, Health - Amount);
OnDamaged(Amount); // runs the Blueprint graph
if (Health <= 0.f) { OnDied(); } // runs the BP override if any, else OnDied_Implementation
}
float AMyActor::GetHealth() const { return Health; }
void AMyActor::OnDied_Implementation() {} // the C++ default, overridable in Blueprint
通过纯名称引发事件(OnDied()),绝不要调用 OnDied_Implementation()——纯名称是生成的跳板函数,会将调用分派到 Blueprint。子类重写 virtual void OnDied_Implementation() override;,并通过 Super::OnDied_Implementation(); 调用父类实现,绝不要调用 Super::OnDied();。
参数和引脚
| C++ 参数 | Blueprint 引脚 |
|---|---|
float Amount | 输入 |
const FVector& Base | 输入,按引用传递 |
FVector& Out | 输出引脚,而非输入 |
UPARAM(ref) FVector& InOut | 按引用传递的输入,同时也是输出 |
UPARAM(DisplayName="Event") FMyReadySignature Callback | 节点上重命名的输入 |
裸 T& 会由 UHT 添加 OutParm 标志;UPARAM(ref) 会添加 ReferenceParm,使引脚保留为输入。meta=(AutoCreateRefTerm="Offset") 会在引用输入未连接时,为其提供内联字面值。
UPROPERTY 暴露
| 限定符 | 在 Blueprint 中的效果 |
|---|---|
BlueprintReadWrite | Get 和 Set 节点 |
BlueprintReadOnly | 仅 Get 节点 |
BlueprintGetter=Fn | Get 通过 UFUNCTION(BlueprintGetter) 路由 |
BlueprintSetter=Fn | Set 通过 UFUNCTION(BlueprintSetter) 路由;隐含 BlueprintReadWrite |
BlueprintAssignable | 绑定事件节点(仅限多播动态委托) |
BlueprintCallable | 广播节点(仅限多播动态委托) |
BlueprintAuthorityOnly | 委托仅接受带有 BlueprintAuthorityOnly 的事件 |
在 private 成员上使用 BlueprintReadWrite/BlueprintReadOnly 会导致 UHT 错误(“BlueprintReadWrite should not be used on private members”),除非属性带有 meta=(AllowPrivateAccess="true")。仅限编辑器的属性不能作为 Blueprint 暴露的结构体成员。编辑器 meta= 键(ClampMin/ClampMax、UIMin/UIMax、EditCondition、EditConditionHides、InlineEditConditionToggle、TitleProperty、ShowOnlyInnerProperties、MultiLine、GetOptions、AllowedClasses、MustImplement、Categories、Bitmask/BitmaskEnum、ExposeOnSpawn):references/blueprint-meta-specifiers.md。
| C++ 成员类型 | Blueprint 引脚 |
|---|---|
double / float | Real |
TObjectPtr<UStaticMesh> | 对象引用 |
TSubclassOf<AActor> | 类(由 meta=(MustImplement=…)、AllowedClasses、BlueprintBaseOnly 筛选) |
TSoftObjectPtr<T> / TSoftClassPtr<T> | 软引用;Blueprint 会按需解析或加载 |
FInstancedStruct | 结构体引脚,其具体类型在编辑器中选择 |
TScriptInterface<IMyInteractable> | 接口引用 |
FMyHealthChangedSignature(多播动态) | 通过 BlueprintAssignable 使用绑定事件/广播节点 |
FTimerHandle | USTRUCT(BlueprintType) 句柄,直接传递 |
展示 ExposeOnSpawn、EditCondition/EditConditionHides、InlineEditConditionToggle、MustImplement 和一对 BlueprintGetter/BlueprintSetter 的组件示例:references/blueprint-meta-specifiers.md。
浮点数和双精度数。 float 和 double 属性在 Blueprint 中都会显示为 Real 引脚(UEdGraphSchema_K2 会将 FFloatProperty 和 FDoubleProperty 映射为带有 PC_Float/PC_Double 子类别的 PC_Real),而 Blueprint 创建的变量是双精度数。对于流经 Blueprint 数学运算的标量,请声明为 double,以免插入窄化转换;仅在引擎类型本身已是 float 时保留 float。
函数库
UBlueprintFunctionLibrary(Kismet/BlueprintFunctionLibrary.h)是 UCLASS(Abstract),且所有成员都是 static。其 CDO 上的 GetWorld() 会返回 null,因此应传入 const UObject* WorldContextObject,为其添加标签 meta=(WorldContext="WorldContextObject"),并使用 GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull) 解析;meta=(DefaultToSelf="Param") 会将引脚预先连接到调用对象。
// MyBlueprintLibrary.h
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Templates/SubclassOf.h"
#include "MyBlueprintLibrary.generated.h"
class AActor;
UENUM(BlueprintType)
enum class EMyConsumeResult : uint8
{
Consumed,
NotEnough
};
UCLASS()
class MYGAME_API UMyBlueprintLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
/** WorldContextObject is hidden on the node and filled in by the graph. */
UFUNCTION(BlueprintCallable, Category="MyGame|Utility", meta=(WorldContext="WorldContextObject"))
static int32 CountPawns(const UObject* WorldContextObject);
/** The return pin takes the type plugged into ActorClass, so Blueprint needs no cast. */
UFUNCTION(BlueprintPure, Category="MyGame|Utility", meta=(DefaultToSelf="Target", DeterminesOutputType="ActorClass"))
static AActor* FindAttachedActorOfClass(AActor* Target, TSubclassOf<AActor> ActorClass);
/** One input enum becomes one output exec pin per enumerator. */
UFUNCTION(BlueprintCallable, Category="MyGame|Utility", meta=(ExpandEnumAsExecs="Result"))
static void TryConsume(int32 Available, int32 Amount, EMyConsumeResult& Result);
};
对应的 .cpp:references/blueprint-node-patterns.md。ExpandEnumAsExecs 只能接受一个输入参数(第二个参数会导致 UHT 报错);将返回值命名为 ReturnValue 以展开返回值,bool 则使用 ExpandBoolAsExecs。通配符数组节点使用 meta=(ArrayParm="Items", ArrayTypeDependentParams="Item")。
潜伏节点
潜伏节点会在多个帧之间保留一个待处理的执行引脚。它需要 meta=(Latent, LatentInfo="LatentInfo", WorldContext="WorldContextObject")、一个末尾的 FLatentActionInfo 参数,以及一个注册到世界 FLatentActionManager 的 FPendingLatentAction 子类。引擎参考实现是 UKismetSystemLibrary::Delay 和 FDelayAction(Engine/Public/DelayAction.h)。
// MyLatentLibrary.h — declaration
UFUNCTION(BlueprintCallable, Category="MyGame|Flow", meta=(Latent, LatentInfo="LatentInfo", WorldContext="WorldContextObject", Duration="1.0"))
static void WaitSeconds(const UObject* WorldContextObject, float Duration, FLatentActionInfo LatentInfo);
// MyLatentLibrary.cpp — the UUID guard is mandatory
void UMyLatentLibrary::WaitSeconds(const UObject* WorldContextObject, float Duration, FLatentActionInfo LatentInfo)
{
if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull))
{
FLatentActionManager& Manager = World->GetLatentActionManager();
if (Manager.FindExistingAction<FMyWaitAction>(LatentInfo.CallbackTarget, LatentInfo.UUID) == nullptr)
{
Manager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, new FMyWaitAction(Duration, LatentInfo));
}
}
}
如果没有 FindExistingAction 防护,重新进入节点会在同一 UUID 上叠加第二个操作,导致执行引脚触发多次。操作子类会重写 UpdateOperation(FLatentResponse& Response),并使用 Response.FinishAndTriggerIf(bDone, ExecutionFunction, OutputLink, CallbackTarget) 完成;它会在构造函数中从 FLatentActionInfo 复制 ExecutionFunction、Linkage 和 CallbackTarget,还会重写 NotifyObjectDestroyed()、NotifyActionAborted(),以及在 WITH_EDITOR 下重写 GetDescription() const。FLatentResponse 还提供了 DoneIf(bool) 和 TriggerLink(ExecutionFunction, LinkID, CallbackTarget),可用于处理多个退出链接。完整的子类及 FLatentActionManager 的其余 API:参见references/blueprint-node-patterns.md。
异步操作节点
UBlueprintAsyncActionBase(Kismet/BlueprintAsyncActionBase.h)会生成一个包含一个执行输入、且每个 BlueprintAssignable 委托对应一个输出执行引脚的节点。静态工厂必须带有 meta=(BlueprintInternalUseOnly="true"),这样只有生成的节点才能调用它。
// MyWaitForHealthAction.h
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintAsyncActionBase.h"
#include "MyWaitForHealthAction.generated.h"
class UMyStatsComponent;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMyHealthThresholdSignature, double, Health);
UCLASS()
class MYGAME_API UMyWaitForHealthAction : public UBlueprintAsyncActionBase
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category="MyGame|Async", meta=(BlueprintInternalUseOnly="true", WorldContext="WorldContextObject", DisplayName="Wait For Health Below"))
static UMyWaitForHealthAction* WaitForHealthBelow(UObject* WorldContextObject, UMyStatsComponent* Stats, double Threshold);
virtual void Activate() override;
UPROPERTY(BlueprintAssignable)
FMyHealthThresholdSignature OnReached; // one output exec pin
UPROPERTY(BlueprintAssignable)
FMyHealthThresholdSignature OnFailed; // a second output exec pin
private:
UFUNCTION()
void HandleHealthChanged(double NewHealth); // AddDynamic targets must be UFUNCTIONs
UPROPERTY()
TObjectPtr<UMyStatsComponent> TrackedStats;
double ThresholdValue = 0.0;
};
工厂会执行 NewObject<UMyWaitForHealthAction>(),存储输入,调用 RegisterWithGameInstance(WorldContextObject) 并返回操作对象;随后节点绑定委托并调用 Activate(),由此开始执行。每条完成路径都会广播一次,并调用 SetReadyToDestroy()。未注册的操作只会存活到创建它的那一帧(RF_StrongRefOnFrame),因此稍后触发的委托会发现该对象已被回收。当同时存活的操作数量超过 bp.MaxAsyncActionCount(默认值为 10000)时,引擎会确保这一点。
UCancellableAsyncAction(Engine/CancellableAsyncAction.h,UCLASS(Abstract, BlueprintType, meta=(ExposedAsyncProxy=AsyncAction)))新增了 Cancel()、IsActive() const 和 ShouldBroadcastDelegates() const,使节点能够返回对象供之后取消;每次广播前都应检查 ShouldBroadcastDelegates()。完整的 Activate/Cancel 实现:参见references/blueprint-node-patterns.md。
蓝图接口
UINTERFACE 自身的说明符(UI::)包括 MinimalAPI、Blueprintable、NotBlueprintable 和 ConversionRoot,以及 meta=;UHT 共享的 BlueprintType 也受支持,引擎接口会使用它,使接口可用作蓝图变量类型。蓝图可以实现 BlueprintImplementableEvent 和 BlueprintNativeEvent 成员;普通的 virtual C++ 方法对蓝图不可见。
// MyInteractable.h
#pragma once
#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "MyInteractable.generated.h"
class AActor;
UINTERFACE(Blueprintable, BlueprintType, MinimalAPI)
class UMyInteractable : public UInterface
{
GENERATED_BODY()
};
class MYGAME_API IMyInteractable
{
GENERATED_BODY()
public:
/** Blueprint-only implementations are allowed; C++ implementers write Interact_Implementation. */
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="MyGame|Interaction")
bool Interact(AActor* Interactor);
virtual bool Interact_Implementation(AActor* Interactor);
virtual float GetInteractRange() const { return 200.f; } // native-only: invisible to Blueprint
};
// Calling an interface that may be implemented in C++ or in Blueprint
bool MyTryInteract(UObject* Target, AActor* Interactor)
{
return Target && Target->Implements<UMyInteractable>()
&& IMyInteractable::Execute_Interact(Target, Interactor);
}
| 形式 | C++ 实现者 | 蓝图实现者 |
|---|---|---|
IMyInteractable::Execute_Interact(Obj, …) | 是 | 是 — 始终使用此形式 |
Obj->Implements<UMyInteractable>() | 是 | 是 |
Obj->GetClass()->ImplementsInterface(UMyInteractable::StaticClass()) | 是 | 是 |
Cast<IMyInteractable>(Obj) 后直接调用 | 是 | 否 — 转换结果为 null |
TScriptInterface<IMyInteractable> 成员 | 是 | 接口指针为 null;仍需通过 Execute_ 调用 |
TScriptInterface<T>(UObject/ScriptInterface.h)会将对象和接口指针一同存储,文档说明它仅适用于原生接口。声明 UPROPERTY(EditAnywhere, BlueprintReadWrite) TScriptInterface<IMyInteractable> Target;,即可让设计人员获得经过筛选的选择器;调用时仍需通过 Execute_ 分发。
Blueprint 中的枚举和结构体
// MyTypes.h
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataTable.h"
#include "MyTypes.generated.h"
UENUM(BlueprintType)
enum class EMyWeaponState : uint8 // BlueprintType enums must be uint8-based
{
Idle,
Firing UMETA(DisplayName="Firing Weapon"),
Reloading,
MAX UMETA(Hidden)
};
USTRUCT(BlueprintType)
struct MYGAME_API FMyWeaponStats
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Weapon")
double BaseDamage = 10.0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Weapon")
TArray<FName> Tags; // container of a Blueprint-supported type
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Weapon")
TMap<FName, double> Modifiers; // key and value must both be Blueprint-supported
};
USTRUCT(BlueprintType)
struct MYGAME_API FMyWeaponRow : public FTableRowBase // data table row type
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Weapon")
FMyWeaponStats Stats;
};
UENUM(BlueprintType)要求使用enum class : uint8;UHT 会拒绝任何其他基类型(“Invalid BlueprintType enum base - currently only uint8 supported”)。UMETA(DisplayName="…")可重命名枚举项,UMETA(Hidden)可将其从选择器中隐藏。USTRUCT(BlueprintType)会提供 Make 和 Break 节点。结构体不能包含UFUNCTION——请将结构体辅助函数放在函数库中。- 不支持嵌套容器。
TArray、TSet和TMap会在 UHT 中清除自身作为容器键/值的能力,因此TArray<TArray<int32>>和TMap<FName, TArray<int32>>无法编译。请将内部容器封装在USTRUCT中。 - 只有当容器的元素类型——以及
TMap的键类型——本身受 Blueprint 支持时,该容器才能在 Blueprint 中使用。 FTableRowBase(Engine/DataTable.h)是USTRUCT(BlueprintInternalUseOnly);请让行结构体继承它,并将派生结构体标记为BlueprintType。表加载和行查找:ue-data-assets-tables。FInstancedStruct(StructUtils/InstancedStruct.h)是USTRUCT(BlueprintType),可为 Blueprint 提供一个引脚,其具体结构体类型可在编辑器中选择;TInstancedStruct<FMyBase>是带类型的 C++ 封装。
委托和事件
只有动态委托可以跨越 Blueprint 边界。声明宏和原生绑定规则:ue-cpp-foundations。
- 在多播动态委托上使用
UPROPERTY(BlueprintAssignable),可为 Blueprint 提供 Bind Event 节点;同时添加BlueprintCallable还会提供 Broadcast 节点。两者都只能用于多播动态委托。 - 委托参数必须命名——它们会成为事件节点上的引脚名称。
- 任何使用
AddDynamic绑定的 C++ 处理程序都必须是UFUNCTION();否则运行时名称查找会失败,处理程序也不会执行。 - 单播动态委托(
DECLARE_DYNAMIC_DELEGATE、DECLARE_DYNAMIC_DELEGATE_OneParam等)可用作UFUNCTION参数,异步节点正是通过这种方式接收一次性回调:UPARAM(DisplayName="Event") FMyReadySignature Callback。 - 原生(非动态)委托无法直接公开;请将其封装在动态委托或异步操作中。
从原生代码调用 Blueprint
| 需求 | 使用 |
|---|---|
| 由设计师填充的钩子 | UFUNCTION(BlueprintImplementableEvent),使用其普通名称调用 |
| 带有 C++ 默认实现的钩子 | UFUNCTION(BlueprintNativeEvent),使用其普通名称调用 |
| 触发供多个图表监听的事件 | UPROPERTY(BlueprintAssignable) 多播委托,Broadcast(...) |
| 调用仅存在于 Blueprint 资源中的函数 | FindFunction + ProcessEvent |
| 读取 Blueprint 类的默认值 | TSubclassOf<T>::GetDefaultObject()、UClass::GetDefaultObject<T>() |
// The params struct must match the function's parameter list exactly, in declaration order.
struct FMyOnScoredParams
{
int32 Points;
FString Reason;
};
void MyNotifyScored(UObject* Target, int32 Points)
{
if (!Target) { return; }
if (UFunction* Func = Target->FindFunction(FName(TEXT("OnScored"))))
{
checkf(Func->ParmsSize == sizeof(FMyOnScoredParams), TEXT("OnScored signature changed"));
FMyOnScoredParams Params;
Params.Points = Points;
Params.Reason = TEXT("Kill");
Target->ProcessEvent(Func, &Params);
}
}
如果 Blueprint 被重命名或重新编译后移除,FindFunction 会返回 null,因此请始终对其结果进行分支处理(FindFunctionChecked 则会触发断言)。只要契约由 C++ 负责定义,就优先使用 BlueprintImplementableEvent 声明,因为它能在编译时进行类型检查。
// SpawnClassPath is a UPROPERTY(EditDefaultsOnly) TSoftClassPtr<AMyActor> set to BP_MyThing
void MyInspectSpawnClass(const TSoftClassPtr<AMyActor>& SpawnClassPath)
{
if (UClass* LoadedClass = SpawnClassPath.LoadSynchronous())
{
const AMyActor* Defaults = LoadedClass->GetDefaultObject<AMyActor>();
const bool bIsBlueprintClass = Cast<UBlueprintGeneratedClass>(LoadedClass) != nullptr; // UClass hides IsA (Class.h:4853)
UE_LOG(LogMyGame, Log, TEXT("Default health %.1f, Blueprint class %d"), Defaults->GetHealth(), bIsBlueprintClass ? 1 : 0);
}
}
LoadSynchronous 会阻塞游戏线程;对于流式加载,请使用 Asset Manager(ue-data-assets-tables)。UBlueprintGeneratedClass(Engine/BlueprintGeneratedClass.h)是已编译 Blueprint 生成的运行时 UClass——当原生类和 Blueprint 类需要区别处理时,请检查它。
Blueprint 中的子系统和设置
- 每个
UGameInstanceSubsystem、UWorldSubsystem、ULocalPlayerSubsystem和UEngineSubsystem子类都会自动获得一个Get <Subsystem>节点(UK2Node_GetSubsystem);其BlueprintCallable成员随后会显示在该对象上。无需额外的说明符。子系统生命周期和原生访问器:ue-cpp-foundations。 UDeveloperSettings(Engine/DeveloperSettings.h,模块DeveloperSettings)会自动注册一个 Project Settings 页面。声明为UCLASS(Config=Game, DefaultConfig, meta=(DisplayName="My Game")),将字段标记为UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category=…),并添加一个返回GetDefault<UMyGameSettings>()的UFUNCTION(BlueprintPure)静态访问器——Blueprint 没有GetDefault节点。可以重载GetContainerName()、GetCategoryName()和GetSectionName()来移动此页面。UFUNCTION(Exec)用于声明控制台命令;控制台会将其路由到 PlayerInput、PlayerController、Pawn、HUD、GameMode、CheatManager、GameState、PlayerCameraManager 和 GameInstance。meta=(DevelopmentOnly)会在 Shipping 构建中移除BlueprintCallable函数,UKismetSystemLibrary::PrintString也是如此。
可在编辑器中调用的函数
CallInEditor——参见上文的 AMyActor::SnapToGround——是 UFUNCTION 说明符,而不是 meta= 键,并且函数不接受任何参数。它会在编辑器世界中运行,因此对于任何假定处于 PIE 状态的内容,都应添加保护。Details 自定义、编辑器实用工具控件和资产操作:ue-editor-tools。
已弃用——请勿使用
| 请勿使用 | 在 5.8 中使用 | 来源 |
|---|---|---|
meta=(HideAssetPicker) | meta=(HidePinAssetPicker) | UObject/ObjectMacros.h:1673 中的 UE_DEPRECATED(5.6) |
Delegate.ProcessMulticastDelegate<UObject>(Params) | Delegate.ProcessDelegate<UObject>(Params) | UObject/ScriptDelegates.h:1462 中的 UE_DEPRECATED(5.8) |
SomeClass->ClassDefaultObject | GetDefault<T>()、UClass::GetDefaultObject<T>() | UObject/Class.h:4045 中的 UE_DEPRECATED(5.6) |
GENERATED_UCLASS_BODY()、GENERATED_IINTERFACE_BODY() | GENERATED_BODY() | UObject/ObjectMacros.h:802-805 中的旧版别名 |
在 Blueprint 实现类上使用 Cast<IMyInterface>(Obj)->Foo() | IMyInterface::Execute_Foo(Obj, …) | Blueprint 实现类没有 IMyInterface 指针(UObject/ScriptInterface.h:135) |
使用 Obj->Foo_Implementation() 触发 BlueprintNativeEvent | Obj->Foo() | 普通名称是生成的分发跳板函数(UObject/ObjectMacros.h:997) |
为 BlueprintImplementableEvent 编写 C++ 函数体 | 声明后即可 | UHT 会生成函数体(UObject/ObjectMacros.h:992) |
在私有成员上使用 UPROPERTY(BlueprintReadWrite) | 添加 meta=(AllowPrivateAccess="true"),或将其设为 protected | UHT 错误:"BlueprintReadWrite should not be used on private members" |
UENUM(BlueprintType) enum class EMyState : uint32 | enum class EMyState : uint8 | UHT 错误:"Invalid BlueprintType enum base - currently only uint8 supported" |
在 UPROPERTY 中使用 TArray<TArray<T>> 或 TMap<K, TArray<V>> | 将内部容器封装在 USTRUCT 中 | UHT 中的 TArray/TMap 会清除 CanBeContainerValue/CanBeContainerKey |
不检查 FindExistingAction 就使用 AddNewAction | 先检查 LatentInfo.UUID | Engine/LatentActionManager.h:121,143 |
返回未注册的 NewObject<UMyAsyncAction>() | RegisterWithGameInstance(WorldContextObject) | Kismet/BlueprintAsyncActionBase.h:37 |
常见错误
**BlueprintNativeEvent 没有 _Implementation,或为 BlueprintImplementableEvent 编写了 C++ 函数体:**前者会导致外部符号未解析,后者会导致重复符号(UHT 已经生成了一个)。原生事件需要定义 virtual void Foo_Implementation(...);可实现事件则完全不需要定义。
**BlueprintPure 带有副作用:**编译器会裁剪未使用的纯节点,并在每个使用者处重新求值其余节点,因此副作用执行的次数不可预测。请使用 BlueprintCallable。
本来打算作为输入的非 const 引用参数:
static void Scale(FVector& Value, double Factor); // WRONG: Value becomes an output pin
static void Scale(UPARAM(ref) FVector& Value, double Factor); // RIGHT: by-reference input
static void Scale(const FVector& Value, double Factor); // RIGHT: plain input
需要世界对象但未声明 WorldContext 的库函数:UBlueprintFunctionLibrary 的 CDO 上,GetWorld() 会返回 null。请接收 const UObject* WorldContextObject,添加标签 meta=(WorldContext="WorldContextObject"),并通过 GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull) 解析。
**对私有成员使用 BlueprintReadWrite:**这会导致 UHT 错误,而非警告。请添加 meta=(AllowPrivateAccess="true")。
**在 UPROPERTY 中使用嵌套容器:**UHT 会拒绝 TArray<TArray<T>> 和 TMap<K, TArray<V>>。请将内层容器封装在 USTRUCT(BlueprintType) 中。
**添加潜伏操作时未检查 UUID:**重新进入节点会堆叠重复操作,完成引脚会为每个副本触发一次。请先检查 FindExistingAction<FMyWaitAction>(LatentInfo.CallbackTarget, LatentInfo.UUID)。
**异步操作在执行过程中被回收:**从未调用 RegisterWithGameInstance 的操作只会存活到创建它的那一帧,因此之后触发的委托无法到达该操作。请在工厂函数中注册,并在完成时仅调用一次 SetReadyToDestroy()。
**对 Blueprint 实现者使用 Cast<IMyInterface>(Obj):**会返回 null,调用也会被静默跳过。请通过 Obj->Implements<UMyInterface>() 检查,并使用 IMyInterface::Execute_Foo(Obj, …) 分发调用。
**静态异步工厂未添加 meta=(BlueprintInternalUseOnly="true"):**Blueprint 会同时提供代理节点和对工厂函数的直接调用,而直接调用会返回一个永远不会被激活的对象。
相关技能
ue-cpp-foundations—UCLASS/UPROPERTY/UFUNCTION基础知识、容器、委托、子系统生命周期、GCue-actor-component-architecture— Actor 和组件生命周期、生成、BlueprintSpawnableComponentue-gameplay-framework— GameMode、PlayerController、伤害,以及 Blueprint 最常扩展的类ue-editor-tools— 详情自定义、编辑器实用工具控件、资产操作、编辑器子系统ue-async-threading— 异步节点背后的AsyncTask、UE::Tasks、计时器和线程安全ue-data-assets-tables—UDataAsset、DataTable 行、软引用和 Asset Manager 加载ue-ui-umg-slate—UUserWidgetC++ 基类、BindWidget、向 Blueprint 暴露控件数据