[从零开始的Unity网络同步] 4.如何实现确定性的网络同步

发表于2018-11-15
评论6 6.2k浏览

上一篇文章中,总结得出确定性网络同步的必要性,那么接下来就考虑如何在Unity中实现确定性了.

1.服务端与客户端相同频率模拟(Simulate)

在Unity中,有三个更新方法Update, LateUpdate,FixedUpdate.
UpdateLateUpdate属于渲染帧,它们每帧间隔的时间会受到渲染物体的时间影响(LateUpdate是在所有的Update方法执行完后再执行),打个比方说:相同的游戏,在性能好的机器上可以跑60帧每秒,但是在差的机器上,可能只能跑30帧每秒.两者相差了1倍,甚至更多.
FixedUpdate是固定频率更新,常常用来处理Unity中物理相关的东西,它不受渲染效率的影响,以固定的时间间隔调用,
所以为了保证服务端和客户端的模拟频率一致,那么在Unity中,就选用FixedUpdate方法.在Unity中可以在Edit->Project Setting->time中找到Fixed timestep进行修改,也可以在代码中设置Time.fixedDeltaTime的值.

public void SetFixedDeltaTimeForServer ()
{ 
    Time.fixedDeltaTime = 1f/60;  //当服务器成功启动,设置FixedUpdate更新间隔为每秒60次    
}

public void SetFixedDeltaTimeForClient()
{
    Time.fixedDeltaTime = 1f/60;    //与服务端保持一致
}

这样,服务器和客户端的FixedUpdate方法都会按照相同的频率调用,然后把操作的模拟(Simulate)放在里面执行.

2.相同的状态 + 相同的操作指令 = 相同的新状态

为了让服务端和客户端模拟的结果相同,首先必须保证服务端和客户端的模拟逻辑代码一致,尽量减少使用默认的物理模拟(PS:引擎的物理模拟有些会带有随机数,一旦服务器和客户端的随机数不一致,会导致结果不一致),先来定义操作指令类(Command)

public class Command
{
    public uint sequence;          //指令序号
    public CommandInput input;  //操作指令的输入
    public CommandResult result;  //操作指令执行后得到的结果
}

Simulate方法需要做的应该就是收集操作指令CommandInput,然后执行,得到CommandResult

public void FixedUpdate()
{
    Simulate();
}

public void Simulate()
{
    OnSimulateBefore();
    if(isServer && isOwner)            //如果是服务器的物体,获取指令,直接执行指令即可
    {
        Command cmd = new Command ();
        cmd.input =  CollectCommandInput();      // 获取指令      
        ExecuteCommand(cmd);                    // 执行指令
    }
    OnSimulateAfter();
}

public override void ExecuteCommand(Command command)
{
    float movingSpeed = 4;
    Vector3 movingDir = Vector3.zero;
    if (input.forward ^ input.backward)
        movingDir.z = input.forward ? +1 : -1;
    if (input.left ^ input.right)
        movingDir.x = input.right ? +1 : -1;

    Vector3 velocity = movingDir * movingSpeed;                                  //通过输入计算出速度
    transform.position = transform.position + velocity * Time.fixedDeltaTime;    //立即计算出结果
    command.result.position = transform.position;                                //将结果保存到CommandResult中
}

CollectCommandInputExecuteCommand方法中,客户端和服务端的代码应该是一致的.
服务器和客户端,执行完Command以后,填充result需要的数据.这样,一个Command就完成了,经过网络同步以后,利用sequence(指令序号)来对比操作的结果是否一致.
操作结果如果:

3.小结

有了这个基本的Command的结构和相同频率的Simulate,后续就要考虑服务端和客户端如何去同步这些Command.

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引