Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

UE C++ 相关内容总结

1.作为 UE 程序员为什么都需要掌握 UE C++和蓝图编程?

因为二者在引擎中的定位不同,却又相辅相成:

  • C++: 负责底层基础,适合实现性能关键模块、框架系统,以及扩展引擎功能。
  • 蓝图:面向上层逻辑,强调快速迭代、关卡交互和玩法组合,方便策划与美术直接参与。

只会 C++,灵活性不足,每次修改逻辑都要重新编译,效率低; 只会蓝图,性能和功能有限,无法胜任复杂系统(如多人联机、GAS、定制渲染管线)。所以最佳实践是 C++ 写底层与性能逻辑,蓝图处理上层与关卡逻辑。一句话总结:C++ 打地基,蓝图盖房子;两者结合,才能高效又稳健。

2. UE C++和普通 C++的区别

Unreal Engine(UE)在底层使用 C++ 语言开发,但 UE C++ 并不等同于标准 C++,它有自己的一套扩展体系和开发规范。

2.1 UE 对象体系:UObject 和 Actor

UE 对象由引擎管理生命周期,普通 C++ 对象需要手动管理。

  • 普通 C++:类是普通的类型,继承和生命周期完全由程序员控制。
  • UE C++:所有游戏对象大多继承自 UObject 或 AActor。UE 引入垃圾回收(Garbage Collection) 管理 UObject 生命周期。内存分配与销毁不建议直接 new/delete,而是通过 UE 自动进行。
// 普通 C++
MyClass* Obj = new MyClass();
delete Obj;

// UE C++, 不用手动 delete,GC 会管理
UObject* Obj = NewObject<UMyObject>();

2.2 UE 宏与反射系统

普通 C++ 无法直接支持蓝图编辑器、序列化和 GC,UE C++ 通过宏实现。

  • 普通C++:没有内置的反射,属性、函数都不能在运行时被动态访问。
  • UE C++:使用宏 UCLASS, UPROPERTY, UFUNCTION 扩展类、变量、函数。实现运行时类型信息、序列化、蓝图可访问、网络同步等。
UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    int32 Health;

    UFUNCTION(BlueprintCallable)
    void TakeDamage(int32 Amount);
};

2.3指针与智能指针

UE 自带指针体系,兼容 GC 和蓝图系统。

  • 普通 C++:*、&、std::shared_ptr、std::unique_ptr 常用。
  • UE C++:强烈依赖 UObject* 指针,由 GC 管理。对非 UObject 类型可以使用 UE 提供的智能指针 TSharedPtr、TWeakPtr、TUniquePtr 专门用于非 UObject 类型的对象管理。新版本引入 TObjectPtr 以增强指针安全性。

2.4编译机制与模块化

UE 编译器不只是 C++ 编译,还包括宏解析、蓝图接口生成等。

  • 普通 C++:编译器直接处理 .cpp 文件,依赖头文件。模板和宏在编译时展开。
  • UE C++:UE 引入 UHT(Unreal Header Tool) 解析 UCLASS、UPROPERTY、UFUNCTION 宏,生成辅助代码.generated.h 文件。UBT(Unreal Build Tool)负责整个 UE 项目的构建管理。整个编译过程如下:
1. 开始:写代码
   ├── .h/.cpp 文件(包含 UCLASS / UPROPERTY / UFUNCTION 宏)
   └── .Build.cs / .Target.cs 配置模块依赖

2. 调用 Unreal Build Tool (UBT)
   ├── 解析 .uproject、.Build.cs、.Target.cs
   ├── 计算模块依赖
   └── 判断哪些模块需要编译

3. 调用 Unreal Header Tool (UHT)
   ├── 扫描所有头文件
   ├── 解析 UCLASS / USTRUCT / UPROPERTY / UFUNCTION
   └── 生成 .generated.h 文件(反射、GC、蓝图注册)

4. 回到 UBT
   ├── 收集 .cpp + 生成的 .generated.h
   ├── 调用编译器(MSVC / Clang / LLVM)
   └── 编译生成模块对象文件 (.obj)

5. 链接阶段
   ├── 按模块依赖顺序链接各个 .obj
   └── 生成最终二进制:
        - 编辑器:.dll
        - 游戏可执行:.exe / 目标平台二进制      

6. 完成
   └── 可运行的游戏或模块加载到 UE 编辑器

