算法流程
客户端逻辑:
1.判断当前帧F是否关键帧K1:如果不是跳转(7)。
2.如果是关键帧,则察看有没有K1的UPDATE数据,如果没有的话重复2等待。
3.采集当前K1的输入作为CTRL数据与K1编号一起发送给服务器
4.从UPDATE K1中得到下一个关键帧的号码K2以及到下一个关键帧之间的输入数据I。
5.从这个关键帧到下 一个关键帧K2之间的虚拟输入都用I。
6.令K1 = K2。
7.执行该帧逻辑
8.跳转(1)
服务端逻辑:
1.收集所有客户端本关键帧K1的CTRL数据(Ctrl-K)等待知道收集完成所有的CTRL-K。
2.根据所有CTRL-K,计算下一个关键帧K2的Update,计算再下一个关键帧的编号K3。
3.将Update发送给所有客户端
4.令K1=K2
5.跳转(1)
服务器根据所有客户端的最大RTT,平滑计算下一个关键帧的编号,让延迟根据网络情况自动调整。
乐观帧锁定
针对传统严格帧锁定算法中网速慢会卡到网速快的问题,实践中线上动作游戏通常用“定时不等待”的乐观方式再每次Interval时钟发生时固定将操作广播给所有用户,不依赖具体每个玩家是否有操作更新:
- 单个用户当前键盘上下左右攻击跳跃是否按下用一个32位整数描述,服务端描述一局游戏中最多8玩家的键盘操作为:int player_keyboards[8];
- 服务端每秒钟20-50次向所有客户端发送更新消息(包含所有客户端的操作和递增的帧号):
update=(FrameID,player_keyboards) - 客户端就像播放游戏录像一样不停的播放这些包含每帧所有玩家操作的 update消息。
- 客户端如果没有update数据了,就必须等待,直到有新的数据到来。
- 客户端如果一下子收到很多连续的update,则快进播放。
- 客户端只有按键按下或者放开,就会发送消息给服务端(而不是到每帧开始才采集键盘),消息只包含一个整数。服务端收到以后,改写player_keyboards
————-
虽然网速慢的玩家网络一卡,可能就被网速快的玩家给秒了(其他游戏也差不多)。但是网速慢的玩家不会卡到快的玩家,只会感觉自己操作延迟而已。另一个侧面来说,土豪的网宿一般比较快,我们要照顾。
随机数需要服务端提前将种子发给各个客户端,各个客户端算逻辑时用该种子生成随机数,另外该例子以键盘操作为例,实际可以以更高级的操作为例,比如“正走向A点”,“正在攻击”等。该方法目前也成功的被应用到了若干实时动作游戏中。
指令缓存
针对高级别的抽象指令(非前后可以覆盖的键盘操作),比如即时战略游戏中,各种高级操作指令,在“乐观帧锁定”中,客户端任何操作都是可靠消息发送到服务端,服务端缓存在对应玩家的指令队列里面,然后定时向所有人广播所有队列里面的历史操作,广播完成后清空队列,等待新的指令上传。客户端收到后按顺序执行这些指令,为了保证公平性,客户端可以先执轮询行每个用户的第一条指令,执行完以后弹出队列,再进入下一轮,直到没有任何指令。这样在即时战略游戏中,选择 250ms一个同步帧,每秒四次,已经足够了。如果做的好还可以象 AOE一样根据网速调整,比如网速快的时候,进化为每秒10帧,网速慢时退化成每秒4帧,2帧之类的。