December 5, 2024

Unity技能系统设计理解

GAS框架

概述

GameplayAbilitySystem(简称GAS)是由UE发布的一个插件,以下是官方文档的解释

GameplayAbilitySystem是一个高度灵活的框架,可用于构建你可能会在RPG或MOBA游戏中看到的技能和属性类型。你可以构建可供游戏中的角色使用的动作或被动技能,使这些动作导致各种属性累积或损耗的状态效果,实现约束这些动作使用的”冷却”计时器或资源消耗,更改技能等级及每个技能等级的技能效果,激活粒子或音效,等等。简单来说,此系统可帮助你在任何现代RPG或MOBA游戏中设计、实现及高效关联各种游戏中的技能,既包括跳跃等简单技能,也包括你喜欢的角色的复杂技能集。

其包含以下几个核心概念:

GAS的运行逻辑

源自EX-GAS文档


AbilitySystemComponent

AbilitySystemComponent(简称ASC)是GAS运行的基本单位,是GAS执行大多数事情的起源,他可以代表一个角色,敌人,Boss等一切实体

来自Gasify的Wiki文档

Attributes,Tags,GameplayEffects,GameplayAbility都应用于ASC,也就是说ASC持有这些东西的容器,以下是EX-GAS和GASify的ASC的源码

EX-GAS的ASC源码

GASify的ASC源码


GameplayTag

GameplayTag用于分类和描述对象的状态,以下是EX-GAS的解释:

  • Gameplay Tag以树形层级结构(如Parent.Child.Grandchild)组织,用于描述角色状态/事件/属性/等,如眩晕(State.Debuff.Stun)。

  • Gameplay Tag主要用于替代布尔值或枚举值,进行逻辑判断。

  • 通常将Gameplay Tag添加到AbilitySystemComponent(ASC)以与系统(GameplayEffect,GameplayCue,Ability)交互。

Tag的目的就是简化传统的布尔值判断,可以用于简单的状态描述,以及处理复杂的技能效果交互

以英雄联盟举例,一个被“击晕”的角色可以被分配一个“State.Debuff.Stun”的游戏标签来代表它的当前状态

或者当我们玩亚索残血被追击时,被寒冰攻击到(减速Buff),然后被提莫攻击到(中毒Buff),我们使用净化解除这些效果并成功逃生,净化的效果是:移除身上的所有限制效果和召唤师技能的减益效果,我们把这些减益效果和限制效果都称作Debuff,那么整个过程就非常的简单

传统的做法,我们需要让减速类中毒类都实现Debuff接口,然后判断实体是否持有实现了Debuff接口的对象并一一将他们删除,但是这属于具体的实现,可能会在代码中写下大量的if-else的判断,如果还有更多复杂的效果,更是增加了代码的复杂度,而GameplayTag就解决了这个问题,我只需要为减速效果和中毒效果添加Tag(中毒:Debuff.Poison,减速:Debuff.SpeedDown),具体的判断逻辑则是写在GAS框架内部,开发者不需要编写额外的实现

不过需要注意的是,在项目初期,设计师就应该对Tag进行大致分类以及设计,并在后期尽可能做到只增添,不修改,不删除

以下是一些其他的Tag的例子:

Enemy.Boss”, “Weapon.Ranged”, “Environment.Underwater”, “Status.Poisoned”,”State.Jump”


Attribute

Attribute用于描述游戏中的各种核心数据,称作属性,例如生命值,魔法值,耐久值,攻击力,速度等等等

一个Attribute存在两个值

其中的关系为CurrentValue = BaseValue + GameplayEffect

例如英雄联盟的治疗术直接(InstantGameplayEffect)更改血量的BaseValue

疾跑则是修改CurrentValue(DurationGameplayEffect),在持续时间结束后,恢复为原来的速度,比如说英雄的基础速度(BaseValue)为500,使用疾跑后获得持续5秒的加速GameplayEffect,加速的值为50,

那么

CurrentValue = 500(BaseValue) + 50(GameplayEffect) = 550

并在五秒后变回500

在这里会有一个误区就是会误将BaseValue当作其Attribute的最大值,对于可以变更的最大值或最小值应该单独定义一个Attribute,例如已经定义了一个生命值(Hp)的属性,如果有一些效果需要获取到最大生命值(例如一些治疗效果:恢复最大生命值的30%或者UI上要根据当前生命值和最大生命值的比例来显示生命值),这个时候就需要单独定义一个Attribute(MaxHp)

Attribute还有一个重要的概念叫做AttributeSet(属性集),它是属性的集合,一个ASC可能有一个或多个AttributeSet,例如有以下属性

那么我们就可以定义一个人物的属性集,其包含生命值和速度,然后我们定义一个可以被破坏的石头,其属性集就只包含生命值这一个属性而不需要速度这个属性

属性集的设计可以比较随意,但是更推荐一个单位只有一个属性集


GameplayEffect

GAS的一切数值交互都基于GameplayEffect,简单来说就是一切Attribute的变化都基于GameplayEffect,虽然在代码中可以手动修改属性的值,但并不推荐这样做,避免数值体系混乱,难以追踪问题产生 ,更推荐尽可能只使用GameplayEffect对数值进行修改

GameplayEffect存在以下三种类型:

瞬发型 Instant 对Attribute中的BaseValue进行永久性修改
持续型 Duration 对Attribute中CurrentValue进行临时修改,持续一定后自动移除修改
无限型 Infinite 对Attribute中CurrentValue进行临时修改,需要手动移除

Modifier

Modifier是一个重要的概念,存储在GameplayEffect中,负责GameplayEffect的数值计算逻辑,简而言之包含三种信息:改谁,怎么改,改多少

Modifier包含了修改的属性,幅值,操作类型,MMC

简单来说,就是向MMC中输入一个数值,MMC可以返回一个数值,并根据操作类型作用于需要修改的属性上,而Magnitude是否使用则取决于MMC的具体实现,开发者可以自定义MMC

如果不使用MMC,则默认使用Magnitude作为返回的数值

一个GameplayEffect可以有多个Modifier,其执行顺序是按照其添加顺序

Level

Level属性用于放大效果,需要手动调用,手动编写相关逻辑

在EX-GAS源码内部,Level并没有被调用,仅仅只有对外暴露的Setter和Getter,而在Gasify的源码中可以发现其预设的MMC中是存在调用GameplayEffect的Level的,意味着我们可以在自定义的MMC通过调用GE的level来动态变更MMC的计算值

GameplayAbility

GameplayAbility是AbilitySystemComponent可以触发的一起行为和技能,通常由程序编写其对应的逻辑,Ability的业务逻辑取决于游戏类型和玩法

引用至EX-GAS的文档:Ability的具体实现需要策划和程序配合。 这并不是废话,而是在EX-GAS的Ability制作流程中,确确实实的把策划和程序的工作分开了:

  • 策划的工作:配置AbilityAsset

  • 程序的工作:编写Ability(AbilityAsset,Ability,AbilitySpec)类

GameplayCue

GameplayCue是一个用于播放游戏提示的类,它的作用是在游戏运行时播放游戏效果,比如播放一个特效、播放一个音效等,与GameplayAbility类似, Cue也是需要程序开发人员大量实现的,毕竟游戏不同导致游戏提示千变万化。

参考资料

GitHub - No78Vino/gameplay-ability-system-for-unity: Gameplay-Ability-System For Unity

GitHub - BillEliot/GASDocumentation_Chinese

feliperoddd.gitbook.io

sjai013/unity-gameplay-ability-system

About this Post

This post is written by Yuan, licensed under CC BY-NC 4.0.

#Unity