草履虫也能看懂的虚幻引擎的委托机制

委托是什么:

委托允许一个对象注册回调函数,当特定事件发生时,这些回调函数会被自动调用。与直接函数调用不同,委托提供了更大的灵活性。一个功能对应一个委托,一个委托可以调用多个类多个函数

一个生动的比喻:

想象一下你在学校里,老师要通知大家明天去春游。但是老师不想一个一个地告诉每个学生,她就在教室里贴了一个公告板(委托),然后告诉同学们:“如果你们想知道春游的消息,就请在这个公告板上登记你的名字和联系方式(绑定函数)。当我确定春游时间后,我会通过公告板通知大家(广播委托)。”

委托就是这样一个公告板,它允许我们在某个事件发生时,自动通知所有登记了的人。

这样做有个好处:老师无需知道到底会有多少学生,或者到底是哪些学生.

委托的种类:

在虚幻引擎中,委托分为几种:

单播委托:只能绑定一个函数。

多播委托:可以绑定多个函数。

动态委托:可以被序列化,并且可以在蓝图中使用。

委托的优势:

1.解耦:委托允许两个对象之间通过事件进行通信,而无需直接引用彼此。这降低了类之间的耦合度。

2.灵活性:可以动态地绑定和解除绑定函数,使得在运行时改变行为变得容易。

3.多播:多播委托允许一个事件通知多个回调函数,这使得观察者模式很容易实现。

4.类型安全:与原始函数指针相比,委托是类型安全的,这意味着在编译时会检查参数和返回类型。

5.与虚幻引擎集成:虚幻引擎的委托系统与蓝图和反射系统集成,使得C++和蓝图之间的通信变得简单。

非委托对比委托:

非委托:

// 角色类需要知道所有事情
void ACharacter::PickupHealth()
{
    Health += 50;
    
    // 必须直接调用所有相关系统
    UIManager->UpdateHealthBar(Health);    // 需要知道UI
    SoundManager->PlayHealSound();         // 需要知道音效
    AchievementSystem->CheckHealthAchievement(); // 需要知道成就系统
    ParticleSystem->SpawnHealEffect();     // 需要知道特效
    
    // 每加一个新功能都要修改这里!
}

委托:

// 角色类只关心自己的逻辑
void ACharacter::PickupHealth()
{
    Health += 50;
    
    // 只需要广播一个事件
    OnHealthChanged.Broadcast(Health);
    // 谁关心血量变化?我不知道,也不关心!
}

/*--------以下每一个函数的绑定都发生在不同的CPP文件中---------*/

// 各个系统自己决定是否关心这个事件
UIManager->OnHealthChanged.AddUObject(this, &UUIManager::UpdateHealthBar);
SoundManager->OnHealthChanged.AddUObject(this, &USoundManager::PlayHealSound);
AchievementSystem->OnHealthChanged.AddUObject(this, &UAchievementSystem::CheckHealth);

例子:

一个最简单的例子,游戏控制器(UGameManager)需要调用UI界面(UUIManager)的函数;

两个功能分别使用两个不同的委托来实现:

  1. 当玩家得分时触发,在UI界面更新分数(用单播委托)
  2. 当玩家回血时触发,在UI界面更新血量(用多播委托)

步骤1:声明委托类型(通常在头文件中)

// 声明一个不带参数的单播委托
DECLARE_DELEGATE(FOnScoreChanged, int32);
// 声明一个带一个参数的多播委托
DECLARE_MULTICAST_DELEGATE_OneParam(FOnHealthChanged, float);

步骤2:在类中定义委托实例

委托具备一对多的特性,一个委托类创建后只能在规定的类中使用

比如在游戏管理器中:

// 声明一个不带参数的单播委托
DECLARE_DELEGATE(FOnScoreChanged, int32);
// 声明一个带一个参数的多播委托
DECLARE_MULTICAST_DELEGATE_OneParam(FOnHealthChanged, float);

class UGameManager : public UObject
{
public:
    // 创建一个得分改变的委托实例
    FOnScoreChanged OnScoreChanged;
  	// 创建一个血量改变的委托实例
  	FOnHealthChanged OnHealthChanged;
private:
    void ChangeScore(int32 NewScore);//用此函数内部调用OnScoreChanged
    void ChangeHealth(float NewHealth);//用此函数内部调用OnHealthChanged
};

