52. UE5 RPG 应用自定义FGameplayEffectContext到项目

70 篇文章 8 订阅

在前面一篇文章中,我们创建了自定义的FGameplayEffectContext结构体,用于存储所需的内容。在自定义的结构体内,我们主要是为了增加暴击和格挡两个参数,用于后面的UI显示给玩家,让玩家知道当前触发的状态。并且我们还对齐做了序列化处理,能够在后续处理中,数据能够成功传递到服务器端展示。
在这一篇中,我们将实现如何将自定义的FGameplayEffectContext应用到我们的代码逻辑中,替换默认的FGameplayEffectContext。

模版特化

模板参数在某种特定类型下的具体实现称为模板的特化。
在GameplayEffectTypes.h文件中,有这么一段代码
它是针对于FGameplayEffectContext进行了模版特化处理。当你在项目中使用到了对应的类型结构体时,模版特化修改的内容也将起作用。

template<>
struct TStructOpsTypeTraits< FGameplayEffectContext > : public TStructOpsTypeTraitsBase2< FGameplayEffectContext >
{
	enum
	{
		WithNetSerializer = true,
		WithCopy = true		// Necessary so that TSharedPtr<FHitResult> Data is copied around
	};
};

这段代码是在针对于FGameplayEffectContext 类型时,实现的具体设置,它继承至TStructOpsTypeTraitsBase2,接下来我们看一下TStructOpsTypeTraitsBase2。
TStructOpsTypeTraitsBase2是专门用于针对C++结构体进行模版特化,它作为结构体的默认设置。

/** 用于为自定义的脚本结构体提供类型特性 **/
template <class CPPSTRUCT>
struct TStructOpsTypeTraitsBase2
{
	enum
	{
		WithZeroConstructor            = false,                         // 结构体是否可以通过将其内存占用填充为零来构造为有效对象。
		WithNoInitConstructor          = false,                         // 结构体是否具有一个构造函数,该构造函数接受一个 EForceInit 参数,用于强制执行初始化,而默认构造函数执行“未初始化”。
		WithNoDestructor               = false,                         // 当结构体被销毁时,其析构函数是否不会被调用。
		WithCopy                       = !TIsPODType<CPPSTRUCT>::Value, // 结构体是否可以通过其复制赋值操作符进行复制。
		WithIdenticalViaEquality       = false,                         // 结构体是否可以通过其 operator== 进行比较。这与 WithIdentical 应该是互斥的。
		WithIdentical                  = false,                         // 结构体是否可以通过一个 Identical(const T* Other, uint32 PortFlags) 函数进行比较。这与 WithIdenticalViaEquality 应该是互斥的。
		WithExportTextItem             = false,                         // 结构体是否具有一个 ExportTextItem 函数,用于将其状态序列化为字符串。
		WithImportTextItem             = false,                         // s结构体是否具有一个 ImportTextItem 函数,用于从字符串反序列化对象。
		WithAddStructReferencedObjects = false,                         // 结构体是否具有一个 AddStructReferencedObjects 函数,允许它向垃圾收集器添加引用。
		WithSerializer                 = false,                         // 结构体是否具有一个 Serialize 函数,用于将其状态序列化为 FArchive
		WithStructuredSerializer       = false,                         // 结构体是否具有一个 Serialize 函数,用于将其状态序列化为 FStructuredArchive。
		WithPostSerialize              = false,                         // 结构体是否具有一个在序列化后被调用的 PostSerialize 函数。
		WithNetSerializer              = false,                         // 结构体是否具有一个 NetSerialize 函数,用于将状态序列化为用于网络复制的 FArchive。
		WithNetDeltaSerializer         = false,                         // 结构体是否具有一个 NetDeltaSerialize 函数,用于序列化与先前 NetSerialize 操作中的状态差异。
		WithSerializeFromMismatchedTag = false,                         // 结构体是否具有一个 SerializeFromMismatchedTag 函数,用于从其他属性标签进行转换。
		WithStructuredSerializeFromMismatchedTag = false,               // 结构体是否具有一个基于 FStructuredArchive 的 SerializeFromMismatchedTag 函数,用于从其他属性标签进行转换。
		WithPostScriptConstruct        = false,                         //  结构体是否具有一个在蓝图中构造后被调用的 PostScriptConstruct 函数。
		WithNetSharedSerialization     = false,                         // 结构体的 NetSerialize 函数是否不需要包映射来序列化其状态。
		WithGetPreloadDependencies     = false,                         // 结构体是否具有一个 GetPreloadDependencies 函数,用于在加载时序列化结构体时返回所有将被 Preload() 的对象。
		WithPureVirtual                = false,                         //  结构体是否具有 PURE_VIRTUAL 函数,并且在 CHECK_PUREVIRTUALS 为true时无法构造。
		WithFindInnerPropertyInstance  = false,							// 结构体是否具有一个 FindInnerPropertyInstance 函数,该函数可以在给定属性 FName 时提供一个 FProperty 和数据指针。
		WithCanEditChange			   = false,							// 结构体是否具有一个仅在编辑器中使用的 CanEditChange 函数,该函数可以有条件地使子属性在详细信息面板中变为只读(与 UObject::CanEditChange 相同的想法)。
	};

