技能系统设计笔记 5
记录时间:2009年12月14日
接12月12日
另外一个问题是操作与逻辑的一致性和即时性,例如本地玩家在执行一个技能时突然中断了当前操作,技能的逻辑也应该立即中断,后面的关键事件不会执行。看起来合理的需求在网络上实现起来就有问题了,因为中断信号也许不能即时的到达网络终端。实现时这带来了或多或少的麻烦。例如在技能开始后0.5″打断它,在网络终端上可能表现成0.6″才被打断,如果两个终端都进行逻辑计算,那情况就会变得更加严重。
由于早期实现时两个终端都要进行逻辑计算,因此唯一的办法是保证两个终端的运行时间完全一致。我们使用客户端时间去更新服务器上的技能逻辑,虽然问题被解决了,但带来了更严重的设计问题,服务器的更新不在是时间唯一的了。有些系统需要用服务器的本地时间更新,有些系统又需要用客户端时间更新,这种不一致可能会带来更深层次的实现问题和安全问题。而且在更新频率上相信客户端也会带来很多安全性问题。
其实如果逻辑计算都统一到服务器终端,不同终端间的运行时间也不需要完全一致,我们完全可以使用不同的本地时间去更新技能逻辑,这样带来的缺陷必然是对操作上的体验会有一定延时,但不会造成逻辑错误和奇怪的设计。
技能事件和处理流程
如果将技能设计为一个逻辑独立的系统,必然需要通过某种方式与其他系统进行交互。典型的交互行为为读取,修改其他系统的数据。当然从另一个角度来讲,其他系统也需要在技能的处理过程中得到某种通知,以便进行自身的逻辑运算(例如PK中每次攻击带来的其他逻辑关系)。设计的时候,我们将上述关系拆分成了两种行为:直接访问和间接通知。在技能系统读取其他逻辑数据的时候,采用直接访问的方式,因为这里不需要考虑时序(如果使用间接方式)问题,也不影响技能系统本身的内封闭性。而技能系统本身可以看成是一个大的事件处理器,根据时间和部分简单的外部接口(主要是操作层面),会源源不断的产生各种事件,并将这些事件通知到外围的各个系统。
技能事件是技能系统产生逻辑的一个重要步骤,其实在实现层面上,技能系统被拆分为技能执行和技能状态两个模块,这两模块间也是通过技能事件来交互的。这里实际上有设计问题,下面再详细说明。技能事件大体上可以分为
enum Skill_Event_Type
{
SET_INVALID = 0,
SET_POSITIVE_EVENT = 1, //主动事件 脚本产生
SET_INTERRUPT = 2, //打断事件
SET_ATTACK = 3, //攻击事件
SET_DAMAGE = 4, //伤害事件 参数1:伤害逻辑ID 参数2:伤害类型 参数3:伤害值
SET_BEGIN_CAST_SKILL = 5,
SET_END_CAST_SKILL = 6,
SET_MISS = 7, //未命中 参数1:伤害逻辑ID 参数2:伤害类型
SET_CRITICAL_DAMAGE = 8, //暴击 参数1:伤害逻辑ID 参数2:伤害类型 参数3:伤害值
SET_KILL = 9, //杀死
SET_HP_RECOVER = 10, //HP恢复 参数1:来源类型 参数2:恢复值
SET_MP_RECOVER = 11, //MP恢复 参数1:来源类型 参数2:恢复值
SET_HP_COST = 12, //消耗HP事件 参数1:来源类型 参数2: HP
SET_MP_COST = 13, //消耗MP事件 参数1:来源类型 参数2: MP
SET_ATTACH_STATE = 14, //添加buff事件 参数1: 来源类型,参数2: stateTypeID
SET_DETACH_STATE = 15, //驱散buff事件 参数1: 来源类型, 参数2: stateTypeID
SET_NEGATIVE_EVENT = 21, //被动事件 脚本处理
SET_BE_INTERRUPT = 22,
SET_BE_ATTACK = 23,
SET_BE_DAMAGE = 24, //被伤害事件 参数1:伤害逻辑ID 参数2:伤害类型 参数3:伤害值
SET_BE_BEGIN_CAST_SKILL = 25,
SET_BE_END_CAST_SKILL = 26,
SET_BE_MISS = 27, //未被命中 参数1:伤害逻辑ID 参数2:伤害类型
SET_BE_CRITICAL_DAMAGE = 28, //被暴击
SET_BE_KILL = 29, //被杀死
SET_BE_HP_RECOVER = 30, //HP被恢复 参数1:来源类型 参数2:恢复值
SET_BE_MP_RECOVER = 31, //MP被恢复 参数1:来源类型 参数2:恢复值
SET_BE_ATTACH_STATE = 32, //被添加buff事件 参数1: 来源类型,参数2: stateTypeID
SET_BE_DETACH_STATE = 32, //被驱散buff事件 参数1: 来源类型,参数2: stateTypeID
SET_MAX_COUNT,
};
将整个流程设计为事件触发是有好处的,因为它很大程度上降低了模块之间的嵎合度,而且也使逻辑流程简化。可惜的是整个游戏系统中的事件无法用技能事件完全描述,有很多其他系统产生的事件没有被包含到这个设计中。
上面的设计其实还有一个比较严重的问题,就是技能执行和技能状态之间的事件机制,其实当一个事件产生的时候,事件双方的技能状态都会参与到这个事件的逻辑计算中,并以最终计算结果作为该事件的结果发放给其他系统。开始实现的时候没有按照这个逻辑来作,而是将原始事件本身发放事件双方的状态独立处理,这导致一个事件的最终结果对于双方来说可能是不一样的。