UE委托的使用

参考文章:

Delegate(委托)

UE4 C++进阶进行中

UE4代理上

一文理解透UE委托Delegate

UE官方文档

委托简介

委托本质上是一个函数指针的封装,它可以绑定一个函数,之后在某个时刻调用这个函数。

主要作用是将实时监测的逻辑转换为触发式逻辑,同时降低代码之间的耦合性

常用的委托有单播、多播、动态多播和事件等;

上述委托的概念不在赘述,详细请参考UE官方文档一文理解透UE委托Delegate以及上面提及的视频连接

委托的使用

由于每种委托的使用方法几乎一样,在此只使用单播作为介绍委托的使用。

委托使用的原理

下图是委托的整个事件类型

deepseek_mermaid_20250719_206cfb

下图是使用委托的三个基本类

image-20250719184658679

从上图可以看到,使用委托和不使用委托的区别,使用委托可以降低事件触发类和被调用类之间的耦合,将这两个类的操作放在委托制造类里面进行操作,这对于大型项目十分重要。

这里做个比喻就是:你点了一家饭店的吃饭,你要吃的饭汇报给饭店,饭店指定厨师做饭并将饭端给你。这里的你就是事件触发类,饭店就是委托制造类,厨师就是被调用类。

补充

委托的核心类型

根据使用场景的不同,UE 提供了四种主要的委托类型:

委托类型 描述
单播委托 (Single-cast) 只能绑定 1个 函数。执行时有返回值。
多播委托 (Multi-cast) 可以绑定 N个 函数。触发时按顺序执行所有函数。没有返回值。
动态委托 (Dynamic) 可以在 蓝图 中使用,支持序列化。执行速度稍慢于普通委托。
事件 (Events) 类似于多播委托,但只能由声明它的类触发(更安全)。

宏命名规则

声明委托时,你需要使用特定的宏。宏的名称直接告诉了你它的功能:

  • DECLARE_DELEGATE... (单播)
  • DECLARE_MULTICAST_DELEGATE... (多播)
  • DECLARE_DYNAMIC_DELEGATE... (动态单播)
  • DECLARE_DYNAMIC_MULTICAST_DELEGATE... (动态多播)

参数后缀:

  • 无后缀:0个参数
  • _OneParam:1个参数
  • _TwoParams:2个参数(以此类推,最多支持9个)

代码实现

简单代码的实现

声明与定义

假设我们要制作一个“玩家得分”的委托。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 1. 在头文件顶部或类上方声明委托
// 动态多播委托:支持蓝图,1个参数(新分数)
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnScoreChanged, int32, NewScore);

UCLASS()
class MYGAME_API AMyGameMode : public AGameModeBase
{
GENERATED_BODY()
public:
// 2. 定义委托变量
UPROPERTY(BlueprintAssignable, Category = "Events")
FOnScoreChanged OnScoreChanged;
};

绑定 (Binding)

你可以在 C++ 或蓝图中监听这个事件。

1
2
// 在某个 UI 类或监听者类中
MyGameMode->OnScoreChanged.AddDynamic(this, &AMyUserWidget::UpdateScoreDisplay);

触发 (Execution)

在 GameMode 中,当分数变化时执行委托:

1
2
3
4
5
6
void AMyGameMode::AddScore(int32 Amount)
{
CurrentScore += Amount;
// 3. 触发委托
OnScoreChanged.Broadcast(CurrentScore);
}

UE 委托机制中最核心的特性之一:参数自动传递

当你执行 OnScoreChanged.Broadcast(CurrentScore) 时,UE 的反射机制会自动将这个 CurrentScore 变量“喂”给所有通过 AddDynamic 绑定了该委托的函数。

这里的代码修改了 UEC++实现人物按照A星路线移动 里面的算法,有需要可以去参考

事件触发类

AStar_test2Character.h

1
2
// 获取委托
UMyDelegateMaker* Maker;

