Awesome
EX Gameplay Ability System For Unity
前言
该项目为Unreal Engine的Gameplay Ability System的Unity实现,目前实现了部分功能,后续会继续完善。
该项目完全开源,欢迎大家一起参与开发,提出建议,共同完善。可以基于该项目进行二次开发。
!!提醒!!请注意!!
该项目依赖Odin Inspector插件(付费),请自行解决!!!!!!!!
Odin Inspector插件请使用3.2+版本
若没有方法解决,可以加反馈qq群(616570103),群内提供帮助
目前EX-GAS没有非常全面的测试,所以存在不可知的大量bug和性能问题。所以对于打算使用该插件的朋友请谨慎考虑,当然我是希望更多人用EX-GAS,毕竟相当于是变相的QA。
但是要讲良心嘛,现在的EX-GAS算不上很稳定的版本,如果你是业余时间开发rpg类的独立游戏,开发时间十分充裕,我当然建议你试试EX-GAS,我会尽可能修复bug,提供使用上的帮助。
如果有好兄弟确实打算用,那么请务必加反馈群(616570103)。我会尽力抽时间修复提出的bug。
我非常希望EX-GAS能早日稳定,为更多游戏提供支持帮助。
入门教学案例系列文章
俯视角2D弹幕射击游戏
- PART.1 准备工作:https://zhuanlan.zhihu.com/p/688111182
- PART.2 基础逻辑:https://zhuanlan.zhihu.com/p/689241379
- PART.3 使用EX-GAS完成交互逻辑:https://zhuanlan.zhihu.com/p/689386644
- PART.4 使用EX-GAS进行润色:https://zhuanlan.zhihu.com/p/689660650
目录
- 1.快速开始
- 2.GAS系统介绍
- 3.API && Source Code Documentation (W.I.P 施工中)
- 4.可视化功能
- 5.如果...我想...,应该怎么做?(持续补充)
- 6.暂不支持的功能(可能有遗漏)
- 7.后续计划
- 8.特别感谢
- 9.插件反馈渠道
1.快速开始
安装
- 导入Odin Inspector插件(付费),Odin Inspector来源请自行解决。建议使用3.2+版本。
- 导入本插件,建议以下两种方式:
- 使用Unity Package Manager安装 在Unity Package Manager中添加git地址:https://github.com/No78Vino/gameplay-ability-system-for-unity.git?path=Assets/GAS
【国内镜像】https://gitee.com/exhard/gameplay-ability-system-for-unity.git?path=Assets/GAS
- 使用git clone本仓库[镜像同上],然后将Assets/GAS文件夹拷贝到你的项目中即可
使用
GAS十分复杂,使用门槛较高。因为本项目是对UE的GAS的模仿移植,所以实现逻辑基本一致。建议先粗略了解一下UE版本的GAS整体逻辑,参考项目文档:https://github.com/BillEliot/GASDocumentation_Chinese
使用流程
- 基础设置
在ProjectSetting中(或者Edit Menu栏入口:EX-GAS -> Setting),找到EX Gameplay Ability System的基本设置界面:
设置好以下两个路径
- 配置文件Asset路径: 这是该项目有关GAS的配置的路径,包括MMC,Cue,GameplayEffect,Ability,ASCPreset。
- 脚本生成路径: GAS的基础配置(Tag,Attribute,AttributeSet)都会有对应的脚本生成。
首次设置完路径后,点击检查子目录文件夹,确保必要的子文件夹都已生成。
【生成AbilitySystemComponentExtension类脚本】这个按钮,请在生成了Attribute,AttributeSet,Ability的Lib集合类之后再点击。特别提醒,AbilitySystemComponentExtension是工具类,理论上只生成一次即可。
-
配置Tag: Tag是GAS核心逻辑运作的依赖,非常重要。关于Tag的使用及运作逻辑详见章节(GameplayTag)
-
配置Attribute: Attribute是GAS运行时数据单位。关于Attribute的使用及运作逻辑详见章节(Attribute)
-
配置AttributeSet: AttributeSet是GAS运行时数据单位集合,合理的AttributeSet设计能够帮助程序解耦,提高开发效率。关于AttributeSet的使用及运作逻辑详见章节(AttributeSet)
-
设计MMC,Cue: 详见MMC, GameplayCue
-
设计Gameplay Effect: 详见 Gameplay Effect
-
设计Ability: 详见 Ability
-
设计ASC预设(可选): 详见 AbilitySystemComponent
GAS的预缓存
- 为了解决EX-GAS框架中关于Type.Name造成的GC问题。 可以在游戏内合适的地方(一般是游戏初始化阶段)调用GasCache.CacheAttributeSetName(GAttrSetLib.TypeToName) 这行代码。他会将GAttrSetLib.TypeToName中的所有Key-Value对缓存起来,避开Type.Name的使用。 如果你不调用这个方法,也不会对运行逻辑造成影响,不过会有额外的GC。
2.EX-GAS系统介绍
2.1 EX-GAS概述
EX-GAS是对UnrealEngine的GAS(Gameplay Ability System)的模仿和实现。
GAS 是 "Gameplay Ability System" 的缩写,是一套游戏能力系统。 这个系统的目的是为开发者提供一种灵活而强大的框架,用于实现和管理游戏中的各种角色能力、技能和效果。
如果把EX-GAS高度概括为一句话,那就是:WHO DO WHAT。
- Who:AbilitySystemComponent(ASC),EX-GAS的实例对象,是体系运转的基础单位
- Do:Ability,是游戏中可以触发的一切行为和技能
- What:GameplayEffect(GE),掌握了游戏内元素的属性实际控制权,GameplayEffect本身应该理解为结果
GAS本质是一套属性数值的管理系统,GameplayCue我个人理解为附加价值(虽然这个附加价值很有分量)。 纵使GAS的Tag体系解决复杂的GameplayEffect和Ability的逻辑,但最终的结果目的也只是掌握属性数值变化。 而属性的最底层修改权力交由了GameplayEffect。所以我把GE理解为结果。
UE的GAS的使用门槛很高,这一点在我构筑完EX-GAS雏形后更是深有体会。 所以在EX-GAS的设计上,我尽可能的做简化,优化,来降低了使用门槛。 我制作了几个关键的编辑器,来帮助开发者快速的使用EX-GAS。 但即便如此,GAS本身的繁多参数依然让编辑器的界面看上去十分臃肿,这很难简化,没有哪个参数是可以被删除的。 甚至,雏形阶段的EX-GAS还有很多功能还未实现,也就是说还有更多的参数是没有被编辑器暴露出来的。
GAS的使用者必须至少有一名程序开发人员,因为GAS的使用需要编写大量自定义业务逻辑。 Ability,Cue,MMC等都是必须根据游戏类型和内容玩法而定的。 非程序开发人员则需要完全理解EX-GAS的运作逻辑,才能更好的配合程序开发人员快速配置出各种各样的技能,完善玩法表现。
2.2 GameplayTag
Gameplay Tag,标签,它用于分类和描述对象的状态,非常有用于控制游戏逻辑。
- Gameplay Tag以树形层级结构(如Parent.Child.Grandchild)组织,用于描述角色状态/事件/属性/等,如眩晕(State.Debuff.Stun)。
- Gameplay Tag主要用于替代布尔值或枚举值,进行逻辑判断。
- 通常将Gameplay Tag添加到AbilitySystemComponent(ASC)以与系统(GameplayEffect,GameplayCue,Ability)交互。
Gameplay Tag在GAS中的使用涉及到标签的添加、移除以及对标签变化的响应。 开发者可以通过GameplayTag Manager在项目设置中管理这些标签,无需手动编辑配置文件。 Gameplay Tag的灵活性和高效性使其成为GAS中控制游戏逻辑的重要工具。 它不仅可以用于简单的状态描述,还可以用于复杂的游戏逻辑和事件触发。
举个例子,GameplayEffect中有一个字段RequiredTags,其含义是当前GameplayEffect生效的AbilitySystemComponent(ASC) 需要拥有【所有】的RequiredTags(需求标签)。
上述例子,如果用传统的思路去做,可能需要写很多if-else判断,同时元素的实例脚本可能会增加很多状态标记的变量, 而且还需要考虑多个游戏效果的交互,这使得代码的设计和实现变得复杂,耦合。
GameplayTag的使用可以大大简化这些逻辑,使得代码更加清晰,易于维护。 他把状态和标记全部抽象成了一个独立的Tag系统,而且最巧妙的是树形结构的设计。 他解决了很多Gameplay设计上的问题,常见的问题比如:移除所有Debuff,传统的做法可能是让(中毒,减速,灼伤,等等)继承自Debuff类/接口; 而GameplayTag只需要添加一个Tag(中毒:Debuff.Poison,减速:Debuff.SpeedDown,灼伤:Debuff.Burning)
GameplayTag自身可以作为一个独立的系统去使用。 我在开发Demo的过程中就发现了GameplayTag的强大之处,他几乎替代了我的所有状态值。 甚至我设计了一个全局ASC,专门用来管理全局状态,我不需要对每个系统的状态管理,转而维护一个ASC即可。(虽然最后并没有落地这个设计,因为DEMO没有那么复杂。)
2.2.a GameplayTag Manager
我模仿了UE的GAS的Tag管理视图,做了树结构管理。通过选中节点,可以添加子节点,删除节点。要修改父子级关系时,只需要拖动节点即可。 Tag的分类设计需要谨慎,策划(设计师)需要再项目初就规划好Tag的大致分类。如果开发后期出现了Tag的父子级关系大变动,会严重影响原有游戏运作逻辑。
【注意!!!】 每次编辑完Tag后,一定要点击右上角的【生成TagLib】按钮。GTagLib是包含了当前所有Tag的库类, 便于程序开发使用。GTagLib中Tag变量名会用‘_’代替‘.’ ,比如 State.Buff.PowerUp -> State_Buff_PowerUp
2.3 Attribute
Attribute,属性,是GAS中的核心数据单位,用于描述角色的各种属性,如生命值,攻击力,防御力等。
Attribute和AttributeSet(属性集)需要结合起来才能作为唯一标识,简单点说AttributeSet是姓氏,Attribute是名字。 不同的AttributeSet可以有相同名字的Attribute,但是同一组的AttributeSet不可以有相同名字的Attribute。 常见的情况,如下:
AttributeSet 人物: 生命值, 法力, 攻击力, 防御力
AttributeSet 武器: 生命值(耐久度), 攻击力, 防御力
而这两组AttributeSet中的生命值,攻击力,防御力,都是不同的属性,他们的意义和作用不同。但他们可以属于同一个单位。
2.3.a Attribute Manager
Attribute Manager的作用很简单,提供属性名字的管理。然后作为AttributeSet的选项使用。
【注意!!!】 每次编辑完Attribute后,一定要点击【生成AttrLib】按钮。 只有AttrLib生成后,AttributeSet的Attribute选项才会发生改变。
2.4 AttributeSet
AttributeSet,属性集,是GAS中的核心数据单位集合,用于描述角色的某一类别的属性集合。
在上文的[2.3 Attribute]中,我们提到了AttributeSet是姓氏,Attribute是名字。二者结合起来才能作为唯一标识。 而对于AttributeSet的设计,可以较为随意,大多数情况,大家会更乐意一个单位只有一个AttributeSet。 因为这样便于管理和分类,不同类别的单位直接使用不同的AttributeSet。但实际上一个单位是可以拥有复数AttributeSet。 我其实比较认同一个单位只有一个AttributeSet的设计,因为这对程序开发也是好事,逻辑处理会更简单直白。
配置时的注意项:
- AttributeSet的名字禁止重复或空。这是因为AttributeSet的名字会作为类名。
- AttributeSet内的Attribute禁止重复。
2.4.a AttributeSet Manager
AttributeSet Manager统筹属性集的命名和属性管理。
【注意!!!】 每次编辑完后,一定要点击【生成AttrSetLib】按钮。AttrSetLib会在AbilitySystemComponent的预设配置中用到。 AttrSetLib.gen.cs脚本中会包含所有的AttributeSet类(详见下文API章节),AttributeSet对应的类名是:“AS_名字”。 比如AttributeSet名字是Fight,那它对应的类名是AS_Fight。
2.5 ModifierMagnitudeCalculation
ModifierMagnitudeCalculation,修改器,负责GAS中Attribute的数值计算逻辑。
MMC(下文开始会使用缩写指代ModifierMagnitudeCalculation)唯一的使用场景是在GameplayEffect中。 GAS中,体系内运作的情况下,只有GameplayEffect才能修改Attribute的数值。而GameplayEffect就是通过MMC修改Attribute的数值。
MMC具有以下特点:
- 【与Attribute集成】: MMC 与 GAS 中的Attribute系统一起使用。这意味着计算效果幅度时可以考虑角色的属性,使得效果的强度与角色的属性值相关联。
- 【实时性】: MMC 用于在运行时动态计算 Attribute 的修改幅度。这样可以根据角色的状态、属性或其他因素,实时地调整效果的强度。
- 【自定义】: 通过继承 MMC的基类,开发者可以实现自定义的计算逻辑。这允许在计算效果幅度时考虑复杂的游戏逻辑、属性关系或其他条件。
- 【复用性】: 由于 MMC 是一个独立的类(Scriptable Object),开发者可以在多个 GameplayEffect 中重复使用相同的计算逻辑。这样可以确保在整个游戏中保持一致的效果计算。
- 【灵活性】: 使用 MMC 提高了系统的灵活性,使得效果的强度不再是固定的数值,而可以根据需要在运行时进行调整,适应不同的游戏情境和需求。
MMC在GameplayEffect中的运作逻辑,结合GameplayEffect配置中MMC界面来解释。如下图:
MMC被存储在Modifier中,Modifier是GameplayEffect的一部分。 Modifier包含了修改的属性,幅值(Magnitude),操作类型和MMC。
- 修改的属性:指的是GameplayEffect作用对象被修改的属性。可以看到属性名是“AS_Fight.POSTURE”,这对应了上文的提到的属性识别是AttrSet和Attr组合而成的。
- 幅值Magnitude:修改器的基础数值。这个数值如何使用由MMC的运行逻辑决定。
- 操作类型:是对属性的操作类型,有3种:
- Add : 加法(取值为负便是减法)
- Multiply: 乘法(除法取倒数即可)
- Override:覆写属性值
- MMC:计算单位,Modifier的核心,是一个ScriptableObject。MMC的类别如下:
- ScalableFloatModCalculation:可缩放浮点数计算
- 该类型是根据Magnitude计算Modifier模值的,计算公式为:
ModifierMagnitude * k + b
实际上就是一个线性函数,k和b为可编辑参数,可以在编辑器中设置。
- 该类型是根据Magnitude计算Modifier模值的,计算公式为:
- AttributeBasedModCalculation:基于属性的计算
- 该类型是根据属性值计算Modifier模值的,计算公式为:
AttributeValue * k + b
计算逻辑与ScalableFloatModCalculation一致。 - 重点在于属性值的来源,确定属性值来源的参数有3个:
- attributeFromType:属性值从谁身上取?是从游戏效果的来源(创建者),还是目标(拥有者)。
- attributeName:属性值的名称,比如战斗属性集里的生命值:AS_Fight.Health
- captureType:属性值的捕获类型
- Track: 追踪,在Modifier被执行时,当场去取属性值
- SnapShot: 快照,在游戏效果被创建时会对来源和目标的属性进行快照。在Modifier被执行时,去取快照的属性值。
- 该类型是根据属性值计算Modifier模值的,计算公式为:
- SetByCallerModCalculation:由调用者设置的计算
- 不使用任何值计算模值,而是在执行时由调用者给出Modifier模值。
- 通过对GameplayEffectSpec注册数值来实现设置值。
- 设置数值映射有2种:
- 自定义键值:通过GameplayEffectSpec的RegisterValue(string key, float value)
- GameplayTag:通过GameplayEffectSpec的RegisterValue(GameplayTag tag, float value)
- CustomCalculation:自定义计算(必须继承自抽象基类ModifierMagnitudeCalculation)
- 上述3种类型显然不够方便且全面的满足游戏开发者的所有需求,所以提供了自定义计算类的功能。
- 允许开发者自由发挥给出各种各样的计算逻辑。
- ScalableFloatModCalculation:可缩放浮点数计算
2.6 GameplayCue
目前EX-GAS的GameplayCue功能还未完善。功能相对简陋。
- 【GameplayCue的作用】:GameplayCue是一个用于播放游戏提示的类,它的作用是在游戏运行时播放游戏效果,比如播放一个特效、播放一个音效等。
- 【GameplayCue的原则】: Cue是游戏提示,他必须遵守以下原则:
- Cue不应该对游戏的数值体系产生影响,比如不应该对游戏的属性进行修改,不应该对游戏的Buff进行修改等。
- Cue不应该对游戏玩法产生实际影响,比如即时战斗类的游戏,Cue不应该影响角色的位移、攻击等。
第一条原则是所有类型游戏必须遵守的。
而第二条原则就见仁见智了,因为游戏类型和玩法决定了cue的影响范围。 比如即时战斗类游戏,cue对角色位移有操作显然就是干涉了战斗,但如果是回合制游戏,cue对角色位移的操作就可以被当成是动画表现。 (甚至,即便是即时战斗类游戏,cue对角色位移的操作也可以被当成是动画表现,只要游戏开发人员认为cue的位移操作不影响游戏的战斗结果即可。)
- 【GameplayCue的类型】 GameplayCue的类型分两大类:
- GameplayCueInstant:瞬时性的Cue,比如播放动画,伤害UI提示等
- GameplayCueDurational:持续性的Cue,比如持续性的特效、持续性的音效等
GameplayCueInstant和GameplayCueDurational都是抽象类,它们的子类才是真正的可使用Cue类。 Cue是需要程序开发人员大量实现的,毕竟游戏不同导致游戏提示千变万化。
-
【关于Cue的子类实现】: Cue的完整组成为GameplayCue和GameplayCueSpec:
- GameplayCue< T >(抽象基类,T为对应的Spec类):Cue的数据实类,是一个可编辑类,开发人员可以在编辑器中设置Cue的各种参数。该类只可以被视作数据类。
- 必须实现CreateSpec方法:用于创建对应的Spec类
- GameplayCueSpec(抽象基类):Cue的规格类,是Runtime下Cue的真正实例,Cue的具体逻辑在该类中实现。
-
Spec类 需要实现的方法 方法触发时机 GameplayCueInstantSpec<br/>瞬时性Cue的规格类 Trigger() 执行时触发 GameplayCueDurationalSpec<br/>持续性Cue的规格类 OnAdd() Cue被添加时触发 GameplayCueDurationalSpec OnRemove() Cue被移除时触发 GameplayCueDurationalSpec OnGameplayEffectActivated() Cue所属的GameplayEffect被激活时触发 GameplayCueDurationalSpec OnGameplayEffectDeactivated() Cue所属的GameplayEffect被移除时触发 GameplayCueDurationalSpec OnTick() Cue的每帧更新逻辑
-
- GameplayCue< T >(抽象基类,T为对应的Spec类):Cue的数据实类,是一个可编辑类,开发人员可以在编辑器中设置Cue的各种参数。该类只可以被视作数据类。
-
【关于Cue的参数传递】: 目前EX-GAS的Cue参数传递非常简陋,依赖于结构体GameplayCueParameters,成员如下:
- GameplayEffectSpec sourceGameplayEffectSpec:Cue所属的GameplayEffect实例(如果是GE触发)
- AbilitySpec sourceAbilitySpec:Cue所属的Ability实例(如果是Ability触发)
- object[] customArguments:自定义参数,不同于GameplayCue中的数据。 customArguments是供程序开发人员在业务逻辑内自由传递参数的载体。
注意:customArguments是一个object数组,开发人员需要自己保证传递的参数类型正确,否则会导致运行时错误。 customArguments是最暴力的设计,往后EX-GAS的Cue参数传递设计还会进行优化。
- 【GameplayCue的使用】:
GameplayCue的使用手段很多,最基础的是在GameplayEffect中使用,Cue最开始的设计基础也是依附于GameplayEffect。Ability也可以对Cue进行操作。
除此之外,Cue的使用不限制于EX-GAS的体系内。开发者可以在任何地方使用Cue,只要能获取到GameplayCue的资源实例并且遵守Cue的原则即可。
-
在GameplayEffect中使用Cue
- GameplayEffect中使用Cue会根据GameplayEffect执行策略产生变化。
-
GameplayEffect类型 Cue名称 Cue类别 执行时机 Instant CueOnExecute Instant GameplayEffect执行时触发 Durational CueDurational Durational 生命周期完全和GameplayEffect同步 Durational CueOnAdd Instant GameplayEffect添加时触发 Durational CueOnRemove Instant GameplayEffect移除时触发 Durational CueOnActivate Instant GameplayEffect激活时触发 Durational CueOnDeactivate Instant GameplayEffect失活时触发
-
- GameplayEffect中使用Cue会根据GameplayEffect执行策略产生变化。
-
在Ability中使用Cue
- Ability种Cue的使用完全依赖于Ability自身的业务逻辑,因此程序开发者在AbilitySpec中实现Cue逻辑时一定要保证合理性。 特别是对于Durational类型的Cue,一定要保证Cue生命周期的合理性,切记不要出现遗漏销毁Cue的情况。
-
2.7 GameplayEffect
GameplayEffect是EX-GAS的核心之一,一切的游戏数值体系交互基于GameplayEffect。
GameplayEffect掌握了游戏内元素的属性控制权。理论上,只有它可以对游戏内元素的属性进行修改 (这里指的是修改,数值的初始化不算是修改)。当然,实际情况下,游戏开发人员当然可以手动直接修改属性值。 但是还是希望游戏开发者尽可能的不要打破EX-GAS的数值体系逻辑,因为过多的额外操作可能会导致游戏的数值体系变得混乱,难以追踪数值变化等等。
另外GameplayEffect还可以触发Cue(游戏提示)完成游戏效果的表现,以及控制获取额外的能力等。
- GameplayEffect的使用
GameplayEffect的配置界面如图,接下来逐一解释各个参数的含义。
- Name
- GameplayEffect的名称,纯粹用于显示,不会影响游戏逻辑。方便编辑者区分GameplayEffect。
- Description
- GameplayEffect的描述,纯粹用于显示,不会影响游戏逻辑。方便编辑者阅读理解GameplayEffect。
- DurationPolicy:GameplayEffect的执行策略,有以下几种:
-
策略类型 执行逻辑 Instant 即时执行,GameplayEffect被添加时立即执行,执行完毕后销毁自身。 Duration 持续执行,GameplayEffect被添加时立即执行,持续时间结束后移除自身。 Infinite 无限执行,GameplayEffect被添加时立即执行,执行完毕后不会移除,需要手动移除。
-
- Duration
- 持续时间,只有DurationPolicy为Duration时有效。
- Every(Period)
- 周期,只有DurationPolicy为Duration或者Infinite时有效。每隔Period时间执行一次PeriodExecution。
- PeriodExecution
- 周期执行的GameplayEffect,只有DurationPolicy为Duration或者Infinite,且Period>0时有效。每隔Period时间执行一次PeriodExecution。 _PeriodExecution禁止为空!!_PeriodExecution原则上只允许是Instant类型的GameplayEffect。但如果根据开发者需求,也可以使用其他类型的GameplayEffect。
- GrantedAbilities
- 授予的能力,只有DurationPolicy为Duration或者Infinite时有效。在GameplayEffect生命周期内,GameplayEffect的持有者会被授予这些能力。 GameplayEffect被移除时,这些能力也会被移除。具体详见GrantedAbility
- Modifiers: 属性修改器。详见MMC
- Tags:标签。Tag具有非常重要的作用,合适的tag可以处理GameplayEffect之间复杂的关系。
-
Tag类型 作用 AssetTags 描述性质的标签,用来描述GameplayEffect的特性表现,比如伤害、治疗、控制等。 GrantedTags GameplayEffect的持有者会获得这些标签,GameplayEffect被移除时,这些标签也会被移除。Instant类型的GameplayEffect的GrantedTags是无效的。 ApplicationRequiredTags GameplayEffect的目标单位必须拥有【所有】这些标签,否则GameplayEffect无法被施加到目标身上。 OngoingRequiredTags GameplayEffect的目标单位必须拥有【所有】这些标签,否则GameplayEffect不会被激活。 (施加和激活是两个概念,如果已经被施加的GameplayEffect持续过程中,目标的tag变化了,不满足,效果就会失活;满足了,就会被激活)。Instant类型的GameplayEffect的OngoingRequiredTags是无效的。 RemoveGameplayEffectsWithTags GameplayEffect的目标单位当前持有的所有GameplayEffect中,拥有【任意】这些标签的GameplayEffect会被移除。 Application Immunity Tags GameplayEffect的目标单位拥有【任意】这些标签,就对该GameplayEffect免疫。 - DurationPolicy为Instant时
- CueOnExecute(Instant):GameplayEffect执行时触发。
- DurationPolicy为Duration或者Infinite时
- CueDurational(Durational):生命周期完全和GameplayEffect同步
- CueOnAdd(Instant):GameplayEffect添加时触发
- CueOnRemove(Instant):GameplayEffect移除时触发
- CueOnActivate(Instant):GameplayEffect激活时触发。
- CueOnDeactivate(Instant):GameplayEffect失活时触发。
-
- Stacking:堆叠。该系列参数是为了处理常见的叠层类型Buff。比如《黑帝斯》中酒神,爱神,冬神的叠攻buff。stacking的参数基本囊括了绝大多数的叠层型buff的设计。
- 生效的GE类型:只有非Instant类型(持续型)的GameplayEffect,可以产生叠加(stacking)。
- stackingCodeName: 堆叠GE的唯一标识码,用于可堆叠GE的识别。
- 本身是字符串,但是runtime实际使用的是其对应的HashCode。如果为空,则视为不可堆叠
- stackingCodeName除了基础的堆叠类GE识别功能外,另一个作用是用于支持不同GE的共同堆叠。举个例子:有一个团队性质的增伤buff【元素增伤】,团队所有成员对同一个目标都可以叠加【元素增伤】,至多10层,增伤 随层数增加而增加。但是增伤是指定第一个施加buff成员的元素,比如第一层打的是【火增伤】,那么之后不管是【水增伤】,【雷增伤】,都是【火增伤】buff往上叠加。遇到这种特殊情况,就可以把【水增伤】,【雷增伤】,【火增伤】 的stackingCodeName设置为同一个值,这样就可以实现【元素增伤】的共同堆叠。
- stackingType:GameplayEffect的叠加类型,有三种:
-
stacking类型 作用 None 不叠加 AggregateBySource 基于GE来源(ASC)的叠加计数,所有释放单位各自管理一个叠加计数的GE。 举例:BUFF【聚能】效果是单位被叠加三次该buff(来自同一单位)后触发爆炸。 小怪被A玩家叠了2次【聚能】,然后B玩家又对小怪施加了1次【聚能】,但是不会触发爆炸。因为叠加计数是按来源单位各自计数,需要A再叠1次或者B叠2次,小怪才会爆炸。 AggregateByTarget 基于GE目标(ASC)的叠加计数,所有释放单位共享一个叠加计数的GE。举例:BUFF【诅咒】效果是单位被叠加3次该buff(无关来源单位)后触发即死效果。经典魂游的咒蛙攻击buff。玩家被数只咒蛙围攻,只要被咒蛙打到3次就死亡。在场所有咒蛙的【诅咒】都会叠加在玩家身上一个计数器上。
-
- limitCount:叠加上限。
- 需要注意一点,叠加溢出的效果触发是在叠加计数【大于】limitCount时触发。举个例子,如果某个buff叠加3层后触发爆炸伤害,那limitCount应该是2。
- DurationRefreshPolicy:持续时间刷新策略。GE叠加成功后,GE的持续时间的刷新策略。
-
DurationRefreshPolicy 作用 NeverRefresh 从不刷新持续时间。即叠加的BUFF持续时间从第一层生效后计时就不再受影响。 RefreshOnSuccessfulApplication 每次Effect叠加apply成功后刷新Effect的持续时间。
-
- PeriodResetPolicy:周期重置策略。GE叠加成功后,GE的周期(Period)的刷新策略。
-
PeriodResetPolicy 作用 NeverReset 从不重置周期。 ResetOnSuccessfulApplication 每次apply成功后重置Effect的周期计时。
-
- ExpirationPolicy:过期策略(持续时间结束时逻辑处理)。GE叠加成功后,GE的过期时间(Expiration)的刷新策略。
-
ExpirationPolicy 作用 ClearEntireStack 持续时间结束时,清楚所有层数 RemoveSingleStackAndRefreshDuration 持续时间结束时减少一层,然后重新经历一个Duration,一直持续到层数减为0 RefreshDuration 持续时间结束时,再次刷新Duration,这相当于无限Duration。
-
- denyOverflowApplication:布尔类型。是否允许溢出的GE叠加生效。
- 对应于DurationRefreshPolicy = RefreshOnSuccessfulApplication时,如果为true则多余的Apply不会刷新Duration
- clearStackOnOverflow: 布尔类型。是否溢出时清空所有层数,移除GE。
- 当DenyOverflowApplication为True是才有效,当Overflow时是否直接删除所有层数,移除GE。
- overflowEffects:GameplayEffect的数组,溢出时施加的游戏效果。当Stack计数溢出时,对生效单位执行这些GE。
GameplayEffect的施加(Apply)和激活(Activate)
- GameplayEffect的施加(Apply)和激活(Activate)是两个概念,施加是指GameplayEffect被添加到目标身上,激活是指GameplayEffect实际生效。
- 为什么做区分?
- 举个例子:固有被动技能(Ability)是持续回血,被动技能的逻辑显然是永久激活的状态,而持续回血的效果(GameplayEffect) 来源于被动技能,那如果单位受到了外部的debuff禁止所有的回血效果,那么是不是被动技能被禁止?显然不是,被动技能还是会持续激活的。 那应该是移除回血效果吗?显然也不是,被动技能整个过程是不做任何变化,如果移除回血效果,那debuff一旦消失,谁再把回血效果加回来? 所以,这里需要区分施加和激活,被动技能的持续回血效果被施加到单位身上,而debuff做的是让回血效果失活,而不是移除回血效果,一旦debuff结束, 回血效果又被激活,而这个激活的操作可以理解为回血效果自己激活的(依赖于Tag系统)。
2.8 Ability
Ability是EX-GAS的核心类之一,它是游戏中的所有能力基础。
同时Ability也是程序开发人员最常接触的类,Ability的完整逻辑都是由程序开发人员实现的。
在EX-GAS内,Ability是游戏中可以触发的一切行为和技能。多个Ability可以在同一时刻激活, 例如移动和持盾防御。 Ability作为EX-GAS的核心类之一,他起到了Do(做)的功能。
Ability的业务逻辑取决于游戏类型和玩法。所以不存在一个通用的Ability模板,当然可以针对游戏类型制作一些通用的ability。 Ability的逻辑并非自由,如果胡乱的实现Ability逻辑,可能会导致游戏逻辑混乱,所以需要遵循一些规则。
Ability的具体实现需要策划和程序配合。 这并不是废话,而是在EX-GAS的Ability制作流程中,确确实实的把策划和程序的工作分开了:
- 策划的工作:配置AbilityAsset
- 程序的工作:编写Ability(AbilityAsset,Ability,AbilitySpec)类
-
类 功能 AbilityAsset Ability的配置文件,同一类的Ability可以通过配置不同的AbilityAsset参数,实现复数Ability,比如跳跃段数不同,可以实现普通跳,二段跳。 Ability Ability的Runtime数据类,通常数据还是依赖AbilityAsset。同时也允许运行时不依赖AbilityAsset生成Ability AbilitySpec Ability的运行实例,Ability的游戏内的表现逻辑,就在该类中实现。
建议 AbilitySpec和Ability在同一个脚本中编辑,因为二者本身就是成对出现。AbilityAsset单独一个脚本,因为它是Scriptable Object,应该遵从脚本名和类一致的原则。
Ability运作逻辑的组成可以拆成两部分:
- GAS系统内的运作逻辑:所有Ability通用的数据字段,被保存在AbstractAbility这个抽象基类中。所有的Ability都是继承自AbstractAbility。
- 具体游戏内的表现逻辑:每个Ability都有自己的表现逻辑,这部分逻辑是由程序开发人员自行实现的。
接下来结合Ability的配置界面来解释Ability的数据和运作逻辑。
2.8.a Ability编辑界面
图中A的部分就是GAS系统内的运作逻辑的参数,B的部分就是具体游戏内的表现逻辑的参数。
注意到最上方显示了Ability Class,这是Ability的类名,与之成对的AbilitySpec决定了Ability游戏内的表现逻辑。
- GAS系统内的运作逻辑参数
- U-Name: Ability的名称,唯一标识符,禁止重复或空。U-Name会用于AbilityLib的Ability生成和查找。
- Description: Ability的描述,纯粹用于显示,不会影响游戏逻辑。方便编辑者区分Ability。
- 消耗Cost:Ability的消耗GameplayEffect
- 冷却CD:Ability的冷却GameplayEffect
- CD时间:冷却时间,它会覆盖冷却GameplayEffect的持续时间。
- Tags: Ability的标签,与GameplayEffect的Tag些许类似。具体Tag功能如下
Tag | 功能 |
---|---|
Asset Tag | 描述性质的标签,用来描述Ability的特性表现,比如伤害、治疗、控制等 |
CancelAbility With Tags | Ability激活时,Ability持有者当前持有的所有Ability中,拥有**【任意】**这些标签的Ability会被取消 |
BlockAbility With Tags | Ability激活时,Ability持有者当前持有的所有Ability中,拥有**【任意】**这些标签的Ability会被阻塞激活 |
Activation Owned Tags | Ability激活时,持有者会获得这些标签,Ability被失活时,这些标签也会被移除 |
Activation Required Tags | Ability只有在其拥有者拥有**【所有】**这些标签时才可激活 |
Activation Blocked Tags | Ability在其拥有者拥有**【任意】**这些标签时不能被激活 |
-
具体游戏内的表现逻辑参数
- 这部分的参数面板是由程序开发人员自行实现的,这部分参数的含义和作用不固定。
- 建议使用Odin的特性编辑,一是为了规范,二是美观。
- 不同类的Ability,这部分的面板显示和含义不同。如下图:
-
【!!注意!!】
- 当创建或修改AbilityAsset的U-Name后,一定要去Ability汇总界面点击【生成AbilityLib】按钮。 AbilityLib.gen.cs脚本中会包含所有的Ability信息。 游戏运行时生成Ability依赖于AbilityLib,如果没有保持同步,会导致游戏运行时找不到Ability。
- Ability汇总界面入口:在菜单栏EX-GAS -> Asset Aggregator -> 左侧菜单列点击 C-Ability
2.8.b TimelineAbility 通用性Ability(W.I.P TimelineAbility还在完善中)
在实际的开发过程中,我发现,许多的Ability都有顺序和时限两个特点。 每次都新写一个Ability类来实现某个指定技能让我十分烦躁,于是我制作了TimelineAbility,一个极具通用性的顺序,时限Ability。
这是TimelineAbilityAsset的面板。
TimelineAbilityAsset的大多数表现逻辑参数在AbilityAsset面板都是隐藏的(HideInInspector)。 转而都是在TimelineAbilityEditor面板中可视化编辑。 唯一在AbilityAsset面板中可见的参数是【手动结束能力】的bool值选项。这个选项决定Ability是手动结束还是播放完成后自动结束。
通过点击【查看/编辑能力时间轴】按钮,可以打开TimelineAbilityEditor面板。
这是TimelineAbilityEditor的面板。
接下来详细介绍TimelineAbilityEditor的面板参数含义及操作逻辑。
- 顶部菜单栏
- Ability配置 : 当前编辑的TimelineAbilityAsset
- 查看能力基本信息:点击按钮后,右侧的子Inspector会显示TimelineAbilityAsset的面板信息,和Asset Aggregator中的AbilityAsset面板一致。
- 预览实例:场景内的GameObject. 选取后,TimelineAbility会以该GameObject为参照物,预览TimelineAbility的表现逻辑。 用到的常见预览有Animation,特效,物体挂载,伤害碰撞盒等等。
- 预览场景:点击该按钮后会,进入空场景。目前还没有自定义LookDev场景,所以只是一个临时创建空场景。
- 返回旧场景:点击该按钮后会,返回到原来的场景。
- 显示子Inspector:点击该按钮后会,刷新显示右侧的子Inspector的布局。
- 时间轴编辑部分
- 左侧播放栏
- 【<】:上一帧,只有当预览实例不为空时,才会生效。
- 【>】:下一帧,只有当预览实例不为空时,才会生效。
- 【▶】:播放/暂停 ,只有当预览实例不为空时,才会生效。
- 左侧帧数:当前预览帧数,只有当预览实例不为空时,才会生效。
- 右侧帧数:Ability执行总帧数
- 左侧轨道菜单栏
- TimelineAbility基础轨道有6种。【添加轨道只需点击右侧的‘+’,删除轨道只需右键对应轨道选择Delete Track即可】
- Instant Cue 【即时Cue轨道】
- 轨道Item类型:Mark
- 一个Mark可以挂多个Cue。理论上,一个TimelineAbility只需要一条Instant Cue轨道。
- 扩展:详见上文中提到的Instant Cue自定义实现
- Release Effect【GameplayEffect释放轨道】
-
轨道Item类型:Mark
-
一个Mark持有一个TargetCatcher和数个GameplayEffectAsset
- TargetCatcher:GameplayEffect释放需要对象,而TargetCatcher的作用就是找到这些对象。 TargetCatcher固有初始化会获取Owner(ASC),核心是方法CatchTargets()。 基类如下:
public abstract class TargetCatcherBase { public AbilitySystemComponent Owner; public TargetCatcherBase() { } public virtual void Init(AbilitySystemComponent owner) { Owner = owner; } // mainTarget为TimelineAbility的指向性目标单位,为可选参数。具体在API中会介绍。 public abstract List<AbilitySystemComponent> CatchTargets(AbilitySystemComponent mainTarget); }
我提供了几个基础TargetCatcher(后续会陆续添加常用的Catcher)
Catcher名 作用 CatchSelf 捕捉自己 CatchTarget 捕捉指向性目标 CatchAreaBox2D 捕捉2d矩形内的目标(适用2D游戏) CatchAreaCircle2D 捕捉2d圆形内的目标(适用2D游戏) - TargetCatcher的UI面板绘制: 自定义TargetCatcher的UI面板需要继承自TargetCatcherInspector<T> T为TargetCatcher类 (必须直接继承,不可以多级继承,因为Inspector的Type查找规则依赖第一泛型类做匹配)
-
Release Effect的执行逻辑:先调用TargetCatcher的CatchTargets()方法,然后对捕获的目标单位施加所有指定GameplayEffect。
-
- Instant Task 【即时Task轨道】
- 轨道Item类型:Mark
- 一个Mark可以挂载复数的Instant Task。 关于Ability Task的详细介绍见下文。
- Task是自定义事件,可以是任何游戏逻辑,纯粹由开发者决定。
- Instant Task的面板绘制:自定义Task的UI面板需要继承自InstantTaskInspector<T> T为InstantAbilityTask类 (必须直接继承,不可以多级继承,因为Inspector的Type查找规则依赖第一泛型类做匹配)
- Durational Cue【持续GameplayCue轨道】
- 轨道Item类型:Clip
- 每段Clip只含一个Duration Cue
- 注意!! TimelineAbility下的持续性Cue, 只会执行OnAdd(Cue播放的第一帧),OnRemove(Cue播放的最后一帧),OnTick,和GameplayEffect相关的方法不会被执行
- Buff【Buff轨道】
- 轨道Item类型:Clip
- 每段Clip只含一个Buff(GameplayEffect),且Buff的作用对象只会是Ability的持有者自己。
- 【注意!!】请确保设置的GameplayEffect类型为Durational或Infinite。 非持续类型的GameplayEffect不会生效。且GameplayEffect执行时会设置为Infinite执行策略, 生命周期由Clip长度(Duration)决定。
- Ongoing Task【持续Task轨道】
- 轨道Item类型:Clip
- 每段Clip只含一个Ongoing Task。 关于Ability Task的详细介绍见下文。
- Ongoing Task的面板绘制:自定义Task的UI面板需要继承自OngoingTaskInspector<T> T为OngoingAbilityTask类 (必须直接继承,不可以多级继承,因为Inspector的Type查找规则依赖第一泛型类做匹配)
- Instant Cue 【即时Cue轨道】
- TimelineAbility基础轨道有6种。【添加轨道只需点击右侧的‘+’,删除轨道只需右键对应轨道选择Delete Track即可】
- 右侧Inspector
- 所有Track,TrackItem参数在点击后都会显示在Inspector上
- 左侧播放栏
TimelineAbility的执行逻辑很直观,就是沿着时间轴从左往右执行,每个轨道的Item都会在对应的时间点执行。 所有事件的执行,都遵照时间轴的顺序,以及Mark内参数排列顺序。如果存在前后逻辑关系,那么配置的时候请务必注意顺序。 最大帧数决定了Ability的执行时间,如果【手动结束能力】设置为false,那么在播放完TimelineAbility后,会自动调用TryEndAbility(), 反之,需要开发人员在代码中决定调用TryEndAbility()的时机。
TimelineAbility的配置可能还满足不了一些设计时,程序开发人员可以对TimelineAbility进行继承,拓展功能需求。
特别是在TargetCatcher,AbilityTask的自定义上,还是很可能遇到这个问题。 因为TargetCatcher和AbilityTask的持续化存储是以JsonData的格式,ScriptableObject类型参数的Json存储是存在GUID不匹配问题的。 所以,TargetCatcher和AbilityTask的参数中,不建议出现ScriptableObject类型的参数。
2.8.c Granted Ability From GameplayEffect 来自游戏效果授予的能力
能力不仅仅可以由AbilitySystemComponent直接授予,还可以通过GE来授予,甚至是GE来全权控制。
我们为了更通俗的去理解BUFF的概念,就必须允许GE可以实现自由的逻辑自定义。 但是GAS本身的GE仅仅是遵循体系内固定逻辑,不存在开发者自定义GE逻辑。 GAS为GE提供了Granted Ability的解决方案。
为了更好理解这种情况,举个例子:
在一个RPG游戏中,有一个名为“亡灵收割”的BUFF。 “亡灵收割”效果为:BUFF持有者,在x米范围内,每当有单位死亡便获得y点生命值。
一般的做法可能是把“亡灵收割”视作一个被动技能(Ability),然后根据设计需求动态的添加/移除/激活/失活“亡灵收割”效果。 可能有些设计者为了使之更符合BUFF的逻辑,会通过GE的Add/Remove/Active/Deactive回调,来关联“亡灵收割”添加/移除/激活/失活。
上述例子的这个做法当然是没问题的,而且十分合理。 而在EX-GAS中,我为了减少Ability管理的事件注册这个繁琐步骤。我对GE的逻辑进行了优化,兼并了这一设计方案。 做法就是在GE中,添加了GrantedAbility变量。 GrantedAbility有5个参数:
- Ability:授予的能力(数据)
- AbilityLevel:授予的能力等级
- ActivationPolicy: 授予的能力的激活策略,类型如下:
-
激活策略 作用 None 无激活逻辑, 需要用户自己调用ASC能力激活接口 WhenAdded 能力添加时激活(GE添加时激活) SyncWithEffect 同步GE,GE激活时激活
-
- DeactivationPolicy: 授予的能力的取消激活策略,类型如下:
-
取消激活策略 作用 None 无相关取消激活逻辑, 需要用户调用ASC能力取消激活接口 SyncWithEffect 同步GE,GE失活时取消激活
-
- RemovePolicy: 授予的能力的移除策略,类型如下:
-
移除策略 作用 None 不移除 SyncWithEffect 同步GE,GE移除时移除 WhenEnd 能力结束时自己移除 WhenCancel 能力取消时自己移除 WhenCancelOrEnd 能力结束或取消时自己移除
-
到这里Granted Ability的逻辑就清晰了。我们提前将Ability的生命周期通过参数,来确定哪些阶段交给GE来管理。
有一点需要注意,Granted Ability的激活不会传任何参数,请保证Ability执行逻辑中依赖的参数,都可以通过Owner(ASC)直接或间接获取。
Granted Ability只是EX-GAS给出的一个现成设计方案,依然可以通过各个事件监听/回调,来实现同样的效果。
2.9 AbilitySystemComponent
AbilitySystemComponent是EX-GAS的核心之一,它是GAS的基本运行单位。
ASC(之后都使用缩写指代AbilitySystemComponent),持有Tag,Ability,AttributeSet,GameplayEffect等数据。 其主要职责如下:
- 管理能力(Abilities): ASC 负责管理角色的所有能力。它允许角色获得、激活、取消和执行各种不同类型的能力,如攻击、防御、技能等。
- 处理效果(GameplayEffects): ASC 负责处理与能力相关的效果,包括伤害、治疗、状态效果等。它能够跟踪和应用这些效果,并在需要时触发相应的回调或事件。
- 处理标签(Tags): ASC 负责管理角色身上的标签。标签用于标识角色的状态、属性或其他特征,以便在能力和效果中进行条件检查和过滤。
- 处理属性(Attributes): ASC 负责管理角色的属性。属性通常表示角色的状态,如生命值、能量值等。ASC 能够增减、修改和监听这些属性的变化。
整个GAS的运作都是围绕着ASC的,所有的Tag,GameplayEffect的作用对象最后都是ASC。而Ability也必须依赖ASC来执行。 为了直观的理解ASC,接下来参考GAS Watcher的监视器界面:
- ID Mark:这部分可以忽略,只是Unity运行时的Gameobject的标识符相关
- Ability: 图中显示了这个单位有6个Ability。Ability表示了单位的持有能力,但不代表所有的单位能力都要由Ability来做。 虽然我把Move作为Ability了,但是在FPS,MOBA等类型的游戏中,像Move移动这种强Input关联的行为是不建议用Ability来做的。
- Attribute:单位持有的属性。 ASC的属性是以AttributeSet为集合,通常ASC的属性集,只加不减。 因为删除属性集存在很大的隐患,在GAS体系运作时,属性相当于串联GAS运行的线,线上挂着许多GameplayEffect,贸然删除属性集可能会导致线断裂,GameplayEffect运行逻辑失效甚至崩溃。
- GameplayEffects:单位当前持有的GameplayEffect。这里可以直接将GameplayEffect理解为Buff。 GAS体系内,ASC之间是通过GameplayEffect来交互。 比如A单位对B单位施法,那么A单位会通过施法(Ability)对B施加一个效果(GameplayEffect),效果如果是持续性的就会被留在B身上(Buff)。 当然,GAS体系外时,可以通过接口对ASC单位添加/移除GameplayEffect。通常这种情况下,Effect的来源和目标都是指定的那个ASC。
- Tags:单位当前持有的Tag。标签的价值只有在被挂载到ASC时才会体现出来。所有Tag的比较逻辑都直接或间接依赖于ASC。
- Fixed Tag:固有标签,这些标签是ASC固有的。GAS体系内是不会对FixedTag做任何增减的。 只有GAS体系外的才会对FixedTag有影响。FixedTag通常是在ASC初始化的时候设置好,之后就尽可能不动。
- Dynamic Tag:动态标签,这些标签是ASC动态增减的。GAS体系只会对DynamicTag做增减,且只有GAS体系可以管理。 GAS体系外只能通过GameplayEffect的一些指定接口,间接对DynamicTag进行操作。
ASC是GAS中最复杂,且操作空间最多的组件。对ASC的良好管理和操作就是程序开发人员的重任了。 GAS本身是被动的,而让推动和改变GAS的是ASC。换言之,Runtime下开发者其实是在操作ASC,而不是GAS。 增删管理ASC,调用ASC的Ability执行,以及ASC的体系外Tag,Effect管理才是Runtime下开发者的主要工作。 这之外的GAS配置和拓展,应该由策划承担大部分工作。(但实际上对于中小型团队,程序开发人员还是在做GAS配置的维护工作。)
2.9.a AbilitySystemComponent Preset
AbilitySystemComponent Preset是ASC的预设,用于方便初始化ASC的数据。 ASC预设是为了可视化角色(单位)的参数。
- 基本信息:ASC的基本信息,仅用于显示,方便配置人员阅读,Runtime不会用到这些参数。
- 属性集:上文提到过,ASC的属性集设计建议只有一个属性集。不建议多个。
- 固有Tag:ASC的基础Tag,通常会把描述性的Tag作为固有Tag, 比如种族(Race.Human,Race.Monster ),职业(Job.Wizard,Job.Archer),阵营(Camp.Good,Camp.Evil)等等。 当然Tag本身是不做任何限制的,但从Gameplay设计的角度上,状态性质的Tag不建议作为固有Tag。就算设计一个绝对无敌的 角色,那也应该是把无敌的Tag放在一个永久GameplayEffect上,然后挂到ASC上。而不是把无敌Tag直接当作固有Tag。
- 固有能力:Abilities,单位的基础能力。通常会把单位的基础能力放在这里,比如攻击,防御,跳跃等等。
如何使用ASC预设?
1.AbilitySystemComponent组件自带了序列化的ASC预设字段,可以通过预制体添加,也可以实例化添加。 2.依赖ASC预设的初始化,通过AbilitySystemComponentExtension中的静态扩展方法InitWithPreset即可。
InitWithPreset的参数:
- AbilitySystemComponent asc:初始化的ASC,
- int level:初始等级
- AbilitySystemComponentPreset preset:初始化用的ASC预设
示例: asc.InitWithPreset(1,ascPreset); // 如果预制体已经设置了参数,那么可以不传ascPreset。
3.API && Source Code Documentation
本章节会在介绍API和源码的同时,从代码的角度来理解GAS的运作逻辑。
该图简单的解释了GAS的运作逻辑。GAS其实简单的只干一件事:ASC使用Ability对指定的(可以包括自己)ASC释放GameplayEffect。
GAS的推进和运行,就是在不断的重复这件事。 体系外的脚本不断的拨动ASC的Ability,而GAS内部会对Ability的运行结果自行消化。
3.1 Core
3.1.1 GameplayAbilitySystem
GameplayAbilitySystem作为核心类,他的作用有2个:管理ASC,控制GAS的运行与否。
static GameplayAbilitySystem GAS
- GAS的单例,所有的GAS操作都是通过GAS单例来进行的。
List<AbilitySystemComponent> AbilitySystemComponents { get; }
- GAS当前运行的所有AbilitySystemComponent的集合。
void Register(AbilitySystemComponent abilitySystemComponent)
- 注册ASC到GAS中。
void UnRegister(AbilitySystemComponent abilitySystemComponent)
- 从GAS中注销ASC。
bool IsPaused
- GAS是否暂停运行。
void Pause()
- 暂停GAS运行。
void Unpause()
- 恢复GAS运行。
3.1.2 GASTimer
GASTimer是GAS的计时器,它是GAS的时间基准。
static long Timestamp()
- GAS当前时间戳(毫秒)
static long TimestampSeconds()
- GAS当前时间戳(秒)
static int CurrentFrameCount
- GAS当前运行帧数
static long StartTimestamp
- GAS启动时间戳
static void InitStartTimestamp()
- GAS初始化启动时间戳
static void Pause()
- 暂停GASTimer计时
static void Unpause()
- 恢复GASTimer计时
static int FrameRate
- GAS帧率
3.1.3 GasHost
GasHost是GAS的宿主,它是GAS的运行机器和环境,GasHost没有API可以从外部干涉。
3.2 AbilitySystemComponent
3.2.1 AbilitySystemComponent
AbilitySystemComponent是GAS的基本运行单位,它是GAS的核心类。 ASC的public方法和属性就是外部干涉GAS的唯一手段。
AbilitySystemComponentPreset Preset
- ASC的预设。外部读取用,修改preset需要通过SetPreset方法
void SetPreset(AbilitySystemComponentPreset preset)
- 修改ASC的预设。
int Level { get; protected set; }
- ASC的等级
GameplayEffectContainer GameplayEffectContainer { get; private set; }
- ASC当前所有GameplayEffect的容器,可以通过GameplayEffectContainer对GameplayEffect进行一定的外部干涉。
GameplayTagAggregator GameplayTagAggregator { get; private set;}
- ASC的GameplayTag聚合器,单位的Tag全部都由聚合器管理,外部可以通过聚合器对Tag进行一定的外部干涉。
AbilityContainer AbilityContainer { get; private set;}
- ASC的Ability容器,可以通过AbilityContainer对Ability进行一定的外部干涉。
AttributeSetContainer AttributeSetContainer { get; private set;}
- ASC的AttributeSet容器,可以通过AttributeSetContainer对AttributeSet进行一定的外部干涉。
void Init(GameplayTag[] baseTags, Type[] attrSetTypes,AbilityAsset[] baseAbilities,int level)
- 初始化ASC
- baseTags:ASC的基础Tag
- attrSetTypes:ASC的初始化AttributeSet类型
- baseAbilities:ASC的初始化Ability
- level:ASC的初始化等级
void SetLevel(int level)
- 设置ASC的等级
bool HasTag(GameplayTag gameplayTag)
- 判断ASC是否持有指定Tag
- gameplayTag:指定Tag
- 返回值:是否持有
bool HasAllTags(GameplayTagSet tags)
- 判断ASC是否持有指定Tag集合中的所有Tag
- tags:指定Tag集合
- 返回值:是否持有
bool HasAnyTags(GameplayTagSet tags)
- 判断ASC是否持有指定Tag集合中的任意一个Tag
- tags:指定Tag集合
- 返回值:是否持有
void AddFixedTags(GameplayTagSet tags)
- 添加固有Tag
- tags:添加的Tag集合
void RemoveFixedTags(GameplayTagSet tags)
- 移除固有Tag
- tags:移除的Tag集合
void AddFixedTag(GameplayTag tag)
- 添加固有Tag
- tag:添加的Tag
void RemoveFixedTag(GameplayTag tag)
- 移除固有Tag
- tag:移除的Tag
void RemoveGameplayEffect(GameplayEffectSpec spec)
- 移除指定的GameplayEffect
- spec:指定的GameplayEffect的规格类实例
GameplayEffectSpec ApplyGameplayEffectTo(GameplayEffect gameplayEffect, AbilitySystemComponent target)
- 对指定的ASC施加指定的GameplayEffect
- gameplayEffect:指定的GameplayEffect
- target:目标ASC
- 返回值:施加的GameplayEffect的规格类实例
GameplayEffectSpec ApplyGameplayEffectToSelf(GameplayEffect gameplayEffect)
- 对自己施加指定的GameplayEffect
- gameplayEffect:指定的GameplayEffect
- 返回值:施加的GameplayEffect的规格类实例
void GrantAbility(AbstractAbility ability)
- 获得指定的Ability
- ability:指定的Ability
void RemoveAbility(string abilityName)
- 移除指定的Ability
- abilityName:指定的Ability的U-Name
float? GetAttributeCurrentValue(string setName, string attributeShortName)
- 获取指定Attribute的当前值
- setName:AttributeSet的名字
- attributeShortName:Attribute的短名
- 返回值:Attribute的当前值
float? GetAttributeBaseValue(string setName, string attributeShortName)
- 获取指定Attribute的基础值
- setName:AttributeSet的名字
- attributeShortName:Attribute的短名
- 返回值:Attribute的基础值
Dictionary<string, float> DataSnapshot()
- 获取ASC的数据快照
- 返回值:ASC的数据快照
bool TryActivateAbility(string abilityName, params object[] args)
- 尝试激活指定的Ability
- abilityName:指定的Ability的U-Name
- args:激活Ability的参数
- 返回值:是否激活成功
void TryEndAbility(string abilityName)
- 尝试结束指定的Ability
- abilityName:指定的Ability的U-Name
void TryCancelAbility(string abilityName)
- 尝试取消指定的Ability
- abilityName:指定的Ability的U-Name
void ApplyModFromInstantGameplayEffect(GameplayEffectSpec spec)
- 从Instant GameplayEffect中应用Mod
- spec:Instant GameplayEffect的规格类实例
CooldownTimer CheckCooldownFromTags(GameplayTagSet tags)
- 通过Tag检查冷却时间
- tags:指定的Tag集合
- 返回值:冷却计时器
T AttrSet<T>() where T : AttributeSet
- 获取指定类的AttributeSet
- 返回值:指定类的AttributeSet
void ClearGameplayEffect()
- 清空ASC的所有GameplayEffect
3.2.2 AbilitySystemComponentPreset
AbilitySystemComponentPreset是ASC的预设,用于方便初始化ASC的数据。
string[] AttributeSets
- ASC的初始化AttributeSet类型
GameplayTag[] BaseTags
- ASC的基础Tag
AbilityAsset[] BaseAbilities
- ASC的初始化Ability
3.2.3 AbilitySystemComponentExtension
AbilitySystemComponentExtension是ASC的扩展方法类,用于方便ASC的初始化和操作。 AbilitySystemComponentExtension不是EX-GAS框架内脚本的,需要EX-GAS框架基础配置完成后,通过生成脚本生成。
static Type[] PresetAttributeSetTypes(this AbilitySystemComponent asc)
- 获取ASC的预设AttributeSet类型
- 返回值:ASC的预设AttributeSet类型
static GameplayTag[] PresetBaseTags(this AbilitySystemComponent asc)
- 获取ASC的预设基础Tag
- 返回值:ASC的预设基础Tag
static void InitWithPreset(this AbilitySystemComponent asc,int level, AbilitySystemComponentPreset preset = null)
- 通过预设初始化ASC
- level:ASC的初始化等级
- preset:ASC的预设
3.3 GameplayTag
3.3.1 GameplayTag
GameplayTag是GAS的标签类,它是GAS的核心类。Tag的设计结构虽然简单,但是在实际应用中十分高效有用。
int HashCode => _hashCode;
- Tag的HashCode
string[] AncestorNames => _ancestorNames;
- Tag的父级名
int[] AncestorHashCodes => _ancestorHashCodes;
- Tag的父级HashCode集合
bool Root => _ancestorHashCodes.Length == 0;
- Tag是否是根Tag
bool IsDescendantOf(GameplayTag other)
- Tag是否是指定Tag的子Tag
- other:指定Tag
- 返回值:是否是子Tag
bool HasTag(GameplayTag tag)
- Tag是否持有指定Tag,比如‘Buff.Burning’ 持有 ‘Buff’
- tag:指定Tag
- 返回值:是否持有
3.3.2 GameplayTagSet
GameplayTagSet是Tag集合类之一。GameplayTagSet适用于稳定不会改变的Tag集合。通常数据类的Tag集合都用GameplayTagSet。
readonly GameplayTag[] Tags
- Tag数据
bool Empty => Tags.Length == 0;
- Tag集合是否为空
bool HasTag(GameplayTag tag)
- TagSet是否持有指定Tag
- tag:指定Tag
- 返回值:是否持有
bool HasAllTags(GameplayTagSet other) / bool HasAllTags(params GameplayTag[] tags)
- TagSet是否持有指定Tag集合中的所有Tag
- other:指定Tag集合
- 返回值:是否持有
bool HasAnyTags(GameplayTagSet other) / bool HasAnyTags(params GameplayTag[] tags)
- TagSet是否持有指定Tag集合中的任意一个Tag
- other:指定Tag集合
- 返回值:是否持有
bool HasNoneTags(GameplayTagSet other) / bool HasNoneTags(params GameplayTag[] tags)
- TagSet是否不持有指定Tag集合中的所有Tag
- other:指定Tag集合
- 返回值:是否不持有
3.3.3 GameplayTagContainer
GameplayTagContainer是Tag集合类之一。GameplayTagContainer适用于经常改变的Tag集合。
List<GameplayTag> Tags { get; }
- Tag数据
void AddTag(GameplayTag tag)
- 添加Tag
- tag:指定Tag
void AddTag(GameplayTagSet tagSet)
- 添加Tag集合
- tagSet:要添加的Tag集合
void RemoveTag(GameplayTag tag)
- 移除Tag
- tag:指定Tag
void RemoveTag(GameplayTagSet tagSet)
- 移除Tag集合
- tagSet:要移除的Tag集合
bool HasTag(GameplayTag tag)
- TagContainer是否持有指定Tag
- tag:指定Tag
bool HasAllTags(GameplayTagSet other) / bool HasAllTags(params GameplayTag[] tags)
- TagContainer是否持有指定Tag集合中的所有Tag
- other:指定Tag集合
- 返回值:是否持有
bool HasAnyTags(GameplayTagSet other) / bool HasAnyTags(params GameplayTag[] tags)
- TagContainer是否持有指定Tag集合中的任意一个Tag
- other:指定Tag集合
- 返回值:是否持有
bool HasNoneTags(GameplayTagSet other) / bool HasNoneTags(params GameplayTag[] tags)
- TagContainer是否不持有指定Tag集合中的所有Tag
- other:指定Tag集合
- 返回值:是否不持有
3.3.4 GameplayTagAggregator
GameplayTagAggregator是专门针对ASC的Tag管理类,会针对固有Tag和动态Tag做不同的处理。
void Init(GameplayTag[] tags)
- 初始化
- tags:初始化的固有Tag
void AddFixedTag(GameplayTag tag)
- 添加固有Tag
- tag:添加的Tag
void AddFixedTag(GameplayTagSet tagSet)
- 添加固有Tag集合
- tagSet:添加的Tag集合
void RemoveFixedTag(GameplayTag tag)
- 移除固有Tag
- tag:移除的Tag
void RemoveFixedTag(GameplayTagSet tagSet)
- 移除固有Tag集合
- tagSet:移除的Tag集合
void ApplyGameplayEffectDynamicTag(GameplayEffectSpec source)
- 从GameplayEffect中应用动态Tag(Granted Tags)
- source:GameplayEffect的规格类实例
void ApplyGameplayAbilityDynamicTag(AbilitySpec source)
- 从Ability中应用动态Tag(Activation Owned Tags)
- source:Ability的规格类实例
RestoreGameplayEffectDynamicTags(GameplayEffectSpec effectSpec)
- 从GameplayEffect中恢复动态Tag(Granted Tags)
- effectSpec:GameplayEffect的规格类实例
RestoreGameplayAbilityDynamicTags(AbilitySpec abilitySpec)
- 从Ability中恢复动态Tag(Activation Owned Tags)
- abilitySpec:Ability的规格类实例
bool HasTag(GameplayTag tag)
- TagAggregator是否持有指定Tag
- tag:指定Tag
- 返回值:是否持有
bool HasAllTags(GameplayTagSet other) / bool HasAllTags(params GameplayTag[] tags)
- TagAggregator是否持有指定Tag集合中的所有Tag
- other:指定Tag集合
- 返回值:是否持有
bool HasAnyTags(GameplayTagSet other) / bool HasAnyTags(params GameplayTag[] tags)
- TagAggregator是否持有指定Tag集合中的任意一个Tag
- other:指定Tag集合
- 返回值:是否持有
bool HasNoneTags(GameplayTagSet other) / bool HasNoneTags(params GameplayTag[] tags)
- TagAggregator是否不持有指定Tag集合中的所有Tag
- other:指定Tag集合
- 返回值:是否不持有
3.3.5 GTagLib(Script-Generated Code)
GTagLib是GAS的标签库,它是GAS的标签管理类。 GTagLib不是EX-GAS框架内脚本的,需要EX-GAS框架Tag配置改动后,通过生成脚本生成。
public static GameplayTag XXX { get;} = new GameplayTag("XXX");
public static GameplayTag XXX_YYY { get;} = new GameplayTag("XXX.YYY");
- GTagLib会把所有的Tag都生成为静态字段,方便外部调用。格式如上所示。
- A.B.C的Tag会生成为A_B_C的静态字段。
public static Dictionary<string, GameplayTag> TagMap = new Dictionary<string, GameplayTag> { ["A"] = A, ["A.B"] = A_B, ["A.C"] = A_C, };
- GTagLib还包含了一个TagMap,方便外部通过Tag的字符串名来获取Tag。
3.4 Attribute
3.4.1 AttributeValue
AttributeValue是一个数据结构体。是实际存储Attribute的值的单位。
float BaseValue => _baseValue;
- Attribute的基础值,是属性,只读。修改baseValue需要通过AttributeBase的SetBaseValue方法
float CurrentValue => _currentValue;
- Attribute的当前值,是属性,只读。修改currentValue需要通过AttributeBase的SetCurrentValue方法
void SetBaseValue(float value)
- 设置Attribute的基础值
- value:指定的值
void SetCurrentValue(float value)
- 设置Attribute的当前值
- value:指定的值
3.4.2 AttributeBase
AttributeBase是GAS的属性基类,它是GAS的核心类之一。 负责管理AttributeValue的值变化,已经Attribute相关回调处理。
readonly string Name
- Attribute的名字(完整)
readonly string ShortName
- Attribute的短名
readonly string SetName
- Attribute所属的AttributeSet的名字
AttributeValue Value => _value;
- Attribute的值类,数据类
float BaseValue => _value.BaseValue;
- Attribute的基础值
float CurrentValue => _value.CurrentValue;
- Attribute的当前值
void SetCurrentValue(float value)
- 设置Attribute的当前值,会触发_onPreCurrentValueChange和_onPostCurrentValueChange回调
- value:指定的值
void SetBaseValue(float value)
- 设置Attribute的基础值,会触发_onPreBaseValueChange和_onPostBaseValueChange回调
- value:指定的值
void SetCurrentValueWithoutEvent(float value)
- 设置Attribute的当前值,但不会触发_onPreCurrentValueChange和_onPostCurrentValueChange回调
- value:指定的值
void SetBaseValueWithoutEvent(float value)
- 设置Attribute的基础值,但不会触发_onPreBaseValueChange和_onPostBaseValueChange回调
- value:指定的值
void RegisterPreBaseValueChange(Func<AttributeBase, float,float> func)
- 注册Attribute的基础值变化前回调
- func:回调函数
- AttributeBase:AttributeBase实例
- float:变化前的值
- float:准备变化的值
- 返回值:回调处理完的变化值
void RegisterPostBaseValueChange(Action<AttributeBase, float, float> action)
- 注册Attribute的基础值变化后回调
- action:回调函数
- AttributeBase:AttributeBase实例
- float:变化前的值
- float:变化后的实际值
void RegisterPreCurrentValueChange(Func<AttributeBase, float, float> func)
- 注册Attribute的当前值变化前回调
- func:回调函数
- AttributeBase:AttributeBase实例
- float:变化前的值
- float:准备变化的值
- 返回值:回调处理完的变化值
void RegisterPostCurrentValueChange(Action<AttributeBase, float, float> action)
- 注册Attribute的当前值变化后回调
- action:回调函数
- AttributeBase:AttributeBase实例
- float:变化前的值
- float:变化后的实际值
void UnregisterPreBaseValueChange(Func<AttributeBase, float,float> func)
- 注销Attribute的基础值变化前回调
- func:注销的回调函数
void UnregisterPostBaseValueChange(Action<AttributeBase, float, float> action)
- 注销Attribute的基础值变化后回调
- action:注销的回调函数
void UnregisterPreCurrentValueChange(Func<AttributeBase, float, float> func)
- 注销Attribute的当前值变化前回调
- func:注销的回调函数
void UnregisterPostCurrentValueChange(Action<AttributeBase, float, float> action)
- 注销Attribute的当前值变化后回调
- action:注销的回调函数
3.4.3 AttributeAggregator
AttributeAggregator是Attribute的单位性质的聚合器,每个AttributeBase会对应一个AttributeAggregator。 AttributeAggregator是完全闭合独立运作,除了构造函数外不提供任何对外方法。 每当AttributeBase的BaseValue变化时,AttributeAggregator会自动更新自己的CurrentValue。
3.4.4 DerivedAttribute(W.I.P)
推导性质的Attribute,理论上不是一个类,而是一个Attribute的设计策略。
3.5 AttributeSet
3.5.1 AttributeSet
AttributeSet是一个抽象基类。
public abstract AttributeBase this[int index] { get; }
- 通过AttributeBase的短名作为索引获取AttributeBase
public abstract string[] AttributeNames { get; }
- AttributeSet的所有Attribute的短名
public void ChangeAttributeBase(string attributeShortName, float value)
- 修改AttributeBase的基础值
- attributeShortName:Attribute的短名
- value:指定的值
3.5.1.a GAttrSetLib.gen( Script-Generated Code)
GAttrSetLib.gen是便于读取,管理AttributeSet工具脚本。 GAttrSetLib.gen不是EX-GAS框架内脚本的,需要EX-GAS框架AttributeSet配置改动后,通过生成脚本生成。
-
脚本内包含如下静态工具类
-
public static class GAttrSetLib { public static readonly Dictionary<string,Type> AttrSetTypeDict = new Dictionary<string, Type>() { {"Fight",typeof(AS_Fight)}, }; public static List<string> AttributeFullNames=new List<string>() { "AS_Fight.HP", "AS_Fight.MP", "AS_Fight.STAMINA", "AS_Fight.POSTURE", "AS_Fight.ATK", "AS_Fight.SPEED", }; }
-
AttrSetTypeDict:AttributeSet的类型字典,方便外部通过字符串名获取AttributeSet的类型。
-
AttributeFullNames:所有AttributeSet的所有Attribute的完整名
-
举例:由脚本生成的AttributeSet类
public class AS_XXX:AttributeSet
{
private AttributeBase _A = new AttributeBase("AS_XXX","A");
public AttributeBase A => _A;
public void InitA(float value)
{
_A.SetBaseValue(value);
_A.SetCurrentValue(value);
}
public void SetCurrentA(float value)
{
_A.SetCurrentValue(value);
}
public void SetBaseA(float value)
{
_A.SetBaseValue(value);
}
public override AttributeBase this[string key]
{
get
{
switch (key)
{
case "A":
return _A;
}
return null;
}
}
public override string[] AttributeNames { get; } =
{
"A",
};
}
- 配置的AttributeSet名为XXX,包含一个Attribute名为A。
3.5.2 AttributeSetContainer
AttributeSetContainer是AttributeSet的容器类,用于ASC管理AttributeSet。
Dictionary<string,AttributeSet> Sets => _attributeSets;
:AttributeSet的集合,为属性,只读。void AddAttributeSet<T>() where T : AttributeSet
:添加AttributeSetT
:指定的AttributeSet类
void AddAttributeSet(Type attrSetType)
:添加AttributeSetattrSetType
:指定的AttributeSet类型
bool TryGetAttributeSet<T>(out T attributeSet) where T : AttributeSet
:尝试获取AttributeSetattributeSet
:获取的AttributeSet- 返回值:是否获取成功
float? GetAttributeBaseValue(string attrSetName,string attrShortName)
- 获取指定Attribute的基础值
- attrSetName:AttributeSet的名字
- attrShortName:Attribute的短名
- 返回值:Attribute的基础值
float? GetAttributeCurrentValue(string attrSetName,string attrShortName)
- 获取指定Attribute的当前值
- attrSetName:AttributeSet的名字
- attrShortName:Attribute的短名
- 返回值:Attribute的当前值
Dictionary<string, float> Snapshot()
- 获取AttributeSetContainer的数据快照
- 返回值:数据快照
3.5.3 CustomAttrSet
CustomAttrSet是AttributeSet的自定义类,适用于Runtime时动态生成AttributeSet。
void AddAttribute(AttributeBase attribute)
- 添加Attribute
- attribute:添加的Attribute
void RemoveAttribute(string attributeName)
- 移除Attribute
- attributeName:移除的Attribute的短名
3.6 GameplayEffect
3.6.1 GameplayEffectAsset
GameplayEffectAsset是GAS的游戏效配置类,是预设用ScriptableObject。
EffectsDurationPolicy DurationPolicy;
:GameplayEffect的持续时间策略float Duration
:GameplayEffect的持续时间float Period
: GameplayEffect的周期GameplayEffectAsset PeriodExecution
:GameplayEffect的周期执行的GameplayEffectGameplayEffectModifier[] Modifiers
:GameplayEffect修改器GameplayTag[] AssetTags
:GameplayEffect的描述标签GameplayTag[] GrantedTags
:GameplayEffect的授予标签,GameplayEffect生效时会授予目标ASC这些标签,失效时会移除这些标签GameplayTag[] ApplicationRequiredTags
:GameplayEffect的应用要求标签,只有目标ASC持有【所有】这些标签时,GameplayEffect才会生效GameplayTag[] OngoingRequiredTags
: GameplayEffect的持续要求标签,只有目标ASC持有【所有】这些标签时,GameplayEffect才会持续生效GameplayTag[] RemoveGameplayEffectsWithTags
:GameplayEffect的移除标签,只要目标ASC的GameplayEffect持有【任意】这些标签时,这些GameplayEffect就会被移除GameplayTag[] ApplicationImmunityTags
:GameplayEffect的免疫标签,只要目标ASC持有【任意】这些标签时,这个GameplayEffect就不会生效GameplayCueInstant[] CueOnExecute;
:GameplayEffect执行时触发的GameplayCueGameplayCueDurational[] CueDurational
:GameplayEffect持续时触发的GameplayCueGameplayCueInstant[] CueOnAdd
:GameplayEffect添加时触发的GameplayCueGameplayCueInstant[] CueOnRemove
:GameplayEffect移除时触发的GameplayCueGameplayCueInstant[] CueOnActivate
:GameplayEffect激活时触发的GameplayCueGameplayCueInstant[] CueOnDeactivate
:GameplayEffect失效时触发的GameplayCue
3.6.2 GameplayEffect
GameplayEffect是GAS的Runtime的游戏效果数据类.运行游戏运行时动态生成GameplayEffect。
- GameplayEffect的数据结构与GameplayEffectAsset几乎一致。这里就不再多赘述数据变量了。
3.6.3 GameplayEffectSpec
void Apply()
:应用游戏效果。void DisApply()
:取消游戏效果的应用。void Activate()
:激活游戏效果。void Deactivate()
:停用游戏效果。bool CanRunning()
:检查游戏效果是否可以运行。void Tick()
:更新游戏效果的周期性行为。void TriggerOnExecute()
:触发游戏效果执行时的事件。void TriggerOnAdd()
:触发游戏效果添加时的事件。void TriggerOnRemove()
:触发游戏效果移除时的事件。void TriggerOnTick()
:触发游戏效果进行周期性更新时的事件。void TriggerOnImmunity()
:触发游戏效果免疫时的事件。void RemoveSelf()
:移除游戏效果自身。void RegisterValue(GameplayTag tag, float value)
:注册与游戏标签关联的值。tag
:游戏标签。value
:与游戏标签关联的值。
void RegisterValue(string name, float value)
:注册与名称关联的值。name
:名称。value
:与名称关联的值。
bool UnregisterValue(GameplayTag tag)
:取消注册与游戏标签关联的值。tag
:游戏标签。- 返回值:如果成功取消注册,则返回
true
,否则返回false
。
bool UnregisterValue(string name)
:取消注册与名称关联的值。name
:名称。- 返回值:如果成功取消注册,则返回
true
,否则返回false
。
float? GetMapValue(GameplayTag tag)
:获取与游戏标签关联的值。tag
:游戏标签。- 返回值:如果找到与指定游戏标签关联的值,则返回该值;否则返回
null
。
float? GetMapValue(string name)
:获取与名称关联的值。name
:名称。- 返回值:如果找到与指定名称关联的值,则返回该值;否则返回
null
。
3.6.4 GameplayEffectContainer
GameplayEffectContainer是GameplayEffect的容器类,用于ASC管理GameplayEffect。
List<GameplayEffectSpec> GetActiveGameplayEffects()
:获取当前生效的游戏效果列表。void Tick()
:处理所有生效游戏效果的周期性更新。void RegisterOnGameplayEffectContainerIsDirty(Action action)
:注册效果容器变为脏状态时的回调函数。action
:回调函数。
void UnregisterOnGameplayEffectContainerIsDirty(Action action)
:取消注册效果容器变为脏状态时的回调函数。action
:回调函数。
void RemoveGameplayEffectWithAnyTags(GameplayTagSet tags)
:移除具有指定标签的游戏效果。tags
:指定的标签。
bool AddGameplayEffectSpec(GameplayEffectSpec spec)
:添加一个游戏效果实例。spec
:指定的游戏效果规范。
void RemoveGameplayEffectSpec(GameplayEffectSpec spec)
:移除指定的游戏效果实例。spec
:指定的游戏效果规范。
void RefreshGameplayEffectState()
:刷新游戏效果的状态,包括激活新效果和移除已停用的效果。CooldownTimer CheckCooldownFromTags(GameplayTagSet tags)
:检查指定标签的冷却状态。tags
:指定的标签。- 返回值:冷却计时器。
void ClearGameplayEffect()
:清除所有游戏效果,包括移除已应用的效果和停用的效果。
3.6.5 CooldownTimer
CooldownTimer是冷却计时结构体,用于保存冷却时间数据。
public float TimeRemaining;
: 剩余时间public float Duration;
: 总时间
3.6.6 GameplayEffectModifier
GameplayEffectModifier是游戏效果修改器类,用于实现对Attribute的修改。
string AttributeName
:属性名称,用于标识游戏效果修改器所影响的属性。float ModiferMagnitude
:修改器的幅度值,用于指定属性修改的具体数值。GEOperation Operation
:修改器的操作类型,指定属性修改的方式,如增加、减少等。ModifierMagnitudeCalculation MMC
:修改器的计算方式,用于指定如何计算修改的幅度值。void SetModiferMagnitude(float value)
:设置修改器的幅度值。value
:修改器的新幅度值。
void OnAttributeChanged()
:当属性名称发生变化时调用的方法,用于更新相关字段的值。static void SetAttributeChoices()
:设置属性选择列表。string AttributeSetName
:属性集名称,用于标识游戏效果修改器所影响的属性集。string AttributeShortName
:属性短名称,用于标识游戏效果修改器所影响的属性的简短版本。
3.6.6.0 ModifierMagnitudeCalculation
ModifierMagnitudeCalculation是一个抽象基类,所有MMC必须继承自他。
public abstract float CalculateMagnitude(GameplayEffectSpec spec, AttributeBase attribute, float value)
:计算修改器的幅度值方法是MMC的根本。spec
:游戏效果规范。attribute
:属性基类。value
:指定的值。- 返回值:修改器的幅度值。
3.6.6.1 ScalableFloatModCalculation
ScalableFloatModCalculation是一个MMC的实现类,用于实现可缩放的浮点数修改器。
public class ScalableFloatModCalculation:ModifierMagnitudeCalculation
{
[SerializeField] private float k;
[SerializeField] private float b;
public override float CalculateMagnitude(GameplayEffectSpec spec,float input)
{
return input * k + b;
}
}
float k
:缩放系数。float b
:偏移量。- 执行逻辑:
input * k + b
。线性缩放。
3.6.6.2 AttributeBasedModCalculation
AttributeBasedModCalculation是一个MMC的实现类,用于实现基于属性的修改器。
public class AttributeBasedModCalculation : ModifierMagnitudeCalculation
{
public enum AttributeFrom
{
Source,
Target
}
public enum GEAttributeCaptureType
{
SnapShot,
Track
}
public string attributeName;
public string attributeSetName;
public string attributeShortName;
public AttributeFrom attributeFromType;
public GEAttributeCaptureType captureType;
public float k = 1;
public float b = 0;
public override float CalculateMagnitude(GameplayEffectSpec spec, float modifierMagnitude)
{
if (attributeFromType == AttributeFrom.Source)
{
if (captureType == GEAttributeCaptureType.SnapShot)
{
var snapShot = spec.Source.DataSnapshot();
var attribute = snapShot[attributeName];
return attribute * k + b;
}
else
{
var attribute = spec.Source.GetAttributeCurrentValue(attributeSetName, attributeShortName);
return (attribute ?? 1) * k + b;
}
}
if (captureType == GEAttributeCaptureType.SnapShot)
{
var attribute = spec.Owner.DataSnapshot()[attributeName];
return attribute * k + b;
}
else
{
var attribute = spec.Owner.GetAttributeCurrentValue(attributeSetName, attributeShortName);
return (attribute ?? 1) * k + b;
}
}
}
string attributeName
:属性名称。string attributeSetName
:属性集名称。string attributeShortName
:属性短名称。AttributeFrom attributeFromType
:属性来源类型。GEAttributeCaptureType captureType
:游戏效果属性捕获类型。float k
:缩放系数。float b
:偏移量。- 执行逻辑:根据属性来源类型和游戏效果属性捕获类型,获取属性的当前值或快照值,并进行线性缩放。
3.6.6.3 SetByCallerFromNameModCalculation
SetByCallerFromNameModCalculation是一个MMC的实现类,用于实现根据名称设置的修改器。
public class SetByCallerFromNameModCalculation : ModifierMagnitudeCalculation
{
[SerializeField] private string valueName;
public override float CalculateMagnitude(GameplayEffectSpec spec,float input)
{
var value = spec.GetMapValue(valueName);
return value ?? 0;
}
}
string valueName
:键值值名称。- 执行逻辑:根据值名称获取与名称关联的值。
3.6.6.4 SetByCallerFromTagModCalculation
SetByCallerFromTagModCalculation是一个MMC的实现类,用于实现根据标签设置的修改器。
public class SetByCallerFromTagModCalculation:ModifierMagnitudeCalculation
{
[SerializeField] private GameplayTag _tag;
public override float CalculateMagnitude(GameplayEffectSpec spec ,float input)
{
var value = spec.GetMapValue(_tag);
return value ?? 0;
}
}
GameplayTag _tag
:键值标签。- 执行逻辑:根据游戏标签获取与游戏标签关联的值。
3.7 Ability
3.7.1 AbilityAsset
AbilityAsset是GAS的游戏能力配置类,是预设用ScriptableObject。他本身是一个抽象基类,所有的AbilityAsset都必须继承自他。
abstract Type AbilityType()
:能力的类型。用于把AbilityAsset和Ability类一一匹配。- 返回值:能力的类型。
string UniqueName
:唯一名称,用于标识该能力。GameplayEffectAsset Cost
:花费效果,该能力的消耗效果。GameplayEffectAsset Cooldown
:冷却效果,该能力的冷却效果。如果为空,冷却时间也不会生效。float CooldownTime
:冷却时间,该能力的冷却时间长度。GameplayTag[] AssetTag
:资产标签,该能力的标签。GameplayTag[] CancelAbilityTags
:取消能力标签,用于取消该能力的标签。GameplayTag[] BlockAbilityTags
:阻止能力标签,用于阻止该能力的标签。GameplayTag[] ActivationOwnedTag
:激活所需标签,该能力激活所需的标签。GameplayTag[] ActivationRequiredTags
:激活要求标签,该能力激活所需的标签。GameplayTag[] ActivationBlockedTags
:激活阻止标签,用于阻止该能力的激活标签。
3.7.2 AbstractAbility
AbstractAbility是GAS的游戏能力数据基类,他本身是一个抽象基类,所有的Ability都必须继承自他。
string Name
:名称,表示能力的名称。AbilityAsset DataReference
:数据引用,指向与该能力相关联的能力资产。AbilityTagContainer Tag
:标签,该能力的标签容器。GameplayEffect Cooldown
:冷却效果,该能力的冷却效果。float CooldownTime
:冷却时间,该能力的冷却时间长度。GameplayEffect Cost
:花费效果,该能力的消耗效果。AbstractAbility(AbilityAsset abilityAsset)
:抽象能力构造函数,初始化抽象能力实例。abilityAsset
:能力资产,与该能力相关联的能力资产。
abstract AbilitySpec CreateSpec(AbilitySystemComponent owner)
:创建能力规格的抽象方法,用于生成能力的规格实例。owner
:所有者,拥有该能力的实体。
void SetCooldown(GameplayEffect coolDown)
:设置冷却效果的方法。coolDown
:冷却效果,要设置的冷却效果。
void SetCost(GameplayEffect cost)
:设置花费效果的方法。cost
:花费效果,要设置的花费效果。
3.7.2.a AbstractAbility<T> :AbstractAbility where T : AbilityAsset
AbstractAbility<T>是AbstractAbility的泛型子类,用于实现AbstractAbility的泛型版本。 通常Ability都继承自他。方便对应的AbilityAsset和Ability一一匹配。
3.7.3 AbilitySpec
AbilitySpec是GAS的游戏能力规格类,用于实现对Ability的实例化。本身是一个抽象基类,所有的AbilitySpec都必须继承自他。 AbilitySpec是用于实现Ability游戏内实际的表现逻辑。
AbstractAbility Ability
:能力,与该能力规格类相关联的能力实例。AbilitySystemComponent Owner
:所有者,拥有该能力规格的单位。float Level
:等级,该能力的等级。bool IsActive
:是否激活,表示该能力当前是否处于激活状态。int ActiveCount
:激活计数,记录该能力被激活的次数。void RegisterActivateResult(Action<AbilityActivateResult> onActivateResult)
:注册激活结果的方法,用于注册激活结果的回调函数。void UnregisterActivateResult(Action<AbilityActivateResult> onActivateResult)
:注销激活结果的方法,用于注销激活结果的回调函数。void RegisterEndAbility(Action onEndAbility)
:注册结束能力的方法,用于注册结束能力的回调函数。void UnregisterEndAbility(Action onEndAbility)
:注销结束能力的方法,用于注销结束能力的回调函数。void RegisterCancelAbility(Action onCancelAbility)
:注册取消能力的方法,用于注册取消能力的回调函数。void UnregisterCancelAbility(Action onCancelAbility)
:注销取消能力的方法,用于注销取消能力的回调函数。virtual AbilityActivateResult CanActivate()
:检查能力规格是否可以被激活。- 返回值:激活结果:
- Success:成功
- FailHasActivated:失败,已经激活
- FailTagRequirement:失败,Tag要求不满足
- FailCost: 失败,消耗不足
- FailCooldown: 失败,还在冷却
- FailOtherReason: 失败,其他原因
- 返回值:激活结果:
void DoCost()
:执行花费的方法,用于执行激活该能力规格的花费操作。virtual bool TryActivateAbility(params object[] args)
:尝试激活能力virtual void TryEndAbility()
:尝试结束能力virtual void TryCancelAbility()
:尝试取消能力void Tick()
:处理能力的帧更新。abstract void ActivateAbility(params object[] args)
:激活能力的抽象方法,用于执行激活该能力的操作。abstract void CancelAbility()
:取消能力的抽象方法,用于执行取消该能力的操作。abstract void EndAbility()
:结束能力的抽象方法,用于执行结束该能力的操作。
3.7.4 AbilityContainer
能力容器,是ASC的间接管理能力的对象。
void Tick()
:处理的方法,用于处理能力容器中所有能力的Tick逻辑。void GrantAbility(AbstractAbility ability)
:授予能力的方法,用于向能力容器中添加新的能力。ability
:能力,要添加的新能力实例。
void RemoveAbility(AbstractAbility ability)
:移除能力的方法,根据能力实例从能力容器中移除能力规格。ability
:能力,要移除的能力实例。
public void RemoveAbility(string abilityName)
:移除能力的方法,根据能力名称从能力容器中移除能力规格。abilityName
:能力名称,要移除的能力名称。
bool TryActivateAbility(string abilityName, params object[] args)
:尝试激活能力的方法abilityName
:能力名称,要激活的能力名称。args
:参数,激活能力所需的额外参数。- 返回值:布尔值,表示能否成功激活能力。
void EndAbility(string abilityName)
:结束能力abilityName
:能力名称,要结束的能力名称。
void CancelAbility(string abilityName)
:取消能力abilityName
:能力名称,要取消的能力名称。
Dictionary<string, AbilitySpec> AbilitySpecs()
:获取容器内所有能力字典- 返回值:包含所有能力规格的能力名称与对应的能力规格实例。
3.7.5 AbilityTask(W.I.P)
Ability我们只能控制他的激活,结束等,并且这些接口都是功能性的即时方法,不存在异步,持续管理的说法。
但是Ability不可能都是瞬时逻辑,因此在Ability的逻辑实现中需要开发者对Tick处理,或者使用异步自行实现逻辑。 而在UE的GAS中,为了解决这个问题,设计团队创造了AbilityTask的概念,他们让AbilityTask来承载实现Ability 逻辑的任务。在UE版本的GAS中,AbilityTask的种类很多,他们能实现即时/异步/持续/等待的逻辑处理。功能非常强大。
因此,我也试着模仿了这个概念,但目前的版本来说,AbilityTask的功能和目的性还很弱。在之后的版本迭代中,我会慢慢完善 AbilityTask,以此来强化GAS中的Ability的逻辑处理能力和可编辑性。
- AbilityTaskBase:基类,Task是依附于Ability的存在,因此他的初始化必须依赖于AbilitySpec。
-
public abstract class AbilityTaskBase { protected AbilitySpec _spec; public AbilitySpec Spec => _spec; public virtual void Init(AbilitySpec spec) { _spec = spec; } }
-
- InstantAbilityTask: 即时类型的Task,最为常见的Task之一。
-
public abstract class InstantAbilityTask:AbilityTaskBase { #if UNITY_EDITOR /// <summary> /// 编辑器预览用 /// 【注意】 覆写时,记得用UNITY_EDITOR宏包裹,这是预览表现用的函数,不该被编译。 /// </summary> public virtual void OnEditorPreview() { } #endif public abstract void OnExecute(); }
-
- OngoingAbilityTask: 持续类型的Task,目前这类Task和TimelineAbility强关联,往后的设计里会抽象出来,让Task更加灵活。
-
public abstract class OngoingAbilityTask:AbilityTaskBase { #if UNITY_EDITOR /// <summary> /// 编辑器预览用 /// 【注意】 覆写时,记得用UNITY_EDITOR宏包裹,这是预览表现用的函数,不该被编译。 /// </summary> /// <param name="frame"></param> /// <param name="startFrame"></param> /// <param name="endFrame"></param> public virtual void OnEditorPreview(int frame, int startFrame, int endFrame) { } #endif public abstract void OnStart(int startFrame); public abstract void OnEnd(int endFrame); public abstract void OnTick(int frameIndex,int startFrame,int endFrame); }
-
3.8 GameplayCue
3.8.1 GameplayCue
GameplayCue是GAS的游戏提示配置类,用于实现对游戏效果的提示。他本身是一个抽象基类,所有的GameplayCue都必须继承自他。
GameplayTag[] RequiredTags;
:GameplayCue的要求标签,持有【所有】RequiredTags才可触发GameplayTag[] ImmunityTags;
:GameplayCue的免疫标签,持有【任意】ImmunityTags不可触发
3.8.1.a public abstract class GameplayCue<T> : GameplayCue where T : GameplayCueSpec
这个泛型类是为了方便对应的GameplayCueSpec和GameplayCue一一匹配,方便使用。
3.8.2 GameplayCueSpec
GameplayCueSpec是GAS的游戏提示规格类,用于实现对GameplayCue的实例化。本身是一个抽象基类,所有的GameplayCueSpec都必须继承自他。 GameplayCueSpec内实现GameplayCue游戏内实际的表现逻辑。
public virtual bool Triggerable()
{
return _cue.Triggerable(Owner);
}
- Triggerable():检查是否可以触发游戏提示的方法。
3.8.3 GameplayCueParameters
GameplayCueParameters是GAS的游戏提示参数结构体,用于实现对GameplayCue的参数化。 目前逻辑简单粗暴,存在拆装箱过程。
public struct GameplayCueParameters
{
public GameplayEffectSpec sourceGameplayEffectSpec;
public AbilitySpec sourceAbilitySpec;
public object[] customArguments;
}
3.8.4 GameplayCueInstant
GameplayCueInstant是GAS的GameplayCue中的一大类,属于OneShot类型的Cue。
3.8.4.a GameplayCueInstant
InstantCueApplyTarget applyTarget
:立即提示应用目标,指示立即提示的应用目标类型。virtual void ApplyFrom(GameplayEffectSpec gameplayEffectSpec)
:从GameplayEffectSpec应用InstantCue。gameplayEffectSpec
:游戏效果规格,触发立即提示的游戏效果规格实例。
virtual void ApplyFrom(AbilitySpec abilitySpec, params object[] customArguments)
:从AbilitySpec应用InstantCue。abilitySpec
:能力规格,触发立即提示的能力规格实例。customArguments
:自定义参数,自定义参数数组。
3.8.4.b GameplayCueInstantSpec
GameplayCueInstantSpec必须覆写Trigger()方法,用于实现对GameplayCueInstant触发。
public abstract class GameplayCueInstantSpec : GameplayCueSpec
{
public GameplayCueInstantSpec(GameplayCueInstant cue, GameplayCueParameters parameters) : base(cue,
parameters)
{
}
public abstract void Trigger();
}
3.8.5 GameplayCueDuration
GameplayCueDuration是GAS的GameplayCue中的一大类,属于持续类型的Cue。
3.8.5.a GameplayCueDurational
public GameplayCueDurationalSpec ApplyFrom(GameplayEffectSpec gameplayEffectSpec)
: 从GameplayEffectSpec应用DurationalCue。gameplayEffectSpec
:游戏效果规格,触发持续提示的游戏效果规格实例。
public GameplayCueDurationalSpec ApplyFrom(AbilitySpec abilitySpec, params object[] customArguments)
: 从AbilitySpec应用DurationalCue。abilitySpec
:能力规格,触发持续提示的能力规格实例。customArguments
:自定义参数,自定义参数数组。
3.8.5.b GameplayCueDurationalSpec
GameplayCueDurationalSpec必须覆写 OnAdd(), OnRemove(), OnGameplayEffectActivate(), OnGameplayEffectDeactivate(), OnTick()方法, 用于实现对GameplayCueDurational触发和运作。
public abstract class GameplayCueDurationalSpec : GameplayCueSpec
{
protected GameplayCueDurationalSpec(GameplayCueDurational cue, GameplayCueParameters parameters) :
base(cue, parameters)
{
}
public abstract void OnAdd();
public abstract void OnRemove();
public abstract void OnGameplayEffectActivate();
public abstract void OnGameplayEffectDeactivate();
public abstract void OnTick();
}
4.可视化功能
1. GAS Setting Manager (GAS基础配置管理器)
基础配置是与项目工程唯一对应的,所以入口放在了ProjectSetting,另外还有Edit Menu栏入口:EX-GAS -> Setting
- GameplayTag Manager
- Attribute Manager
- AttributeSet Manager
2. GAS Asset Aggregator (GAS配置资源聚合器)
因为GAS使用过程需要大量的配置(各类预设:ASC,游戏能力,游戏效果/buff,游戏提示,MMC),为了方便集中管理,我制作了一个配置资源聚合器。
通过在菜单栏EX-GAS -> Asset Aggregator 可以打开配置资源聚合器。
聚合器支持:分类管理,文件夹树结构显示,搜索栏快速查找,快速创建/删除配置文件(右上角的快捷按钮)
- ASC预设管理
- 能力配置管理
- 游戏效果管理
- 游戏提示 & MMC 管理
3. GAS Runtime Watcher (GAS运行时监视器)
注意!由于该监视器的监视刷新逻辑过于暴力,因此存在明显的性能问题。监视器只是为了方便调试,所以建议不要一直后台挂着监视器,有需要时再打开。
目前监视器较为简陋,以后可能会优化监视器。
5.如果...我想...,应该怎么做?(W.I.P)
- Q:我想实现血量(HpMax)上限,随力量(STR)每增加1点,血量上限增加10%,怎么办?
- A: 有两种常见的方法:
-
- 采用Derived Attribute的设计方法,给单位添加一个Infinite的GameplayEffect, 在修改器参数列表中添加一个修改器:修改属性为HpMax;操作类型为乘法;模值随意; MMC为STR属性依赖的MMC,来源为Target,并且Capture必须为Track(只有为Track时才能触发实时重计算),剩下的线性参数为k=0.1,b=1 (Magnitude = 1 + 10% * STR )。
-
- 采用监听STR属性的变化事件,手动对HpMax的BaseValue进行修改同步。
-
- A: 有两种常见的方法:
6.暂不支持的功能(可能有遗漏)
- RPC相关的GE复制广播
- GameplayEffect Execution,目前只有Modifier,没有Execution
- Ability的触发判断用的Source/Target Tag目前不生效
- GE过期时,触发的游戏效果
7.后续计划
- 修复bug ,性能优化
- 将GAS采用ECS结构来运行
- 补全遗漏的功能
- 优化Ability的编辑
- 支持RPC的GE复制广播,网络同步
8.特别感谢
本插件全面参考了UE的GAS解析,来自github --@tranek
同时还有中译版本,来自github --@BillEliot
没有上述二位的文章,本项目的开发会非常痛苦。
另外还要感谢开源项目:UnityToolchainsTrick
多亏UnityToolchainsTrick中的大量Editor开发技巧,极大的缩减了项目中编辑器的制作时间,省了很多事儿。非常感谢!
感谢参与EX-GAS开发的朋友们:
- BCC :优化了很多编辑器的体验以及bug,还提出了很多问题和反馈。
9.插件反馈渠道
QQ群号:616570103
目前该插件是一定有大量bug存在的,因为有非常多的细节没有测试到,虽然有Demo演示,但也只是一部分的功能。所以我希望有人能使用该插件,多多反馈,来完善该插件。
GAS使用门槛高,所以有任何GAS相关使用的疑问,bug或者建议,欢迎来反馈群里交流。我都会尽可能回答的。