委托是什么:
委托允许一个对象注册回调函数,当特定事件发生时,这些回调函数会被自动调用。与直接函数调用不同,委托提供了更大的灵活性。一个功能对应一个委托,一个委托可以调用多个类多个函数
一个生动的比喻:
想象一下你在学校里,老师要通知大家明天去春游。但是老师不想一个一个地告诉每个学生,她就在教室里贴了一个公告板(委托),然后告诉同学们:“如果你们想知道春游的消息,就请在这个公告板上登记你的名字和联系方式(绑定函数)。当我确定春游时间后,我会通过公告板通知大家(广播委托)。”
委托就是这样一个公告板,它允许我们在某个事件发生时,自动通知所有登记了的人。
这样做有个好处:老师无需知道到底会有多少学生,或者到底是哪些学生.
委托的种类:
在虚幻引擎中,委托分为几种:
单播委托:只能绑定一个函数。
多播委托:可以绑定多个函数。
动态委托:可以被序列化,并且可以在蓝图中使用。
委托的优势:
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)的函数;
两个功能分别使用两个不同的委托来实现:
- 当玩家得分时触发,在UI界面更新分数(用单播委托)
- 当玩家回血时触发,在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。
1362

被折叠的 条评论
为什么被折叠?



