51. UE5 RPG 自定义FGameplayEffectContext

61 篇文章 7 订阅

我们期望能够通过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()) //判断当前是否在保存数据
	{
		//保存数据时,如果有对应的配置项数据,那么将对应位的值设置为1
		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;
		}
	}

	//序列化RepBits
	Ar.SerializeBits(&RepBits, 7);

	//如果对应位的值为1,那么将数据存入Ar
	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;
	}

	//如果是加载数据(即反序列化)时,需要调用对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;
}

  • 18
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值