UE 事件分发机制(二) day10

自定义事件分发机制

自建事件分发机制与结构

  • Unreal推荐的游戏逻辑开发流程
    在这里插入图片描述
  • 基于 Unreal推荐的游戏逻辑开发流程,一般我们的整体规划也就是这样
    在这里插入图片描述
  • 大致结构类图
    在这里插入图片描述

创建接口类与管理类以及所需函数

  • 新建一个Unreal接口类作为接口
    在这里插入图片描述
  • 然后创建一个蓝图函数库的基类
    在这里插入图片描述

EventInterface接口类

EventInterface.h

  • 复习一下BlueprintNativeEvent这个参数:会在C++中提供一个默认的实现,然后蓝图中去覆盖它改写它,在蓝图中实现这个函数时,如果调用一个父类的版本,它会先调用C++里面加了_Implementation这个函数,然后再去做蓝图其他的操作
// Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "EventInterface.generated.h"// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UEventInterface : public UInterface
{GENERATED_BODY()
};/*** */
class DISTRIBUTE_API IEventInterface
{GENERATED_BODY()// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:UFUNCTION(BlueprintNativeEvent,Category="Event Dispather Tool")void OnReceiveEvent(UObject* Data);
};

EventInterface.cpp

// Fill out your copyright notice in the Description page of Project Settings.#include "EventInterface.h"// Add default functionality here for any IEventInterface functions that are not pure virtual.

EventManager蓝图函数库的基类管理类

EventManager.h

// Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "EventManager.generated.h"/*** */
UCLASS()
class DISTRIBUTE_API UEventManager : public UBlueprintFunctionLibrary
{GENERATED_BODY()private://监听者static TMap<FString, TArray<UObject*>> AllListeners;public:UFUNCTION(BlueprintCallable, Category = "Event Dispather Tool")static void AddEventListener(FString EventName, UObject* Listener);UFUNCTION(BlueprintCallable, Category = "Event Dispather Tool")static void RemoveEventListener(FString EventName, UObject* Listener);UFUNCTION(BlueprintCallable, Category = "Event Dispather Tool")static FString  DispatchEvent(FString EventName, UObject* Data);UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Event Dispather Tool")static UObject* NameAsset(UClass* ClassType);
};

EventManager.cpp

// Fill out your copyright notice in the Description page of Project Settings.#include "EventManager.h"void UEventManager::AddEventListener(FString EventName, UObject* Listener)
{
}void UEventManager::RemoveEventListener(FString EventName, UObject* Listener)
{
}FString UEventManager::DispatchEvent(FString EventName, UObject* Data)
{return FString();
}UObject* UEventManager::NameAsset(UClass* ClassType)
{return nullptr;
}

编写AddEventListener函数

  • 加入接口的头文件,判断添加进来的事件名字是否为空,监听指针是否为空,检测对象是否有效,是否实现了指定的接口类

  • Listener->IsValidLowLevel():检查对象是否有效

  • Listener->GetClass()->ImplementsInterface(UEventInterface::StaticClass()):检测接口是否实现指定接口,这里传入的UEventInterface就是接口类中的参与蓝图的类名
    在这里插入图片描述

    //初始化监听器
    TMap<FString, TArray<UObject*>> UEventManager::AllListeners;void UEventManager::AddEventListener(FString EventName, UObject* Listener)
    {if (EventName == "" || Listener == nullptr || !Listener->IsValidLowLevel() ||!Listener->GetClass()->ImplementsInterface(UEventInterface::StaticClass())){return;}
    }
    
  • 然后查找是否存在这个事件数组,如果为空说明目前没有这个事件,就重新添加一下,存在就直接添加事件进入到数组即可

  • AddEventListener函数完整代码逻辑

#include "EventManager.h"
#include "EventInterface.h"//初始化监听器
TMap<FString, TArray<UObject*>> UEventManager::AllListeners;void UEventManager::AddEventListener(FString EventName, UObject* Listener)
{if (EventName == "" || Listener == nullptr || !Listener->IsValidLowLevel() ||!Listener->GetClass()->ImplementsInterface(UEventInterface::StaticClass())){return;}//查找是否存在这个事件数组TArray<UObject*>* EventArr = UEventManager::AllListeners.Find(EventName);//如果为空说明目前没有这个事件,就重新添加一下if (EventArr == nullptr || EventArr->Num() == 0){TArray<UObject*> NewEventArr = { Listener };UEventManager::AllListeners.Add(EventName, NewEventArr);}//存在就直接添加事件进入到数组即可else{EventArr->Add(Listener);}
}

