事件处理(Handling Events)和委托(Delegate)代码示例(二)【UE4】【C++】

简介: 3. 创建带参数的委托

3. 创建带参数的委托

我们可以通过修改委托的签名来使其接受参数

比如我们需要接受一个参数的话,可以在 GameMode 中这样声明:

DECLARE_DELEGATE_OneParam(FParamDelegateSignature, FLinearColor)

注意:这个宏与之前稍有不同,后缀多出了一个 _OneParam,而且我们还需要指定接受参数的类型——本例为 FLinearColor

接着再添加一个 FParamDelegateSignature 成员

FParamDelegateSignature MyParameterDelegate;  

这和之前一样,创建一个委托实例作为 GameMode 成员

然后创建一个 Actor 子类,取名为 ParamDelegateListener,

在头文件中添加以下声明

UFUNCTION()
void SetLightColor(FLinearColor LightColor);
UPROPERTY()
UPointLightComponent* PointLight;

ParamDelegateListener.cpp

#include "Test.h"
#include "UE4TestGameMode.h"
#include "ParamDelegateListener.h"
// Sets default values
AParamDelegateListener::AParamDelegateListener()
{
  // 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;
  PointLight = CreateDefaultSubobject<UPointLightComponent>("PointLight");
  RootComponent = PointLight;
}
// Called when the game starts or when spawned
void AParamDelegateListener::BeginPlay()
{
  Super::BeginPlay();
  UWorld* TheWorld = GetWorld();
  if (TheWorld != nullptr)
  {
    AGameMode* GameMode = Cast<AGameMode>(UGameplayStatics::GetGameMode(TheWorld));
    AUE4TestGameMode * MyGameMode = Cast<AUE4TestGameMode>(GameMode);
    if (MyGameMode != nullptr)
    {
      // Binds a UObject-based member function delegate. UObject delegates keep a weak reference to your object. You can use ExecuteIfBound() to call them.(注意绑定的还是 UFUNCTION)
      MyGameMode->MyParameterDelegate.BindUObject(this, &AParamDelegateListener::SetLightColor);
    }
  }
}
// Called every frame
void AParamDelegateListener::Tick( float DeltaTime )
{
  Super::Tick( DeltaTime );
}
// 1个参数
void AParamDelegateListener::SetLightColor(FLinearColor LightColor)
{
  PointLight->SetLightColor(LightColor);
}

回到 MyTriggerVollume.cpp,在 NotifyActorBeginOverlap 函数中添加以下代码:

MyGameMode->MyParameterDelegate.ExecuteIfBound(FLinearColor(1, 0, 0, 1)); // 带一个参数

与之前不同的是,我们需要多指定一个参数,参数类型和我们之前的委托声明一致。


显然,MyTriggerVolume 压根就无需知道 ParamDelegateListener 的存在,却通过 GameMode 就可以调用 ParamDelegateListener 的函数了,很大程度上降低了类间的耦合度。


解绑委托方式与之前相同,不再赘述。


4.通过委托绑定传递负载数据(Payload Data)

稍加修改,我们就可以在委托被调用时传递额外创建时的参数(additional creation-time parameter),即我们在 MyTriggerVolume 中的调用方式不变,仍然是 ExecuteIfBound(FLinearColor(1, 0, 0, 1)),但可以额外添加一些负载数据,在 ParamDelegateListener 中的 BindUObject 上添加。


首先修改 AParamDelegateListener::BeginPlay 中的 BindUObject,为其添加一个 bool 负载数据

MyGameMode->MyParameterDelegate.BindUObject(this, &AParamDelegateListener::SetLightColor, false);

并修改 SetLightColor 的定义(增加 bool 参数)

UFUNCTION()
void SetLightColor(FLinearColor LightColor, bool EnableLight);
// 2个参数
void AParamDelegateListener::SetLightColor(FLinearColor LightColor, bool EnableLight)
{
  PointLight->SetLightColor(LightColor);
  PointLight->SetVisibility(EnableLight);
}

注意:负载数据并不局限于带参数的委托,其他的委托形式也可以使用


5. 多播委托(Multicast Delegate)

之前说的委托,都是只绑定了一个函数指针,而多播委托绑定的是一个函数指针集合,每个函数指针都有对应的一个委托句柄,当广播(Broadcast)委托的时候,他们将会被激活。


首先在 GameMode 中添加多播的委托声明

需要明确声明为多播

DECLARE_MULTICAST_DELEGATE(FMulticastDelegateSignature)

其次,创建一个新 Actor 类,命名为 MulticastDelegateListener

在其头文件中添加以下声明:

FDelegateHandle MyDelegateHandle;
UPROPERTY()
UPointLightComponent* PointLight;
UFUNCTION()
void ToggleLight();
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;

大部分和之前的 Listener 类很相似,但是多一个 委托句柄实例,将用它来存储委托实例的引用,我们的添加(AddUObject)和移除(Remove)都需要它作为参数

源文件的代码如下:

