技能系统的功能在于管理能不能打,能打谁,能打多远,打多少目标,以什么方式打这些关键问题。作为战斗系统的中枢,技能系统充当着胶水层,粘合了外围的 AOI、属性计算、碰撞检测、子弹、buff、ai 等系统。从使用者的角度看,技能分为怪物技能和人物技能两部分,下面主要讲讲人物技能

技能系统决定技能能不能打,这里有两个方面,一是从各种成长系统过来的技能,经过整合计算,得到战场上可用的技能列表,再加上战场本身给的技能,决定了玩家能放什么技能;二是技能的当前状态,是否处于 cd 中,包括 gcd、技能本身 cd、同类技能的 cd 等配置限制,从时间维度上控制了战斗的节奏

能打多远,是技能本身的施法距离判断,用于控制战斗场面的大小,配合移动速度和视角,影响着战斗规模和节奏。能打的前提是能看见攻击目标,所以一定程度上还受到战斗场景 AOI 范围的限制

能打谁,是说技能对目标的筛选作用,当前技能是治疗还是攻击,涉及的是目标阵营的筛选;特殊技能只能针对特定怪物,则是目标类型筛选。这些条件更多是起到过滤器的作用,过滤掉不想要的攻击对象。结合攻击数量的限制,就会引起一个潜在的问题:该技能只能打 10 个目标,根据技能的作用范围,圈出来 10 个目标,结果筛选后都不能打,导致技能落空。但是,说不定该区域人数特别多,只不过前面限制了数量,导致其他潜在符合条件的对象被过滤掉了

在上一个项目中,采用了放宽数量限制的做法,比如数量上限放大 3 倍,如果这里面都没有符合条件的,就直接忽略了,简单粗暴,但大多数战斗都是 5v5 的小规模作战,野外基本没有战斗,这么做没有问题。对于现在的项目,策划希望做大战斗场面,一部分野外也放开战斗,原有的做法就显得有点粗糙了。目前,通过将丰富的角色数据下沉到底层碰撞检测,我们实现了较为多样的条件查询,现在不仅是搜指定点附近的 n 个目标,还能搜索指定点附近的友军,碰撞类型是怪物,从而简化掉 Lua 层的过滤,提升了速度,也解决了漏目标的问题。这种做法的挑战在于往后会添加更多的查询,能不能全部放在底层实现,会是一个巨大的挑战

技能系统的核心部分是时间线,时间线类似于技能动作编辑器,安排了技能时间内以特定顺序实现各色技能效果。时间线的最小单位是一个功能单元,比如上 buff、召唤子弹、放嘲讽、单体攻击、放治疗等。实际配置时,使用的是单元的实例,固化了一组单元所用的参数,可以通过复用单元实例减少技能的配置量

时间线的另一个作用,则是配合客户端的技能效果展现。服务器出于性能考虑,技能组件的 update 频率是大大低于客户端的,如何配合这两套不同节奏的离散事件系统,实现较好的技能效果和打击手感,是需要策划同学花点心思的。一般的原则是服务器先行,客户端按美术表现来播放相应效果。其中比较难搞的是子弹攻击与飘字,服务器没有实际计算客户端子弹的飞行轨迹,也没法做到客户端播完子弹打击特效后,刚好塞一个扣血协议到客户端进行飘字。为了实现子弹打击效果和飘字的配合,服务器侧会先行计算,将飘字提前发到客户端进行缓存,客户端依次播放即可

一般技能的持续时间都比较短,为了实现更长的状态效果,便引入 buff 来处理状态间的互斥、顶替、叠加。状态类的效果包含了属性效果的加成削弱,也包括了眩晕等禁制效果,还有一些诸如吸附、击退等强制移动的效果,也有纯展示用,给客户端标记两个特定对象,以方便客户端做效果的。总的方向是 buff 只管理 buff 间的顶替规则,在创建、销毁等时机发事件,具体效果应该交由其他模块实现,目前并没有很好的做到这一点

除了人物技能外,还有怪物技能。由于人物技能策划和怪物技能策划希望实现的效果差别比较大,怪物技能用到的技能部分比较少,相当于一个阉割版的技能系统,只负责其中造成伤害的部分。技能释放的校验函数需要跳过,技能的前摇(预警)特效也要根据 AI 逻辑做差异化,所以这里技能系统只是充当着战斗公式模块的一个基石,提供一下属性对抗的数据而已。以后做更复杂的技能对抗,可能会有坑,比如之前做的技能打断,就需要怪物技能将前摇后摇都塞回技能里,不然连 AI 都需要处理技能打断效果了