[概述]
对于玩家自身而言, 场景中的角色分两种:自己,别的生物(包括别的玩家,monster, npc等)。而生物本身是一个集合{属性数据(状态), 行为(动作表现)}。
站在玩家自身的角度来看, 对于别的生物, 他们的所有数据和行为都是来自服务器, 客户端要做的相当于播放录像的功能, 根据服务器送过来的指令对他们进行控制和表现。
而玩家自身, 其行为来自两个地方:1、操作模块(比如说客户端的io操作, 程序发出的操作指令等, 操作模块内部封装了所有的操作指令, 对玩家对象进行控制), 2、服务器指令。操作模块的指令会向两个路径分发:1、向服务器发送请求,2、向玩家自身发送指令.
[角色行为]
和角色行为相关的变量:
1、 完成条件(即条件满足前不能进行其他行为)。
2、 位置(产生行为的角色位置)。
3、 方向。
4、 状态(此行为对应的角色状态)。
5、 动作表现。
[行为分类]
1、walk. 终点位置, 速度
2、run. 终点位置, 速度
3、攻击(施法). 位置,方向
4、受伤(被攻击).
5、死亡. 位置
6、冲撞、被冲撞、死亡击飞等运动轨迹相关. 终点位置
[玩家自身]
[一] 连续移动
1、客户端角色Me每预先跑一步A-->B, 都要等待服务器的返回(失败或者成功)。在服务器返回响应之前(Me有可能在移动过程中, 也有可能已经到达B点等候),客户端有可能存在以下操作:
a、移动操作, 即点击场景其他地方C(即玩家想下一步移动到C),此时应该把C立即加入到移动队列中。
如果服务器返回成功, 则继续处理移动队列;如果服务器返回失败, 则将Me拉回到A点, 并清空移动队列。
b、其他操作(如攻击)
如果服务器返回成功, 则清空移动队列, 并处理操作队列;如果服务器返回失败, 则将Me拉回到A点, 并清空移动队列, 操作队列。
[二] 攻击
客户端角色Me的位置CP, 服务器位置SP
1、CP与SP位置一致, 则立即发送attack指令到服务器, 并立即切换到attack状态,表现攻击动作。
2、CP与SP位置不一致(有可能正在移动过程中, 即已经发送move指令到服务器), 则立即发送attack指令到服务器(预先攻击)
a、如果attack指令返回成功, 则等待CP与SP相同时则前端进行攻击行为流程。
b、如果attack指令返回失败, 则清除标志, 并等待下一步操作。
3、尝试释放技能的时机
a、任务开始时在当前位置判断能否释放。
(有可能攻击失败, 因为玩家位置已变, 延时通知)
b、开始一次新的移动,先向服务请求移动,前端预移动,并且判断能否释放, 如果可以则预释放。服务器返回移动请求和释放请求成功后,等到达目的点之后表现攻击动作和特效,否则清除预攻击状态。
(有可能攻击失败, 因为玩家位置已变, 延时通知。这时候是否需要根据当前玩家移动的时间做一个判断策略?一次移动的时间间隔是560ms, 正常的网络传输延时极限(玩家移动广播到达之前+自己发送攻击请求到服务器延时)3个传输延时和2个服务器处理延时)。
c、收到攻击对象位置变化广播时,判断能否释放,如果可以, 则根据自己当前的状态进行释放或者预释放。
(这个时候成功率最高, 因为玩家位置延时最短。)
以上三个时机形成了开始一个攻击任务整个阶段的闭环。
[其它场景对象]
这里的处理模型, 主要用于处理行为队列中存在多个行为的情况(大部分原因是网络延迟引起的)
[一] 移动
1、 行为队列全部是移动行为, 两种处理方式:
a、 依次处理每个移动行为。
b、 根据队列长度,直接处理队尾的移动行为,并清空行为队列。
2、 行为队列最后一个行为是攻击行为, 同样是两种处理方式:
a、 依次处理每个移动行为。
b、根据队列长度,直接处理队尾的攻击行为,并清空行为队列。
[二] 攻击
处理模型同上。
[三] 服务器强制同步行为
服务器有时候会强制同步行为, 如瞬移等。 则立即清空行为队列, 并执行强制同步行为。
[操作模块]
操作模块是针对玩家自身的操作(io操作、自动挂机产生的操作等),将其转换为控制玩家对象的一个逻辑控制任务, 这里用operation表示,比如移动、释放技能、访问npc等都可称之为operation。
1、创建一个operation之前, 要先判断能否创建这个operation,不能则不响应玩家操作;若能, 则创建一个operation,并且插入到operation队列中。
例如玩家当前处于摆摊或则死亡等不能移动的状态, 如果这时候玩家操作想移动, 则直接返回不响应。
例如技能cd没到或魔法值不够等处于不能释放技能的状态, 如果这时候玩家操作想释放技能, 则直接返回不响应。
2、若当前正在执行的operation失败, 则要判断失败原因, 判断是否要重新尝试。
例如移动任务,在移动过程中遇到阻挡点或则服务器返回移动失败, 则重新寻路。寻路成功,则继续执行;失败,则放弃任务。
例如释放技能(有可能会创建一个移动的子任务),在执行过程中发现魔法值不足等问题, 则直接放弃任务。若是移动失败,则尝试重新寻路。
[行为队列模式(FIFO)]
无论是某个具体的动作行为,还是某个改变状态的操作, 都可以抽象为一个action cmd。
每个entity持有一个ActionModel, 每个ActionModel维护一个action list。
[一]实现细节
1、因为要频繁产生action cmd, 如何防止碎片化问题, 在解决这个问题的时候, 不能影响可维护性和扩展性, 尤其是扩展性。
2、action list: a1 a2 a3, 在表现上, 由于a2是一个持续性过程, a3有可能会影响a2的表现, 也就是a3可以在a2的执行过程中直接处理, 从而达到并发性表现(并发性的cmd可以使得表现更为丰富)。据此可以抽象, action cmd可以分为:互斥性action和并发性action。
action list的处理流程:
if(curAction.isEnd)
{
curAction = list.empty ? action_idle : list.pop_front;
} while(!list.front.isMutex(curAction))
{
Process(list.pop_front);
} curAction.update;
[事件与状态]
1、移动
{run, walk} --> {move}
failed:
拉回原地
2、攻击
{物理攻击} --> {物理攻击状态}
{魔法攻击} --> {魔法攻击状态}
failed:
只播放攻击动作, 不播放后续的特效
3、冲撞
{rush} --> {冲撞状态}
{rushed} --> {被冲撞状态}
4、受伤
{beHit} --> {hurt}
5、死亡
{die} --> {死亡状态}
6、采集
{gatherItem} --> {采集状态}
实时战斗系统问题锦集
1、人物动作处理策略
由于某个原因(网络延迟等), 当前正在处理的动作尚未结束(比如移动尚未到达目的地), 此时又收到另外一个要处理的动作协议,这时候的处理策略。
a、直接将人物状态设置到当前动作的目标状态, 然后开始新的动作。
b、按正常流程处理当前动作, 并且把要处理的动作入队, 待当前动作完成, 再依次处理动作队列里的内容。
问题衍生:如果把问题放大化, 即当前动作尚未结束, 后面收到一串待处理的动作, 此时的处理策略。
有可能出现的情况:
a、正在移动, 网络送来攻击行为 -->位置不一致
b、正在攻击, 网络送来移动行为 -->位置不一致
c、正在攻击, 网络送来新的攻击行为 -->表现不一致
处理原则:优先保证位置一致,其次保证表现上的一致性。
处理策略(实际上这里的动作处理, 仅仅是影响人物的表现, 还有坐标位置):
动作按处理的紧急:
<1>可以延迟处理的,放入队列缓存
<2>需要立即处理的(如冲撞, 不立即处理的话,冲撞和被冲撞的表现不一致了。如怪物正在攻击(表现攻击动作))
2、技能释放的处理策略
技能分类:
按技能的释放方式:主动、被动
按攻击目标的数量:群攻、单体
按是否需要目标:需要、不需要
按是否魔法锁定:锁定、不锁定
按施法距离:近身、远程
按作用目标:只能对自己、只能对队友、只能对敌人
技能释放的前提条件:
cd、距离、魔法量、目标
技能释放的要素:
释放人、目标、方向、 目标点
<1>从当前位置释放,作用于目标(或目标点)(要判断施法距离)
<2>在当前位置朝当前方向释放技能(战士技能)
<3>在当前位置朝鼠标方向释放技能(shift+左键攻击或者两个比较特殊的技能(地狱冰封,极光电影))
<4>施加于自身(可以合并到<1>, 只是不用判断施法距离)
特殊案例:
群体治疗术、群体隐身术(作用于当前鼠标位置)
单体治疗术、单体隐身术(作用于当前鼠标所在的目标, 如果没有目标,作用于自身)
3、打击感的体现
4、鼠标和键盘按着不放进行操作的延时机制
如果按着鼠标或者键盘不放,一秒钟可以触发次数:
鼠标:由程序主线程控制, window系统不会主动触发
键盘:34次/s
如果窗口样式指定了CS_DBLCLKS, 则双击鼠标产生的事件:
down->up->double click->up
否则:
down->up->down->up,此时需要使用者根据一定的规则自己检测并生成双击事件。