# 前言
在 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 就先到这儿了吧。