说说UE中的多播代理、事件和广播器

本文详细比较了UnrealEngine中的多播代理、事件和广播器,解释了它们的声明方式、添加/移除监听者、触发事件的机制,并展示了在实际项目中的应用示例。

        多播代理、事件和广播器都是在软件开发中用于实现观察者模式或发布-订阅模式的机制。它们有一些相似之处,但也有一些区别。以下是它们之间的主要区别和联系:

多播代理 (Multicast Delegates):

  • 声明方式: 使用 DECLARE_MULTICAST_DELEGATE 宏声明多播代理。

  • 语法: 多播代理是一种特殊类型的代理,允许多个函数或成员函数(监听者)注册到同一个代理上。

  • 添加和移除监听者: 使用 Add 方法添加监听者,使用 Remove 方法移除监听者。

  • 触发: 使用 Broadcast 方法触发代理,调用所有注册的监听者的处理函数。

  • 内置于UE框架: 多播代理是UE框架中的一部分,被广泛用于实现事件系统。

事件 (Events):

  • 声明方式: 使用 DECLARE_EVENT 宏声明事件。

  • 语法: 事件是一种类似于多播代理的机制,允许多个监听者注册到同一个事件上。

  • 添加和移除监听者: 使用 Add 方法添加监听者,使用 Remove 方法移除监听者。

  • 触发: 使用 Broadcast 方法触发事件,调用所有注册的监听者的处理函数。

  • UE框架内建: 事件也是UE框架中的一部分,通常用于在蓝图和C++中实现事件系统。

广播器 (Broadcaster):

  • 实现方式: 广播器是一个自定义的机制,你需要自己实现来管理和通知监听者。

  • 语法: 你可以创建一个类来表示广播器,其中包含方法用于添加和移除监听者,以及触发事件。

  • 添加和移除监听者: 通常通过自定义的方法添加和移除监听者。

  • 触发: 通过调用广播器的方法触发事件,通知所有注册的监听者。

  • 灵活性: 广播器提供了更大的灵活性,因为你可以完全控制事件的订阅和取消订阅逻辑。

联系和共通点:

  • 实现目标: 多播代理、事件和广播器都用于实现观察者模式或发布-订阅模式,让一个对象可以通知其他对象关于特定事件的发生。

  • 支持多个监听者: 三者都支持多个监听者,可以动态添加和移除监听者。

  • 触发事件: 在事件发生时,它们都能够通知所有注册的监听者。

                在Unreal Engine中,多播代理和事件是相对内置的机制,更容易在UE框架中使用。广播器则是一种更自定义的方式,可以根据项目的具体需求来实现。选择使用哪种方式取决于项目的设计和开发需求。

多播代理示例

        以下是一个简单的使用多播代理的示例,演示了如何在Unreal Engine中使用多播代理进行事件通知。在这个示例中,我们创建了一个名为FMyMulticastDelegate的多播代理,并在一个类中触发了这个代理,同时有两个不同的类作为监听器注册到了这个代理上。

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyMulticastDelegateExample.generated.h"

DECLARE_MULTICAST_DELEGATE(FMyMulticastDelegate);

UCLASS()
class YOURGAME_API AMyMulticastDelegateExample : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AMyMulticastDelegateExample();

	// Trigger the multicast delegate
	void TriggerDelegate();

	// Multicast delegate instance
	FMyMulticastDelegate MyDelegate;
};

UCLASS()
class YOURGAME_API AListenerClass1 : public AActor
{
	GENERATED_BODY()

public:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	// Function to handle the multicast delegate
	UFUNCTION()
	void HandleDelegateFunction()
	{
		UE_LOG(LogTemp, Warning, TEXT("ListenerClass1 received the delegate broadcast!"));
	}
};

UCLASS()
class YOURGAME_API AListenerClass2 : public AActor
{
	GENERATED_BODY()

public:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	// Function to handle the multicast delegate
	UFUNCTION()
	void HandleDelegateFunction()
	{
		UE_LOG(LogTemp, Warning, TEXT("ListenerClass2 received the delegate broadcast!"));
	}
};

        在这个示例中,我们有一个 AMyMulticastDelegateExample 类表示一个触发多播代理的对象。该类包含一个名为 MyDelegate 的多播代理实例,并有一个函数 TriggerDelegate() 用于触发代理。同时,我们有两个监听器类 AListenerClass1AListenerClass2,它们分别包含了处理多播代理的函数 HandleDelegateFunction()

// MyMulticastDelegateExample.cpp

