# 前言

在 U3D 中,因为主要是以 C# 为主,C# 原生支持事件、委托。用起来很方便,不但可以降低模块之前的耦合,而且它还可以提供一种优秀的性能提升方式:只有当值真的改变,并且必要的时候,对需要的对象进行通知,避免外部不断地对变量进行读取判断。所以也经常用到。

虽然委托是 C# 中的语法,C++ 标准语法里边虽然没这东西,不过在 UE4 中,被改造后的语法同样提供了相应的实现方法。

那么,接下来就来看看吧。

# 实现

# 委托

# 无参委托

对于声明非常简单,在类的声明之前,使用 DECLARE_DELEGATE 进行定义:

DECLARE_DELEGATE(FAction)

为了方便测试,我建立了一个 “ADelegateTestActor” 的 Actor 以及一个 “UDelegateTestActorComponent” 的组件,并在该组件开始游戏中,向委托进行注册:

	AActor* actor = GetOwner();
	if (actor != nullptr)
	{
		ADelegateTestActor* del = Cast<ADelegateTestActor>(actor);
		if (del != nullptr)
		{
			del->OnTestInvoke.BindLambda([]() {GEngine->AddOnScreenDebugMessage(-1, 5, FColor::Magenta, TEXT("委托测试!")); });
		}
	}

而 Actor 的构造函数中,则直接为自己添加一个 “UDelegateTestActorComponent”,这样,将 Actor 放入场景之后,自动就具有了测试组件:

ADelegateTestActor::ADelegateTestActor()
{
	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	UDelegateTestActorComponent* o = CreateDefaultSubobject<UDelegateTestActorComponent>("DelCmp");

}

接下来就是委托的调用了,简单地使用了一个延时调用方法:

	GetWorldTimerManager().SetTimer(_timeHandle, [this]() {OnTestInvoke.ExecuteIfBound(); GEngine->AddOnScreenDebugMessage(-1, 5, FColor::Magenta, TEXT("调用>>>")); }, 1, false, 3);

OK,第一个委托及其测试都搞定了。
返回 UE4,编译一下,然后将 “ADelegateTestActor” 放入场景,运行:

见到上述打印,就说明成功执行了。
看起来是很简单吧?其实也跟 C# 的实现差不多。

# 有参委托

有参委托与无参数委托用法差不多,只是定义改了一下,这儿就来简单看看一个参数的委托吧.
首先是定义:

DECLARE_DELEGATE_OneParam(FAction_One, FString)

定义:

	FAction_One OnOneParamInvoke;

注册,依然是在 “UDelegateTestActorComponent” 开始时进行,这次除了打印一个固定的语句,同时加上了委托传过来的参数值:

	AActor* actor = GetOwner();
	if (actor != nullptr)
	{
		ADelegateTestActor* del = Cast<ADelegateTestActor>(actor);
		if (del != nullptr)
		{
			del->OnOneParamInvoke.BindLambda([](FString param) {GEngine->AddOnScreenDebugMessage(-1, 5, FColor::Magenta, TEXT("委托测试!  "+ param)); });
		}
	}

调用:

	GetWorldTimerManager().SetTimer(_timeHandle, [this]() {OnOneParamInvoke.ExecuteIfBound(">Hello World!<"); GEngine->AddOnScreenDebugMessage(-1, 5, FColor::Magenta, TEXT("调用>>>")); }, 1, false, 3);

在调用时,同时传入了一个字符串参数,我这儿还是以 “Hello World” 为例吧。

最后运行效果:

其他与委托相关,多个参数均需要使用相应的宏定义进行声明,如下:

DECLARE_DELEGATE_OneParam
DECLARE_DELEGATE_TwoParams
DECLARE_DELEGATE_ThreeParams
DECLARE_DELEGATE_FourParams
DECLARE_DELEGATE_FiveParams
DECLARE_DELEGATE_SixParams
DECLARE_DELEGATE_SevenParams
DECLARE_EVENT_EightParams
DECLARE_DELEGATE_NineParams

# 多播委托

上述委托用起来没问题。
但是,会有一个限制:只能绑定一个方法。

如果有新的方法绑上去,就会变成新的方法,旧有的就会被丢掉,不相信的话,可以创建两个 “” 组件:

	CreateDefaultSubobject<UDelegateTestActorComponent>("DelCmp");
	CreateDefaultSubobject<UDelegateTestActorComponent>("DelCmp2");

然后在绑定委托方法中加一句,顺便打印一下组件的名字:

			del->OnOneParamInvoke.BindLambda([this](FString param) {GEngine->AddOnScreenDebugMessage(-1, 5, FColor::Magenta, TEXT(  " 委托测试!  " + GetName() +"  "+ param)); });

就是这样。

所以就有了多播委托,多播委托运行绑定多个方法,并在绑定方法时,返回一个唯一的指针,以便后续对指定单个方法进行解绑。

声明:

DECLARE_MULTICAST_DELEGATE_OneParam(FMultiAction, FString);

定义:

	FMultiAction OnMultiOneParamInvoke;

绑定:

	AActor* actor = GetOwner();
	if (actor != nullptr)
	{
		ADelegateTestActor* del = Cast<ADelegateTestActor>(actor);
		if (del != nullptr)
		{
			del->OnMultiOneParamInvoke.AddLambda([this](FString param) {GEngine->AddOnScreenDebugMessage(-1, 5, FColor::Magenta, TEXT(" 委托测试!  " + GetName() + "  " + param)); });
		}
	}

调用:

		GetWorldTimerManager().SetTimer(_timeHandle, [this]() {OnMultiOneParamInvoke.Broadcast(">Hello World!<"); GEngine->AddOnScreenDebugMessage(-1, 5, FColor::Magenta, TEXT("调用>>>")); }, 1, false, 3);

这儿我只是简单地测试,就没有缓存其返回值,如果有需要的话,是可以使用其进行方法解绑的。

效果:

# 事件

事件跟委托差不多,与 C# 中的概念也没差多少,它只是相当于 “受限的委托 “。限制其调用只能由指定的类进行。

并且,事件默认就是 “多播”,不存在像默认委托一样,一个事件只能绑一个方法的说法。

声明:

DECLARE_EVENT_OneParam(ADelegateTestActor, FEvent_One, FString)

在声明中与委托唯一的不同就是,需要多传入一个参数:限制只能调用的类的类型。
比如在此定义之后,就只能允许 “ADelegateTestActor” 这个类进行委托的调用 —— 本来我是很想这样写的,但是... 实际用的时候,还是发现跟多播委托差不多,外部都可以调用。
例如,在 “” 组件中,绑定事件后。立刻调用:

	AActor* actor = GetOwner();
	if (actor != nullptr)
	{
		ADelegateTestActor* del = Cast<ADelegateTestActor>(actor);
		if (del != nullptr)
		{
			del->OnEventInvoke.AddLambda([this](FString param) {GEngine->AddOnScreenDebugMessage(-1, 5, FColor::Magenta, TEXT(" 事件测试:  " + GetName() + "  " + param)); });

			del->OnEventInvoke.Broadcast(FString("Hello Event"));
		}
	}

并不报错,编译也能通过.....

运行:

So... 容我再研究下,这篇 Blog 就先到这儿了吧。