AStar_test2Character.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void AAStar_test2Character::SpineSet()
{
UWorld* World = GetWorld();
if (Maker == nullptr)
{
Maker = NewObject<UMyDelegateMaker>();
}
// 绑定并执行委托
Maker->BindSpineSet(World).Execute();

}

void AAStar_test2Character::AIMove()
{
UE_LOG(LogTemp, Warning, TEXT("按下3,开始执行AIMOVE"));
UWorld* World = GetWorld();
if (!World) return;

if (Maker == nullptr)
{
Maker = NewObject<UMyDelegateMaker>();
}
// 绑定并执行委托
Maker->BindAIMove(World).Execute();
}

委托制造类(重点)

MyDelegateMaker.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "MyDelegateMaker.generated.h"

// 声明 委托/代理 类型
DECLARE_DELEGATE(FDelegateNoParam)
/**
*
*/
UCLASS()
class ASTAR_TEST2_API UMyDelegateMaker : public UObject
{
GENERATED_BODY()

public:
//定义委托变量
FDelegateNoParam Delegate;
FDelegateNoParam Delegate_AIMove;

// 绑定委托函数

// 绑定样条点设置 由于委托类并不存在于真实蓝图世界中,故其需要外部传入的world
FDelegateNoParam& BindSpineSet(UWorld* World);
// 绑定AI移动
FDelegateNoParam& BindAIMove(UWorld* World);

class ASpine* USpineRef = nullptr;

class AMyAIController* AIControllerRef = nullptr;

};

MyDelegateMaker.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include "MyDelegateMaker.h"

#include "EngineUtils.h"
#include "MyAIController.h"
#include "Spine.h"
#include "Kismet/GameplayStatics.h"

FDelegateNoParam& UMyDelegateMaker::BindSpineSet(UWorld* World)
{
// 若已经绑定,则解绑
if (Delegate.IsBound())
{
Delegate.Unbind();
}

//
if (USpineRef == nullptr)
{
if(World == nullptr) {
UE_LOG(LogTemp, Error, TEXT("World is nullptr"));
}
else
{
for (TActorIterator<ASpine> It(World); It; ++It)
{
USpineRef = *It;
// 可添加if判断是否满足条件
}
}

}
// 绑定函数
Delegate.BindUObject(USpineRef, &ASpine::setSpine);

return Delegate;
}

FDelegateNoParam& UMyDelegateMaker::BindAIMove(UWorld* World)
{
if (Delegate.IsBound())
{
Delegate.Unbind();
}

if (AIControllerRef == nullptr)
{
if (World)
{
for (TActorIterator<AMyAIController> It(World); It; ++It)
{
AIControllerRef = *It;
}
Delegate_AIMove.BindUObject(AIControllerRef, &AMyAIController::goAIMove);
}
}

return Delegate_AIMove;
}

被调用类

该类中的方法主要是依据A星路线动态生成样条点,可以掠过不看,注意使用方法即可

Spine.h

1
2
3
// 创建样条点路径
UFUNCTION()
void setSpine() const;

Spine.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 创建样条点路径
void ASpine::setSpine() const
{
UWorld* World = GetWorld();

// 获取样条组件
USplineComponent* SplineComp = this -> FindComponentByClass<USplineComponent>();
if (!SplineComp) return;

// 获取样条点数组中所有点的位置
APlayerController* PlayerController = UGameplayStatics::GetPlayerController(World, 0);
if (!PlayerController) return;
AAStar_test2Character* Character = Cast<AAStar_test2Character>(PlayerController->GetPawn());
if (!Character) return;
TArray<FVector> Vectors = Character->AllPositions;

// 遍历所有位置并添加样条点
for (const FVector& Position : Vectors)
{
SplineComp->AddSplinePoint(Position, ESplineCoordinateSpace::World);
}

// 更新样条
SplineComp->UpdateSpline();

}