编写其他函数

RemoveEventListener函数编写

  • 查询事件数组中是否有这个事件,如果有就删除这个事件
void UEventManager::RemoveEventListener(FString EventName, UObject* Listener)
{TArray<UObject*>* EventArr = UEventManager::AllListeners.Find(EventName);if (EventArr != nullptr && EventArr->Num() != 0){EventArr->Remove(Listener);}
}

DispatchEvent函数编写

  • 分派事件,也是首先考虑事件是否存在,然后循环事件数组里事件,不存在的事件就直接剔除,存在的就进行派发事件通知,注意派发事件通知时也要进行安全检测
FString UEventManager::DispatchEvent(FString EventName, UObject* Data)
{TArray<UObject*>* EventArr = UEventManager::AllListeners.Find(EventName);//如果不存在此事件数组if (EventArr == nullptr || EventArr->Num() == 0){return "'" + EventName + "'No Listener.";}//使用UE_LOG换行,方便查看FString ErrorInfo = "\n";for (int i = 0; i < EventArr->Num(); i++){UObject* Obj = (*EventArr)[i];//安全判断一下事件是否存在if (Obj == nullptr || !Obj->IsValidLowLevel() || !Obj->GetClass()->ImplementsInterface(UEventInterface::StaticClass())){//不存在直接剔除EventArr->RemoveAt(i);//剔除后要i--,因为TArray删除一个元素后会自动补齐,这样会导致我们当前循环出现bugi--;}//通过了安全检测就直接派遣事件通知else{UFunction* FUN = Obj->FindFunction("OnReceiveEvent");//安全检测这个FUN是否有效if (FUN == nullptr || !FUN->IsValidLowLevel()){//打印错误信息ErrorInfo += "'" + Obj->GetName() + "'No 'OnReceiveEvent' Function.\n";}else{//调用Obj->ProcessEvent(FUN, &Data);}}}return ErrorInfo;
}

NameAsset函数编写

  • 这个函数是方便我们在蓝图中去创建某一个新的指定类实例
  • 加入头文件 #include “Engine.h”,使用NewObject函数来创建一个新的指定对象
  • UObject* Obj = NewObject<UObject>(GetTransientPackage(), ClassType);
    • GetTransientPackage():返回一个临时包(主要用于对象池)的指针
    • ClassType:ClassType 是一个 UClass 指针,指定了新对象的类型。
UObject* UEventManager::NameAsset(UClass* ClassType)
{UObject* Obj = NewObject<UObject>(GetTransientPackage(), ClassType);return Obj;
}

EventManager.cpp

// Fill out your copyright notice in the Description page of Project Settings.#include "EventManager.h"
#include "EventInterface.h"
#include "Engine.h"
//初始化监听器
TMap<FString, TArray<UObject*>> UEventManager::AllListeners;void UEventManager::AddEventListener(FString EventName, UObject* Listener)
{if (EventName == "" || Listener == nullptr || !Listener->IsValidLowLevel() ||!Listener->GetClass()->ImplementsInterface(UEventInterface::StaticClass())){return;}//查找是否存在这个事件数组TArray<UObject*>* EventArr = UEventManager::AllListeners.Find(EventName);//如果为空说明目前没有这个事件,就重新添加一下if (EventArr == nullptr || EventArr->Num() == 0){TArray<UObject*> NewEventArr = { Listener };UEventManager::AllListeners.Add(EventName, NewEventArr);}//存在就直接添加事件进入到数组即可else{EventArr->Add(Listener);}
}void UEventManager::RemoveEventListener(FString EventName, UObject* Listener)
{TArray<UObject*>* EventArr = UEventManager::AllListeners.Find(EventName);if (EventArr != nullptr && EventArr->Num() != 0){EventArr->Remove(Listener);}
}FString UEventManager::DispatchEvent(FString EventName, UObject* Data)
{TArray<UObject*>* EventArr = UEventManager::AllListeners.Find(EventName);//如果不存在此事件数组if (EventArr == nullptr || EventArr->Num() == 0){return "'" + EventName + "'No Listener.";}//使用UE_LOG换行,方便查看FString ErrorInfo = "\n";for (int i = 0; i < EventArr->Num(); i++){UObject* Obj = (*EventArr)[i];//安全判断一下事件是否存在if (Obj == nullptr || !Obj->IsValidLowLevel() || !Obj->GetClass()->ImplementsInterface(UEventInterface::StaticClass())){//不存在直接剔除EventArr->RemoveAt(i);//剔除后要i--,因为TArray删除一个元素后会自动补齐,这样会导致我们当前循环出现bugi--;}//通过了安全检测就直接派遣事件通知else{UFunction* FUN = Obj->FindFunction("OnReceiveEvent");//安全检测这个FUN是否有效if (FUN == nullptr || !FUN->IsValidLowLevel()){//打印错误信息ErrorInfo += "'" + Obj->GetName() + "'No 'OnReceiveEvent' Function.\n";}else{//调用Obj->ProcessEvent(FUN, &Data);}}}return ErrorInfo;
}UObject* UEventManager::NameAsset(UClass* ClassType)
{UObject* Obj = NewObject<UObject>(GetTransientPackage(), ClassType);return Obj;
}