	static constexpr EPropertyObjectReferenceType WithSerializerObjectReferences = EPropertyObjectReferenceType::Conservative; // 当结构体(或类)的 Serialize 方法遇到这些类型的对象引用时,可能会进行序列化。默认情况下,使用 Conservative(保守)策略,意味着如果对象引用的处理方式未知,那么对象引用收集器(可能是负责序列化和反序列化的组件)应该序列化这个结构体。
};

所以,我们自定义的也需要一个模版特化进行处理,基于FGameplayEffectContext我们自定义个即可。
在这里,我们将网络序列化和可复制设置为true,但是对于Hit Result这种复杂类型来说,它只会复制引用。

template<>
struct TStructOpsTypeTraits< FRPGGameplayEffectContext > : public TStructOpsTypeTraitsBase2< FRPGGameplayEffectContext >
{
	enum
	{
		WithNetSerializer = true,
		WithCopy = true		// Necessary so that TSharedPtr<FHitResult> Data is copied around
	};
};

所以,我们还需要在结构体内实现复制函数,对Hit Result进行深拷贝,这里直接复制父类的复制函数修改即可。

	/** 创建一个副本,用于后续网络复制或者后续修改 */
	virtual FRPGGameplayEffectContext* Duplicate() const override
	{
		FRPGGameplayEffectContext* NewContext = new FRPGGameplayEffectContext();
		*NewContext = *this; //WithCopy 设置为true,就可以通过赋值操作进行拷贝
		if (GetHitResult())
		{
			// 深拷贝 hit result
			NewContext->AddHitResult(*GetHitResult(), true);
		}
		return NewContext;
	}

使用自定义的FGameplayEffectContext类

既然要使用自定义的类,那么我们需要找到创建的位置,比如我们当前火球术使用的技能类里面,会去创建它
通过ASC的MakeEffectContext()去创建的
在这里插入图片描述
我们进入此方法查看它是如何它的内部实现
在函数内部,它输出的是FGameplayEffectContextHandle类型,FGameplayEffectContextHandle内部包含FGameplayEffectContext,而FGameplayEffectContext是通过UAbilitySystemGlobals::Get().AllocGameplayEffectContext()实现的创建
在这里插入图片描述
而AllocGameplayEffectContext的实现则是直接new FGameplayEffectContext()了一个新的实例返回。
在这里插入图片描述
看到这里,我们就明白了,我们需要去修改UAbilitySystemGlobals使用,然后实现以后创建FGameplayEffectContext时,都是创建我们自定义的FGameplayEffectContext来实例化。所以,我们我们需要自定义一个AbilitySystemGlobals类,然后覆写这个函数
打开UE,在AbilitySystem目录下面新增一个C++类,选择AbilitySystemGlobals
在这里插入图片描述
命名为RPGAbilitySystemGlobals
在这里插入图片描述
在.h文件中,我们设置覆写AllocGameplayEffectContext函数。

UCLASS()
class AURA_API URPGAbilitySystemGlobals : public UAbilitySystemGlobals
{
	GENERATED_BODY()

	virtual FGameplayEffectContext* AllocGameplayEffectContext() const override;
};

在函数实现的cpp文件中,我们返回创建的自定义的FGameplayEffectContext

