网络游戏的移动同步(一)网络同步演示

引言

网络游戏的同步处理是在制作网络游戏中独有的处理方案,现在网络游戏几乎都是C/S架构,也就是说需要同步其他主机发送给服务器,并转发回本机的包,这里就涉及到如何发包给服务器,发哪些包,另外就是接收到这些包之后,客户端如何处理这些包的问题。

在不进行示例之前,如果简单的思考一下这些问题,一般人都会想到的是,其实只需要发布初始状态,然后每次变更发送更新状态即可。为了使这个问题简单化,本文的处理主要是移动同步,这个也是最常见,并且包量最大的一个协议,下面我们先创建这个更新包。

简单的更新协议

1
2
3
4
5
6
7
public class PlayerStatePack
{
    public var velocity:Vector2;
    public var position:Vector2;
    public var acceleration:Vector2;
    public var time:uint;
}

3个2维向量分别是位置,速度,加速度,此示例以2维为主,一般游戏可能用加速度比较少一些,但是为了更好的模拟更多的游戏,比如一些赛车或者炮弹之类的,这里的更新包中还有加速度。时间是发送包的时间,加这个主要是为了接收方能预知此时的真实位置。因为接收到的时候,发送方实际已经不在那个位置。

虚拟服务器

为了在客户端中直接模拟服务器延迟,可以直接做个简单的类模拟延迟。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Client
{
    // 模拟收到的网络包
    public var packets:Vector.;
 
    public function Client()
    {
        packets = new Vector.();
    }
 
    public function send(p:PlayerStatePack):void {
        // 延迟加入队列
        var delayTime:int = MathUtil.rand(Global.delay - Global.range, Global.delay + Global.range);
 
        setTimeout(sendNow(p), delayTime);
    }
 
    private function sendNow(p:PlayerStatePack):Function
    {
        return function():void {
            packets.push(p);
        }
    }
}

类的功能很简单,就是发送的时候增加setTimeout,另外这里有两个配置量,延迟量以及延迟波动量,这两个即可比较好的模拟出真实的网络环境,接收方处理的时候,直接从packets数组中获取包处理即可。

第一次尝试 假设这是一个简单的键盘移动游戏,因为鼠标移动同步相对比较简单,甚至于包都可以不需要那么复杂,只需要目标点就可以。 我们开始写发送及接收处理的代码。主循环代码如下

 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
// 一般的线性移动
localPlayer.velocity.x = 0;
localPlayer.velocity.y = 0;
if (InputManager.instance.keyDown(Keyboard.LEFT)) {
    localPlayer.velocity.x = -localPlayer.speed;
}
if (InputManager.instance.keyDown(Keyboard.RIGHT)) {
    localPlayer.velocity.x = localPlayer.speed;
}
if (InputManager.instance.keyDown(Keyboard.UP)) {
    localPlayer.velocity.y = -localPlayer.speed;
}
if (InputManager.instance.keyDown(Keyboard.DOWN)) {
    localPlayer.velocity.y = localPlayer.speed;
}
// 发送协议
var p:PlayerStatePack = new PlayerStatePack();
p.velocity = localPlayer.velocity.clone();
p.position = localPlayer.position.clone();
p.acceleration = localPlayer.acceleration.clone();
p.time = getTimer();
client.send(p);
 
while (client.packets.length > 0) {
    var rp:PlayerStatePack = client.packets.shift();
    // 直接拉扯
    if (smoothMethodCb.selectedIndex == 0) {
        netPlayer.position.x = rp.position.x;
        netPlayer.position.y = rp.position.y;
        netPlayer.velocity = rp.velocity;
    }
}
 
// 更新场景
localScene.update();
netScene.update();

看这个简单的游戏主循环,主要的流程是获取输入->本地玩家更新->发送更新包->接收更新包->处理游戏逻辑。为了使整个游戏代码清晰,我清理了不太需要的细节代码。

这个简单的网络同步演示如下

Network.swf

这个演示明显有几个问题,比如发送包太频繁(停止也在发送,因为是每帧发送),另外接受包的处理也很生硬,当网络延迟波动过大,拉扯现象非常明显。

updatedupdated2021-01-202021-01-20