属性系统重构
一、背景
角色的属性在一个游戏中至关重要,他关联到战斗计算、道具增益、状态增减益等,关联了几个大的模块,不可谓不重要。
本着先实现的原则,刚开始做属性系统没有想太多,直接在角色配置里用角色属性结构StaticData存,代码如下:
角色配置里看起来是这样的:
由于属性会受运行时道具状态等系统影响,分为数值和百分比加成,于是使用RuntimeData继承StaticData,将游戏中动态变化的叠加部分放到子类字段中。
然而如果我想要在技能中配置属性增减益状态,还需要使用枚举让其成为可选列表,然后该逻辑生效的时候需要根据该枚举找到对应的RuntimeData中的变化对象。
二、问题
大家可能也发现了,如果想加一个属性,首先要在StatcData里加一个字段,然后再RuntimeData里添加对应的附加变化对象,再在属性枚举中添加一个枚举值,然后再为该枚举对应到RuntimeData中的对象变化中,这个过程好像有点费劲了。
问了AI,他给我的定义是:数据和结构耦合。
经过不断地问答和拉扯,终于得到了一套我还比较满意的重构方案。
三、重构方案
1、配置持有字段
角色的配置文件ScriptObject——或者说是StaticData并不需要知道有哪些具体的属性,可以用List<AttributeEntity>将角色的所有初始属性持有,所有属性由AttributeResData资源定义,后面新增属性只需要在list中加一个属性配置,此部分无需改代码。
2、运行时变化
删除原来RuntimeData中各属性增益字段,改为修改器实现,每个引起属性改变的行为都转化为Modifier填入修改器列表,修改后的属性作为字典,在修改器列表发生改变(触发属性变更)的时候根据基础属性和修改器得到最终值。新增属性此部分也无需改代码。
modifiers记录了每一处的变化:装备使用、buff加成等,包括加的是数值还是百分比,加了多少
每次发生改变,设置脏标记isDirty,取值的时候如果isDirty为true则把最终值更新为base+modifiers
3、面板配置属性
由于面板配置我不希望使用字符串属性id填入,这个太反人类了,想到了两个办法:
1、取消定制手选状态变化,每种状态变化都封装成固定状态;
2、保留定制手选状态变化,需要属性枚举,但是枚举不再手写,而是根据配置表生成AttributeResData资源时同步生成枚举脚本。
我选的是后者
这样虽然不用手写代码,但是由于自动生成的枚举是根据excel配置顺序,所以属性表中属性顺序尽量不发生变化,否则已经配置好的枚举值错乱就非常麻烦。
四、牵扯到的其他重构
1、属性和资源的区分
之前对于生命上限和生命值这种概念区分不清,生命上限是属性,而生命值是资源,所以属性表中出现的是生命上限而不是生命值。所以之前设计的形如MaxProperty这种将生命值和生命上限整合到一个结构中的设计需要拆分出来。
2、刷新机制
重构过程中发现血条的更新是在血条脚本LateUpdate中每帧Refresh刷新,显然是没有必要的。只有生命值或者生命上限发生变化时刷新就可以了。
3、配置文件枚举值更新
由于枚举新方案是从excel导出的属性生成枚举值脚本,导致重构前手写的同名枚举类型(被替换)已经配在技能配置里的值和新的对应不上,所以写了两个编辑器扩展:自动替换新旧枚举值、打印出包含枚举字段名的技能配置。如果放心程序执行的话只要第一个也行,前提是做好版本管理,避免程序化修改改乱没法回退。
五、总结
最近工作没那么多,但是心理压力蛮大,第一次做这个行业也基本上没有人带,事情越来越多的时候就有点难受。不过一想到自己做的游戏就充满了干劲,趁着脑子清醒,连着三天下班就回家重构,一天定义数据结构写打表工具导出属性配置、一天删除旧字段换上新字段以及修改所有错误引用让编译通过,一天写枚举值迁移工具,周六再把属性叠加刷新逻辑写完基本完工。
这部分做完感觉我的战斗框架已经比较让我满意了,后面继续抠游戏细节和铺内容,把战士、盗贼这两个职业和技能先做完,加上已经基本完成的法师——法力值、怒气值、能量值三种主资源类型就都覆盖了,然后做一个奶和一个T,奶就神牧,T直接DKT,欢迎大家继续关注,有开发的新动态和心得我也会继续分享,谢谢大家!
一周后:
本来以为一周铆足劲重构完就没事了,结果发现有一个大坑没处理——带标签属性。
顾名思义就是对“法术”“火焰”这类标签的“减免伤害”、或是“增加暴击率”,通用属性+标签。本来以为像通用属性一样:基础值(baseAttributes)+变化量(modifiers)=最终值(outlooks)就完事了
但是如果把百分比已经算进去outlooks,那么在战斗计算的时候已经乘了百分比的带标签属性和不带标签属性再计算就错了,必须把可加减的数值、增益百分比分开存到additives,战斗计算的时候在对应算才是对的。比如伤害减免部分:
暴击伤害和暴击率等部分也是同理,但又各有点区别,暴击率暂时只有数值部分flat,没有percentAdd和percentMul;而暴击伤害则可能都有(虽然我目前也只实现了flat,后面需要了再加)。
最后,给法杖附加增伤50%和减免伤害50%测试,验证普通属性
然后再给反魔法护罩技能设置在范围内法术伤害减免,验证标签属性
伤害减免有效, 测试通过,完工!