51. UE5 RPG 自定义FGameplayEffectContext

我们期望能够通过FGameplayEffectContext将此次技能造成的伤害是否触发格挡和暴击的参数传递到AttributeSet中,所以需要实现自定义一个FGameplayEffectContext类,来增加对应的配置。

创建自定义类文件

首先在Public目录上右键,选择添加一个文件
在这里插入图片描述
创建一个.h文件
在这里插入图片描述
然后在Private目录添加一个cpp文件
在这里插入图片描述
然后,我们在蓝图中定义结构体,并继承FGameplayEffectContext

#pragma once //预处理指令 确保这个头文件只被包含(include)一次,防止重复定义。#include "GameplayEffectTypes.h"
#include "RPGAbilityTypes.generated.h"USTRUCT(BlueprintType) //在蓝图中可作为类型使用
struct FRPGGameplayEffectContext : public FGameplayEffectContext
{GENERATED_BODY() //宏 自动生成构造函数、析构函数、拷贝构造函数等public:protected:};

我们要增加两个参数,用于设置或者获取当前技能是否暴击或者格挡

protected:UPROPERTY()bool bIsBlockedHit = false; //格挡UPROPERTY()bool BIsCriticalHit = false; //暴击

然后增加他的获取和设置函数

public:bool IsBlockedHit() const { return bIsBlockedHit; }bool IsCriticalHit() const { return BIsCriticalHit; }void SetIsBlockedHit(const bool bInIsBlockedHit) { bIsBlockedHit = bInIsBlockedHit; }void SetIsCriticalHit(const bool bInIsCriticalHit) { BIsCriticalHit = bInIsCriticalHit; }

要实现子类,我们有一些需要覆写父类的一些函数,这些函数主要用于序列化,序列化的目的是为了将数据转换为二进制数据用于网络通信,因为最终我们需要将数据提交到服务器端,客户端向服务器端提交数据无法直接传递,所以需要序列化为二进制数据,然后将二进制数据提交给服务器端进行反序列化后使用。

首先覆写用于用于返回用于序列化的实际结构体函数。序列化是将对象的状态转换为可以存储或传输的形式的过程,反序列化则是相反的过程。在游戏开发和网络通信中,序列化和反序列化是非常重要的。这个函数在父类里面注释必须要在子类里面覆写。
在这里插入图片描述

/** 返回用于序列化的实际结构体 */
virtual UScriptStruct* GetScriptStruct() const override
{return FGameplayEffectContext::StaticStruct();
}

接下来,我们还需要覆写用于序列化配置的函数,将对应的参数保存,这样,在服务器端,可以完整的复原这个实例。
在这里插入图片描述
FArchive& Ar 是Unreal Engine中用于序列化和反序列化对象的类,它支持以二进制的形式进行加载保存和垃圾回收。
class UPackageMap* Map 用于查找或记录对象间的引用关系
bool& bOutSuccess 输出是否序列化成功

/** 用于序列化类的参数 */
virtual bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) override;

接下来,我们将覆写这个函数的实现,因为内部很多实现需要我们手动去实现。

二进制运算

首先我们看一下源码里的实现,它教给我们了如何去实现序列化,里面有一些二进制的运算

bool FGameplayEffectContext::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess)
{uint8 RepBits = 0;if (Ar.IsSaving()) //判断当前是否在保存数据{//保存数据时,如果有对应的配置项数据,那么将对应位的值设置为1if (bReplicateInstigator && Instigator.IsValid()){RepBits |= 1 << 0;}if (bReplicateEffectCauser && EffectCauser.IsValid() ){RepBits |= 1 << 1;}if (AbilityCDO.IsValid()){RepBits |= 1 << 2;}if (bReplicateSourceObject && SourceObject.IsValid()){RepBits |= 1 << 3;}if (Actors.Num() > 0){RepBits |= 1 << 4;}if (HitResult.IsValid()){RepBits |= 1 << 5;}if (bHasWorldOrigin){RepBits |= 1 << 6;}}//序列化RepBitsAr.SerializeBits(&RepBits, 7);//如果对应位的值为1,那么将数据存入Arif (RepBits & (1 << 0)){Ar << Instigator;}if (RepBits & (1 << 1)){Ar << EffectCauser;}if (RepBits & (1 << 2)){Ar << AbilityCDO;}if (RepBits & (1 << 3)){Ar << SourceObject;}if (RepBits & (1 << 4)){SafeNetSerializeTArray_Default<31>(Ar, Actors);}if (RepBits & (1 << 5)){if (Ar.IsLoading()){if (!HitResult.IsValid()){HitResult = TSharedPtr<FHitResult>(new FHitResult());}}HitResult->NetSerialize(Ar, Map, bOutSuccess);}if (RepBits & (1 << 6)){Ar << WorldOrigin;bHasWorldOrigin = true;}else{bHasWorldOrigin = false;}//如果是加载数据(即反序列化)时,需要调用对ASC进行初始化if (Ar.IsLoading()){AddInstigator(Instigator.Get(), EffectCauser.Get()); // Just to initialize InstigatorAbilitySystemComponent}	bOutSuccess = true;return true;
}

