网络游戏的移动同步(四)帧锁定算法
发表于2016-07-16
一、引言
我一直想了解早期游戏的网络同步,特别是早期的很多对战平台,做了很多单机游戏的对战,而这些同步又可以达到很不可思议的效果。从网络上我得知这种方案叫帧锁定,即保证每帧都是一致的。否则就锁定。比如玩dota时出现的万恶的等待框,本篇文章就希望简单的从客户端代码层面模拟这个过程(实际上到最后,我觉得纯粹用客户端模拟还是不足),另外作为整个网络同步系列的最后一个章节。
二、基本原理
对于单机游戏或者可联网的局域网游戏来说,大部分是没有服务器的,所有的游戏逻辑都放在客户端中处理,这导致的,只要每个用户所给的输入一致,时间一致,就能达到同样的模拟效果,所以帧锁定的基本原理便是如此。
三、算法流程
1、客户端定时(比如每五帧)上传控制信息。
2、服务器收到所有控制信息后广播给所有客户。
3、客户端用服务器发来的更新消息中的控制信息进行游戏。
4、如果客户端进行到下一个关键帧(5帧后)时没有收到服务器的更新消息则等待。
5、如果客户端进行到下一个关键帧时已经接收到了服务器的更新消息,则将上面的数据用于游戏,并采集当前鼠标键盘输入发送给服务器,同时继续进行下去。
6、服务端采集到所有数据后再次发送下一个关键帧更新消息。
四、C/S逻辑
客户端逻辑:
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)
五、客户端模拟代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | // 如果当前是关键帧 if (Global.frame == keyFrame) { // 查看是否有服务器的更新包,当前关键帧编号,下一关键帧编号,所有玩家的控制信息 // 获取更新包 var rp:PlayerInputPack; while (client.packets.length > 0) { rp = client.packets.shift(); if (rp.frame == keyFrame) { break ; } } // 如果等不到当前帧的控制数据,则返回 if (rp && rp.frame == keyFrame) { var nextFrame: int = keyFrame + 5; // 采集当前的输入作为包发送 var p:PlayerInputPack = new PlayerInputPack(); p.frame = nextFrame; p.input = InputManager.instance.keyStatus; client.send(p); // 以rp.input做输入数据 // 模拟移动本地及网络客户端 // 每个客户端的逻辑一致 var players:Array = [localPlayer, netPlayer]; for each (var player:Player in players) { player.velocity.x = 0; player.velocity.y = 0; if (rp.input[Keyboard.LEFT]) { player.velocity.x = -player.speed; } if (rp.input[Keyboard.RIGHT]) { player.velocity.x = player.speed; } if (rp.input[Keyboard.UP]) { player.velocity.y = -player.speed; } if (rp.input[Keyboard.DOWN]) { player.velocity.y = player.speed; } } // 下一个关键帧 keyFrame = nextFrame; waitTime = 0; } else { waitTime += Global.elapse; // 等待太久了,类似魔兽争霸的那个超时面板 if (waitTime > 1000) { trace( '等待不到控制包信息' , keyFrame); } } } else { // 当前帧步进 Global.frame++; } // 更新场景 localScene.update(); netScene.update(); |
六、演示

七、总结
可以从flash演示中看到,两个场景在低延迟的情况下同步效果非常理想,甚至远远超过之前的客户端预测例子,不过这种做法的缺点就是当延迟过大时会极大的影响控制手感,另外延迟受最慢的客户端影响。
当服务器迟迟没有收到每五帧的所有控制输入时,就会发送通知给所有客户端,出现dota或者war3联网时的那个网络延迟框。在war3中,因为没有专门服务器,所以这个责任是由主机承担的。
对于游戏中的随机元素,则可以在游戏初始化时,由主机同步随机种子到所有客户端中。
八、参考文献
http://blog.csdn.net/skywind
This entry was posted in 游戏开发, 网络编程 and tagged frame lock, lock-step, 帧锁定, 移动同步, 网络同步 on February 19, 2014.