2.5蓝图与可视化支持

  • 普通 C++:无法直接被可视化工具访问。
  • UE C++:可通过 BlueprintCallable、BlueprintReadWrite 等宏让 C++ 类和函数暴露给蓝图。可视化编辑器和 C++ 无缝结合,大幅提升开发效率。

2.6事件、委托和回调

  • 普通 C++:通过函数指针、std::function、虚函数实现回调。
  • UE C++:提供 Delegate、MulticastDelegate、Event,封装函数指针和对象绑定。支持动态绑定、蓝图可调用。

3.关于裸指针 T*

指针本质就是:记录一个对象在内存中的地址。当你想操作某个对象时,持有它的指针就能直接访问它,而不是重新创建一个副本。T*(原始指针):直接指向对象内存,不参与垃圾回收,也不能自动保存/加载(不可序列化),容易悬空。引用的对象销毁时不会自动清理指针。 举个例子:

APlayerController* PC = GetWorld()->GetFirstPlayerController();

这里我们并没有“新建”一个 PlayerController,而是找到了当前世界里已有的对象,并保存了它的内存地址到 PC。之后我们就可以通过 PC 调用它的函数或访问变量。在使用指针前,需要检查指针是不是 nullptr。例如,我们使用 controller 设置游戏的输入模式。

if (PC)
{
    PC->SetInputMode(FInputModeGameAndUI());
}

例如,我们在一个 Character 子类 HeroCharacter.h 和 HeroCharacter.cpp 中,添加 CameraComponent,使用下面的方法,创建 CameraComponent 实例。Character 会负责子组件的生命周期。

//HeroCharacter.h

UPROPERTY(VisibleAnywhere)
UCameraComponent* CameraComp;

//HeroCharacter.cpp

CameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT(「CameraComp」));
CameraComp->SetupAttachment(RootComponent);

4.关于 TObjectPtr

在虚幻 5 以后,推荐使用 TObjectPtr 替换*,上面的可以写成下面的方式

UPROPERTY(VisibleAnywhere)
TObjectPtr<UCameraComponent> CameraComp;

TObjectPtr<T>:轻量级智能指针,参与 GC 垃圾回收,指向对象被销毁时自动置空,可序列化,适合 UPROPERTY 成员使用。

建议:在 UE5 中,凡是 UObject 成员且需要序列化、编辑器操作或 GC 管理,都推荐使用TObjectPtr<T>,几乎是裸指针的安全升级版。

5.软引用 TSoftObjectPtr

在 Unreal Engine 5 中,TSoftObjectPtr(软引用)是一种存储资产路径而非直接指向对象的指针。适合大型资源、插件化资产或延迟加载资源,能够节省内存和加快启动速度。具有延迟加载,节约内存,运行时显式加载。例如:

UPROPERTY(EditAnywhere)
TSoftObjectPtr<UMaterial> WeaponMaterial; // 保存路径而不占内存

void ApplyMaterial()
{
    if (WeaponMaterial.IsValid())
    {
        MyMesh->SetMaterial(0, WeaponMaterial.LoadSynchronous()); // 显式加载
    }
}

6.弱引用 TWeakObjectPtr

弱引用不会阻止 GC 回收对象,对象被销毁后,指针自动失效,不会悬空。访问前需要进行 IsValid()检查。比如,标记最近攻击敌人,敌人有可能被击杀。当敌人被销毁后,指针自动失效,避免悬空,不阻止 GC 回收。

TWeakObjectPtr<AActor> LastHitEnemy = EnemyA;
if(LastHitEnemy.IsValid())
{
    LastHitEnemy->Destory();
}

7.访问运算符「.」和「->」

平时使用,通过编码 IDE 自动提示即可。

  • 「.」访问对象的实例成员,结构体类型使用。比如,FVector, FRotator,FTransform,FHitResult, FColor
  • 「->」访问指针所指对象的成员。比如,UObject, AActor,UActorComponent

8. 静态函数

静态函数特点是属于类本身,不依赖对象实例。调用时,不需要创建对象。内存只会保存一份,不会随着实例复制。静态函数经常用于:工具函数、蓝图函数库,全局工厂方法。

比如,UGameplayStatics 在 Unreal Engine C++ 里几乎是最常用的工具类之一,很多初学者和老手都会用到它。它其实就是一大堆 静态函数 的集合,主要用于方便获取游戏世界中的常用信息和执行通用操作。