刚开始看的时候确实有点懵,比如这个RepBits |= 1 << 0; RepBits & (1 << 0)还有Ar << Instigator;,接下来,我们就分析一下这些内容是什么。
RepBits |= 1 << 0; RepBits & (1 << 0)属于二进制运算,首先我们先将它们分开理解。
|=在正常运算中见过很多,*= /=这种,它的意思也一样
RepBits |= 1 << 0; 可以转换成 RepBits = RepBits | 1 << 0;
|&是二进制的运算符
unit8 相当于8位二进制无符号8位整数类型 0000 0000
unit32 相当于32位二进制无符号8位整数类型 0000 0000 0000 0000 0000 0000
为了方便分析,我们后面使用八位的分析
1 << 0中间使用了左移操作符,相当于1向左位移0位 结果为 0000 0001
如果是1 << 3相当于1向左位移3位 结果为0000 1000

那么再说|&二进制的运算符,它们都是位的运算符,对应的位置的运算
| 是两个二进制,如果同一个位置有一个值为1,那么返回的值就是1 举例:
1000 1000 | 1000 0001 的结果为1000 1001
&则是同一个位置,两个值都为1,返回的结果对应的位置的值才为1,举例:
1000 1000 & 1000 0001 的结果为1000 0000

所以,现在再看上面的代码,RepBits |= 1 << 0;这些意思就是如果条件成为,将把参数对应位的值设置为1,而RepBits & (1 << 0)则是判断条件,就是为了判断对应位置的值是否为1,如果返回的结果不是0000 0000,条件则会成立。

先对而言,Ar << Instigator;就简单了很多,它是一个简写,它用于序列化或反序列化对象。这里的 <<不是一个标准的C++流插入操作符,而是在Unreal Engine的序列化系统中有特殊含义。
当执行 Ar << Instigator; 时,FArchive& Ar类会调用Instigator对象的序列化方法。Instigator的当前状态会被写入到Ar所代表的存储介质中,根据右侧的类型<<也会切换对应类型的方法去处理,并且它还可以通过判断Ar.IsSaving()去序列化或者反序列化。

实现自定义的NetSerialize

首先,在函数中,我们将RepBits 设置成32位整型,因为之前的8位已经不足以存下我们的内容。

