前言
最近在开发一个自走棋+塔防的游戏,游戏玩法的最底层逻辑来自塔对敌人造成伤害,击杀敌人防止敌人移动至终点,游戏中的塔和敌人有各种各样的Buff和技能,在自走棋的玩法下,还存在一个羁绊系统,不同羁绊的数值也会影响技能和Buff的数值,虽然是使用的一个开源的基于UE的GAS思想开发的技能系统,但要在原有的框架下,满足复杂的动态伤害计算还要易于拓展也是极其难的一件事,在看了100万遍GAS相关资料,还跟完了一个Unity的技能系统开发教程,终于是从中有些许启发(当然直接学习UE的GAS是最好的路子,但是对于我UE零基础来说没有更多的时间去折腾了QWQ)
本文只进行了普通的代码和基础的思路展示,目前的动态伤害计算还需要在更多的技能需求中不断迭代
失败的做法
在GAS的框架下,【塔】攻击【敌人】 最简单最直观也最符合直觉的做法就是,【塔】对敌人施加一个【伤害GE】,这个GE的Modifier使用一个AttrbuteBasedMMC获取【塔】的攻击力,并作用于【敌人】的生命值,完成了基于塔攻击力,对敌人造成伤害的效果
但当需要实现一个暴击的效果怎么做?一个最直接的想法就是在每次Apply【伤害GE】时,根据暴击率进行判定,判定成功则将塔的攻击力乘以暴击倍率,在对敌人施加GE后再恢复原攻击力
这样做的弊端在于,单一的伤害效果与单位的Attribute相互耦合,当【其他的伤害GE】需要同样的Attribute做计算时,就会被影响,当然不同的伤害GE用不同的攻击力Attribute也是一种办法,但是显然这种方式极其不优雅
我称这种伤害为特殊伤害效果,暴击是一个简单的例子,简单来讲,暴击是基于概率的,如果有一个技能,会根据目标的某种Buff层数,造成额外伤害,则这是基于Buff的,如果塔存在一个防御值,则对伤害进行减伤,当然还有各种千奇百怪的情况
并且这种方案在每个需要有特殊伤害效果的地方Apply都要在其前后改变Attribute的数值,这种方式显然不妥,在游戏的各种技能越来越多后,多种特殊伤害效果叠加,伤害效果还要进行动态计算时,其维护难度是指数级上升的
需要解决的问题
前面一种拍脑袋方案在我开发了七八个Ability和十几个GE后慢慢感受到了不对劲,照这样下去,我将在开发第20个技能的时候,花费一整年的时间开发这个技能,并花大量的时间适配先前开发的一些技能,并修复新出现的几十个Bug(bushi
动态伤害计算的复杂性主要体现在以下几个方面:
条件判断多样性:例如暴击率、连击计数、目标Buff层数等
效果叠加顺序:不同伤害效果的叠加顺序可能影响最终结果(如先计算暴击还是先计算护甲减伤)。
模块化需求:每个伤害效果应独立实现,避免相互干扰
可配置性:伤害效果的触发条件和计算逻辑应支持灵活配置,以适应不断变化的游戏需求
重新审视这个问题,一个GE对目标造成的伤害应该是
最终伤害=基础伤害值 + 特殊伤害效果1+ 特殊伤害效果2+ 特殊伤害效果3+ 特殊伤害效果……
举个例子,当特殊伤害效果是暴击时,那么就需要对暴击进行判定,判定成功则计算暴击值作为一个加权值
当特殊伤害效果为,当每第五次攻击时,本次攻击造成120%的伤害,那么就计算攻击力Attribute * 0.2作为该效果的加权值
总结特殊伤害效果就是一个加权值,而一个动态的伤害数值的GE就需要知道特殊伤害效果,能不能加(条件),加多少(逻辑)
对于GE来说,条件可以使用Tag来简单配置,但是显然难以满足像“每第五次攻击”,“当判定暴击”这种需求,而GE作为一个单纯的配置文件,是无法实现很多复杂伤害效果的逻辑的,但我们又不能在应用GE时去做条件判定和逻辑运算(前面说到这种方式的不妥)
那么我们的需求就在于:使伤害计算有较高的颗粒度,低耦合,伤害计算模块化,可配置
解决方案
我从MMC中得到启发,结合SetByCaller得出目前的解决方案
Adjustment系统
这里引入Adjustment的概念,我们称其为调整器系统,作用于GE(就像MMC作用于GE一样)
核心设计思想
双向调整机制:将调整器分为来源方(攻击者)和目标方(受击者),分别处理加成(如暴击、连击)和减益(如护甲、抗性)
条件式触发:每个
Adjustment
通过CanApplyAdjustment
实现自定义条件判断(如概率判定、状态检查),只有符合条件的调整才会生效无侵入式修改:通过直接操作
GameplayEffectSpec
的临时数值,而非修改角色的基础属性,避免了对其他系统的副作用
他是一个简单的抽象类,包含两个方法,这两个方法也就是我们前面说到的条件和逻辑
1 | public abstract class GameplayEffectAdjustment : ScriptableObject |
然后在GE的配置处,除了常规的Modifier,Tag,叠层等配置外,新增目标调整器和源调整器,让设计者可以自由配置
在AbilitySystemCompoent中声明两个列表用于存储来源方和目标方的调整器,并在需要应用调整器时,调用一下以下方法
1 | public void TryApplyGameplayEffectAdjustments( |
使用者只需要继承GameplayEffectAdjustment并实现条件和逻辑的代码,且以一种低耦合的状态存在,并自由配置到任意GE上
在为任意目标添加该GE时,就会在ACS的对应列表中存储调整器
下面以一个简单的示意图展示整体的流程
致谢
一个UEGAS系统的Unity实现的开源框架,虽然功能还有待完善,但目前的版本给我的开发带来了极大的便利,感谢作者EX-Hard,赞美开源!
https://github.com/No78Vino/gameplay-ability-system-for-unity
About this Post
This post is written by Yuan, licensed under CC BY-NC 4.0.