四、效果Gameplay Effect (GE)

系列文章目录

文章未完成。。。


文章目录


1.Gameplay Effect (GE)

选项设置快速过一遍即可

[1]Gamepplay Effects及其设置介绍

游戏玩法效果是一个UGameplayEffect类型的对象,我们使用Gameplay Effect来改变属性和游戏标签

  • 游戏标签 可以暂时理解成用来识别各种事物的东西( 事实上功能要更加多样化)
  • 不需要建立子类,因为游戏效果类已经足够灵活了,不需要再根据这个基类创建子类 (一般是创建蓝图子类,而不是自定义的C++子类)
  • Gameplay Effects 只是数据、我们不在里面添加逻辑
  • GE 的效果可以叠加,它们有自己的叠加策略
  • GE 也可以添加 Gameplay Tags
  • 还可以授予能力(Grant abilities) 等等…
  • 游戏玩法效果通过修改器(Modifiers)和执行(executions)来改变属性(GAS中的属性集的Attributes)

(1)Modifiers 修改器选项

  • 修改器(Modifiers)有多种类型,能实现复杂的自定义计算,比如Gameplay属性变化复杂或者某些特别的情况下
  • 用Modifiers改变属性(Attributes)时需指定要对相关属性执行的操作类型,称为 Modifier Op
  • Modifier Op 是有多种类型的,它们统一接受一个叫 Magnitude(幅度) 的输入值,并使用 Magnitude 来改变 attribute,改变的方式取决于修改器操作(Modifier Op)
  • Modifier Op 有以下几种:
    • Add: 简单地将给定的幅度添加到属性值,当然可以加上一个负值来当减法用
    • Multiply: 乘上属性的值
    • Dovode:除
    • Override: 使用你提供的Magnitude 值来替换属性的值

这些操作中使用的 幅度值是由 幅度计算(Magnitude calculation)产生的,GameplayEffects 支持如下几种计算类型:

  • Scalable float: 可缩放的浮点数,可以直接为magnitude 写死一个值也可以用基于表格来缩放值
  • Attribute Based: 基于已存在的属性,比如根据力量值计算生命值
  • Custom Calculation Class (MMC -Modifer Magnitude Calculations) : 我们可以创建一个类,用于捕获其它值,然后在任意复杂的计算中使用
  • Set by Caller : 这是一个键值对,以 gamePlay tag 或者 name为键,分配一个相关的magnitude作为值

(2)Executions 执行

  • 在GE中改变属性值的更强大方法是使用自定义执行(custom execution)
  • 也称为GE execution calcultion 或则 Exec cal , 它可以改变多个属性,它是在GE中修改属性的最强大方式

(3)Duration policy 持续时间策略

GE中有一个 Duration policy :

  • Instant:它可以即时影响属性,即modifier在一次动作中立即应用
  • Has Duration : 它们可以有持续时间,意味着它可以在一段时间内持续修改属性,之后该修改也可以被撤销
  • Infinite: 当然也可以是无限持续,影响的这个属性的效果不会消失,不是意味着永久当然可以移除无限效果

(4)GE Spec (游戏效果规格)

GE可以直接应用,但通常我们会创建一个更轻量级的版本 GamePlay Effect Spec

  • GamePlay Effect Spec 是指对 GE 实例进行规范化的结构,它包含 GE 的定义以及适用于该游戏效果的一些参数值
  • Gameplay Effect Spec 用来创建、配置和应用游戏效果,并将其应用于游戏中的不同实体,如角色、怪物等
  • Spec的概念在GAS中很常见,它是一种优化形式
  • GE Spec 也携带信息,比如 GE 拥有的Gameplay tags
  • 此外还有GE context 上下文,这是一个额外的类,可以存储更多关于GE的信息,效果的施加者和目标的引用很容易访问

② 创建 GE 蓝图

创建一个继承自Gameplay effect的蓝图
目前我们只是需要简单测试一下效果,因此只关心Gameplay Effect、Duration标签下的内容:

  • Duration 就是效果影响的持续时间,立即影响还是 影响一段时间之类的
  • Modifier前面说过,可以用它改变属性值

![[image-20240716160538307.png]]

Attribute 就是你希望当前效果能改变的属性,红框框中的是我自己之前创建的Attribute Set 里的属性,它自动列出来了

![[image-20240716160847135.png]]

Modifier op,即我们要对你希望影响的属性干啥操作,我们这里只是简单的加减血,因此选Add即可
Modifier op的操作需要接受一个magnitude值作为输入来影响属性前面也说过了
目前选择可缩放的浮点值 当然你也可以创建一个曲线表, 目前我们选择简单的
所以当我们应用这个GE时 我们会先取得当前的属性值在加上这个 Scalable float magnitude ,这就是这个GE的全部作用了

![[image-20240716162001508.png]]

③ C 中 应用GE

该函数接受一个要应用GE的对象(对象得有AbilitySystemComponent),另外一个参数是 GE 的类,选择我们刚刚创建的蓝图类即可

  • 带有什么什么Handle的实际上就是多了一层包装
  • GameplayEffectContextHandle : GE的上下文,包含和当前GE有关的信息,比如说发起GE的源、被应用GE的对象
  • FGameplayEffectSpecHandle: 其内部实际上装着我们那个之前在蓝图类里配置的GameEffect(即UGameplayEffect类),可以通过FGameplayEffectSpecHandle.Data.Get()->Def.Get()获取

![[image-20240716181553134.png]]

④ 蓝图中 应用 GE

和C中的步骤一样

![[image-20240716191721944.png]]

⑤ Duration 参数

Has Duration

Has Duration,该效果是持续影响物体N秒后消失,同时Modifier也会被撤销
以下图为例,角色当前血量100,应用GE 之后血量会提高20 变成120,但是2秒之后效果消失,同时这个血量的增益也会撤销 变回100

![[image-20240716205412806.png]]

Source Tags 源标签/Target Tags 目标的标签

可以根据源或者目标的tag设置应不应用这个修改器

  • MustHaveTags,只有当目标或者源有这个标签才能应用当前这个GE
  • MustNotHaveTags:只有当目标或者源没有这个标签才能应用当前这个GE
  • QueryMustMatch:创建更复杂的标签匹配规则,满足这个才能应用当前这个GE

![[image-20240725235412640.png]]

⑥ Periodic 属性

