Home

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弹幕射击游戏

目录

1.快速开始

安装

  1. 导入Odin Inspector插件(付费),Odin Inspector来源请自行解决。建议使用3.2+版本。
  2. 导入本插件,建议以下两种方式:

【国内镜像】https://gitee.com/exhard/gameplay-ability-system-for-unity.git?path=Assets/GAS

使用

GAS十分复杂,使用门槛较高。因为本项目是对UE的GAS的模仿移植,所以实现逻辑基本一致。建议先粗略了解一下UE版本的GAS整体逻辑,参考项目文档:https://github.com/BillEliot/GASDocumentation_Chinese

使用流程

  1. 基础设置

在ProjectSetting中(或者Edit Menu栏入口:EX-GAS -> Setting),找到EX Gameplay Ability System的基本设置界面:

S0X2@(E97LP_SWIJY2SJ@F3.png

设置好以下两个路径

首次设置完路径后,点击检查子目录文件夹,确保必要的子文件夹都已生成。

【生成AbilitySystemComponentExtension类脚本】这个按钮,请在生成了Attribute,AttributeSet,Ability的Lib集合类之后再点击。特别提醒,AbilitySystemComponentExtension是工具类,理论上只生成一次即可。

  1. 配置Tag: Tag是GAS核心逻辑运作的依赖,非常重要。关于Tag的使用及运作逻辑详见章节(GameplayTag)

  2. 配置Attribute: Attribute是GAS运行时数据单位。关于Attribute的使用及运作逻辑详见章节(Attribute)

  3. 配置AttributeSet: AttributeSet是GAS运行时数据单位集合,合理的AttributeSet设计能够帮助程序解耦,提高开发效率。关于AttributeSet的使用及运作逻辑详见章节(AttributeSet)

  4. 设计MMC,Cue: 详见MMC, GameplayCue

  5. 设计Gameplay Effect: 详见 Gameplay Effect

  6. 设计Ability: 详见 Ability

  7. 设计ASC预设(可选): 详见 AbilitySystemComponent

GAS的预缓存


2.EX-GAS系统介绍

2.1 EX-GAS概述

EX-GAS是对UnrealEngine的GAS(Gameplay Ability System)的模仿和实现。

GAS 是 "Gameplay Ability System" 的缩写,是一套游戏能力系统。 这个系统的目的是为开发者提供一种灵活而强大的框架,用于实现和管理游戏中的各种角色能力、技能和效果。

如果把EX-GAS高度概括为一句话,那就是:WHO DO WHAT

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在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

QQ20240313114652.png 我模仿了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

QQ20240313115953.png Attribute Manager的作用很简单,提供属性名字的管理。然后作为AttributeSet的选项使用。

【注意!!!】 每次编辑完Attribute后,一定要点击【生成AttrLib】按钮。 只有AttrLib生成后,AttributeSet的Attribute选项才会发生改变。

2.4 AttributeSet

AttributeSet,属性集,是GAS中的核心数据单位集合,用于描述角色的某一类别的属性集合。

在上文的[2.3 Attribute]中,我们提到了AttributeSet是姓氏,Attribute是名字。二者结合起来才能作为唯一标识。 而对于AttributeSet的设计,可以较为随意,大多数情况,大家会更乐意一个单位只有一个AttributeSet。 因为这样便于管理和分类,不同类别的单位直接使用不同的AttributeSet。但实际上一个单位是可以拥有复数AttributeSet。 我其实比较认同一个单位只有一个AttributeSet的设计,因为这对程序开发也是好事,逻辑处理会更简单直白。

配置时的注意项:

2.4.a AttributeSet Manager

QQ20240313121300.png 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具有以下特点:

MMC在GameplayEffect中的运作逻辑,结合GameplayEffect配置中MMC界面来解释。如下图:

QQ20240313145154.png

MMC被存储在Modifier中,Modifier是GameplayEffect的一部分。 Modifier包含了修改的属性,幅值(Magnitude),操作类型和MMC。

2.6 GameplayCue

目前EX-GAS的GameplayCue功能还未完善。功能相对简陋。

第一条原则是所有类型游戏必须遵守的。

而第二条原则就见仁见智了,因为游戏类型和玩法决定了cue的影响范围。 比如即时战斗类游戏,cue对角色位移有操作显然就是干涉了战斗,但如果是回合制游戏,cue对角色位移的操作就可以被当成是动画表现。 (甚至,即便是即时战斗类游戏,cue对角色位移的操作也可以被当成是动画表现,只要游戏开发人员认为cue的位移操作不影响游戏的战斗结果即可。)

GameplayCueInstant和GameplayCueDurational都是抽象类,它们的子类才是真正的可使用Cue类。 Cue是需要程序开发人员大量实现的,毕竟游戏不同导致游戏提示千变万化。

注意:customArguments是一个object数组,开发人员需要自己保证传递的参数类型正确,否则会导致运行时错误。 customArguments是最暴力的设计,往后EX-GAS的Cue参数传递设计还会进行优化。

2.7 GameplayEffect

GameplayEffect是EX-GAS的核心之一,一切的游戏数值体系交互基于GameplayEffect。

GameplayEffect掌握了游戏内元素的属性控制权。理论上,只有它可以对游戏内元素的属性进行修改 (这里指的是修改,数值的初始化不算是修改)。当然,实际情况下,游戏开发人员当然可以手动直接修改属性值。 但是还是希望游戏开发者尽可能的不要打破EX-GAS的数值体系逻辑,因为过多的额外操作可能会导致游戏的数值体系变得混乱,难以追踪数值变化等等。

另外GameplayEffect还可以触发Cue(游戏提示)完成游戏效果的表现,以及控制获取额外的能力等。

GameplayEffect的配置界面如图,接下来逐一解释各个参数的含义。

GameplayEffect的施加(Apply)和激活(Activate)

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制作流程中,确确实实的把策划和程序的工作分开了:

建议 AbilitySpec和Ability在同一个脚本中编辑,因为二者本身就是成对出现。AbilityAsset单独一个脚本,因为它是Scriptable Object,应该遵从脚本名和类一致的原则。

Ability运作逻辑的组成可以拆成两部分:

接下来结合Ability的配置界面来解释Ability的数据和运作逻辑。

2.8.a Ability编辑界面

QQ20240313162642.png

图中A的部分就是GAS系统内的运作逻辑的参数,B的部分就是具体游戏内的表现逻辑的参数。

注意到最上方显示了Ability Class,这是Ability的类名,与之成对的AbilitySpec决定了Ability游戏内的表现逻辑。

Tag功能
Asset Tag描述性质的标签,用来描述Ability的特性表现,比如伤害、治疗、控制等
CancelAbility With TagsAbility激活时,Ability持有者当前持有的所有Ability中,拥有**【任意】**这些标签的Ability会被取消
BlockAbility With TagsAbility激活时,Ability持有者当前持有的所有Ability中,拥有**【任意】**这些标签的Ability会被阻塞激活
Activation Owned TagsAbility激活时,持有者会获得这些标签,Ability被失活时,这些标签也会被移除
Activation Required TagsAbility只有在其拥有者拥有**【所有】**这些标签时才可激活
Activation Blocked TagsAbility在其拥有者拥有**【任意】**这些标签时不能被激活

2.8.b TimelineAbility 通用性Ability(W.I.P TimelineAbility还在完善中)

在实际的开发过程中,我发现,许多的Ability都有顺序和时限两个特点。 每次都新写一个Ability类来实现某个指定技能让我十分烦躁,于是我制作了TimelineAbility,一个极具通用性的顺序,时限Ability。 TimelineAbilityAsset.png

这是TimelineAbilityAsset的面板。

TimelineAbilityAsset的大多数表现逻辑参数在AbilityAsset面板都是隐藏的(HideInInspector)。 转而都是在TimelineAbilityEditor面板中可视化编辑。 唯一在AbilityAsset面板中可见的参数是【手动结束能力】的bool值选项。这个选项决定Ability是手动结束还是播放完成后自动结束。

通过点击【查看/编辑能力时间轴】按钮,可以打开TimelineAbilityEditor面板。 TimelineAbilityEditor.png

这是TimelineAbilityEditor的面板。

接下来详细介绍TimelineAbilityEditor的面板参数含义及操作逻辑。

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个参数:

到这里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等数据。 其主要职责如下:

整个GAS的运作都是围绕着ASC的,所有的Tag,GameplayEffect的作用对象最后都是ASC。而Ability也必须依赖ASC来执行。 为了直观的理解ASC,接下来参考GAS Watcher的监视器界面: QQ20240313180923.png

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的数据。 QQ20240315172608.png ASC预设是为了可视化角色(单位)的参数。

如何使用ASC预设?

1.AbilitySystemComponent组件自带了序列化的ASC预设字段,可以通过预制体添加,也可以实例化添加。 2.依赖ASC预设的初始化,通过AbilitySystemComponentExtension中的静态扩展方法InitWithPreset即可。

InitWithPreset的参数:

示例: asc.InitWithPreset(1,ascPreset); // 如果预制体已经设置了参数,那么可以不传ascPreset。


3.API && Source Code Documentation

本章节会在介绍API和源码的同时,从代码的角度来理解GAS的运作逻辑。 GAS_IMG_Intro.png

该图简单的解释了GAS的运作逻辑。GAS其实简单的只干一件事:ASC使用Ability对指定的(可以包括自己)ASC释放GameplayEffect。

GAS的推进和运行,就是在不断的重复这件事。 体系外的脚本不断的拨动ASC的Ability,而GAS内部会对Ability的运行结果自行消化。

3.1 Core

3.1.1 GameplayAbilitySystem

GameplayAbilitySystem作为核心类,他的作用有2个:管理ASC,控制GAS的运行与否。

3.1.2 GASTimer

GASTimer是GAS的计时器,它是GAS的时间基准。

3.1.3 GasHost

GasHost是GAS的宿主,它是GAS的运行机器和环境,GasHost没有API可以从外部干涉。


3.2 AbilitySystemComponent

3.2.1 AbilitySystemComponent

AbilitySystemComponent是GAS的基本运行单位,它是GAS的核心类。 ASC的public方法和属性就是外部干涉GAS的唯一手段。

3.2.2 AbilitySystemComponentPreset

AbilitySystemComponentPreset是ASC的预设,用于方便初始化ASC的数据。

3.2.3 AbilitySystemComponentExtension

AbilitySystemComponentExtension是ASC的扩展方法类,用于方便ASC的初始化和操作。 AbilitySystemComponentExtension不是EX-GAS框架内脚本的,需要EX-GAS框架基础配置完成后,通过生成脚本生成。

3.3 GameplayTag

3.3.1 GameplayTag

GameplayTag是GAS的标签类,它是GAS的核心类。Tag的设计结构虽然简单,但是在实际应用中十分高效有用。

3.3.2 GameplayTagSet

GameplayTagSet是Tag集合类之一。GameplayTagSet适用于稳定不会改变的Tag集合。通常数据类的Tag集合都用GameplayTagSet。

3.3.3 GameplayTagContainer

GameplayTagContainer是Tag集合类之一。GameplayTagContainer适用于经常改变的Tag集合。

3.3.4 GameplayTagAggregator

GameplayTagAggregator是专门针对ASC的Tag管理类,会针对固有Tag和动态Tag做不同的处理。

3.3.5 GTagLib(Script-Generated Code)

GTagLib是GAS的标签库,它是GAS的标签管理类。 GTagLib不是EX-GAS框架内脚本的,需要EX-GAS框架Tag配置改动后,通过生成脚本生成。


3.4 Attribute

3.4.1 AttributeValue

AttributeValue是一个数据结构体。是实际存储Attribute的值的单位。

3.4.2 AttributeBase

AttributeBase是GAS的属性基类,它是GAS的核心类之一。 负责管理AttributeValue的值变化,已经Attribute相关回调处理。

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是一个抽象基类。

3.5.1.a GAttrSetLib.gen( Script-Generated Code)

GAttrSetLib.gen是便于读取,管理AttributeSet工具脚本。 GAttrSetLib.gen不是EX-GAS框架内脚本的,需要EX-GAS框架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",
      };
}

