mcpField
U
ue-blueprint-cpp-interop

ue-blueprint-cpp-interop

github.com/quodsoler开发419255 次安装4 周前更新
代码仓库

关于 ue-blueprint-cpp-interop

面向 UE 5.8 开发者的 C++ 与 Blueprint 互操作技能,说明反射说明符如何塑造节点,以及原生代码如何向 Blueprint 暴露数据、操作和事件。

主要功能

  1. 01UFUNCTION 节点与事件配置
  2. 02UPROPERTY 数据暴露规则
  3. 03函数库与异步节点模式
  4. 04Blueprint 接口和委托处理
  5. 05原生代码调用 Blueprint

适用场景

  1. 01让 C++ 函数可在 Blueprint 中调用或重写
  2. 02为属性配置 Blueprint 读写方式
  3. 03实现延迟或异步 Blueprint 节点
  4. 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/setterUPROPERTY 暴露
静态辅助函数、WorldContext、DefaultToSelf函数库
带有执行引脚、稍后才完成的节点(Delay 风格)潜伏节点
具有多个输出执行引脚、由事件触发的节点异步操作节点
Blueprint 可实现的接口、Execute_、TScriptInterfaceBlueprint 接口
UENUM、USTRUCT、作为 Blueprint 类型的容器Blueprint 中的枚举和结构体
BlueprintAssignable 事件、将 Blueprint 绑定到 C++ 委托委托和事件
从原生代码运行 Blueprint 逻辑、Blueprint 类从原生代码调用 Blueprint
子系统、UDeveloperSettings、Exec 命令Blueprint 中的子系统和设置
Details 面板中的按钮、仅限编辑器的操作可在编辑器中调用的函数

UFUNCTION 暴露方式

说明符节点形状C++ 函数体
BlueprintCallable带执行引脚输入和输出由你实现
BlueprintPure不带执行引脚;隐含 BlueprintCallable由你实现,且无副作用
BlueprintImplementableEvent由 Blueprint 实现的事件无 — UHT 会生成该跳板函数
BlueprintNativeEventBlueprint 可以重写的事件仅实现 Name_Implementation
BlueprintGetter / BlueprintSetter隐藏;通过属性访问由你实现
BlueprintAuthorityOnly缺少网络权限时会跳过 Blueprint 调用(直接调用 C++ 仍会执行)由你实现
BlueprintCosmetic专用服务器上会跳过 Blueprint 调用(直接调用 C++ 仍会执行)由你实现
BlueprintInternalUseOnly不会显示在面板中;只有生成的节点会调用它由你实现
CallInEditorDetails 面板上的按钮由你实现
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 中的效果
BlueprintReadWriteGet 和 Set 节点
BlueprintReadOnly仅 Get 节点
BlueprintGetter=FnGet 通过 UFUNCTION(BlueprintGetter) 路由
BlueprintSetter=FnSet 通过 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 / floatReal
TObjectPtr<UStaticMesh>对象引用
TSubclassOf<AActor>类(由 meta=(MustImplement=…)、AllowedClasses、BlueprintBaseOnly 筛选)
TSoftObjectPtr<T> / TSoftClassPtr<T>软引用;Blueprint 会按需解析或加载
FInstancedStruct结构体引脚,其具体类型在编辑器中选择
TScriptInterface<IMyInteractable>接口引用
FMyHealthChangedSignature(多播动态)通过 BlueprintAssignable 使用绑定事件/广播节点
FTimerHandleUSTRUCT(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->ClassDefaultObjectGetDefault<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() 触发 BlueprintNativeEventObj->Foo()普通名称是生成的分发跳板函数(UObject/ObjectMacros.h:997)
为 BlueprintImplementableEvent 编写 C++ 函数体声明后即可UHT 会生成函数体(UObject/ObjectMacros.h:992)
在私有成员上使用 UPROPERTY(BlueprintReadWrite)添加 meta=(AllowPrivateAccess="true"),或将其设为 protectedUHT 错误:"BlueprintReadWrite should not be used on private members"
UENUM(BlueprintType) enum class EMyState : uint32enum class EMyState : uint8UHT 错误:"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.UUIDEngine/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 基础知识、容器、委托、子系统生命周期、GC
  • ue-actor-component-architecture — Actor 和组件生命周期、生成、BlueprintSpawnableComponent
  • ue-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 — UUserWidget C++ 基类、BindWidget、向 Blueprint 暴露控件数据

评论

支持 Markdown