// 获得玩家控制器
APlayerController* PC = UGameplayStatics::GetPlayerController(this, 0);
// 获得 Pawn
APawn* Pawn = UGameplayStatics::GetPlayerPawn(this, 0);
// 查询 Actors
TArray<AActor*> FoundActors;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), AMyEnemy::StaticClass(), FoundActors);
// 播放声音
UGameplayStatics::PlaySoundAtLocation(this, ExplosionSound, GetActorLocation());
// 播放特效
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ExplosionFX, GetActorLocation());
// 应用伤害
UGameplayStatics::ApplyDamage(TargetActor, 50.f, GetController(), this, UDamageType::StaticClass());
// 切换关卡
UGameplayStatics::OpenLevel(this, FName(「MainMenu」));

9. 引用、地址运算符、函数指针

&在 UE C++中的用法与 C++类似,主要包括下面几种:

9.1引用(Reference)

给变量起别名,操作引用等于操作原变量。避免大对象拷贝(如 FVector、FTransform)。用于函数参数传递,避免复制,提高性能。

// 引用版本
void ModifyVector(FVector& Vec)
{
    Vec.X += 1.0f;
    Vec.Y += 1.0f;
}

// 引用版本 调用
FVector MyVec(0,0,0);
ModifyVector(MyVec);  // 直接传变量

9.2地址运算符

获取变量在内存中的地址,返回指针。

// 指针版本
void ModifyVector(FVector* Vec)
{
    if(Vec)
    {
        Vec->X += 1.0f;
        Vec->Y += 1.0f;
    }
}

// 指针版本调用
FVector MyVec(1,2,3);
ModifyVector(&MyVec); //传入地址

引用版本的 ModifyVector 函数,调用语法简单,保证不为 null,不需要解引用。

指针版本的 ModifyVector 函数,需要取地址 &MyVec,函数内部要检查是否为 nullptr,可以传入动态分配指针或空指针,函数内部通过检查保证安全。

UE C++ 常用推荐:

  • 普通 struct(FVector、FTransform、FQuat 等),引用(Reference)优先。原因:语法简洁、安全、无需检查 null。内部仍然修改原对象
  • 对于 UObject / Actor / Component,指针传递,可以为空,函数需检查指针有效性。

9.3函数指针

函数指针就是一个变量,它 保存函数的地址,可以通过它调用函数。在 UE C++ 中,函数指针常用于 Delegate、回调、绑定函数 等场景。下面是一个成员函数绑定 Delegate 的例子。参考的代码片段如下:

// 创建 Actor 实例
AMyActor* MyActor = World->SpawnActor<AMyActor>();

// 声明 Delegate 类型
DECLARE_DELEGATE(FMyDelegate);

// 声明 Delegate
FMyDelegate Delegate;

// 绑定成员函数:对象 + 成员函数指针
Delegate.BindUObject(MyActor, &AMyActor::PrintActorName);

// 执行 Delegate
if (Delegate.IsBound())
{
    Delegate.Execute();
}

10.关于前向声明

在 Unreal Engine C++ 开发中,前向声明就是在文件中 提前声明一个类、结构体或枚举的名字,而不包含其完整定义。前向声明是提升编译效率、减少头文件依赖、避免循环引用的重要手段。

10.1 前向声明的使用场景:

  • 成员指针和引用类型

如果类成员是指针或引用,可以只做前向声明,不需要包含完整头文件。常见 UObject, Component, Actor 等。

class UStaticMeshComponent; // 前向声明

class AMyActor : public AActor
{
    GENERATED_BODY()

private:
    UStaticMeshComponent* MeshComp; // 指针可以使用前向声明
};
  • 函数参数、返回值为指针或引用

函数声明时,如果参数或返回值是指针或引用,也可以使用前向声明。如果返回值或参数是对象本身(非指针/引用),必须包含完整头文件。

class AMyActor;

AMyActor* SpawnActor();       // 返回值为指针
void ProcessActor(AMyActor* Actor); // 参数为指针

10.2 不可以使用前向声明的情况

// class AMyActor; 
// 1. 不能是对象成员(值类型)
class UMyComponent
{
    AMyActor Actor; // 前向声明不能用,编译器需要知道大小
};

// 2. 不能是继承
class AMyChild : public AMyActor // 需要完整定义
{};

