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

在前面一篇文章中,我们创建了自定义的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 函数,用于将其状态序列化为 FArchiveWithStructuredSerializer       = 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 resultNewContext->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上面将显示对应的效果。

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

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

相关文章

【JavaEE进阶】 Bean的作用域与生命周期

文章目录 &#x1f343;Bean的作用域&#x1f6a9;作用域的使用&#x1f6a9;观察Bean的作用域&#x1f388;单例作用域&#x1f388;多例作用域&#x1f388;请求作用域&#x1f388;会话作⽤域&#x1f388;Application作⽤域 &#x1f384;Bean的⽣命周期⭕总结 &#x1f34…

janus源码分析(1)--代码结构整理

基础说明 janus官网 https://janus.conf.meetecho.com/index.html janus源码地址 https://github.com/meetecho/janus-gateway 编译及部署参考 https://pro-hnb.blog.csdn.net/article/details/137730389?spm1001.2014.3001.5502 https://pro-hnb.blog.csdn.net/article/deta…

java代码混淆工具ProGuard混淆插件

java代码混淆工具ProGuard混淆插件 介绍 ProGuard是一个纯java编写的混淆工具&#xff0c;有客户端跟jar包两种使用方式。可以将程序打包为jar&#xff0c;然后用工具进行混淆&#xff0c;也可以在maven中导入ProGuard的插件&#xff0c;对代码进行混淆。 大家都知道 java代…

Pencils Protocol 获合作伙伴 Galxe 投资,加快了生态进展

近日&#xff0c;Scroll 生态项目 Penpad 将品牌进一步升级为 Pencils Protocol&#xff0c;全新升级后其不仅对 LaunchPad 平台进行了功能上的升级&#xff0c;同时其也进一步引入了 Staking、Vault 以及 Shop 等玩法&#xff0c;这也让 Pencils Protocol 的叙事方向不再仅限于…

小程序(四)

十四、分包加载 &#xff08;一&#xff09;普通分包与主包 随着项目越来越大&#xff0c;为了用户更好的体验&#xff0c;小程序引用了分包技术&#xff0c;分包技术将tabBar页面及一些全局使用的静态资源&#xff0c;放到主包中&#xff0c;开发者可以根据需要添加分包&…

php+vant van-uploader手机拍照 上传

设置capture”user”&#xff0c;则只会调起摄像头&#xff1b; 不设置capture“user”&#xff0c;则会弹窗让选择相册或拍照。 HTML: <link rel"stylesheet" href"/vue/vant.css"><van-field name"uploader" label"照片"&…

关于 vs2019 c++20 规范里的一个全局函数 _Test_callable

&#xff08;1&#xff09;看名思议&#xff0c;觉得这个函数可以测试其形参是否是可以被调用的函数&#xff0c;或可调用对象&#xff1f; 不&#xff0c;这个名字不科学。有误导&#xff0c;故特别列出。看下其源码&#xff08;该函数位于 头文件&#xff09;&#xff1a; 辅…

使用可接受gitlab参数的插件配置webhook

jenkins配置 安装Generic Webhook Trigger 配置远程触发令牌 勾选Print post content和Print contributed variables用于打印值 配置gitlab 选择新增webhook 配置webhook http://JENKINS_URL/generic-webhook-trigger/invoke,将JENKINS_URL修改成自己的jenkins地址 先保存…

基于springboot+vue+Mysql的校园闲置物品交易网站

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

AWS简介

AWS AWS&#xff0c;全称为Amazon Web Services&#xff0c;是亚马逊公司旗下的云计算服务平台&#xff0c;自2006年起向全球用户提供广泛而深入的云计算服务。AWS是全球最全面、应用最广泛的云平台之一&#xff0c;它从全球的数据中心提供超过200项功能齐全的服务&#xff0c…

分布式系统的一致性与共识算法(三)

顺序一致性(Sequential Consistency) ZooKeeper 一种说法是ZooKeeper是最终一致性&#xff0c;因为由于多副本、以及保证大多数成功的ZAB协议&#xff0c;当一个客户端进程写入一个新值&#xff0c;另外一个客户端进程不能保证马上就能读到这个值&#xff0c;但是能保证最终能…

mysql的explain

explain可以用于select&#xff0c;delete&#xff0c;insert&#xff0c;update的statement。 当explain用于statement时&#xff0c;mysql将会给出其优化器&#xff08;optimizer&#xff09;的执行计划。 通过explain字段生成执行计划表。下面来解析这个执行计划表的每一列…