步骤3:绑定函数到委托

我们想要UI响应UGameManager的需求,我们要把需要的UI的成员函数绑定到委托中,一般来说可以直接在UI的BeginPlay(){}里面设置委托,但是在下面的例子中:我们可以设置一个函数统一管理所有和UGameManager相关的委托,再把这个函数在BeginPlay(){}里面调用:

注意:想把UUIManager组件把函数绑到UGameManager的委托,需要在UUIManager类中声明一个UObject* GameManager;因为我们需要用UGameManager指针来传导委托实例.

UPROPERTY(EditAnywhere)
UObject* GameManager;

常规方法:

void UUIManager::BeginPlay(){
    Super::BeginPlay();
    // 将UpdateScore函数绑定到OnScoreChanged委托
    GameManager->OnScoreChanged.BindUObject(this, &UUIManager::UpdateScore);
    // 将UpdateHealth函数绑定到OnHealthChanged委托
    GameManager->OnHealthChanged.AddUObject(this, &UUIManager::UpdateHealth);
}

/*

GameManager->OnScoreChanged.BindUObject(this, &UUIManager::UpdateScore);
        这行代码的意思是:将当前对象(this)的成员函数 UUIManager::UpdateScore 绑定到         GameManager 的 OnScoreChanged 委托上。当 OnScoreChanged 委托被触发(广播)时,就会调用当前对象的 UpdateScore 函数。
BindUObject 是虚幻引擎中用于将 UObject 的成员函数 绑定到委托上的函数。它专门用于绑定继承自 UObject 的类的成员函数。
&UUIManager::UpdateScore 是一个成员函数指针。它指向 UUIManager 类的 UpdateScore 成员函数。
*/

用函数管理:

先定义函数,把所有的关于GameManager的委托写在一个函数BindToGameManager()里

class UUIManager : public UObject
{
public:
    //必须在这个类的BeginPlay()里面调用此函数,把本类的成员函数绑定给委托
    void BindToGameManager(UGameManager* GameManager)
    {
        // 将UpdateScore函数绑定到OnScoreChanged委托
        GameManager->OnScoreChanged.BindUObject(this, &UUIManager::UpdateScore);
        
        // 将UpdateHealth函数绑定到OnHealthChanged委托
        GameManager->OnHealthChanged.AddUObject(this, &UUIManager::UpdateHealth);
    }

    //UI的具体的功能函数,正是我们所需的
   void UpdateScore(int32 NewScore)
   {
        // 更新得分显示
        UE_LOG(LogTemp, Warning, TEXT("分数更新为: %d"), NewScore);
   }

   void UpdateHealth(float NewHealth)
   {
        // 更新血量显示
        UE_LOG(LogTemp, Warning, TEXT("血量更新为: %d"), NewHealth);
   }
};

再去BeginPlay()里调用此函数,这样UI组件只要在游戏里启动就会执行此函数把委托关系构建好;

void UUIManager::BeginPlay(){
    Super::BeginPlay();
    BindToGameManager(GameManager);
}

步骤4:触发委托(广播)

既然构建好委托关系了,现在回到游戏管理器,如何利用委托调用目标函数呢?

当得分改变时:

void UGameManager::ChangeScore(int32 NewScore)
{
    // 处理得分逻辑
    Score = NewScore;
    // 广播得分改变委托
    OnScoreChanged.Execute(); // 单播委托用Execute

    /*或者,如果委托被绑定了才执行,可以这样:
    if (OnScoreChanged.IsBound())
    {
   	     OnScoreChanged.Execute();
    }
    */
}

当血量改变时:

void UGameManager::ChangeHealth(float NewHealth)
{
    // 处理血量逻辑
    Health = NewHealth;
    // 广播血量改变委托(多播委托用Broadcast)
    OnHealthChanged.Broadcast(NewHealth);
}

这样,当ChangeScore被调用时,就会通过OnScoreChanged委托调用UUIManager的UpdateScore函数。

同样,当ChangeHealth被调用时,就会通过OnHealthChanged委托调用所有绑定的函数,包括UUIManager的UpdateHealth。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值