// 3. 访问成员函数或变量, 无法前向声明
AMyActor* Actor;
Actor->SetActorLocation(...); // 不能访问成员,编译器不知道内容

11.强制转换Cast

在 Unreal Engine C++ 开发中,Cast 是最常用的类型安全强制转换方式,它用于在 UObject、Actor、Component 等类层级中进行类型转换,同时保证安全性。

Cast是 UE 提供的模板函数,用于将父类指针或 UObject 指针转换为子类类型。如果转换成功,返回目标类型指针;失败返回 nullptr。例如:

AActor* SomeActor = ...;
AMyCharacter* MyChar = Cast<AMyCharacter>(SomeActor);
​
if (MyChar)
{
    MyChar->DoSomething(); // 成功转换后可以安全调用
}

Cast 使用的注意事项:

  • 检查失败情况:Cast 只有在对象真的是目标类型或子类时才会成功。UE C++ 中失败返回 nullptr,蓝图里走 Cast Failed 分支,必须做判空/分支处理。
  • 避免滥用:过多依赖 Cast 会让蓝图或 C++ 紧耦合,维护困难。推荐用接口、事件分发器或更明确的变量类型来减少 Cast。
  • 关注性能:单次 Cast 开销不大,但高频调用(如 Tick 或循环)中频繁使用会拖慢性能。建议 缓存 Cast 结果,避免重复转换。

12.接口用法

在UE中,Interfaces是一种有效的设计,多个类可以通过接口添加函数。比如,玩家可以通过接口与关卡内的不同Actor进行交互,每个Actor都有不同的反应。在UE C++中定义的接口,既可以在C++类里实现,也可以在蓝图类里实现。

12.1 声明C++接口

继承Uinterface定义一个接口,由于UE的约定,需要写两个类:UDoSomeThings和IDoSomeThings。
U前缀:UInterface继承UObject,用于反射系统。
I前缀:IDoSomeThings是接口类,存放自定义的函数。

#include "CoreMinimal.h"
#include "DoSomeThings.generated.h"
​
UINTERFACE(MinimalAPI)
class UDoSomeThings : public UInterface
{
    GENERATED_BODY()
    // 这里就是空的
}
​
class YOURPROJECT_API IDoSomeThings
{
    GENERATED_BODY()
public:    
    // 这里写接口方法
}

12.2 添加实现方法

在接口中,用两种方式添加两个接口方法。

class YOURPROJECT_API IDoSomeThings
{
    GENERATED_BODY()
public:    
    // 1. C++接口方法
    virtual void DoSomeThing() = 0;  // 必须virtual 
    
    // 2. UFUNCTION接口方法
    UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="Things")
    int GetNumberOfThings();
};
  • C++接口方法:必须在子类的C++中实现,不支持蓝图。比如,Gas系统的这个接口,也是C++接口。
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const = 0;
  • UFUNCTION接口方法:使用BlueprintNativeEvent宏修饰符时,在C++中可选实现。如果实现需要在_Implementation中实现逻辑。支持在蓝图中可以重写。如果使用BlueprintImplementableEvent,在C++中不能实现,必须要在蓝图中实现。所以,一般大家都使用BlueprintNativeEvent。

UFUNCTION的接口方法,不需要写virtual, 主要是因为UFUNCTION()和GENERATED_BODY() 宏 会生成必要的虚函数声明。

12.3 C++中实现接口

继承接口,在cpp中写函数实现。

#include "CoreMinimal.h"
#include "DoSomeThings.h"
#include "SomeThingsActor.generated.h"
​
UCLASS(Blueprintable)
class YOURPROJECT_API ASomeThingsActor : public AActor, public IDoSomeThings
{
    GENERATED_BODY()
public:    
    virtual void DoSomeThing() override;
    virtual int GetNumberOfThings_Implementation() override;
};
#include "SomeThingsActor.h"
​
void ASomeThingsActor::DoSomeThing()
{
    //....
}
​
int ASomeThingsActor::GetNumberOfThings_Implementation()
{
    return 1;
}

12.4 在蓝图中实现接口

可以在蓝图的Class Settings中添加刚才C++定义的接口。点击Implemented Interfaces中Add, 选择DoSomeThings。在Interfaces中,可以看到接口函数的定义。

12.5 在C++和蓝图中调用

在C++中调用接口,如果是C++实现的接口,可以直接使用Cast<IDoSomeThings>,例如