Periodic:即定期执行某种GE,
要想使用Periodic,只有当duration policy为 Has duration 或者 infinite 时候才有会显示
(Periodic为非零非负值的时候,对应的效果就会被每N秒调用一次,影响的值是永久性的,不会被撤销 ,周期什么时候停止取决于你在Duration magnitude下的 Scalable float magnitude 设置)
也就是说现在Has duration和infinite这两种效果可以转换为周期性Effects,但是 一旦启用Periodic,它们对属性进行周期性的改变的值会被视为Instant Gameplay effects,即效果移除时这些改变不可撤销,永久的改变基础值.
Periodic与duration policy的一些区别:

  • 当 使用Duration policy 的 Instant 影响角色时,这个影响的值是永久性(Permanent change)的,即直接增加值 然后不会被撤销
  • 当 使用Duration policy 的 Has duration时候,只是改变当前的值(Change Current value)而已,影响的值会经过N秒后被撤销
  • 当 使用Duration policy 的 infinite也是一样的只改变当前值(Change Current value),影响的值会被撤销,只不过它不会自动移除效果,你得手动触发撤销的动作
  • 当 启用Periodic 时,infinite 和 Has duration 影响的值都是 Permanent change the Base value

Periodic使用

当前GE的配置信息意思是:每0.1秒 对当前的生命值加1,总持续时间为2秒,即:

  • Duration Magnitude(持续时间幅度) 的 Scalable Float Magnitu: 为 总时间,周期的持续时间
  • Period: 就是周期

![[image-20240716220352220.png]]

启用 Execute Periodic Effect on Application 的意思是,一旦启用当前这个GE配置的效果就会立马执行一次gameplay effect,可以理解为do…while,先做一遍再进入
以每0.1秒 对当前的生命值加1,总持续时间为2秒为例子:

  • 启用 Execute Periodic Effect on Application :则总共增加21滴血
  • 不启用 Execute Periodic Effect on Application:则总共增加20滴血

![[image-20240716220543542.png]]

当前已经启用Execute Periodic Effect on Application,注意左下角的CurrentHealth 属性

![[3.动画相关.assets/recording.gif]]

Periodic Inhibition Policy 周期抑制策略

⑦ Stacking 堆叠属性

堆叠允许我们选择当我们同时应用多个相同类型的游戏效果时该发生什么,堆叠默认类型是none
没有设置堆叠的时候,GE重复影响同一个对象 只是把它们的值相加而已,比如拾取一个水晶球加20,那么三个水晶球也就是加60,并且这个回复的速度是很快的
设置堆叠种类后,不管你同一时间堆叠到多少个水晶球,回复的速率是固定的
Source 和 Target: 假设堆叠为3,如果是by Target模式,那么3个敌人对我释放的Debuff只能叠三次,如果是by Source模式,那么3个敌人可以对我叠9层

  • 每层Effect如果是Modifiers来计算,则为直接叠加的效果,比如用Modifiers来增加3攻击力,则第一层为增加3攻击力,则第二层为增加6攻击力,则第三层为增加9攻击力
  • 如果需要根据层数不同而改变增加的值,则需要使用Executions
按源聚合 类型 Aggregate by Soure:

这里的源(Source)指的是 对某个物体施加gameplay effect的那个对象, (之前我们在对Actor应用GE的上下文中设置了AddSourceObject),因此源就是那个药水actor

按目标聚合 类型 Aggregate by Target

目标(Target)是指拥有GAS组件的对象并且是被施加Gameplay Effect的那个对象

Stack Limit count 堆叠次数

Stack Limit count是堆叠的次数,我们这里假设为2,当我们同一时间捡起三个药水瓶的时候,只会执行两个药水瓶的效果,总共恢复20点

![[3.动画相关.assets/recording 1.gif]]

Stack Duration Refresh Policy 堆叠时Duration的刷新策略

接受新的GE时是否刷新Duration, 默认情况Overflow(溢出)的GE会影响Stack刷新

Stack Period Reset Policy 堆叠的周期重置策略

接受新的GE时是否刷新Period, 默认情况Overflow的GE会影响Stack刷新

Stack Expiration Policy 堆叠过期策略

即当一层GE的Duration到期后的处理方式:
ClearEntireStack :清空全部堆叠的计数
RemoveSingle Stack and RefreshDuration: 减少一次堆叠计数
Refresh Duration: 直观的效果是 一直无限回复

⑧ 移除正在启用的Gameplay Effect

这里以移除DurationType为无限的Gameplay Effect为例

  1. 首先判断当前的Actor应用的GE的Duration policy 是不是 Infinte
  • 可以通过FGameplayEffectSpecHandle 获取Data
  • 然后data 里有一个叫Def的变量,这个就是实际引用的GE,这时候就可以获得durationType来判断了
  1. 应用GameplayEffect时会返回一个FActiveGameplayEffectHandle变量,我们可以暂存这个变量,同时也要把当前应用了这个GE的ASC组件保存下来 (可以通过键值对保存)
  2. 当需要清除这个Infinite时,通过键FActiveGameplayEffectHandle 获取对应的ASC,再通过ASC的RemoveActiveGameplayEffect移除GE

![[image-20240718190741472.png]]

注意,TMap中FactiveGameplayEffectHandle需要包含头文件#include “GameplayEffectTypes.h”

![[image-20240718191208272.png]]

![[image-20240718191308502.png]]

⑨ 限制GE 影响的属性值的边界

也就是限制属性值以免被GE弄的过大或者过小, 比如生命为负数的情况,方法有很多

PreAttributeChange

重写AttributeSet的PreAttributeChange函数:

  • PreAttributeChange是一个虚函数,它在属性值发生任何修改之前被调用。这是一个比PreAttributeModify/PostAttributeModify更底层的函数
  • 由于任何事情都可以触发这个函数,所以这里没有提供额外的上下文,例如,执行的效果、基于持续时间的效果、效果被移除、应用免疫、改变堆叠规则等都可以触发这个函数
  • 这个函数主要用于执行像"Health = Clamp(Health, 0, MaxHealth)"这样的操作,而不是像"如果应用了伤害,触发这个额外的事情"这样的操作(也就是说Epic建议我们只使用这个函数来执行限制)
  • 在这个函数中,NewValue是一个可变引用,所以你可以对新应用的值进行限制(clamp)
  • 该函数不会永久性地改变某个属性(Attribute)下的修饰符(modifiers) 的值 (也就是那个Scalable Float Magnitude),它只是改变了查询该属性modifiers时返回的值
  • 该函数改变的属性值并不一定是最终的变更,因为还有其它操作是发生在该函数之后的,有可能会把这个值给变更了,导致我们需要重新clamp