FGameplayEffectContext* URPGAbilitySystemGlobals::AllocGameplayEffectContext() const
{
	return new FRPGGameplayEffectContext();
}

接下来就是重要的一点,如何使用自定义的AbilitySystemGlobals,我们需要在配置项内去设置,然后修改了初始化AbilitySystemGlobals的类指向我们自定义的AbilitySystemGlobals类。
在这里插入图片描述
然后编译,在创建FGameplayEffectContext的地方打断点,查看创建的实例里面是否包含我们自定义的属性。并且我还发现我的属性名称写错了,竟然写成了大写,bool类型的第一个字母推荐小写b,我去改一下。
在这里插入图片描述
还有就是在AttributeSet里面是否能够获取到,因为AS是在服务器端运行的,如果能够在AS里面获取到,证明我们真的成功了
在这里插入图片描述

实现设置获取格挡和暴击属性

既然我们实现了自定义FGameplayEffectContext,并且值已经能够获取得到,那么我们需要在获取格挡和暴击的位置去设置布尔值。
在 UGameplayEffectExecutionCalculation中,我们可以获取到GE的Spec,我们通过Spec的函数GetContext获取句柄,并通过句柄的Get获取到Context

FGameplayEffectContext* EffectContext = Spec.GetContext().Get();

可以在句柄的代码中找到获取函数
在这里插入图片描述
接下来,我们将FGameplayEffectContext转换成我们创建的自定义类型,然后在转换这里一定要用static_cast,不然会报错。
static_cast是强制类型转换操作符

FRPGGameplayEffectContext* RPGEffectContext = static_cast<FRPGGameplayEffectContext*>(EffectContext);

如果你使用内置的这种:

FRPGGameplayEffectContext* RPGEffectContext = Cast<FRPGGameplayEffectContext>(EffectContext);

它会编译引发错误
在这里插入图片描述
获取到自定义类型的context的上下文后,我们可以通过调用函数设置格挡

RPGEffectContext->SetIsBlockedHit(bBlocked);

为了方便使用,我们准备将设置和获取方法写入到蓝图函数库中,可以通过直接传入参数获取内容,并且还可以在蓝图中调用。
在蓝图函数库中创建两个静态函数,用于获取暴击和格挡

//获取当前GE是否触发格挡
UFUNCTION(BlueprintPure, Category="MyAbilitySystemLibrary|GameplayEffects")
static bool IsBlockedHit(const FGameplayEffectContextHandle& EffectContextHandle);

//获取当前GE是否触发暴击
UFUNCTION(BlueprintPure, Category="MyAbilitySystemLibrary|GameplayEffects")
static bool IsCriticalHit(const FGameplayEffectContextHandle& EffectContextHandle);

然后在实现中,我们需要从Handle中获取Context并转换为我们自定义的格式
我们需要用到static_cast去强制转换类型。

const FRPGGameplayEffectContext* RPGEffectContext = static_cast<const FRPGGameplayEffectContext*>(EffectContextHandle.Get())

然后通过内置的函数去获取是否暴击或者格挡

bool UMyAbilitySystemBlueprintLibrary::IsBlockedHit(const FGameplayEffectContextHandle& EffectContextHandle)
{
	if(const FRPGGameplayEffectContext* RPGEffectContext = static_cast<const FRPGGameplayEffectContext*>(EffectContextHandle.Get()))
	{
		return RPGEffectContext->IsBlockedHit();
	}
	return false;
}

bool UMyAbilitySystemBlueprintLibrary::IsCriticalHit(const FGameplayEffectContextHandle& EffectContextHandle)
{
	if(const FRPGGameplayEffectContext* RPGEffectContext = static_cast<const FRPGGameplayEffectContext*>(EffectContextHandle.Get()))
	{
		return RPGEffectContext->IsCriticalHit();
	}
	return false;
}

编译在UE的技能蓝图中就可以通过名称去获取对应的值,我们只需要传入对应的Handle,就可以获取到对应的Context的暴击和格挡是否触发。
在这里插入图片描述
接下来,我们还要在函数库实现设置的两个函数,这里有个问题,就是没办法传入常量(前面加const)会出现问题,我们将函数编写完成以后编译在UE里面查看
设置需要传入两个值,一个是修改的Context的Handle,另一个则是bool值,用于设置的值,我们无法设置成静态函数,也就是

