委托
官方文档:Delegates and Lamba Functions in Unreal Engine | 虚幻引擎 5.5 文档 | Epic Developer Community | Epic Developer Community
简单地说,委托就像是一个“函数指针”,但它更加安全和灵活。它允许程序在运行时动态地调用不同的函数。
(1)解耦对象间的关联
委托允许对象之间以松散耦合的方式进行通信。通过委托,一个对象可以在不直接引用另一个对象的情况下,通知其执行特定的操作。这有助于降低对象之间的依赖性和耦合度,从而提高代码的灵活性和可维护性。
(2)事件驱动编程
委托是实现事件驱动编程的关键机制之一。在事件驱动编程中,对象的行为是基于事件的发生来触发的。委托允许对象在事件发生时通知其他对象,并允许这些对象对事件做出响应。这种机制使得代码更加模块化,并易于扩展和维护。
(3)泛型且类型安全
委托提供了一种泛型但类型安全的方式,在C++对象上调用成员函数。通过委托,你可以动态地绑定到任意对象的成员函数,即使调用程序不知道对象的具体类型也可以进行操作。这增加了代码的灵活性和可重用性,同时保证了类型安全。
(4)异步通信
委托还支持异步通信。在虚幻引擎中,许多操作可能需要花费一些时间才能完成,如加载资源、执行物理模拟等。通过使用委托,你可以在不阻塞主线程的情况下,通知其他对象在异步操作完成后执行特定的操作。这有助于提高应用程序的响应性和性能。
(5)广播和多播
虚幻引擎中的委托还支持广播和多播机制。广播允许一个委托通知所有绑定的对象,而多播则允许一个委托通知多个指定的对象。这种机制使得在多个对象之间传递消息变得更加简单和高效。
(6)蓝图可视化编程支持
在虚幻引擎的蓝图可视化编程环境中,委托也扮演着重要的角色。通过委托,蓝图脚本可以轻松地与C++代码进行交互,从而实现更加复杂和灵活的游戏逻辑。此外,蓝图还支持动态多播委托的声明和使用,这使得在蓝图中处理事件和消息变得更加方便。
(7)复制和安全性
委托对象在复制时是很安全的。你可以通过值或引用来传递委托,但需要注意的是,通过值传递需要在堆上分配内存,这通常不是最佳实践。通过引用传递则更加高效且安全。
但传递引用,在异步的情况下会涉及到生命周期问题,容易崩溃,虚幻的委托大部分时候拷贝是安全的,因为它会存一个执行委托对象的弱引用,如果这对象消亡了,那么这个委托被调用的时候就不会执行。
委托的大致使用流程
- 声明委托类型
- 定义委托类型变量
- 通过委托变量绑定委托函数
- 执行委托
- 解绑委托函数(可根据自身情况而实施)
1.单播
先创建一个继承于Actor的类,并在其头文件包含头文件(此处文件命名为LearnSingleDelegateActor)
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "LearnSingleDelegateActor.generated.h"
并在源文件添加GameplayStatics.h头文件
#include"Kismet/GameplayStatics.h"
(1)声明委托类型
DECLARE_DELEGATE(FLearnSingleDelegatePrintLocation);
DECLARE_DELEGATE_RetVal_OneParam(FVector,FLearnSingleDelegateGetLocation,FString);
//DECLARE_DELEGATE_RetVal_OneParam(ReturnType, DelegateName, ParamType);
//ReturnType:委托将要返回的值的类型。
//DelegateName:委托的名称,这个名称将用于在代码中引用这个委托类型。
//ParamType:委托将要接受的参数的类型。
如需声明委托,请使用下文所述的宏。请根据与委托相绑定的函数(或多个函数)的函数签名来选择宏。每个宏都为新的委托类型名称、函数返回类型(如果不是 void
函数)及其参数提供了参数。当前,支持以下使用任意组合的委托签名:
- 返回一个值的函数。
- 声明为常函数。
- 最多4个"载荷"变量。
- 最多8个函数参数。
注意:委托函数支持与UFunctions相同的说明符,但使用 UDELEGATE
宏而不是 UFUNCTION
。
(2)定义委托类型变量
在ALearnSingleDelegateActor类内定义变量
public:FLearnSingleDelegatePrintLocation SingleDelegatePrintLocation;FLearnSingleDelegateGetLocation SingleDelegateGetLocation;
(3)通过委托变量绑定委托函数
为方便在ALearnSingleDelegateActor类头文件再创建一个类,此处命名为LearnLocationActor(继承于Actor)
UCLASS()
class ALearnLocationActor : public AActor
{GENERATED_BODY()public:ALearnLocationActor();protected:virtual void BeginPlay() override;void PrintLocation();FVector GetLocation(FString InStr);//此处类型看委托声明处,与其相对应.
};
在此默认函数中创建根组件来便于观察委托操作 ,并对其他函数进行定义
ALearnLocationActor::ALearnLocationActor()
{RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("LocationRoot"));
}void ALearnLocationActor::PrintLocation()
{FVector MyLocation = GetActorLocation();UE_LOG(LogTemp, Warning, TEXT("[%s]__PrintMyLocation:[%s]"), *FString(__FUNCTION__), *MyLocation.ToString());//用于被绑定时观察步骤实现
}FVector ALearnLocationActor::GetLocation(FString InStr)
{FVector MyLocation = GetActorLocation();UE_LOG(LogTemp, Warning, TEXT("[%s]__GetMyLocation:[%s]"), *FString(__FUNCTION__), *MyLocation.ToString());return MyLocation;
}
此处绑定函数
void ALearnLocationActor::BeginPlay()
{AActor *ActorPtr = UGameplayStatics::GetActorOfClass(this, ALearnSingleDelegateActor::StaticClass());if (ALearnSingleDelegateActor* SingleDelegateActorPtr=Cast<ALearnSingleDelegateActor>(ActorPtr)){//通过SingleDelegatePrintLocation此委托变量使用BindUObject函数模板绑定委托并调用一次绑定函数SingleDelegateActorPtr->SingleDelegatePrintLocation.BindUObject(this,&ALearnLocationActor::PrintLocation);SingleDelegateActorPtr->SingleDelegateGetLocation.BindUObject(this, &ALearnLocationActor::GetLocation);}
}
官方模板函数
(4) 执行委托
为了观察实现步骤,我在LearnSingleDelegateActor类声明函数暴露给蓝图
UFUNCTION(BlueprintCallable, Category = "Learn")void CallLocationActorPrint();UFUNCTION(BlueprintCallable, Category = "Learn")void CallLocationActorGet();
并对其进行定义
void ALearnSingleDelegateActor::CallLocationActorPrint()
{//第一种写法if (SingleDelegatePrintLocation.IsBound()){SingleDelegatePrintLocation.Execute();//SingleDelegatePrintLocation.Unbind();解绑操作}//第二种写法(建议用此写法)SingleDelegatePrintLocation.ExecuteIfBound();
}void ALearnSingleDelegateActor::CallLocationActorGet()
{FVector MyRecetivedLocation = SingleDelegateGetLocation.Execute(TEXT("My Single Delegate Actor"));UE_LOG(LogTemp, Warning, TEXT("[MyRecetivedLocation]__MyLocation:[%s]"), *MyRecetivedLocation.ToString());
}
注意:对于无返回值的委托,可调用ExecuteIfBound()函数,但需注意输出参数可能未初始化。
(5)观察实现步骤(只做演示)
1、将两个类拖入场景中
2、在关卡蓝图处使用函数(此处是使用键盘1、2分别去激活函数)
3、启动关卡,并在输出日志观察