3.5.2 AttributeSetContainer

AttributeSetContainer是AttributeSet的容器类,用于ASC管理AttributeSet。

3.5.3 CustomAttrSet

CustomAttrSet是AttributeSet的自定义类,适用于Runtime时动态生成AttributeSet。


3.6 GameplayEffect

3.6.1 GameplayEffectAsset

GameplayEffectAsset是GAS的游戏效配置类,是预设用ScriptableObject。

3.6.2 GameplayEffect

GameplayEffect是GAS的Runtime的游戏效果数据类.运行游戏运行时动态生成GameplayEffect。

3.6.3 GameplayEffectSpec

3.6.4 GameplayEffectContainer

GameplayEffectContainer是GameplayEffect的容器类,用于ASC管理GameplayEffect。

3.6.5 CooldownTimer

CooldownTimer是冷却计时结构体,用于保存冷却时间数据。

3.6.6 GameplayEffectModifier

GameplayEffectModifier是游戏效果修改器类,用于实现对Attribute的修改。

3.6.6.0 ModifierMagnitudeCalculation

ModifierMagnitudeCalculation是一个抽象基类,所有MMC必须继承自他。

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;
        }
    }
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;
            }
        }
    }
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;
        }
    }
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;
        }
    }

3.7 Ability

3.7.1 AbilityAsset