UFUNCTION(BlueprintCallable, Category="MyAbilitySystemLibrary|GameplayEffects")
static void SetIsBlockHit(FGameplayEffectContextHandle& EffectContextHandle, bool bInIsBlockedHit);

UFUNCTION(BlueprintCallable, Category="MyAbilitySystemLibrary|GameplayEffects")
static void SetIsCriticalHit(FGameplayEffectContextHandle& EffectContextHandle, bool bInIsCriticalHit);

在实现这里,还是老套路,强制转换为自定义类的实例,然后通过实例方法设置

void UMyAbilitySystemBlueprintLibrary::SetIsBlockHit(FGameplayEffectContextHandle& EffectContextHandle,
	bool bInIsBlockedHit)
{
	FRPGGameplayEffectContext* RPGEffectContext = static_cast<FRPGGameplayEffectContext*>(EffectContextHandle.Get());
	RPGEffectContext->SetIsBlockedHit(bInIsBlockedHit);
}

void UMyAbilitySystemBlueprintLibrary::SetIsCriticalHit(FGameplayEffectContextHandle& EffectContextHandle,
	bool bInIsCriticalHit)
{
	FRPGGameplayEffectContext* RPGEffectContext = static_cast<FRPGGameplayEffectContext*>(EffectContextHandle.Get());
	RPGEffectContext->SetIsCriticalHit(bInIsCriticalHit);
}

如果我们不设置const,编译出来Handle会在右边,无法在蓝图中设置Handle。但是设置了const为常量,就无法修改数值
在这里插入图片描述
声明函数时,我们在前面添加UPARAM(ref),即可实现在蓝图节点的左侧

UFUNCTION(BlueprintCallable, Category="MyAbilitySystemLibrary|GameplayEffects")
static void SetIsBlockHit(UPARAM(ref) FGameplayEffectContextHandle& EffectContextHandle, bool bInIsBlockedHit);

UFUNCTION(BlueprintCallable, Category="MyAbilitySystemLibrary|GameplayEffects")
static void SetIsCriticalHit(UPARAM(ref) FGameplayEffectContextHandle& EffectContextHandle, bool bInIsCriticalHit);

在这里插入图片描述

实现在AttributeSet获取

有了我们上面设置的函数库的函数,我们可以很方便的去设置对应的参数,首先我们去计算伤害的地方获取到Handle

	//获取GE的上下文句柄
	FGameplayEffectContextHandle EffectContextHandle = Spec.GetContext();

然后在计算完成格挡后,将格挡的变量设置,需要传入句柄和布尔

	//设置格挡
	UMyAbilitySystemBlueprintLibrary::SetIsBlockHit(EffectContextHandle, bBlocked);

同理,设置暴击

	//设置暴击
	UMyAbilitySystemBlueprintLibrary::SetIsCriticalHit(EffectContextHandle, bCriticalHit);

设置完成后,我们需要在AttributSet类中,将显示UI伤害数字的函数,增加格挡和暴击的参数

	//显示伤害数字
	static void ShowFloatingText(const FEffectProperties& Props, const float Damage, bool IsBlockedHit, bool IsCriticalHit);

在PostGameplayEffectExecute函数中,我们调用了SetEffectProperties,整理了结构体方便获取数据
在这里插入图片描述
所以我们在设置ShowFloatingText之前,获取到暴击和格挡

//获取格挡和暴击
const bool IsBlockedHit = UMyAbilitySystemBlueprintLibrary::IsBlockedHit(Props.EffectContextHandle);
const bool IsCriticalHit = UMyAbilitySystemBlueprintLibrary::IsCriticalHit(Props.EffectContextHandle);

然后传入设置即可。

//显示伤害数字
ShowFloatingText(Props, LocalIncomingDamage, IsBlockedHit, IsCriticalHit);

这一篇文章就更新到这里,接着,我debug查看一下是否生效。
这里我用测试数值设置格挡率百分百,现在在AttributeSet里面实现了获取值为true。
在这里插入图片描述
在下一篇,我们将更新在格挡和暴击时,UI上面将显示对应的效果。

  • 26
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值