#include "MyMulticastDelegateExample.h"

// Sets default values
AMyMulticastDelegateExample::AMyMulticastDelegateExample()
{
	// Set this actor to call Tick() every frame
	PrimaryActorTick.bCanEverTick = false;
}

// Trigger the multicast delegate
void AMyMulticastDelegateExample::TriggerDelegate()
{
	UE_LOG(LogTemp, Warning, TEXT("MyMulticastDelegateExample triggering the delegate!"));
	MyDelegate.Broadcast();
}

// Called when the game starts or when spawned
void AListenerClass1::BeginPlay()
{
	Super::BeginPlay();

	// Get a reference to the delegate owner
	AMyMulticastDelegateExample* DelegateOwner = Cast<AMyMulticastDelegateExample>(GetWorld()->GetFirstPlayerController()->GetPawn());

	// Add listener to the delegate
	if (DelegateOwner)
	{
		DelegateOwner->MyDelegate.AddUObject(this, &AListenerClass1::HandleDelegateFunction);
	}
}

// Called when the game starts or when spawned
void AListenerClass2::BeginPlay()
{
	Super::BeginPlay();

	// Get a reference to the delegate owner
	AMyMulticastDelegateExample* DelegateOwner = Cast<AMyMulticastDelegateExample>(GetWorld()->GetFirstPlayerController()->GetPawn());

	// Add listener to the delegate
	if (DelegateOwner)
	{
		DelegateOwner->MyDelegate.AddUObject(this, &AListenerClass2::HandleDelegateFunction);
	}
}

        在这里,AMyMulticastDelegateExample 类中的 TriggerDelegate() 函数会触发多播代理的 Broadcast,通知所有注册的监听器。两个监听器类在它们的 BeginPlay() 函数中将自己注册为代理的监听者。当 MyDelegate 被触发时,两个监听器的处理函数将被调用,输出相应的日志信息。

事件示例 

        以下是一个简单的示例,演示了如何在UE中使用C++声明和触发事件。

// MyEventExample.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyEventExample.generated.h"

DECLARE_EVENT(AMyEventExample, FMyEvent);

UCLASS()
class YOURGAME_API AMyEventExample : public AActor
{
	GENERATED_BODY()

public:
	// Sets default values for this actor's properties
	AMyEventExample();

	// Function to trigger the event
	void TriggerEvent();

	// Event declaration
	FMyEvent OnMyEvent;
};

        在上面的示例中,我们使用 DECLARE_EVENT 宏声明了一个名为 FMyEvent 的事件。然后,在 AMyEventExample 类中,我们有一个名为 OnMyEvent 的事件实例。

// MyEventExample.cpp

#include "MyEventExample.h"

// Sets default values
AMyEventExample::AMyEventExample()
{
	// Set this actor to call Tick() every frame
	PrimaryActorTick.bCanEverTick = false;
}

// Function to trigger the event
void AMyEventExample::TriggerEvent()
{
	UE_LOG(LogTemp, Warning, TEXT("AMyEventExample triggering the event!"));
	OnMyEvent.Broadcast();
}

        在上述代码中,AMyEventExample 类包含了一个名为 TriggerEvent() 的函数,该函数触发了事件 OnMyEvent。当事件被触发时,它会调用所有注册的监听者的处理函数。

接下来,我们将创建一个Actor类作为事件的监听者:

// MyEventListener.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyEventListener.generated.h"

UCLASS()
class YOURGAME_API AMyEventListener : public AActor
{
	GENERATED_BODY()

public:
	// Sets default values for this actor's properties
	AMyEventListener();

	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	// Function to handle the event
	UFUNCTION()
	void HandleEventFunction();
};
// MyEventListener.cpp

#include "MyEventListener.h"
#include "MyEventExample.h" // Include the header of the event owner class

// Sets default values
AMyEventListener::AMyEventListener()
{
	// Set this actor to call Tick() every frame
	PrimaryActorTick.bCanEverTick = false;
}

// Called when the game starts or when spawned
void AMyEventListener::BeginPlay()
{
	Super::BeginPlay();

	// Get a reference to the event owner
	AMyEventExample* EventOwner = GetWorld()->SpawnActor<AMyEventExample>(AMyEventExample::StaticClass(), FVector::ZeroVector, FRotator::ZeroRotator);

	// Add this listener to the event
	if (EventOwner)
	{
		EventOwner->OnMyEvent.AddUObject(this, &AMyEventListener::HandleEventFunction);
	}
}