自定义事件分发广播事件

  • 因为我们的自定义事件系统里面发布数据只有一个参数,所以我们新建一个Object类专门来放数据,需要传递数据时就通过这个Object类
    在这里插入图片描述
    在这里插入图片描述
  • 然后创建一个Pawn类作为SenderEvent发起者,开始广播事件
// Called when the game starts or when spawned
void AMyBPAndCpp_Sender::BeginPlay()
{Super::BeginPlay();FTimerHandle TimerHandle;auto Lambda = [](){//开始广播事件UMyData* Data = Cast<UMyData>(UEventManager::NameAsset(UMyData::StaticClass()));Data->Param = FMath::RandRange(0, 100);//委派事件UEventManager::DispatchEvent("MyBPAndCpp_DispatchEvent", Data);};GetWorld()->GetTimerManager().SetTimer(TimerHandle, FTimerDelegate::CreateLambda(Lambda), 3.f, true);}

MyBPAndCpp_Sender.h

// Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "MyBPAndCpp_Sender.generated.h"UCLASS()
class DISTRIBUTE_API AMyBPAndCpp_Sender : public APawn
{GENERATED_BODY()public:// Sets default values for this pawn's propertiesAMyBPAndCpp_Sender();UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Properties")class UStaticMeshComponent* StaticMesh;
protected:// Called when the game starts or when spawnedvirtual void BeginPlay() override;public:	// Called every framevirtual void Tick(float DeltaTime) override;// Called to bind functionality to inputvirtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;};

MyBPAndCpp_Sender.cpp

// Fill out your copyright notice in the Description page of Project Settings.#include "MyBPAndCpp_Sender.h"
#include "Components/StaticMeshComponent.h"
#include "UObject/ConstructorHelpers.h"
#include "EventDistributeTool/EventManager.h"
#include "Public/TimerManager.h"
#include "MyData.h"// Sets default values
AMyBPAndCpp_Sender::AMyBPAndCpp_Sender()
{// Set this pawn to call Tick() every frame.  You can turn this off to improve performance if you don't need it.PrimaryActorTick.bCanEverTick = true;StaticMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));RootComponent = StaticMesh;static ConstructorHelpers::FObjectFinder<UStaticMesh> StaticMeshAsset(TEXT("StaticMesh'/Engine/BasicShapes/Sphere.Sphere'"));static ConstructorHelpers::FObjectFinder<UMaterialInterface> MaterialAsset(TEXT("Material'/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial'"));if (StaticMeshAsset.Succeeded() && MaterialAsset.Succeeded()){StaticMesh->SetStaticMesh(StaticMeshAsset.Object);StaticMesh->SetMaterial(0, MaterialAsset.Object);}
}// Called when the game starts or when spawned
void AMyBPAndCpp_Sender::BeginPlay()
{Super::BeginPlay();FTimerHandle TimerHandle;auto Lambda = [](){//开始广播事件UMyData* Data = Cast<UMyData>(UEventManager::NameAsset(UMyData::StaticClass()));Data->Param = FMath::RandRange(0, 100);//委派事件UEventManager::DispatchEvent("MyBPAndCpp_DispatchEvent", Data);};GetWorld()->GetTimerManager().SetTimer(TimerHandle, FTimerDelegate::CreateLambda(Lambda), 3.f, true);}// Called every frame
void AMyBPAndCpp_Sender::Tick(float DeltaTime)
{Super::Tick(DeltaTime);}// Called to bind functionality to input
void AMyBPAndCpp_Sender::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{Super::SetupPlayerInputComponent(PlayerInputComponent);}

自定义事件分发订阅事件

  • 新建一个Actor基类,然后派生三个子类来订阅事件测试,这三个子类需要继承EventInterface这个接口类,父类接口中方法有BlueprintNativeEvent反射参数,所以在这实现加后缀_Implementation
    在这里插入图片描述

Actor_Receive_R.h

// Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h"
#include "EventDistributeTool/EventInterface.h"
#include "BPAndCpp/Actor_Receive.h"
#include "Actor_Receive_R.generated.h"/*** */
UCLASS()
class DISTRIBUTE_API AActor_Receive_R : public AActor_Receive, public IEventInterface
{GENERATED_BODY()protected:virtual void BeginPlay() override;
public:virtual void OnReceiveEvent_Implementation(UObject* Data) override;
};

Actor_Receive_R.cpp

// Fill out your copyright notice in the Description page of Project Settings.#include "Actor_Receive_R.h"
#include "Engine/Engine.h"
#include "MyData.h"
#include "EventDistributeTool/EventManager.h"
void AActor_Receive_R::BeginPlay()
{Super::BeginPlay();//订阅事件UEventManager::AddEventListener("MyBPAndCpp_DispatchEvent", this);
}
void AActor_Receive_R::OnReceiveEvent_Implementation(UObject* Data)
{//打印到屏幕GEngine->AddOnScreenDebugMessage(INDEX_NONE, 10.f, FColor::Red, FString::Printf(TEXT("%i"), Cast<UMyData>(Data)->Param));
}
  • 运行结果
    在这里插入图片描述
  • 触发错误打印log的原因是因为,EventManager类中的都是静态方法,静态方法只有到下次编译之后才会被释放,所以在调试中就会这样触发log
  • 使用在视口中播放就不会触发log,在正式游戏中也不会触发,或者有正常的解绑事件也不会有问题
    在这里插入图片描述

自定义事件分发蓝图订阅与解绑事件

蓝图订阅事件

  • 蓝图订阅事件,创建一个Actor蓝图然后派生三个子蓝图,注意调用接口类中的事件时要添加接口后编译才能找到OnReceiveEvent,然后委派事件时的名字记得填写
    在这里插入图片描述
  • 运行结果
    在这里插入图片描述

解绑事件

  • 解绑调用RemoveEventListener函数即可
    在这里插入图片描述
  • C++中也是差不多调用RemoveEventListener函数即可
    在这里插入图片描述
  • 运行结果,只会播放一轮了
    在这里插入图片描述

当播放结束时自动移除事件

  • 这样也能解决C++中的触发错误打印log
  • 蓝图中结束后自动移除事件
    在这里插入图片描述
  • C++中是重写BeginDestroy函数,调用移除函数即可
    在这里插入图片描述
    在这里插入图片描述
  • 运行结果,这样就不会有log错误了
    在这里插入图片描述

自定义事件之蓝图广播事件

  • 将MyBPAndCpp_Sender类转换为蓝图,首先调用一下父类的BeginPlay这样C++那边的事件才会生效
    在这里插入图片描述
  • 编写蓝图逻辑,发布广播
    在这里插入图片描述
  • 然后找个Actor蓝图接收事件,测试,订阅一下这个事件名即可
    在这里插入图片描述
  • 运行结果
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/230739.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uniapp小程序项目连接微信客服【最新/最全教程】

目录 文档微信官网文档图片微信小程序客服配置官网 效果图聊天地址手机微信电脑端 微信聊天功能实现微信小程序后台添加客服微信号以及配置代码实现参考最后 文档 微信官网文档 微信官网文档 图片 微信小程序客服配置官网 微信小程序客服配置官网 效果图 聊天地址 地址 手…

LESS的叶绿素荧光模拟实现——任意波段荧光模拟

目录 前言一、任意波段荧光模拟的实现二、需要注意的输入参数 前言 此专栏默认您对LESS (LargE-Scale remote sensing data and image Simulation framework) 模型和叶绿素荧光(Sun-Induced chlorophyll Fluorescence, SIF)有一定的了解。当然&#xff0c;您也可以在这里下载中…

单片机中断系统的应用

中断系统是单片机中非常重要的组成部分&#xff0c;它是为了使单片机能够对外部或内部随机发生的事件实时处理而设置的。中断功能的存在&#xff0c;在很大程度上提高了单片机实时处理能力&#xff0c;它也是单片机最重要的功能之一&#xff0c;是我们学习单片机必须掌握的重要…

Python自动化测试数据驱动解决数据错误

数据驱动将测试数据和测试行为完全分离&#xff0c;实施数据驱动测试步骤如下&#xff1a; A、编写测试脚本&#xff0c;脚本需要支持从程序对象、文件或者数据库读入测试数据&#xff1b; B、将测试脚本使用的测试数据存入程序对象、文件或者数据库等外部介质中&#xff1b;…

解决msvcr71.dll丢失5个方法,修复程序运行缺失dll问题

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“msvcr71.dll丢失”。这个错误提示通常出现在运行某些程序或游戏时&#xff0c;给使用者带来了很大的困扰。那么&#xff0c;究竟是什么原因导致了msvcr71.dll文件的丢失呢&#xff1f;本文…

大数据之HBase

HBase介绍 Apache的三篇论文&#xff0c;GFS谷歌文件系统->HDFS&#xff1b;MR -> MR ; BigTable ->HBase;HBase是hadoop数据库&#xff0c;一种分布式、可扩展的大数据NoSQL数据库之一。适合对于大量数据进行随机、实时的读写操作 HBase数据模型 Bigtable是一个稀…

hql面试题之上海某资深数仓开发工程师面试题-求不连续月份的月平均值

1.题目 A,B两组产品的月平均值&#xff0c;月平均值是当月的前三个月值的一个平均值&#xff0c;注意月份是不连续的&#xff0c;如果当月的前面的月份不存在&#xff0c;则为0。如A组2023-04的月平均值为2023年1月的数据加2023-02月的数据的平均值&#xff0c;因为没有其他月…

Matrix电磁阀详解

文章目录 一. 气动电磁阀流量控制技术1. PWM技术2. PFM技术3. PNM技术4. PCM技术5. 组合技术&#xff08;Combined Techniques&#xff09;6. 双张力开关控制技术&#xff08;ON -OFF control Technique with double level of tension&#xff09; 二. Matrix电磁阀特性1. Matr…

史上最全接单平台集锦,程序员不容错过!

非典型程序员不是每天都累成狗&#xff0c;天天”996"甚至”007“。可能&#xff0c;面临着上班摸鱼没事干&#xff0c;下班躺尸打游戏的无聊境况。那么&#xff0c;如果你也是这样的程序员&#xff0c;有没有什么安排可以打发时间&#xff1f; 闲着还不如挣钱~心情好的时…

【css】调整图片样式-铅笔画-以及其它

[css]调整图片样式-铅笔画-以及其它 在这个网址下有很多实例&#xff0c;尝试了其中几个&#xff0c;成功实现的对半分。使用Micsoft&#xff0c;估计是不支持一些特性导致的。 <!DOCTYPE html> <html lang"en"> <head><meta charset"UT…

卡码网语言基础课 | 16. 出现频率最高的字母

目录 一、 哈希表 二、 编写解题 2.1 统计出现次数 2.2 解答 通过本次练习&#xff0c;将学习到C中哈希表的基础知识 题目&#xff1a; 给定一个只包含小写字母的字符串&#xff0c;统计字符串中每个字母出现的频率&#xff0c;并找出出现频率最高的字母&#xff0c;如果…

C51--LCD1602显示屏

LCD602显示&#xff1a; 1、概述 LCD602是一种工业字符型液晶&#xff0c;能够同时显示16x02&#xff0c;即32字符&#xff08;16列&#xff0c;2行&#xff09; 2、引脚&#xff1a; VSS&#xff1a;电源地VDD&#xff1a;电源正极——5V电源VO&#xff1a; 液晶显示偏压 …