auto I = Cast<IDoSomeThings>(Actor);
if (I)
{
    int Num = I->GetNumberOfThings();
}

如果想在C++中,调用蓝图实现的接口方法,Cast<>方法会返回I = nullptr,因为C++不知道蓝图。可以使用反射系统检测蓝图的接口是否可用。检测的几种方法如下:

// 1. Implements
if (Actor && Actor->Implements<UDoSomeThings>())
{
    // Use the interface
}
// 2. DoesImplementInterface 
if (UKismetSystemLibrary::DoesImplementInterface(Actor, UDoSomeThings::StaticClass())
{
    // use the interface
}
// 3. ImplementsInterface
if (Actor && Actor->GetClass()->ImplementsInterface(UDoSomeThings::StaticClass()))
{
    // use the interface    
} 

检测到定义的接口函数,需要通过Interface wrapper调用接口,如下:

if (Actor && Actor->Implements<UDoSomeThings>())
{
    int Num = IDoSomeThings::Execute_GetNumberOfThings(Actor);
}

在蓝图中调用,先判断,再调用接口函数。

12.6 接口定义成变量

如果想把接口像Class类型一样,保存成变量,需要在UE C++中使用(Blueprintable)宏修饰符。

UINTERFACE(Blueprintable)
class UDoSomeThings : public UInterface
{
    GENERATED_BODY()
};

在蓝图中,可以直接创建DoSomething类型的Interface变量IDo。如果把一个Actor类型变量保存成接口变量IDo,需要先Cast to DoSomeThings接口后,设定IDo变量。

在C++中,需要创建接口变量需要使用TScriptInterface

UPROPERTY(BlueprintReadWrite)
TScriptInterface<IDoSomeThings> SomethingInstance;

使用这个变量前可以判断是否为空,如果接口实现是在C++完成,可以直接使用。

int Num;
if (SomethingInstance)
{
    Num = SomethingInstance->GetNumberOfThings();
}

把实现接口的对象赋值给接口变量的方法如下,直接赋值

if (UKismetSystemLibrary::DoesImplementInterface(Actor, UDoSomeThings::StaticClass()))
{
    SomethingInstance = Actor;
}

如果接口的实现是在蓝图中,需要使用

int Num = IDoSomeThings::Execute_GetNumberOfThings(SomethingInstance.GetObject());

使用UOBject类型变量,来直接执行接口也是一种方法。

UPROPERTY(BlueprintReadWrite)
UObject* SomethingInstance;
​
if (SomethingInstance)
{
    int Num = IDoSomeThings::Execute_GetNumberOfThings(SomethingInstance);
}

13.委托用法

UE官方常见委托分类方式:单播,多播和动态。委托是一种常见的回调机制,让一个对象可以把事件通知到另一个对象,解耦逻辑。比如:角色受到伤害,通知UI,更新血条。

13.1 单播委托

Single cast delegate,只能绑定一个函数,没有反射,性能最好。只能在C++中使用,蓝图看不到。适合在“1对1”的通知,比如:一个异步任务完成,通知唯一的回调函数。

无参单播委托

// 声明一个无参的单播委托
DECLARE_DELEGATE(FOnFinished);
​
// 使用
FOnFinished OnFinished;
// 绑定
OnFinished.BindUObject(this, &AMyActor::HandleFinished);
​
// 定义函数
void AMyActor::HandleFinished()
{
    UE_LOG(LogTemp, Log, TEXT("Task Finished!"));
}
​
// 调用
if (OnFinished.IsBound())
{
    OnFinished.Execute();  
    // 或者更安全:
    // OnFinished.ExecuteIfBound();
}

带参数/返回值的单播委托

// 声明一个带参数的单播委托
DECLARE_DELEGATE_OneParam(FOnDamaged, float);
​
// 使用
FOnDamaged OnDamaged;
​
// 绑定
OnDamaged.BindUObject(this, &AMyActor::HandleDamaged);
​
// 定义函数
void AMyActor::HandleDamaged(float Damage)
{
    UE_LOG(LogTemp, Log, TEXT("Actor took %f damage!"), Damage);
}
​
// 调用
if (OnDamaged.IsBound())
{
    OnDamaged.Execute(25.f);  // 传递参数
}

带返回值的代理,只需要在宏定义时,使用带有“RetVal”关键字的宏,例如,在执行后获得返回值。其他使用过程类似。

// 声明
DECLARE_DELEGATE_RetVal(int32, OnDamaged);
// 返回值
int32 Result = OnDamaged.Execute();

13.2 多播委托

可以绑定多个函数,一个事件触发时,所有绑定都会被调用。性能依然很好。同样主要用于C++,蓝图不可见。

// 声明
DECLARE_MULTICAST_DELEGATE(FOnDead);
​
// 使用
FOnDead OnDead;
OnDead.AddUObject(this, &AMyActor::HandleDeath);
OnDead.AddLambda([](){ UE_LOG(LogTemp, Log, TEXT("Lambda called!")); });
​
// 触发
OnDead.Broadcast();

13.3 动态单播

支持反射,可以暴露给蓝图。可以是单播,也可以是多播。有一定性能开销。 动态单播,一个委托绑定一个函数。如果绑定多个,后面的会覆盖前一个。

// 声明一个动态单播委托(无参数)
DECLARE_DYNAMIC_DELEGATE(FSimpleDynamicDelegate);
​
// 声明一个动态单播委托(带参数)
DECLARE_DYNAMIC_DELEGATE_OneParam(FOnHealthChanged, float, NewHealth);
UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()
public:
    // 定义成员变量
    FSimpleDynamicDelegate OnSimpleEvent;
    FOnHealthChanged OnHealthChanged;
​
    void TriggerEvent()
    {
        // 调用无参委托
        OnSimpleEvent.ExecuteIfBound();
        // 调用有参委托
        OnHealthChanged.ExecuteIfBound(75.0f);
    }
};

