引言
我一直想了解早期游戏的网络同步,特别是早期的很多对战平台,做了很多单机游戏的对战,而这些同步又可以达到很不可思议的效果。从网络上我得知这种方案叫帧锁定,即保证每帧都是一致的。否则就锁定。比如玩dota时出现的万恶的等待框,本篇文章就希望简单的从客户端代码层面模拟这个过程(实际上到最后,我觉得纯粹用客户端模拟还是不足),另外作为整个网络同步系列的最后一个章节。
基本原理
对于单机游戏或者可联网的局域网游戏来说,大部分是没有服务器的,所有的游戏逻辑都放在客户端中处理,这导致的,只要每个用户所给的输入一致,时间一致,就能达到同样的模拟效果,所以帧锁定的基本原理便是如此。
算法流程
1.客户端定时(比如每五帧)上传控制信息。
2.服务器收到所有控制信息后广播给所有客户。
3.客户端用服务器发来的更新消息中的控制信息进行游戏。
4.如果客户端进行到下一个关键帧(5帧后)时没有收到服务器的更新消息则等待。
5.如果客户端进行到下一个关键帧时已经接收到了服务器的更新消息,则将上面的数据用于游戏,并采集当前鼠标键盘输入发送给服务器,同时继续进行下去。
6.服务端采集到所有数据后再次发送下一个关键帧更新消息。
C/S逻辑
客户端逻辑:
-
判断当前帧F是否关键帧K1:如果不是跳转(7)。
-
如果是关键帧,则察看有没有K1的UPDATE数据,如果没有的话重复2等待。
-
采集当前K1的输入作为CTRL数据与K1编号一起发送给服务器
-
从UPDATE K1中得到下一个关键帧的号码K2以及到下一个关键帧之间的输入数据I。
-
从这个关键帧到下 一个关键帧K2之间的虚拟输入都用I。
-
令K1 = K2。
-
执行该帧逻辑
-
跳转(1)
服务端逻辑:
-
收集所有客户端本关键帧K1的CTRL数据(Ctrl-K)等待知道收集完成所有的CTRL-K。
-
根据所有CTRL-K,计算下一个关键帧K2的Update,计算再下一个关键帧的编号K3。
-
将Update发送给所有客户端
-
令K1=K2
-
跳转(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();
|
演示
Main.swf
总结
可以从flash演示中看到,两个场景在低延迟的情况下同步效果非常理想,甚至远远超过之前的客户端预测例子,不过这种做法的缺点就是当延迟过大时会极大的影响控制手感,另外延迟受最慢的客户端影响。
当服务器迟迟没有收到每五帧的所有控制输入时,就会发送通知给所有客户端,出现dota或者war3联网时的那个网络延迟框。在war3中,因为没有专门服务器,所以这个责任是由主机承担的。
对于游戏中的随机元素,则可以在游戏初始化时,由主机同步随机种子到所有客户端中。
参考文献