ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

UE5 ARPG战斗框架:基于GAS与GameplayTag的架构设计与实战

UE5 ARPG战斗框架:基于GAS与GameplayTag的架构设计与实战 ARPG的战斗系统向来是项目里最难啃的骨头之一。动作游戏要求帧级响应、连招取消、状态互斥、伤害判定精确到骨骼而RPG部分又要求属性成长、Buff叠加、技能树、装备词条这些数据驱动的逻辑。这两套东西如果各写各的代码很快就会变成一团乱麻——角色身上挂着几十个bool变量if-else嵌套到第七层改一个技能效果要翻三个模块。我在几个UE5项目里反复折腾过这套东西最后稳定下来的方案是围绕GASGameplay Ability System来搭战斗框架用GameplayTag做状态和事件的统一语言。这篇内容就是把这套框架的搭建思路、核心模块的职责划分、以及实际落地时踩过的坑完整梳理一遍适合正在用UE5做ARPG、或者准备从零搭战斗系统的朋友参考。不管你是刚接触GAS的新手还是已经用过但觉得能跑但不好维护的老手应该都能从里面找到一些可以直接抄作业的东西。1. 为什么ARPG战斗框架值得用GAS来重构1.1 手写战斗逻辑在ARPG里会撞上哪些墙大部分团队做ARPG的第一版战斗系统都是直接从Character类里长出来的。攻击就是播放MontageMontage里挂AnimNotifyNotify里做射线检测检测到了就调TakeDamage然后角色身上加一个bIsAttacking的bool防止重复出招。这套东西做Demo没问题但一旦进入正式内容量问题会集中爆发。第一个墙是状态爆炸。一个角色可能同时处于攻击中、受击中、眩晕中、霸体中、无敌帧中、蓄力中、被击飞中。这些状态之间还有互斥和优先级关系——霸体可以覆盖受击硬直但眩晕不能被霸体免疫无敌帧能免疫伤害但不能免疫控制。用bool管理这些状态组合数是2的N次方你永远不知道哪个组合没测到。第二个墙是效果叠加。ARPG里一个Buff可能同时改攻击力、改攻速、改暴击率、附加元素伤害、每帧扣血、死亡时爆炸。这些效果有的要叠层有的要刷新持续时间有的要独立计算。手写的话每个Buff都要在角色Tick里写一段逻辑最后Tick函数能有两千行。第三个墙是网络同步。ARPG如果要做联机技能释放、伤害结算、Buff状态都必须同步。手写的状态机做预测回滚几乎是不可能完成的任务客户端预测和服务器校正的边界根本划不清。GAS恰好就是为解决这三个问题设计的。它把状态抽象成GameplayTag把效果抽象成GameplayEffect把技能抽象成GameplayAbility并且内置了网络预测和属性同步机制。这不是说用了GAS就万事大吉而是说它提供了一套经过验证的抽象让你不用重复造轮子。1.2 GAS在ARPG场景下的能力边界需要说清楚的是GAS不是万能的。它的核心能力集中在**属性Attribute、效果Effect、技能Ability、标签Tag**这四件事上。它不负责动画播放不负责碰撞检测不负责AI决策也不负责输入映射。这些还是要你自己写只是写完之后通过GAS的接口挂进去。我见过一些团队试图把GAS当成万能框架结果把动画逻辑、移动逻辑全塞进Ability里最后Ability变成了一坨什么都干的巨型类。正确的做法是把GAS当成战斗逻辑的中枢而不是全部。动画系统、输入系统、AI系统各自独立通过GameplayTag和GameplayEvent与GAS通信。具体来说GAS在ARPG里承担这些职责管理角色的所有数值属性血量、蓝量、攻击力、防御力等管理所有持续性的状态效果Buff、Debuff、DOT、HOT管理技能的激活、消耗、冷却、取消提供标签系统用于状态查询和互斥判断。而动画蓝图、Montage播放、碰撞检测、输入处理这些仍然由你原有的系统负责。1.3 从手写状态机迁移到GAS的收益账我拿一个实际项目算过账。迁移前角色类有大约40个bool和float成员变量用于状态管理Tick函数1800行每次加新技能平均要改3个文件。迁移后角色类只剩移动和动画相关的成员所有战斗状态都在GAS的AbilitySystemComponent里加新技能只需要新建一个Ability蓝图加一个Effect不改任何已有代码。更关键的是可调试性的提升。GAS自带GameplayDebugger运行时可以实时看到角色身上所有激活的Ability、所有生效的Effect、所有拥有的Tag、所有属性的当前值和修改器来源。手写状态机要做到这个程度得自己写一整套调试工具。当然迁移是有成本的。GAS的学习曲线不低尤其是Effect的修改器堆叠规则、Ability的预测机制、Tag的响应时机这几个地方不踩几个坑是理解不透的。但考虑到ARPG项目通常要做一两年前期投入两周把GAS吃透后面省下的时间是以月计的。2. 用GameplayTag搭建战斗状态的统一语言2.1 Tag命名规范从混乱到可维护GameplayTag是GAS里最重要的基础设施没有之一。它本质上是一个层级化的字符串标识比如State.Attacking、State.Stunned、Ability.Skill.Fireball、Effect.Buff.AttackUp。它的价值在于任何系统都可以通过查询Tag来知道角色当前的状态而不需要依赖具体的类或变量。但Tag用不好会比bool还乱。我见过项目里Tag有三百多个命名毫无规律Buff_Fire和State.Burning和Effect.Dot.Fire三个Tag指的是同一件事。所以第一步是定规范。我的规范是这样的顶层分四类——State表示角色当前状态互斥性状态Ability表示技能标识Effect表示效果标识Event表示一次性事件。State下面再分State.Movement移动相关、State.Combat战斗相关、State.Debuff负面状态。所有Tag必须从这四类里选一个作为根不允许出现无根Tag。命名用大驼峰层级不超过四层。比如State.Combat.Attacking、State.Debuff.Stunned、Ability.Skill.Fireball、Effect.Buff.AttackUp。这样在编辑器里筛选和自动补全都很方便。提示Tag一旦在项目里大量使用后就不要轻易改名因为蓝图和C里都是按名字引用的。建议在项目初期就把Tag表定下来写在一个DataTable里统一管理。2.2 状态互斥与优先级用TagQuery代替if-else手写状态机最头疼的就是互斥判断。比如攻击中不能移动、眩晕中不能攻击、霸体中受击不进入硬直。用bool写就是一堆if嵌套用Tag写就可以用GameplayTagQuery来表达。TagQuery是GAS提供的一个查询结构支持And、Or、Not、Any、All等逻辑组合。比如可以释放技能的条件可以表达为拥有State.Combat下的任意Tag为假且拥有State.Debuff.Stunned为假且拥有State.Debuff.Silenced为假。这个Query可以配置在Ability的ActivationRequiredTags和ActivationBlockedTags里GAS会自动帮你判断。更妙的是TagQuery可以在运行时动态构建。比如某些Boss在特定阶段免疫所有控制就可以在阶段切换时给Boss加一个State.Immune.Control的Tag所有带ActivationBlockedTags包含这个Tag的控制技能就自动失效了不需要改任何技能代码。我实际项目里的做法是把常用的互斥Query做成DataAsset比如可移动Query、可攻击Query、可受击Query然后在需要的地方引用。这样调整互斥规则只需要改DataAsset不用改代码。2.3 Tag驱动的动画与表现层解耦GAS只管逻辑不管表现。但表现层需要知道逻辑状态才能播放正确的动画。这时候Tag就是桥梁。动画蓝图里可以通过GetGameplayTagCount节点查询角色身上某个Tag的数量然后据此切换状态机。比如角色有State.Combat.Attacking这个Tag时动画蓝图切到攻击状态机有State.Debuff.Stunned时切到眩晕动画有State.Combat.Charging时播放蓄力循环。这样动画蓝图不需要知道任何战斗逻辑只需要读Tag。这个解耦带来的好处是美术调整动画状态机时不需要程序介入程序加新状态时也不需要美术改动画蓝图只要Tag命名规范一致。我在项目里甚至让美术自己维护一个Tag到动画状态的映射表程序只负责在正确的时机加正确的Tag。2.4 用Event Tag传递一次性战斗事件除了持续性的State TagGAS还支持Event Tag用于传递一次性事件。比如这次攻击命中了、这个Buff被驱散了、这个技能被打断了。Event Tag通过SendGameplayEvent发送通过WaitGameplayEvent接收。在ARPG里Event Tag的典型用法是攻击Ability在命中判定成功时发送Event.Combat.Hit受击方监听这个事件并触发受击AbilityBuff被驱散时发送Event.Effect.Dispelled相关系统监听后做清理。这样各个系统之间不需要直接引用通过事件Tag解耦。需要注意的是Event Tag和State Tag要分开管理。State Tag是持续性的Event Tag是瞬时的。混用会导致查询逻辑混乱。我的做法是在Tag命名上强制区分State.*是状态Event.*是事件不允许交叉。3. GameplayAbility在ARPG技能系统中的落地方式3.1 技能Ability的粒度划分一个技能一个Ability还是按功能拆这是设计时第一个要做的决策。一个火球术是做成一个Ability还是拆成释放Ability飞行物Ability爆炸Ability三个我的经验是按生命周期阶段拆而不是按视觉效果拆。火球术的释放是一个Ability负责消耗蓝量、播放施法动画、生成飞行物飞行物的飞行和碰撞是一个独立的Actor不需要Ability爆炸伤害结算是一个GameplayEffect由飞行物命中时Apply。这样拆的好处是每个部分职责单一飞行物可以复用给其他技能爆炸Effect也可以复用。但有些技能确实需要多个Ability协作。比如一个持续引导技能引导阶段是一个Ability持续消耗、持续播放特效引导结束的爆发是另一个Ability由第一个Ability触发。这种情况下用Ability的TriggerAbilityFromGameplayEvent机制串联。粒度划分的核心原则是如果一个逻辑单元有独立的激活条件、独立的消耗、独立的冷却就应该是独立的Ability。否则就应该是Effect或者普通逻辑。3.2 连招与取消窗口用AbilityTask管理时间轴ARPG的连招系统本质上是在特定时间窗口内接受输入切换到下一个技能。GAS里可以用AbilityTask来实现。具体做法是攻击Ability激活后创建一个AbilityTask_WaitDelay或者自定义的Task在取消窗口开启时给角色加一个State.Combat.ComboWindow的Tag同时监听输入事件。如果窗口内收到输入就激活下一个连招Ability窗口关闭时移除Tag。取消窗口的时机通常由动画驱动。我的做法是在Montage的AnimNotify里发送GameplayEventAbility监听这个事件来开关窗口。这样动画师调整Montage时取消窗口会自动跟着变不需要程序改代码。连招的衔接还有一个细节当前一个Ability还在激活状态时如何激活下一个。GAS默认不允许同一个Ability重复激活但不同Ability之间是可以的。所以连招的每个阶段应该是不同的Ability通过Ability.Combo.Stage1、Ability.Combo.Stage2这样的Tag区分。如果连招阶段很多可以用一个Ability加参数的方式但那样会失去GAS的很多自动化能力不推荐。3.3 技能消耗与冷却的Effect化实现技能的蓝量消耗和冷却时间在GAS里都是用GameplayEffect实现的。消耗是一个Instant Effect修改Mana属性冷却是一个Duration Effect给角色加一个Cooldown.Skill.Fireball的Tag同时设置持续时间。这里有个容易踩的坑冷却Effect的GrantedTags和Ability的CooldownTags要对应。Ability的CooldownGameplayEffectClass指向冷却EffectGAS会自动检查角色身上是否有这个Effect的GrantedTags有就阻止激活。如果Tag对不上冷却就不生效。另一个坑是冷却缩减。如果装备或Buff要减少冷却时间不能直接改冷却Effect的Duration因为Effect已经Apply了。正确做法是用SetByCaller或者AttributeBased的Duration让冷却时间基于角色的CooldownReduction属性计算。这样属性变化时新激活的Ability会自动用新的冷却时间。消耗也是类似的。如果消耗要受技能消耗降低属性影响就要用AttributeBased的Modifier让消耗量基于ManaCost属性乘以一个系数。这样改属性就能改消耗不需要改每个技能的配置。3.4 预测与回滚联机ARPG里Ability的同步策略如果项目要做联机Ability的预测机制必须理解。GAS的预测核心思想是客户端本地先执行服务器验证后确认或回滚。对于ARPG来说攻击、移动这类高频操作必须预测否则手感会很差而伤害结算、Buff施加这类低频操作可以等服务器确认。预测的关键是区分预测窗口和确认窗口。Ability激活时客户端立即执行同时发送激活请求给服务器。服务器执行后广播给所有客户端。如果服务器拒绝比如蓝量不够客户端回滚。GAS通过PredictionKey来关联本地预测和服务器确认。实际项目里我建议把Ability分成两类LocalPredicted攻击、闪避、移动技能和ServerOnly伤害结算、Buff施加、掉落。前者用预测后者等服务器。这样既保证了手感又避免了复杂的回滚逻辑。需要注意的是预测的Ability里不能有依赖服务器状态的逻辑。比如如果目标血量低于30%则暴击这种判断客户端不知道目标血量就不能放在预测Ability里。这类逻辑要放在服务器执行的Effect里。4. GameplayEffect驱动的属性与Buff体系4.1 AttributeSet的设计哪些属性该放进GASAttributeSet是GAS管理属性的地方。但不是所有数值都适合放进去。我的划分标准是会被GameplayEffect修改的、需要网络同步的、需要被Ability查询的才放进AttributeSet。典型的AttributeSet包含Health、MaxHealth、Mana、MaxMana、AttackPower、Defense、MoveSpeed、AttackSpeed、CritChance、CritDamage。这些都会被Buff修改都需要同步都需要被技能查询。而像当前连击数、上次攻击时间这种临时状态不需要放AttributeSet放在Ability或Component里就行。放进去反而会增加同步开销。AttributeSet的另一个设计点是派生属性。比如MaxHealth可能由基础MaxHealth加上装备加成加上Buff加成。GAS支持用PreAttributeChange和PostGameplayEffectExecute来处理派生计算。我的做法是基础值放一个Attribute加成放Modifier最终值在PostGameplayEffectExecute里计算并Clamp。4.2 Buff叠加规则Stack、Duration、Period的配合Buff系统是ARPG的核心乐趣之一也是GAS里最容易配错的地方。一个Buff Effect有三个关键参数StackingType叠加方式、Duration持续时间、Period周期触发间隔。StackingType有三种None不叠加重复Apply刷新Duration、AggregateBySource按来源叠加同一来源刷新不同来源独立、AggregateByTarget按目标叠加所有来源共享层数。ARPG里大部分Buff用AggregateBySource这样不同玩家施加的Buff可以独立计算。Duration有两种HasDuration有限时间、Infinite无限时间。有限时间的Buff到期自动移除无限时间的需要手动移除。DOT和HOT通常用HasDuration加PeriodPeriod设为伤害间隔。这里有个坑Period的Effect在Duration结束时不会触发最后一次。比如一个持续5秒、每1秒触发一次的DOT实际只触发4次1、2、3、4秒第5秒Duration到期直接移除。如果需要触发5次要么把Duration设为5.1秒要么在Effect移除时手动补一次。另一个坑是Stack和Period的交互。一个叠层的DOT每层独立计算Period还是共享GAS默认是每层独立Period但触发时是同时触发。如果希望每层错开触发需要自己写逻辑。4.3 DOT与HOT周期效果的精确控制DOT持续伤害和HOT持续治疗在ARPG里非常常见。用GAS实现的话核心是Period和Modifier的配合。一个DOT Effect的配置是Duration5秒Period1秒Modifier是Health的Additive值为负数。但实际项目里DOT往往更复杂。比如燃烧效果可能每秒造成火焰伤害伤害量基于施加者的攻击力。这时候Modifier要用AttributeBased基于施加者的AttackPower计算。GAS支持在Effect里引用Source的Attribute通过SetByCaller或者CustomCalculationClass。还有一个细节是DOT的伤害来源。DOT跳伤害时伤害应该算给谁算给施加者还是算给目标自己这影响击杀归属和仇恨计算。GAS里可以通过EffectContext的Instigator和Causer来区分。我的做法是Instigator设为施加者Causer设为DOT Effect本身这样伤害归属正确同时能追溯到是哪个Effect造成的。4.4 用ExecutionCalculation处理复杂伤害公式简单的伤害用Modifier就能算但ARPG的伤害公式往往很复杂基础伤害乘以技能倍率减去目标防御乘以暴击系数乘以元素克制系数再乘以各种增伤减伤。这种公式用Modifier堆会很乱用ExecutionCalculation简称ExecCalc更合适。ExecCalc是一个C类可以在里面写任意复杂的计算逻辑。它接收EffectSpec可以读取Source和Target的所有Attribute可以查询Tag可以修改多个Attribute。我的项目里所有伤害计算都走ExecCalc包括物理伤害、元素伤害、真实伤害、护盾吸收。ExecCalc的一个关键点是捕获属性。GAS提供了CaptureAttribute机制可以在Effect Apply时快照属性值避免计算过程中属性变化导致结果不一致。比如暴击判定用的CritChance应该在Effect Apply时捕获而不是在ExecCalc执行时读取。这样即使中间有Buff变化伤害计算也是基于同一时刻的属性。另一个点是伤害数字的传递。ExecCalc算出的伤害需要传给表现层显示飘字。我的做法是在ExecCalc里把伤害值写入EffectContext的SetByCaller然后通过GameplayEvent广播出去表现层监听后显示。5. 从输入到伤害结算的完整链路拆解5.1 输入层EnhancedInput与Ability的绑定UE5的EnhancedInput系统负责把玩家输入映射成GameplayTag然后由AbilitySystemComponent监听这些Tag来激活对应Ability。具体做法是在InputConfig里配置每个InputAction对应的AbilityTag玩家按键时发送Ability.Input.Attack这样的Event TagASC收到后查找拥有这个Tag的Ability并激活。这个链路的关键是输入缓冲。ARPG里玩家经常在攻击后摇时提前按键希望下一个技能能接上。如果输入直接丢弃手感会很差。我的做法是在输入层加一个缓冲队列按键时先存入队列Ability在取消窗口开启时从队列取输入。缓冲时间设0.2到0.3秒比较合适太短接不上太长会误触发。还有一个细节是输入优先级。闪避应该能取消攻击但攻击不能取消闪避。这通过Ability的CancelAbilitiesWithTag和BlockAbilitiesWithTag来配置。闪避Ability的CancelAbilitiesWithTag包含Ability.Combat.Attack这样闪避时自动取消攻击。5.2 动画层Montage与AbilityTask的同步动画播放本身不是GAS的职责但Ability需要知道动画什么时候到关键帧。我的做法是用AbilityTask_WaitGameplayEvent监听Montage里的AnimNotify事件。比如攻击Montage在命中帧发送Event.Anim.HitAbility收到后执行伤害判定在取消窗口开启帧发送Event.Anim.ComboWindowAbility收到后开启输入缓冲。这样动画和逻辑的同步点由动画师控制程序只需要监听事件。动画师调整Montage时同步点自动跟着变不需要程序改代码。这个解耦在项目后期动画频繁调整时价值巨大。需要注意的是Montage的播放要用PlayMontageAndWait这个AbilityTask它会在Montage结束时自动结束Ability。如果Montage被打断Task会收到Interrupted事件Ability可以据此做清理。5.3 判定层碰撞检测与命中结果的传递伤害判定通常用射线检测或者碰撞盒Overlap。这部分逻辑不在GAS里但判定结果要传给GAS。我的做法是判定成功后构建一个EffectContext包含Source、Target、HitResult、DamageType等信息然后Apply一个伤害Effect。命中结果的传递有个细节一次攻击可能命中多个目标。这时候要对每个目标分别Apply Effect每个Effect的Context里包含各自的HitResult。如果要做范围衰减离中心越远伤害越低可以在Context里带上距离参数ExecCalc根据距离计算衰减。还有一个坑是命中判定的时机。如果用AnimNotify触发判定要注意Notify是在客户端和服务器都触发的。联机时客户端判定只用于表现播放命中特效服务器判定才是权威。所以判定逻辑要区分Authority客户端只发预测服务器做实际结算。5.4 结算层伤害数字、死亡与掉落的事件流伤害结算完成后需要触发一系列后续事件显示伤害数字、播放受击动画、检查死亡、触发死亡掉落、更新仇恨列表。这些事件通过GameplayEvent串联。我的做法是ExecCalc算出伤害后发送Event.Combat.DamageDealt事件携带伤害值和目标信息。表现层监听后显示飘字受击方监听后播放受击动画如果目标死亡发送Event.Combat.Death事件掉落系统监听后生成掉落物AI系统监听后更新仇恨。这个事件流的优点是各系统解耦加新系统只需要监听事件不需要改已有代码。比如后期要加击杀回血的装备效果只需要监听Death事件不需要改伤害结算逻辑。6. 实战中踩过的坑与性能优化经验6.1 Ability激活失败的常见原因排查GAS的Ability激活失败时不会报错只会静默返回false这让排查很痛苦。我总结了几种常见原因和排查方法。第一种是Tag不匹配。Ability的ActivationRequiredTags没满足或者ActivationBlockedTags被满足。排查方法是打开GameplayDebugger看角色当前的Tag列表对比Ability的配置。第二种是冷却未结束。角色身上还有Cooldown Effect的GrantedTags。排查方法是看ASC的ActiveGameplayEffects列表找到冷却Effect看剩余时间。第三种是消耗不足。Mana不够或者消耗属性的CanApply检查失败。排查方法是看AttributeSet的当前值对比Ability的Cost配置。第四种是预测Key冲突。联机时如果PredictionKey没正确管理可能导致激活被拒绝。排查方法是看日志里的PredictionKey相关输出。我的建议是在Ability的CanActivateAbility里加详细日志输出每个检查项的结果。这样激活失败时能直接看到是哪一项没过。6.2 Effect堆叠导致的属性异常与调试方法Effect堆叠是属性异常的高发区。常见问题有Buff叠层后属性没更新、Buff移除后属性没恢复、多个Buff的Modifier互相覆盖。排查这类问题的核心工具是GameplayDebugger的Attributes面板。它会显示每个Attribute的BaseValue、CurrentValue、以及所有Modifier的来源和数值。如果CurrentValue不对看Modifier列表就能定位是哪个Effect的问题。一个常见坑是Modifier的运算顺序。GAS的Modifier有Add、Multiply、Divide、Override四种运算它们的执行顺序是固定的先Override再Add再Multiply再Divide。如果配置时没注意顺序结果可能和预期不符。比如攻击力翻倍和攻击力加100如果先加后乘是(100100)2400先乘后加是1002100300。GAS固定先Add后Multiply所以是400。另一个坑是Infinite Effect的清理。Infinite Effect不会自动移除如果忘记手动移除会一直影响属性。我的做法是给所有Infinite Effect加一个Effect.Duration.Infinite的Tag定期检查有没有不该存在的Infinite Effect。6.3 大量Buff场景下的性能考量ARPG后期一个角色身上可能同时有几十个Buff每个Buff都是一个ActiveGameplayEffect。GAS在每次属性查询时都会遍历所有Effect计算最终值Effect多了会有性能压力。优化的第一个方向是减少不必要的Effect。比如攻击力10和攻击力20两个Buff如果来源相同、持续时间相同可以合并成一个Effect。我的做法是在Apply Effect前先检查有没有同类Effect有就刷新而不是新建。第二个方向是用AttributeBased代替SetByCaller。SetByCaller需要在每次Apply时传值AttributeBased直接读属性后者在大量Apply时更快。第三个方向是限制Effect的Tick频率。Period Effect的Period不要太短0.1秒以下的Period基本没有意义反而增加开销。DOT的Period设0.5到1秒比较合适。实测下来一个角色身上50个Effect时属性查询的开销在0.1ms级别可以接受。超过100个Effect时会有明显压力需要优化。6.4 网络同步下的预测错误与修正联机ARPG最头疼的是预测错误。客户端预测攻击命中服务器判定没命中客户端要回滚。回滚时如果处理不好会出现伤害数字显示了但血量没掉或者血量掉了但伤害数字没显示的诡异现象。我的经验是预测只做表现不做结算。客户端预测时只播放动画和特效不实际扣血。等服务器确认后再扣血和显示伤害数字。这样即使预测错误也只是表现上的小瑕疵不会出现数值不一致。对于必须预测的操作比如闪避用GAS的预测机制但要确保预测的逻辑是确定性的。比如闪避的位移距离不能依赖服务器状态必须是客户端能独立计算的。还有一个细节是预测窗口的管理。GAS的PredictionKey有生命周期窗口外的操作不会被预测。我的做法是在Ability激活时创建PredictionKey在Ability结束时释放。期间的所有Effect Apply都用这个Key确保它们被正确预测和回滚。6.5 从Demo到上线GAS项目的工程化建议最后分享一些工程化方面的经验。GAS项目从Demo到上线有几个地方需要提前规划。第一是Tag的版本管理。Tag改名会导致蓝图和C引用失效所以Tag表要纳入版本控制改名要走Review流程。我的做法是把Tag定义在一个DataTable里改名前先搜索所有引用。第二是Ability的模块化。不要把几百个Ability放在一个文件夹里按功能分文件夹Abilities/Combat、Abilities/Movement、Abilities/Skill。每个Ability的配置用DataAsset方便批量调整。第三是Effect的复用。很多Effect是相似的比如各种攻击力X的Buff。可以做一个Effect模板用SetByCaller传不同的值而不是每个Buff建一个Effect。第四是测试覆盖。GAS的逻辑很难用单元测试覆盖但可以用自动化测试跑一遍所有Ability的激活和Effect的Apply检查有没有报错。我的项目里有一个测试地图里面放了各种测试角色每次提交前跑一遍。第五是文档。GAS的配置项很多不写文档的话新人很难上手。我的做法是给每个Ability和Effect写注释说明它的作用、依赖的Tag、关联的动画。这样即使原作者离职其他人也能维护。
返回列表