美文网首页网络编程游戏同步unity
[从零开始的Unity网络同步] 7.物理状态的网络同步

[从零开始的Unity网络同步] 7.物理状态的网络同步

作者: 烂笔头_27 | 来源:发表于2018-11-14 11:11 被阅读589次

    最近看GDC2018演讲 <<火箭联盟>>的物理与网络细节(需要科学上网),在Rocket League(以下简称RT)中,汽车和足球用的都是真实物理模拟,开发的引擎是Unreal Engine 3,使用的物理引擎是Bullet,在游戏中,客户端操控的东西都是带预测的,包括汽车的运动,球的运动,那么如何在Unity实现物理状态的网络同步呢?接下来就尝试着实现一下.

    1.根据操作输入,改变物理状态

    在场景中创建一个Cube,然后挂上Rigidbody组件,这样就拥有一个很简单的带物理的物体了.
    接收按键输入W,S,A,D,按下一个键就对Cube施加一个力.代码如下:

    //模拟一次
    public void Simulate()
    {
        OnSimulateBefore();
        if(isServer && isOwner)            //如果是服务器的物体,获取指令,直接执行指令即可
        {
            Command cmd = new Command ();
            cmd.input =  CollectCommandInput();      // 获取指令      
            ExecuteCommand(cmd);                    // 执行指令
        }
        OnSimulateAfter();
    }
    //收集键输入
    public CommandInput CollectCommandInput()
    {
        Command cmd = Command.Create();
        cmd.input.forward = Input.GetKey(KeyCode.W);
        cmd.input.backward = Input.GetKey(KeyCode.S);
        cmd.input.left = Input.GetKey(KeyCode.A);
        cmd.input.right = Input.GetKey(KeyCode.D);
        return cmd.input;
    }
    //执行操作输入,根据按键施加不同方向的力
    public override void ExecuteCommand(Command command)
    {
        CommandInput input = command.input;
        if (input.forward)
            rigidbody.AddForce(Vector3.forward * force * rigidbody.mass, ForceMode.Force);
        if (input.backward)
            rigidbody.AddForce(Vector3.back * force * rigidbody.mass, ForceMode.Force);
        if (input.left)
            rigidbody.AddForce(Vector3.left * force * rigidbody.mass, ForceMode.Force);
        if (input.right)
            rigidbody.AddForce(Vector3.right * force * rigidbody.mass, ForceMode.Force);
    }
    

    通过对刚体施加力,基本的物理操作就完成了.

    2.Unity下的确定性物理模拟

    在Unity中,内置的物理引擎是PhysX,依据确定性原则,首先得了解PhysX是确定性的吗?可以预测吗?,我在网上找了很多文章,也没找到一个确定的结果,有的说法说PhysX是确定性的,测试过很多次的结果都一致(文章Deterministic physics options),又有的说法说PhysX为了实现高效,牺牲了确定性,在不同平台下可能导致物理模拟结果不同等等,反正还没有得到确定的答案(毕竟PhysX的物理组件在Unity中没有开源,只有访问接口).
    Any way,先暂且把Unity中的物理模拟是确定性的(如果认为它是不确定的,那这篇就没必要写了:)),要实现物理的预测,需要自己手动调用物理模拟,Unity提供了一个APIPhysics.Simulate,通过将来Physics.autoSimulation = false关闭Unity内部的自动物理模拟,然后手动执行Physics.Simulate(fixedDeltaTime)来模拟一次物理.在调用结束后,就可以拿到模拟的结果了.

    private void Awake()
    {
        Physics.autoSimulation = false;                //关闭物理的自动模拟
    }
    public override void ExecuteCommand(Command command)
    {
        CommandInput input = command.input;
        if (input.forward)
            rigidbody.AddForce(Vector3.forward * force * rigidbody.mass, ForceMode.Force);
        if (input.backward)
            rigidbody.AddForce(Vector3.back * force * rigidbody.mass, ForceMode.Force);
        if (input.left)
            rigidbody.AddForce(Vector3.left * force * rigidbody.mass, ForceMode.Force);
        if (input.right)
            rigidbody.AddForce(Vector3.right * force * rigidbody.mass, ForceMode.Force);
    
        Physics.Simulate(Time.fixedDeltaTime);              //每执行一次指令,就手动模拟一次    
    
        command.result.velocity = rigidbody.velocity;                //模拟完立刻能取到模拟结果
        command.result.angularVelocity = rigidbody.angularVelocity;  //模拟完立刻能取到模拟结果
    }
    

    3.服务器将物理状态同步给客户端

    [从零开始的Unity网络同步] 5.服务器将状态同步给客户端(状态缓存,状态插值,估算帧)文章中,物体的状态只同步了positionrotation,现在把rigidbody的velocityangularVelocity加上

    public class State
    {
        public Vector3 position;                   //位置
        public Quaternion rotation;                //旋转
        public Vector3 velocity;                  //刚体速度
        public Vector3 angularVelocity;          //刚体角速度
    }
    

    按照第五篇文章的流程,服务器将状态同步给客户端的表现如下(6packet/second ):


    服务器模拟,下发给客户端.gif

    4.客户端上传操作给服务端,同时预测物理模拟

    [从零开始的Unity网络同步] 6.客户端本地预表现的流程一样,因为客户端调用了手动物理模拟Physics.Simulate(Time.fixedDeltaTime),每次收到服务器下发新的状态后,预测的客户端都重置状态,然后把缓存的指令执行一遍,同时调用Physics.Simulate(Time.fixedDeltaTime).客户端预测的表现如下(6packet/second ):

    客户端预测,服务器模拟.gif

    5.小结

    从上面的结果来看,Unity中物理模拟在网络同步中都用挺不错的表现,但是,其实还是存在问题的:

    手动物理模拟方法Physics.Simulate()是全局的,这就意味着调用一次,游戏内所有的物理物体都会模拟一次,没办法仅对单个物体进行模拟,想要改进的话,只有自己在代码逻辑中对物体的状态做备份
    如果涉及到游戏内很多的物体,频繁的单帧调用多次Physics.Simulate()尚不明确会带来多严重的效率问题.

    所以如果真的想在Unity中实现物理模拟的网络同步,建议最好能够使用自己可控的,确定性的物理模拟,比如说自己实现的简单物理模拟逻辑,或者使用插件Bullet Physics For Unity(独立于Unity之外,开放源代码,确定性的物理引擎)等等.

    相关文章

      网友评论

      本文标题:[从零开始的Unity网络同步] 7.物理状态的网络同步

      本文链接:https://www.haomeiwen.com/subject/gosdfqtx.html