bool FRPGGameplayEffectContext::NetSerialize(FArchive& Ar, UPackageMap* Map, bool& bOutSuccess)
{uint32 RepBits = 0;

然后,在if (Ar.IsSaving())判断后,在底部加上我们的新增的属性

	if (Ar.IsSaving()){if (bReplicateInstigator && Instigator.IsValid()){RepBits |= 1 << 0;}if (bReplicateEffectCauser && EffectCauser.IsValid() ){RepBits |= 1 << 1;}if (AbilityCDO.IsValid()){RepBits |= 1 << 2;}if (bReplicateSourceObject && SourceObject.IsValid()){RepBits |= 1 << 3;}if (Actors.Num() > 0){RepBits |= 1 << 4;}if (HitResult.IsValid()){RepBits |= 1 << 5;}if (bHasWorldOrigin){RepBits |= 1 << 6;}//自定义内容,增加暴击和格挡触发存储if(bIsBlockedHit){RepBits |= 1 << 7;}if(BIsCriticalHit){RepBits |= 1 << 8;}}

接着在序列长度这里,由于增加了两项,所以将7修改为9

//使用了多少长度,就将长度设置为多少
Ar.SerializeBits(&RepBits, 9);

然后接着将源码中序列或反序列的逻辑代码复制过来

if (RepBits & (1 << 0)){Ar << Instigator;}if (RepBits & (1 << 1)){Ar << EffectCauser;}if (RepBits & (1 << 2)){Ar << AbilityCDO;}if (RepBits & (1 << 3)){Ar << SourceObject;}if (RepBits & (1 << 4)){SafeNetSerializeTArray_Default<31>(Ar, Actors);}if (RepBits & (1 << 5)){if (Ar.IsLoading()){if (!HitResult.IsValid()){HitResult = TSharedPtr<FHitResult>(new FHitResult());}}HitResult->NetSerialize(Ar, Map, bOutSuccess);}if (RepBits & (1 << 6)){Ar << WorldOrigin;bHasWorldOrigin = true;}else{bHasWorldOrigin = false;}

然后在后面添加我们新增的两项

	//新增对暴击格挡的序列化或反序列化处理if (RepBits & (1 << 7)){Ar << bIsBlockedHit;}if (RepBits & (1 << 8)){Ar << BIsCriticalHit;}

将加载时初始化ASC的逻辑复制过来

	if (Ar.IsLoading()){AddInstigator(Instigator.Get(), EffectCauser.Get()); // Just to initialize InstigatorAbilitySystemComponent}

最后,将Success设置为true,完成设置

	bOutSuccess = true;return true;

以下为完整代码,我们实现了对应的网络序列化工作
RPGAbilityTypes.h

#pragma once //预处理指令 确保这个头文件只被包含(include)一次,防止重复定义。#include "GameplayEffectTypes.h"
#include "RPGAbilityTypes.generated.h"USTRUCT(BlueprintType) //在蓝图中可作为类型使用
struct FRPGGameplayEffectContext : public FGameplayEffectContext
{GENERATED_BODY() //宏 自动生成构造函数、析构函数、拷贝构造函数等public:bool IsBlockedHit() const { return bIsBlockedHit; }bool IsCriticalHit() const { return BIsCriticalHit; }void SetIsBlockedHit(const bool bInIsBlockedHit) { bIsBlockedHit = bInIsBlockedHit; }void SetIsCriticalHit(const bool bInIsCriticalHit) { BIsCriticalHit = bInIsCriticalHit; }/** 返回用于序列化的实际结构体 */virtual UScriptStruct* GetScriptStruct() const override{return FGameplayEffectContext::StaticStruct();}/** 用于序列化类的参数 */virtual bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) override;
protected:UPROPERTY()bool bIsBlockedHit = false; //格挡UPROPERTY()bool BIsCriticalHit = false; //暴击
};

RPGAbilityTypes.cpp

#include "RPGAbilityTypes.h"bool FRPGGameplayEffectContext::NetSerialize(FArchive& Ar, UPackageMap* Map, bool& bOutSuccess)
{uint32 RepBits = 0;if (Ar.IsSaving()){if (bReplicateInstigator && Instigator.IsValid()){RepBits |= 1 << 0;}if (bReplicateEffectCauser && EffectCauser.IsValid() ){RepBits |= 1 << 1;}if (AbilityCDO.IsValid()){RepBits |= 1 << 2;}if (bReplicateSourceObject && SourceObject.IsValid()){RepBits |= 1 << 3;}if (Actors.Num() > 0){RepBits |= 1 << 4;}if (HitResult.IsValid()){RepBits |= 1 << 5;}if (bHasWorldOrigin){RepBits |= 1 << 6;}//自定义内容,增加暴击和格挡触发存储if(bIsBlockedHit){RepBits |= 1 << 7;}if(BIsCriticalHit){RepBits |= 1 << 8;}}//使用了多少长度,就将长度设置为多少Ar.SerializeBits(&RepBits, 9);if (RepBits & (1 << 0)){Ar << Instigator;}if (RepBits & (1 << 1)){Ar << EffectCauser;}if (RepBits & (1 << 2)){Ar << AbilityCDO;}if (RepBits & (1 << 3)){Ar << SourceObject;}if (RepBits & (1 << 4)){SafeNetSerializeTArray_Default<31>(Ar, Actors);}if (RepBits & (1 << 5)){if (Ar.IsLoading()){if (!HitResult.IsValid()){HitResult = TSharedPtr<FHitResult>(new FHitResult());}}HitResult->NetSerialize(Ar, Map, bOutSuccess);}if (RepBits & (1 << 6)){Ar << WorldOrigin;bHasWorldOrigin = true;}else{bHasWorldOrigin = false;}//新增对暴击格挡的序列化或反序列化处理if (RepBits & (1 << 7)){Ar << bIsBlockedHit;}if (RepBits & (1 << 8)){Ar << BIsCriticalHit;}if (Ar.IsLoading()){AddInstigator(Instigator.Get(), EffectCauser.Get()); // Just to initialize InstigatorAbilitySystemComponent}	bOutSuccess = true;return true;
}

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

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

相关文章

考研数学|李林《880》做不动,怎么办!?看这一篇!

在考研数学的备考过程中&#xff0c;遇到难题是很常见的情况&#xff0c;尤其是当你尝试解决李林880习题集中的问题时。他以其难度和深度著称&#xff0c;旨在帮助考生深入理解数学分析的复杂概念。 如果你在解题过程中感到困难&#xff0c;这并不是你个人的问题&#xff0c;而…

Git 的原理与使用(中)

Git 的原理与使用&#xff08;上&#xff09;中介绍了Git初识&#xff0c;Git的安装与初始化以及工作区、暂存区、版本库相关的概念与操作&#xff0c;本文接着上篇的内容&#xff0c;继续深入介绍Git在的分支管理与远程操作方面的应用。 目录 五、分支管理 1.理解分支 2.创…

基于spingboot,vue线上辅导班系统

目录 项目介绍 图片展示 运行环境 获取方式 项目介绍 权限划分&#xff1a;用户&#xff0c;管理员 具有前后台展示&#xff0c;前台供用户使用&#xff1b;用户具有自己的后台&#xff0c;查看自己的老师课程等&#xff1b;管理员具有最大的权限后台。 用户&#xff1a…

Jmeter接口测试和性能测试

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 目前最新版本发展到5.0版本&#xff0c;需要Java7以上版本环境&#xff0c;下载解压目录后&…

数据结构之二叉树详解[1]

在前面我们介绍了堆和二叉树的基本概念后&#xff0c;本篇文章将带领大家深入学习链式二叉树。 1.预备知识 2.二叉树结点的创建 3.二叉树的遍历 3.1前序遍历 3.2中序遍历 3.3 后序遍历 4.统计二叉树的结点个数 5.二叉树叶子结点的个数 6.二叉树第k层的结点个数 7.总结 …

自动驾驶占据感知的综述:信息融合视角

24年5月香港理工的论文“A Survey on Occupancy Perception for Autonomous Driving: The Information Fusion Perspective“。 3D 占据感知技术旨在观察和理解自动驾驶车辆的密集 3D 环境。该技术凭借其全面的感知能力&#xff0c;正在成为自动驾驶感知系统的发展趋势&#x…

最新极空间部署iCloudpd教程,实现自动同步iCloud照片到NAS硬盘

【iPhone福利】最新极空间部署iCloudpd教程&#xff0c;实现自动同步iCloud照片到NAS硬盘 哈喽小伙伴们好&#xff0c;我是Stark-C~ 我记得我前年的时候发过一篇群晖使用Docker部署iCloudpd容器来实现自动同步iCloud照片的教程&#xff0c;当时热度还很高&#xff0c;可见大家…

【C++】string|迭代器iterator|getline|find

目录 ​编辑 string 1.string与char* 的区别 2.string的使用 字符串遍历 利用迭代器遍历 范围for遍历 反向迭代器 字符串capacity 字符串插入操作 push_back函数 append函数 运算符 ​编辑 insert函数 substr函数 字符串查找函数 find函数 rfind函数 …

用面向对象的思想编写实时嵌入式C程序

实时嵌入式系统的软件一般由C语言编写&#xff0c;程序结构基本上都是这样的&#xff1a; // 主程序 int main(void) {init(); // 初始化while(1){tick(); // 业务逻辑}return 0; }// 计时器 static unsigned int g_timer_tick_cnt 0; // 时钟中断回调 void isr_time…

CAST: Cross-Attention in Space and Time for Video Action Recognition

标题&#xff1a;CAST: 时空交叉注意力网络用于视频动作识别 原文链接&#xff1a;2311.18825v1 (arxiv.org)https://arxiv.org/pdf/2311.18825v1 源码链接&#xff1a;GitHub - KHU-VLL/CASThttps://github.com/KHU-VLL/CAST 发表&#xff1a;NeurIPS-2023&#xff08;CCF A…

vaspkit 画 Charge-Density Difference

(echo 314;echo $(cat 1))|vaspkit 文件1提前写好使用的CHGCAR路径 SPIN_DW.vasp ../ML2scf/SPIN_DW.vasp ../ML1scf/SPIN_DW.vasp POSite and negative 默认为blue,and 青色 (RGB 30 245 245) 正值&#xff1a;blue 。负值&#xff1a;青色 RGB 30 245 245。 提示&…

自动化测试基础 --- Jmeter

前置环境安装 首先我们需要知道如何下载Jmeter 这里贴上下载网站Apache JMeter - Download Apache JMeter 我们直接解压,然后在bin目录下找到jemter.bat即可启动使用 成功打开之后就是这个界面 每次打开可以用这种方式切换成简体中文 或者直接修改properties文件修改对应的语言…