绑定

// 在另一个类或蓝图中绑定
MyActor->OnSimpleEvent.BindDynamic(this, &UMyComponent::HandleSimple);
MyActor->OnHealthChanged.BindDynamic(this, &UMyComponent::HandleHealth);
​
// 回调函数格式必须是 UFUNCTION
UFUNCTION()
void HandleSimple() { UE_LOG(LogTemp, Warning, TEXT("Simple event triggered!")); }
​
UFUNCTION()
void HandleHealth(float Value) { UE_LOG(LogTemp, Warning, TEXT("Health: %f"), Value); }

13.4 动态多播

动态多播,一个委托可以绑定多个函数。触发时会顺序调用所有绑定的回调。声明如下:

// 无参数
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FSimpleMulticastDelegate);
​
// 带参数
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnScoreChanged, int32, NewScore);

像下面的方法进行使用:

UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()
​
public:
    // 使用 UPROPERTY,支持蓝图绑定
    UPROPERTY(BlueprintAssignable)
    FSimpleMulticastDelegate OnSimpleEvent;
​
    UPROPERTY(BlueprintAssignable)
    FOnScoreChanged OnScoreChanged;
​
    void TriggerEvent()
    {
        // 触发多播委托(依次调用所有绑定函数)
        OnSimpleEvent.Broadcast();
        OnScoreChanged.Broadcast(100);
    }
    
    UFUNCTION()
    void HandleSimple() { UE_LOG(LogTemp, Warning, TEXT("Multicast simple event!")); }
​
    UFUNCTION()
    void HandleScore(int32 Score) { UE_LOG(LogTemp, Warning, TEXT("Score: %d"), Score); }
};

动态多播绑定

// C++绑定
MyActor->OnSimpleEvent.AddDynamic(this, &UMyComponent::HandleSimple);
MyActor->OnScoreChanged.AddDynamic(this, &UMyComponent::HandleScore);
​
// C++解绑
MyActor->OnScoreChanged.RemoveDynamic(this, &UMyComponent::HandleScore);
​
// 蓝图绑定
// 因为用 UPROPERTY(BlueprintAssignable),蓝图里可以直接拖节点绑定

下面是蓝图中的动态绑定

13.5 委托总结

常见定义宏绑定方式支持蓝图绑定函数个数
单播DECLARE_DELEGATEDelegate.BindUObject(this, &Class::Func)X1
多播DECLARE_MULTICAST_DELEGATEDelegate.AddUObject(this, &Class::Func)X多个
动态单播DECLARE_DYNAMIC_DELEGATEDelegate.BindDynamic(this, &Class::Func)V1
动态多播DECLARE_DYNAMIC_MULTICAST_DELEGATEDelegate.AddDynamic(this, &Class::Func)V多个