![[image-20240719024414703.png]]

PostGameplaEffectExecute

PreAttributeChange函数不是实现最终变更属性的最佳选择,因此我们可以使用PostGameplaEffectExecute,这个函数会在GE改变属性之后被调用
通过这个函数,我们可以访问到几乎所有参与其中的实体,比如说发起GE的源、被作用GE的actor、应用到对象身上GameplayEffect蓝图类里的各种属性等等,
可以使用结构体将他们保存起来方便后续操作

![[image-20240719194546053.png]]

修复属性值Clamp后仍然溢出的bug

我们在PreAttributeChange里面clamp了属性值,但是当我们实际捡起药水瓶的时候,它的值仍然会溢出,正常来说当超过105的时候,该值就被clamp在了100

![[image-20240722225027787.png]]

![[image-20240722224932982.png]]

这是因为我们只是改变了从modifier里返回的查询值(大概?)

  • 所以我们的clamp不起作用,因此还需要在post函数里自己手动Set一下生命值
  • 或者删掉pre和post函数里的内容,直接在PreAttributeBaseChange(const FGameplayAttribute &Attribute, float &NewValue)函数里clamp也是可以的
    (还是会有小bug,当药水溢出的持续时间没有结束又扣血会造成血量短暂的反复弹跳,我的建议是添加标签(GameplayTag),以防止之后任何GE对它做任何其他事情)

![[image-20240722225300711.png]]
![[recording 5.gif]]

⑩ Curve Table曲线表

曲线表的作用:根据等级设定不同的值,可以给GE采用

创建曲线表

![[image-20240719195938637.png]]

可以使用线性、常数或三次曲线,这是我们的曲线表将会采用的数学函数

![[image-20240719200142195.png]]

基本操作

  1. 曲线表可以切换成表格形式
  2. 点这个回车键的箭头创建数值
  3. 更改数值也可以直接在曲线图里点击相应的点然后拖拽更改

![[image-20240719201553168.png]]

![[image-20240719201607881.png]]

GE应用曲线表

当应用曲线表时,最终的Float magnitu = 当前硬编码的值 * 曲线表格中对应等级的值
由于我们曲线表1级对应的值是5,然后乘上我们当前的10 , 所以预览的值就是50

![[image-20240719204340454.png]]

C中使用曲线表的值

在我们之前应用GE时,获得FGameplayEffectSpecHandle这一步中使用MakeOutgoingSpec时,传入设定好的level即可,该值为浮点数
这个等级数不一样要刚刚好,比如1.5、1.75也是可以的,它会根据曲线表自动插值

![[image-20240719214403568.png]]

创建关键点值的小技巧

不需要为每一个等级设置详细的值,可以每5个/10个等级设置一次,这样没有设置的关键点的值会自动包含

![[image-20240812003711759.png]]

曲线表 与 CSV、Json格式

  • CSV格式是以某种分隔符分隔数据的格式表,如果用Excel打开看就是正常表格,用记事本打开看就是有分隔符的,比如说逗号
  • 导出和导入格式操作都一样,这只介绍CSV格式的
导出CSV格式

随便选择一个曲线表右键即可导出

![[image-20240812005551989.png]]

首行是我们x轴的关键点值,那三个破折号无关紧要

![[image-20240812010224334.png]]

现在改动了表格中的数据,怎么重新导入到UE里?

![[image-20240812010800537.png]]

找到这个重新导入的按钮即可,但是请注意:

  • 该操作不能撤销
  • 并且导入后的曲线不能设置插值,只能是线性的

![[image-20240812010943514.png]]

从 CSV 导入到曲线表
  • 首先在UE里新建一个CT表 (不要复制刚刚已有的CT表,不然导入会是以刚刚那个表格为源来导入)
  • 打开CT表,找到导入按钮导入即可,和上面导入的按钮是一样的

![[image-20240812011814011.png]]

导入Json 到曲线表 (重点)

曲线表Json格式如下, 导出Json格式操作和导出CSV格式一样,不多说了

![[image-20240812012613970.png]]

采用这种方式导入的曲线表我们可以重新对曲线表里的关键点进行插值平滑

![[image-20240812012920603.png]]

![[image-20240812012938745.png]]

⑪ Modifiers 修改器的5种幅度计算类型

1.修改器幅度的计算类型 Scalable Float

根据数据表里的值进行影响属性,或者直接硬编码一个值

2.修改器幅度的计算类型 Attribute Based

Attribute Based能让我们根据其他属性的变化来改变一个属性,它有如下设置选项
Attribute Based Magnitude:

  • Cofficient
  • Backing Attribute 支持的属性(这英文可以理解为什么属性支持了当前这个GE,因为我们选择的是从对方身上获取的属性作为base): 选择基于什么属性,因为这个GE的影响值是取决于某物的,所以我们可以捕获(Capture)来自源或者目标的属性再进行计算影响
    • Attribute Source: 属性来源,可以选择来自源(Source)或目标(Target) , 比如说和某物重叠的时候,从目标身上捕获属性,再以这个属性为基础计算我们的属性效果
    • Attribute to Capture: 选择从源身上还是从目标身上要捕获的 属性
    • SnapShot: 快照决定我们究竟何时捕获该属性
      • 是在GE蓝图类的GamePlayEffectSpec创建时还是在应用时捕获?
      • 比如一个在空中飞着的火球,快照设置的时候是在创建了这个火球的GamePlayEffectSpec时候立马就捕获设置一个属性,但这个不是真实的值
      • 不设置快照属性就是在应用效果的时候才会捕捉对方的属性,然后以此进行计算
  • 下面这个GE的配置意思是:从目标身上获取Vigor的属性直接add到当前这个GE要应用到的对象里一个属性名为CurrentHealth的属性上

![[image-20240723200109860.png]]

Attribute Based Magnitude