// Function to handle the event
void AMyEventListener::HandleEventFunction()
{
	UE_LOG(LogTemp, Warning, TEXT("AMyEventListener received the event notification!"));
}

        在这个示例中,我们创建了一个名为 AMyEventListener 的Actor类,它实现了 HandleEventFunction 函数来处理事件。在 BeginPlay 函数中,我们获取了对事件拥有者的引用(在这里是 AMyEventExample 实例),并将监听者自身添加到事件的监听列表中。

        这样,当 AMyEventExample 类的 TriggerEvent 函数被调用时,所有注册的监听者(在这里是 AMyEventListener)的处理函数将被触发,输出相应的日志信息。

广播器示例 

        以下是一个简单的示例,演示了如何在UE中使用C++实现广播器。 

// BroadcasterExample.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "BroadcasterExample.generated.h"

class IBroadcastListener
{
public:
    virtual void HandleBroadcast() = 0;
};

UCLASS()
class YOURGAME_API ABroadcasterExample : public AActor
{
    GENERATED_BODY()

public:
    ABroadcasterExample();

    // 添加监听者
    void AddListener(IBroadcastListener* Listener);

    // 移除监听者
    void RemoveListener(IBroadcastListener* Listener);

    // 触发广播
    void BroadcastEvent();

private:
    TArray<IBroadcastListener*> Listeners;
};

        在这个示例中,我们声明了一个 IBroadcastListener 接口,该接口包含一个纯虚函数 HandleBroadcast,表示监听者需要实现这个函数来处理广播事件。然后,我们创建了一个名为 ABroadcasterExample 的Actor类,它包含了添加、移除监听者和触发广播的方法。 

// BroadcasterExample.cpp

#include "BroadcasterExample.h"

ABroadcasterExample::ABroadcasterExample()
{
    PrimaryActorTick.bCanEverTick = false;
}

// 添加监听者
void ABroadcasterExample::AddListener(IBroadcastListener* Listener)
{
    if (Listener && !Listeners.Contains(Listener))
    {
        Listeners.Add(Listener);
    }
}

// 移除监听者
void ABroadcasterExample::RemoveListener(IBroadcastListener* Listener)
{
    Listeners.Remove(Listener);
}

// 触发广播
void ABroadcasterExample::BroadcastEvent()
{
    for (IBroadcastListener* Listener : Listeners)
    {
        if (Listener)
        {
            Listener->HandleBroadcast();
        }
    }
}

        在实现文件中,我们对添加、移除监听者和触发广播的函数进行了实现。AddListenerRemoveListener 函数负责管理监听者列表,而 BroadcastEvent 函数用于遍历所有监听者并调用它们的 HandleBroadcast 函数。

接下来,我们创建一个实现了 IBroadcastListener 接口的Actor类:

// BroadcastListenerExample.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "BroadcasterExample.h"
#include "BroadcastListenerExample.generated.h"

UCLASS()
class YOURGAME_API ABroadcastListenerExample : public AActor, public IBroadcastListener
{
    GENERATED_BODY()

public:
    ABroadcastListenerExample();

    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

    // 实现IBroadcastListener接口
    virtual void HandleBroadcast() override;
};

// BroadcastListenerExample.cpp

#include "BroadcastListenerExample.h"

ABroadcastListenerExample::ABroadcastListenerExample()
{
    PrimaryActorTick.bCanEverTick = false;
}

void ABroadcastListenerExample::BeginPlay()
{
    Super::BeginPlay();

    // 获取广播器的引用
    ABroadcasterExample* Broadcaster = GetWorld()->SpawnActor<ABroadcasterExample>(ABroadcasterExample::StaticClass(), FVector::ZeroVector, FRotator::ZeroRotator);

    // 添加自己作为监听者
    if (Broadcaster)
    {
        Broadcaster->AddListener(this);
    }
}

void ABroadcastListenerExample::HandleBroadcast()
{
    UE_LOG(LogTemp, Warning, TEXT("ABroadcastListenerExample received the broadcast!"));
}

        在这个示例中,我们创建了一个名为 ABroadcastListenerExample 的Actor类,它实现了 IBroadcastListener 接口的 HandleBroadcast 函数。在 BeginPlay 函数中,我们获取了广播器的引用,并将当前实例添加为监听者。

        最后,当广播器的 BroadcastEvent 被调用时,所有注册的监听者都会收到通知,执行相应的处理函数。在这个示例中,ABroadcastListenerExample 将输出日志信息表示它收到了广播事件。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值