AbilityAsset是GAS的游戏能力配置类,是预设用ScriptableObject。他本身是一个抽象基类,所有的AbilityAsset都必须继承自他。

3.7.2 AbstractAbility

AbstractAbility是GAS的游戏能力数据基类,他本身是一个抽象基类,所有的Ability都必须继承自他。

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游戏内实际的表现逻辑。

3.7.4 AbilityContainer

能力容器,是ASC的间接管理能力的对象。

3.7.5 AbilityTask(W.I.P)

Ability我们只能控制他的激活,结束等,并且这些接口都是功能性的即时方法,不存在异步,持续管理的说法。

但是Ability不可能都是瞬时逻辑,因此在Ability的逻辑实现中需要开发者对Tick处理,或者使用异步自行实现逻辑。 而在UE的GAS中,为了解决这个问题,设计团队创造了AbilityTask的概念,他们让AbilityTask来承载实现Ability 逻辑的任务。在UE版本的GAS中,AbilityTask的种类很多,他们能实现即时/异步/持续/等待的逻辑处理。功能非常强大。

因此,我也试着模仿了这个概念,但目前的版本来说,AbilityTask的功能和目的性还很弱。在之后的版本迭代中,我会慢慢完善 AbilityTask,以此来强化GAS中的Ability的逻辑处理能力和可编辑性。