这里建议先看看 [[#⑫ Modifier的执行顺序]],该设置选项下的参数本质就是为modifier的计算添彩
在以Modifier Op 为 Attribute Based 的时候,我们不能像之前那样直接硬编码值,而是基于别的属性去影响,如果我们想在最后的结果乘上一些数该怎么办呢,这就需要用到下列设置了

  • Cofficient 系数: 当前modifier的最终值 乘上这个 系数 ,系数默认为1
  • Pre Multiply Additive Value 前附加值: 当前modifier的最终值先加上Pre 里的值 然后再乘上系数,如果post有值那么最后才加上
  • Post Multiply Additive Value 后附加值: 当前modifier的最终值乘上系数 (如果有pre则先加上Pre 里的值,再乘), 最后再加上post的值
  • 最终这个modifier应用的值公式为: C ∗ ( B a c k i n g   A t t r i b u t e + P r e ) + P o s t C * (Backing \ Attribute + Pre) + Post C(Backing Attribute+Pre)+Post

下面的Modifier操作的顺序,目标的Health值为:

  • 先加上捕获的属性Vigor的值,即9
  • 然后 再加上 pre的值 乘上 系数C
  • 最后加上Post

![[image-20240723220016267.png]]

如果有多个Modifiers的情况下,每个modifier又设置了它们的C、pre、post,那么最终结果就是它们按顺序计算完后的和

![[image-20240723220919056.png]]

3.Set By Caller

建议先学习[[#(5) Gameplay Abilities (GA)]]之后再来学习这节内容

  • 当我们用 游戏能力(GA) 测试的时候一般都是把GE的修改幅度硬编码为一个值,这样对方受到 我们GA发出的 伤害的值就是我们在GE中硬编码的值
  • 那么我们能不能以 游戏能力里的某个变量为基础(该伤害变量还会随等级变化而变化) 设置GE的Modifier Magnitude呢,这时候就需要用到我们的Set By Caller了,最终这个 GA 就可以携带它自己的伤害值,而不是用另外的GE来决定

![[image-20240813145001406.png]]

设置SetByCaller

我们之前创建了一个飞弹的Ability,在里面创建了一个GameplayEffect,这样一旦生成飞弹Overlap之后,对象就会应用这个效果,这里我们改用SetByCaller
主要步骤如下:

  • 在能力类的基类创建一个可缩放的浮点数(就是使用曲线表)
  • 使用函数UAbilitySystemBlueprintLibrary::AssignTagSetByCallerMagnitude分配键值对,键是一个游戏标签,请提前创建
  • 去到你对应的能力类蓝图中设置好曲线表然后再去设置对应的GE蓝图类里的幅度计算方式为SetByCaller

![[image-20240813154054964.png]]

这里暂时不设置等级 , 能力有自己的GetAbilityLevel函数 所以应用的是 曲线表中对应等级 1 的伤害

![[image-20240813154146245.png]]

![[image-20240813154230701.png]]

![[image-20240813154557680.png]]

蓝图中设置为SetByCaller,标签选择我们在cpp里分配的那个GamepalyTags

![[image-20240813144757664.png]]

测试之后可以看到伤害确实是 5 ,可以自行在函数中将等级硬编码成对应的数字进行测试

![[recording 14.gif]]

4. Custom Magnitude Calculation (MMC)

建议先学习[[#⑫ Modifier的执行顺序]]之后再来学习这节内容
我们知道如何用coefficient和pre、post这些来额外影响属性的最终值,但这是不够的,有时候我们还要基于其它属性、或者一些类的属性如玩家的等级(这些属性并不在属性集中)来计算最终值,这时候我们就需要自定义幅度计算类了,我们将这个计算包装成一个MMC,并将其设置在修饰符上,然后我们就可以在MMC(Modifer Magnitude Calculations)中进行任何计算

创建MMC类并使用

选择 GameplayModMagnitudeCalculation
![[image-20240725192157062.png]]

关键模板步骤:

  • 覆写父类的CalculateBaseMagnitude_Implementation
  • 设置要捕获的属性结构体FGameplayEffectAttributeCaptureDefinition
  • 将捕获的属性添加到捕获列表,确保我们后面的函数能够找到这个属性以用于计算RelevantAttributesToCapture.Add(CaptureAttributeDef);
  • 获得要捕获的属性的值GetCapturedAttributeMagnitude
  • 剩下的就是你自己自定义计算了
  • 最后到GE蓝图中把计算类型attribute based 换成 Custom calculation Class即可

![[image-20240726003516530.png]]

![[image-20240726003558744.png]]

![[image-20240726004523182.png]]

MMC的局限

这种方法也有局限,一次一个C++类只能修改一个属性,要想修改多个属性我们又得新建C++类和再添加一个modifiers 元素进行设置

5. Execution Calculation执行计算

  • 除了上面的MMC,还有一种自定义计算的方法,是用Execution Calculation(UGameplayEffectExecutionCalculation),这是修改游戏效果属性的最强大方式
  • 这种方式允许我们使用捕获属性的值进行计算,并且可以同时更改多个属性、可以执行我们编写的任何程序逻辑
  • 但是请注意,使用该计算方式
    • 没有prediction预测
    • 只有瞬时或周期性游戏效果可以使用,持续策略为无限的GE是不能应用的
    • 不会触发当属性值更新时候的PreAttributeChange函数,所以要对属性最终值进行Clamp最好在这个Execution Calculation里执行 (目前我使用的UE5.3版本的GAS会触发该函数)
    • 如果在GameAbility中应用(带有这种计算方式的GE)的话,则此执行计算仅在服务器上运行,本地会进行预测,服务器进行初始化并且只在服务器进行Net Execution policies
    • 还可以捕获由SetByCaller设置的数值(那个键值对[[#设置SetByCaller]])
    • 同时也支持属性修改器的自带的修改器,就是那个Coefficient、Pre Multiply AdditiveValue、PostMultiplyAdditiveValue

![[image-20240815154914299.png]]

0.对源快照 SnapShotting (Source)

我们知道在使用GE的修改器时,可以捕获属性,并且可以选择是否进行快照,一般只有从源头(Source)捕获属性时才重要,也就是执行GameplayEffect的东西(例如玩家发射飞弹,飞弹带有属性,此时source是玩家),而不是这个GE影响的目标

  • 当设置了快照时,这些属性值会在创建游戏效果规格时被捕获
    比如当我们生成火球的时候,我们并不是立马就应用GE,而是要等到火球击中某物
    如果我们在火球的伤害游戏效果中设置了快照和捕获属性这些选项,那么相应的属性值只有在创建GameplayEffectSpec那一步时才会被捕获 (Snapshotting captures the Attribute value when the GameplayEffectSpec is created)
  • 没有设置快照的时候,那么只会在游戏效果被应用时才会捕获属性 (Not snapshotting captures the Attribute value when the Gameplay Effect is applied) 也就是火球击中了某物并且应用了GameplayEffectSpec时

以上只有当快照是对源快照时才有效,因为当我们创建那个效果规格时,我们不一定知道目标是谁,我们肯定知道发射火球的人是谁,但是并不知道火球发射时会击中谁,因此在我们不知道目标是谁的情况下,捕获目标的属性是没有意义的

1.创建Execution Calculation类

继承自UGameplayEffectExecutionCalculation

![[image-20240815211759880.png]]

打开执行计算头文件和我们之前用过的MMC一样,也需要实现一个函数,当执行计算的时候会调用该函数

virtual void Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams,  
                                    FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const override;

进入cpp文件,我们可以仿照仔[[#创建MMC类并使用]]中捕获属性的代码模版 做准备工作:

  • 需要创建我们需要捕获的属性结构体
  • 与在MMC里不同 我们这里直接用宏创建即可,仿照MMC里的也行

![[image-20240815212502409.png]]

去到Execute_Implementation进行一系列测试计算操作

![[image-20240815212805376.png]]

2. 使用Execution Calculation

打开蓝图,找到Executions选择我们刚刚创建的类即可,一旦对象应用了这个GE,就会按照我们的计算方式更新护甲值,在我们的例子中,应用了这个GE的对象每次护甲值都会累加

![[image-20240815212927049.png]]

你也可以新增Modifiers , 我们在蓝图中的 modifier OP 是addictive,在这里修改的OP会覆盖cpp类里的设置,同时也能看到我们确实是对目标护甲进行捕获

![[image-20240815213207640.png]]

注意,当使用了执行计算里的modifier时,GE蓝图类中自带的那个modifier选项可以删除,当然也可以不删除,不删除其中一个的话都会起作用

![[image-20240815220519332.png]]

3.在Execution Calculation里使用SetByCaller的幅度值

建议先 学习 [[#3.Set By Caller]]和[[#⑧ Meta Attribute 元属性]],之前我们在蓝图类里设置了SetByCaller,现在我们可以不在蓝图里设置了,直接在执行计算类中就能进行操作获得一样的结果

主要函数就是:GetSetByCallerMagnitude(),可以通过这个获取以标签为键对应的值

![[image-20240816024915994.png]]

蓝图设置只需要执行计算即可,SetBycaller可以取消了

![[image-20240816025053522.png]]

⑫ Modifier的执行顺序

Modifiers 数组有多个Modifier 的时候(也就是index 大于 1) 时,不管数组元素有多少个,ModifierOp影响的结果值全都是按顺序执行的

![[image-20240723210926573.png]]

当有modifier op 有乘法的时候:
初始生命值为10

  • 10 add 9 = 19 , 生命值现在为 19
  • 19 Multiply 10 = 190 , 生命值现在为 190
  • 190 add 12 = 202 , 生命值现在为 202

![[image-20240723211322807.png]]

这是有除法的情况

![[image-20240723211717836.png]]

⑬ Derived Attributes派生属性

引入

在ARPG游戏中,玩家的主要属性或者次要属性之间的联系比较复杂,它们互相之间或多或少都会都会影响相应的属性,比如

  • 一级属性 (PrimaryAttributes):
    • Strength力量 : 会提高物理伤害
    • Intelligent智力: 提高魔法伤害
    • Resilience 韧性: 提高护甲和护甲穿透
    • Vigor 活力:提升生命值
  • 二级属性(Secondary Attributes):
    • Armor 护甲 , 以一级属性韧性Resilience为基础: 减少受到的伤害,增加格挡几率
    • Armor Penetration 护甲穿透 , 以一级属性韧性Resilience为基础: 它将忽略一定比例的敌人护甲,并提高暴击几率
    • Block Chance 格挡几率 , 以二级属性护甲Armor为基础,成功格挡将会减少一半伤害的几率
    • Critical Hit Chance 暴击几率,以二级属性护甲穿透Armor Penetration 为基础: 随着护甲穿透的提高,暴击几率也会提高,成功造成的伤害为双倍
    • Critical HitDamage 暴击伤害,以二级属性护甲穿透Armor Penetration 为基础: 这是在暴击时额外增加的伤害
    • Critical HitResistance 暴击抗性,以二级属性护甲Armor为基础:可以降低敌人攻击玩家的暴击几率
    • Health Regeneration 生命值回复 , 以一级属性活力Vigor为基础: 可以看到活力不仅增加生命值,还会增加其他属性,生命值回复是每秒回复的生命值数量
    • Mana Regeneration 法力值回复,以一级属性智力Intelligence为基础:这是每秒回复的法力值数量
    • Max Health 最大生命值,以一级属性活力Vigor为基础: ,这是可以获得的最大生命值
    • Max Mana 最大法力值,以一级属性智力Intelligence为基础: ,这是可以获得的最大法力值,随着我们的智力高,最大法力值也会提高

![[image-20240725021432923.png]]

属性集类中先设置好变量

![[image-20240724235606599.png]]

利用GE实现Derived Attributes

简单概括就是在主要属性初始化之后,给当前角色应用一个duration policy为infinite的GE,

  • 这不是说这个GE蓝图类会每帧检查判断之类的,只有当相应的主属性(Backing Attributes)变化时,他才做出反应
  • GE蓝图类设置如下
    • 首先Duration policy设置为infinite
    • Modifier的 属性设置为要影响的属性
    • ModifierOp: 因为当前我们是初始化各个子属性所有 设置为Override,不要设置为add,当你设置功能,比如说升级的时候,那时候才是Add
    • 然后幅度计算类型设置为 基于属性(attribute based)
      • 捕获的属性设置为当前GE以什么为基础的属性,比如修改器影响的属性是护甲,这个护甲值以韧性为基础,那么这里捕获的属性就是韧性,其它同理
      • 捕获的目标:在这种情况下Source和Target一样的,因为我们是将这个GE应用到我们自己身上一次
      • 你也可以自己设置基础幅度,系数coefficient、前加Pre、后加Post

![[image-20240725002002493.png]]

![[image-20240725002023330.png]]

测试

准备要对玩家用到的GE,直接对目标的resilience Add 2

![[image-20240725013429254.png]]

当前玩家派生属性蓝图的设置,Armor初始值会被设置为目标(目标是我们自己,因为我们对自己应用这个GE)属性Resilience的0.5倍

![[image-20240725013509398.png]]

可以看到当我们直接改变韧性时,护甲也会改变,因为我们设置了系数是0.5倍,所以每次只会影响加1护甲值

![[recording 6.gif]]

⑭ GE 上下文 Gameplay Effect Context

效果上下文是与游戏效果一起存在的东西,我们一直都用过,在效果上下文中我们可以手动添加以下信息,以方便我们可以在各种类之间利用这些信息做一些操作

![[image-20240817001148125.png]]

我们只看其中几个比较重要的,因为我们设置这些的过程中其它内容都会被自动填充,例如当我们调用SetAbility的时候,原码里GE上下文内部就会帮我们自动填充了AbilityInstanceNotReplicated、AbilityCDO、AbilityLevel

  • EffectCauser:见名之意,是GameplayEffect的创建者
  • SourceObject:需要我们手动添加,可以和EffectCauser一样
  • Actors:一个弱指针Actor数组,可以把你想放的东西都放进去
  • HitResult:存储FHitResult结构的变量
  • SetAbility:顾名思义,如果你在能力类中使用GE, 那么就可以把当前(this)设置进去

上面的信息可以通过这几个函数填充

![[image-20240817002444997.png]]

1.自定义 Gameplay Effect Context结构

根据我们的了解默认的GE上下文基本上满足我们的使用,但是我们想拓展这个类,或者想向GE上下文中设置我们自己的变量该怎么办呢,这时候就需要自定义该结构了

2.创建自定义 Gameplay Effect Context

创建我们的自定义 Gameplay Effect Context结构就必须要继承自public FGameplayEffectContext, 我们在Protected中新增两个布尔值,并且它有几个我们必须要覆盖的函数:

  • virtual UScriptStruct* GetScriptStruct() const override;
  • virtual bool NetSerialize(FArchive& Ar, UPackageMap* Map, bool& bOutSuccess) override;
  • virtual FRPGAuraGameplayEffectContext* Duplicate() const override
    ![[image-20240818223315340.png]]

3.NetSerialize 网络序列化函数

NetSerialize函数源码中先进行位移然后再进行或运算,意思是进行翻转比特,主要用途是可以用一个uint8来紧密存储相关的信息 :

  • 第一步对Instigator进行有效化判断之后 如果成功, RepBits 就等于1,因为 1 位移0 (1 << 0)之后还是1,0 和 1进行或操作 RepBits就等1了
    这意味着RepBits变量 的第一位表示Instigator有效
  • 到第二步,如果EffectCauser有效,那么表示Instigator的位左移一位变成第二位,现在RepBits的第一位表示EffectCauser有效,以此类推
  • 最后RepBits变量就会有0有1,表名我们是否需要复制或是否应该序列化这些变量

![[image-20240818190351772.png]]

判断完哪些需要序列化之后,就用FArchive这个工具直接对对应的数据进行序列化存到FArchive中
我们这里的比特变量是8位但最多只用到7位,因为只有7个对象

![[image-20240818190450903.png]]

再看剩余的操作,注意这些操作内容不在 if (Ar.IsSaving()) 语句里

  • 观察<< 运算符,我们是从(数据)右到左(FArchive),因此是将右边的内容序列化到 Ar (FArchive)中
  • 对于 RepBits & (1 << 0) 这个操作,我们都知道对应的位表示某个变量是否要序列化,因此 1 << 0 表示 Instigator
  • RepBits 某位与当前要序列化的对象 进行逻辑与运算 , 结果为1则表明当前对象要进行序列化操作
    • 比如说当前 RepBits = 0011,1011 (有0意味着有些对象不需要进行序列化),1 << 0 还是1, 即 0001
    • 0011 , 1011 & 1 = 0000 , 0001 (逻辑与全1才能得1) , 因此表达式最终结果为1 我们就能进入Instigator这个对象的序列化操作
    • 其实我们只需要看当前位移表示的需要序列化对象(1 << N)在RepBits对应的位上有没有1即可,这样结果怎么样都不会为0
  • 我们将以上全部操作解释为由该位移表示的对象如果是需要序列化的,那么就对这个对象实体进行序列化
    ![[image-20240818202840707.png]]
  • << 运算符对很多变量进行了重载,但不是全部类型,因此对于数组,我们只能用SafeNetSerializeTArray_Default进行序列化数组
  • 并且也能看到对FHitResult的数据处理方式也有点不一样,最后这里会帮我们给效果上下文自动设置Instigator,因为要初始化效果上下文中InstigatorAbilitySystemComponent这个变量的值
  • 接下来我们将模仿上述操作为我们的布尔值变量进行网络序列化

![[image-20240818203705029.png]]

![[image-20240818203717015.png]]

4.实现自定义网络序列化函数

直接照猫画虎,需要注意的是,这里我使用uint16位, 如果你有多个变量要序列化那就可以用大一点的比如uint32
![[image-20240818222127212.png]]

5.结构体特征选项 TStructOpsTypeTraitsBase2

要想创建自定义GameplayEffectContext结构,除了覆写相应方法之外,我们还需要做一件事情

  • 我们去到GameplayEffectTypes.h头文件中,搜索定位到下面这一结构体
    ![[image-20240818215749924.png]]

然后来到Class.h头文件的TStructOpsTypeTraitsBase2结构可以看到该结构有许多选项

  • 该结构反射系统使用的脚本结构有关,它只是定义了(我们当前这个结构,也就是我们自定义的这个效果上下文结构)能够做什么
  • 同时TStructOpsTypeTraitsBase2结构体的设置也应用于其它重要的事情,比如序列化
  • 简单来说,把下列某些选项设置为true,那么你这个结构就有对应的能力
  • 这里我们只关注两个选项,即WithNetSerializer和WithCopy,很明显我们需要这两个功能因此我们就默认设置为true即可

![[image-20240818220147426.png]]

去到我们的自定义上下文结构头文件,复制一份类似的继承自该结构即可,选项基于你的功能决定,我这里默认

![[image-20240818223447204.png]]

6.为项目设置自定义GameplayEffectContext

经过我们之前几个步骤的准备,我们已经创建好一个自定义GameplayEffectContext的模板,但是这还没完,我们还需要为项目的GEContext设置为我们的这个GEContext,通过创建继承自ability system globals 的类来实现

  • 创建继承自ability system globals class的全局类
    • 它定义了全局的变量供能力系统使用,这些变量在我们代码的任何地方都可以访问
    • 我们可以将其子类化为我们已有的能力系统全局类,在其中我们可以指定自已的自定义类和结构体
    • 例如我们可以指定我们自己刚刚创建的游戏效果上下文,GAS应该使用这个
      除此之外还能指定GameplayCue(暂未学习后面会讲到)、能力演员信息(ability actor info)使用的类 等等
创建 AbilitySystemGlobals 类

创建AbilitySystemGlobals 类

![[image-20240818224931420.png]]

设置自定义的AbilitySystemGlobals 类

主要就是重写 virtual FGameplayEffectContext* AllocGameplayEffectContext() const override;
这个函数就是但我们调用MakeEffectContext的时候调用的,我们已经用过了

![[image-20240818225754403.png]]

![[image-20240818225817020.png]]

cpp中实现该函数,返回new 一个我们刚刚自定义的GE上下文即可

![[image-20240818230105632.png]]

编辑器中设置自定义AbilitySystemGlobals 类

最后一步,创建完自定义类之后,我们再去编辑器中设置默认的AbilitySystemGlobals 类,这样GAS就会使用我们创建的GE上下文类了
因此找到DefaultGame.ini配置文件,他就在你的项目根目录下Config文件夹里

[/Script/GameplayAbilities.AbilitySystemGlobals]

+AbilitySystemGlobalsClassName="/Script/[你的项目名字].[刚刚创建的自定义AbilitySystemGlobals类名字]"

![[image-20240818231313792.png]]

验证,去到你创建GameGplayEffect的地方,一般会创建一个GE上下文 然后打断点即可,可以看到该GE上下文携带了我们自己创建的变量

![[image-20240818232109409.png]]

7.使用问题

将得到的GE上下文转换成我们自定义的上下文时,需要使用cpp中默认的static_cast,如下:

const auto MyGEContext = static_cast<const FRPGAuraGameplayEffectContext*>(EffectContextHandle.Get());
序列化问题报错

出现这种问题就是你的NetSerialize函数里给变量序列化的时候出问题了,不然就删除掉上面说的Goble类和自定义GE上下文换个名字,重新设置创建Cpp类

  • ReceivedRPC: ReceivePropertiesForRPC - Mismatch read. Function: ClientOnGEAppliedToSelf, Object: BaseAbilitySystemComponent /Game/Maps/UEDPIE_1_Startup.Startup:PersistentLevel.BP_BasePlayerState_C_0.AbilitySystemComponent
  • LogRep:Errors:ReceiveFastArrayItem:Invalid property terminator handle - Handle=261

![[image-20240821134720647.png]]

8. 在GameplayEffectContext中实现对自定义成员的NetSerialize

在自定义上下文中,我们根据基类函数NetSerialize模板可以知道如何对引擎中自带的基本数据类型Ctrl C进行网络序列化进一步添加进GameplayEffectSpec中在整个GAS系统中传递我们想要的数据以便我们操作,但是如果我们想对其它数组容器或者自定义结构体进行网络序列化该怎么办呢,例如:

USTRUCT(BlueprintType)  
struct FMyStruct{
	...
}

FMyStruct MyStruct;
TArray<FMyStruct> StructArray;
FGameplayTagContainer TagContainer;
① 自定义USTRUCT 的网络序列化

1.首先在结构体内覆写NetSeriliaze函数

/// DeBuff信息结构体  
USTRUCT(BlueprintType)  
struct FDeBuffInfo  
{  
    GENERATED_BODY()  
  
    FDeBuffInfo() {}  
  
    UPROPERTY()  
    bool bIsSuccessfulDeBuff = false;  
  
    UPROPERTY()  
    float DeBuffDamage = 0.f;  
  
    UPROPERTY()  
    float DeBuffDuration = 0.f;  
  
    UPROPERTY()  
    float DeBuffFrequency = 0.f;  
  
    /// 重写网络序列化  
    /// @param Ar   
    /// @param Map   
    /// @param bOutSuccess  
    /// @returnbool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess)  
    {  
       uint16 RepBits = 0;  
       if (Ar.IsSaving())  
       {   
          if (bIsSuccessfulDeBuff) { RepBits |= 1 << 0; }  
       }  
       Ar.SerializeBits(&RepBits, 4);  
  
       bool bOutSuccessLocal = true;  
       if (RepBits & (1 << 0))  
       {  
          Ar << bIsSuccessfulDeBuff;  
          Ar << DeBuffDamage;  
          Ar << DeBuffDuration;  
          Ar << DeBuffFrequency;  
       }  
       return bOutSuccessLocal;  
    }  
};
  1. 去到自定义GE上下文中,在GE Context的网络序列化函数中调用结构的NetSerialize即可

![[image-20241011082337400.png]]

![[image-20241011082413519.png]]

② TArray容器模板参数为自定义USTRUCT的容器的网络序列化

1.同理,首先结构体必须要覆写网络序列化函数,然后我们用容器包裹

![[image-20241011082731908.png]]

  1. 使用SafeNetSerializeTArray_WithNetSerialize<数组最大长度>(Ar,数组,Map); 进行序列化

![[image-20241011082811800.png]]

③ GT标签容器的网络序列化

FGameplayTagContainer自带序列化函数,因此直接调用即可

![[image-20241011083325335.png]]

⑮ CostGameplayEffect

建议先学习[[#(5) Gameplay Abilities (GA)]], 游戏能力可以使用GE作为能力的消耗,比如说发射火球然后消耗蓝量

在GA中使用CostGameplayEffect

先创建一个GameplayEffect,该GE是Instant的,Modifiers选择相应的属性

![[image-20240905170139056.png]]

去到对应的GA蓝图类中,点击Class Default 选择我们使用的Costs类,然后调用CommitAbility函数
当蓝量不足的时,当前能力没法激活

![[image-20240905170232915.png]]

CostGameplayEffect的等级

CostGameplayEffect的等级是你在创建当前能力Spec的时候设置的,可以制作一个曲线表根据能力等级而变化

![[image-20240905173040470.png]]

![[image-20240905171238906.png]]

⑯ CoolDown GameplayEffect

在GA中,我们可以为一个能力添加冷却时间,在冷却时间未结束时能力不能被激活,冷却也是用GE来实现的,具体操作如下

  • 创建一个新的GT(游戏标签)
  • 创建一个有持续时间的GE,该持续时间就是冷却时间
  • 该GE授予效果应用者一个我们刚刚创建的标签
  • 在GA蓝图类中调用CommitAbility函数即可

记得设置时间,这里的冷却时间为0.6

![[image-20240905175045806.png]]

在GA中设置好CoolDown

![[image-20240905175144952.png]]

冷却的剩余时间与Widget蓝图类通信

当我们的能力进入冷却时,我们想获取剩余时间并在小部件蓝图上显示该怎么做呢

  • 我们首先可以创建一个异步结点类来实现,这个结点和我们创建[[#(6) Ablility Tasks]] 能力任务类有点类似,把委托作为输出引脚,所以只有当广播时才会有输出,异步结点我们已经用过很多次了比如能力类中的PlayMontageAndWait,它可以根据动画的状态来等待各种输出
  • 在异步结点里绑定当前角色的能力系统组件的两个委托

![[image-20240905180737962.png]]

创建自定义异步蓝图结点类

接下来我们创建基于blueprintasyncActionBase的cpp类并且实现获取剩余冷却时间的功能,进入头文件

![[image-20240906215013814.png]]

cpp文件

![[image-20240906215347764.png]]

Widget蓝图中调用自定义异步节点

当前widget蓝图有一个冷却标签,我们的冷却标签只会对应一种GA,当我们结点内部的Start委托广播时我们就能拿到那个TimeRemaing,然后用这个浮点数在定时器里持续递减就能实现技能冷却倒计时显示

![[image-20240906215752859.png]]

![[recording 16.gif]]

⑰ Dynamic GameplayEffect In Cpp 在C++中应用动态GE (UE5.3)

建议先学习完[[#(3)Gameplay Effect (GE)]]、[[#(4) Gameplay Tags]]
到目前为止,我们只在编辑器的蓝图中创建GE,并且在Cpp中直接引用这个GE蓝图然后通过ASC来Apply GamePlay Effect给对象,但是我们还有一种方法可以动态的创建GE,即在C++中创建GE,但是这种方法有限制:

  • 动态GE不支持Replication
  • 因此如果GE涉及到伤害或者更改某些重要的内容最后就在服务器上进行这样任何更改都将会复制到客户端

① 在AS中创建动态GE并Apply

一般我们创建动态GE就是为了实现给某个GE所引发的副作用效果(比如火焰触发的短时燃烧副作用),因此我们就在属性集(服务器上)进行该操作:

  1. 通过Soruce(Instigator)的ASCmake一个新的FGameplayEffectContextHandle,此处的Soruce指的是给目标应用GE导致对方属性的对象(发起火焰的对象)
// 通过Instigator 的ASC创建新的 FGameplayEffectContextHandleauto GeContextHandle = EffectProperties.SourceAsc->MakeEffectContext();  
GeContextHandle.AddSourceObject(EffectProperties.SourceAvatar);
  1. 设置动态GE的名字
// 动态GE的名字  
FString DeBuffName = FString::Printf(TEXT("DynamicDeBuff_%s"), *DeBuffInfo.DeBuffType.ToString());
  1. 创建GE类

在UE中,包Package是对资源进行组织和管理的方式
GetTransientPackage() 返回的临时包对象用于在运行时动态创建对象和资源,而无需将其永久保存到磁盘上的包文件中
当代码需要在运行时创建临时资源(如动态创建GE、蓝图或其他对象)时 可以使用 GetTransientPackage() 函数来获取一个临时的包对象,并将新创建的对象分配给该包,这样做可以方便地管理这些临时资源,而无需创建和维护额外的包文件
即临时包对象在游戏运行期间存在,并且不会被保存到磁盘上的包文件中一旦游戏结束或重新编译项目,这里new的GE将被清理并释放内存

UGameplayEffect* GameplayEffect = NewObject<UGameplayEffect>(GetTransientPackage(), FName(DeBuffName));  

if (!GameplayEffect) { .... }
  1. 设置GE中的选项,这些选项与蓝图中的GE设置是对应的
GameplayEffect->DurationPolicy = EGameplayEffectDurationType::HasDuration;  
GameplayEffect->Period = DeBuffInfo.DeBuffFrequency;  
GameplayEffect->DurationMagnitude = FScalableFloat(DeBuffInfo.DeBuffDuration);  
// 按施法者设置堆叠策略  
GameplayEffect->StackingType = EGameplayEffectStackingType::AggregateBySource;  
GameplayEffect->StackLimitCount = 1;
  1. 添加GE组件用于授予目标GT

在UE5.3 中引入了GE组件用于取代之前的直接GrantedTag BlockTag等选项

![[image-20241015090706894.png]]

// 添加GE组件,用于添加授予目标的标签
UTargetTagsGameplayEffectComponent& GeComp = GameplayEffect->AddComponent<UTargetTagsGameplayEffectComponent>();  
auto& TagContainer = const_cast<FInheritedTagContainer&>(GeComp.GetConfiguredTargetTagChanges());  
// 授予目标标签
TagContainer.Added.AddTag(YourTag);  
GeComp.SetAndApplyTargetTagChanges(TagContainer);
  1. 创建modifier修改器
// 新建Modifier并且配置Modifier信息  
GameplayEffect->Modifiers.Add(FGameplayModifierInfo());
// 找到我们刚刚创建的Modifier(容器的最后一个)
auto& GameplayModifierInfo = GameplayEffect->Modifiers.Last();  
GameplayModifierInfo.ModifierMagnitude = FScalableFloat(YourFloat);  
GameplayModifierInfo.ModifierOp = EGameplayModOp::Additive;  
GameplayModifierInfo.Attribute = UBaseAttributeSet::GetInComingDamageAttribute();
  1. Apply Dynamic GE

注意,动态GE最好别引发副作用效果(即动态GE还能触发创建动态GE),这样会形成一个循环,因此在我的自定义GE上下文中,有一个bool值用于表示当前的GE是别的GE引发的,所以可以在AS中分辨当前GE是不是动态GE,以进行相应处理

// 通过我们之前Make的GE上下文句柄和创建的GE new 一个GE Spec,以便我们Apply  
if (const FGameplayEffectSpec* MutableSpec = new FGameplayEffectSpec(GameplayEffect, GeContextHandle, Level))  
{  
    // 我的蓝图函数库,用于从GE上下文转换为我自己的GE自定义上下文  
    if (const auto TmpGeContext =  
       UGameAbilitySystemGlobals::GetCustomGeContext(MutableSpec->GetContext().Get()))  
    {  
       TmpGeContext->AddDamageType(DeBuffInfo.DamageType);
       // 用于标记当前动态GE是由另外的GE引发的效果  
       TmpGeContext->SetIsDeBuffSideEffect(true);  
       EffectProperties.TargetAsc->ApplyGameplayEffectSpecToSelf(*MutableSpec);  
       ... 
    }  
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值