// Sets default values
AMulticastDelegateListener::AMulticastDelegateListener()
{
  // 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;
  PointLight = CreateDefaultSubobject<UPointLightComponent>("PointLight");
  RootComponent = PointLight;
}
// Called when the game starts or when spawned
void AMulticastDelegateListener::BeginPlay()
{
  Super::BeginPlay();
  UWorld* TheWorld = GetWorld();
  if (TheWorld != nullptr)
  {
    AGameMode* GameMode = Cast<AGameMode>(UGameplayStatics::GetGameMode(TheWorld));
    AUE4TestGameMode * MyGameMode = Cast<AUE4TestGameMode>(GameMode);
    if (MyGameMode != nullptr)
    {
      // Adds a UObject-based member function delegate. UObject delegates keep a weak reference to your object.
      // 注册一个对象方法
      MyDelegateHandle = MyGameMode->MyMulticastDelegate.AddUObject(this, &AMulticastDelegateListener::ToggleLight);
    }
  }
}
// Called every frame
void AMulticastDelegateListener::Tick( float DeltaTime )
{
  Super::Tick( DeltaTime );
}
void AMulticastDelegateListener::ToggleLight()
{
  PointLight->ToggleVisibility();
}
void AMulticastDelegateListener::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
  Super::EndPlay(EndPlayReason);
  UWorld* TheWorld = GetWorld();
  if (TheWorld != nullptr)
  {
    AGameMode* GameMode = Cast<AGameMode>(UGameplayStatics::GetGameMode(TheWorld));
    AUE4TestGameMode * MyGameMode = Cast<AUE4TestGameMode>(GameMode);
    if (MyGameMode != nullptr)
    {
      // Removes a function from this multi-cast delegate's invocation list (performance is O(N)). Note that the order of the delegates may not be preserved!
      MyGameMode->MyMulticastDelegate.Remove(MyDelegateHandle);
    }
  }
}

MyTriggerVolume.cpp 的实现为:

// Broadcasts this delegate to all bound objects, except to those that may have expired.
MyGameMode->MyMulticastDelegate.Broadcast();

广播函数很像我们之前的 ExecuteIfBound 函数,但有一点不同,它不需要检查是否有函数绑定在委托上。


最后的效果是,如果我们往场景中拖放了四五个 MulticastDelegateListener ,当我们进入触发区域,它们的灯会同时打开或关闭,因为每个实例函数都被添加到委托集合当中;


如果拖放了四五个 DelegateListener 到场景中,当我们进入触发区域,只有最后一个拖进场景的灯会亮,这是因为委托只绑定了最后一个实例函数。


(未完待续)


目录
相关文章
|
2月前
|
C++
C++ 语言异常处理实战:在编程潮流中坚守稳定,开启代码可靠之旅
【8月更文挑战第22天】C++的异常处理机制是确保程序稳定的关键特性。它允许程序在遇到错误时优雅地响应而非直接崩溃。通过`throw`抛出异常,并用`catch`捕获处理,可使程序控制流跳转至错误处理代码。例如,在进行除法运算或文件读取时,若发生除数为零或文件无法打开等错误,则可通过抛出异常并在调用处捕获来妥善处理这些情况。恰当使用异常处理能显著提升程序的健壮性和维护性。
51 2
|
2月前
|
算法框架/工具 C++ Python
根据相机旋转矩阵求解三个轴的旋转角/欧拉角/姿态角 或 旋转矩阵与欧拉角(Euler Angles)之间的相互转换,以及python和C++代码实现
根据相机旋转矩阵求解三个轴的旋转角/欧拉角/姿态角 或 旋转矩阵与欧拉角(Euler Angles)之间的相互转换,以及python和C++代码实现
111 0
|
6天前
|
C++
继续更新完善:C++ 结构体代码转MASM32代码
继续更新完善:C++ 结构体代码转MASM32代码
|
6天前
|
C++ Windows
HTML+JavaScript构建C++类代码一键转换MASM32代码平台
HTML+JavaScript构建C++类代码一键转换MASM32代码平台
|
6天前
|
C++
2合1,整合C++类(Class)代码转换为MASM32代码的平台
2合1,整合C++类(Class)代码转换为MASM32代码的平台
|
6天前
|
前端开发 C++ Windows
C++生成QML代码与QML里面集成QWidget
这篇文章介绍了如何在C++中生成QML代码,以及如何在QML中集成QWidget,包括使用Qt Widgets嵌入到QML界面中的技术示例。
|
2月前
|
程序员 C++ 开发者
C++命名空间揭秘:一招解决全局冲突,让你的代码模块化战斗值飙升!
【8月更文挑战第22天】在C++中,命名空间是解决命名冲突的关键机制,它帮助开发者组织代码并提升可维护性。本文通过一个图形库开发案例,展示了如何利用命名空间避免圆形和矩形类间的命名冲突。通过定义和实现这些类,并在主函数中使用命名空间创建对象及调用方法,我们不仅解决了冲突问题,还提高了代码的模块化程度和组织结构。这为实际项目开发提供了宝贵的参考经验。
48 2
|
2月前
|
C++
拥抱C++面向对象编程,解锁软件开发新境界!从混乱到有序,你的代码也能成为高效能战士!
【8月更文挑战第22天】C++凭借其强大的面向对象编程(OOP)能力,在构建复杂软件系统时不可或缺。OOP通过封装数据和操作这些数据的方法于对象中,提升了代码的模块化、重用性和可扩展性。非OOP方式(过程化编程)下,数据与处理逻辑分离,导致维护困难。而OOP将学生信息及其操作整合到`Student`类中,增强代码的可读性和可维护性。通过示例对比,可以看出OOP使C++代码结构更清晰,特别是在大型项目中,能有效提高开发效率和软件质量。
23 1
|
2月前
|
C++
C++代码来计算一个点围绕另一个点旋转45度后的坐标
C++代码来计算一个点围绕另一个点旋转45度后的坐标
47 0
|
2月前
|
C++
Resharper c++ 使用Enter自动补全代码
Resharper c++ 使用Enter自动补全代码
33 0
下一篇
无影云桌面