3.8 GameplayCue

3.8.1 GameplayCue

GameplayCue是GAS的游戏提示配置类,用于实现对游戏效果的提示。他本身是一个抽象基类,所有的GameplayCue都必须继承自他。

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);
        }

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
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
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基础配置管理器)

QQ20240313174500.png 基础配置是与项目工程唯一对应的,所以入口放在了ProjectSetting,另外还有Edit Menu栏入口:EX-GAS -> Setting

2. GAS Asset Aggregator (GAS配置资源聚合器)

QQ20240313175247.png 因为GAS使用过程需要大量的配置(各类预设:ASC,游戏能力,游戏效果/buff,游戏提示,MMC),为了方便集中管理,我制作了一个配置资源聚合器。

通过在菜单栏EX-GAS -> Asset Aggregator 可以打开配置资源聚合器。

聚合器支持:分类管理,文件夹树结构显示,搜索栏快速查找,快速创建/删除配置文件(右上角的快捷按钮)

3. GAS Runtime Watcher (GAS运行时监视器)

QQ20240313180923.png 注意!由于该监视器的监视刷新逻辑过于暴力,因此存在明显的性能问题。监视器只是为了方便调试,所以建议不要一直后台挂着监视器,有需要时再打开。

目前监视器较为简陋,以后可能会优化监视器。


5.如果...我想...,应该怎么做?(W.I.P)


6.暂不支持的功能(可能有遗漏)

7.后续计划

8.特别感谢

本插件全面参考了UE的GAS解析,来自github --@tranek

同时还有中译版本,来自github --@BillEliot

没有上述二位的文章,本项目的开发会非常痛苦。

另外还要感谢开源项目:UnityToolchainsTrick

多亏UnityToolchainsTrick中的大量Editor开发技巧,极大的缩减了项目中编辑器的制作时间,省了很多事儿。非常感谢!

感谢参与EX-GAS开发的朋友们:

9.插件反馈渠道

QQ群号:616570103

目前该插件是一定有大量bug存在的,因为有非常多的细节没有测试到,虽然有Demo演示,但也只是一部分的功能。所以我希望有人能使用该插件,多多反馈,来完善该插件。

GAS使用门槛高,所以有任何GAS相关使用的疑问,bug或者建议,欢迎来反馈群里交流。我都会尽可能回答的。