Unity Mirror联网游戏开发(3) 玩家游戏对象

838 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情

玩家游戏对象

Mirror对于客户端的连接抽象成了一个Player Game Object,当客户端连上服务器并开始游戏后,就会生成一个玩家游戏对象。这个玩家游戏对象同样是一个网络游戏对象,是存在于服务器上的,但是不同于普通的网络游戏对象,该对象能从客户端接受到玩家的控制信息,并且通过command发送到服务器,然后服务器再将该对象同步到其他客户端。

客户端脚本操作

在客户端的Network Behaviour脚本中,我们通过 isLocalPlayer 来判断当前操作的player game object是否是自己的玩家游戏对象,即Local player对象。如果是local player,则我们可以对player进行操作,比如处理输入,根据Player的位置更新camera,或者任何针对本地玩家的操作。

客户端主动操作的转发

对于Local player的操作,客户端通过command发送到服务器,然后服务器再进行转发,将local player在服务器上对应的player game object的状态同步到其他客户端上对应的game object中。对于位置和动画,可直接使用NetworkTransform和NetworkAniamtor进行同步,而其他属性可通过SyncVars进行同步,并且当某客户端发送Command之后,服务器上的方法被调用,此方法不一定是改变该Player Object的状态,有可能是触发某事件,则可通过在服务器上进行ClinetRPC调用,来让特定的或所有的客户端执行某个方法。通过这一机制,我们写网络游戏程序时,只要关注于当前本地player需要进行的操作,然后去响应服务器给客户端的远程RPC调用,再加上属性的同步,基本就可以完成游戏的主要网络交互了。

一个例子

我们以Mirror Sample中的Tanks项目为例。玩家连上服务后,会生成一个Tank对象,该对象使用Tank预制体,该预制体上挂载了Tank脚本:

public class Tank : NetworkBehaviour
    {
        [Header("Components")]
        public NavMeshAgent agent;
        public Animator animator;
        public TextMesh healthBar;
        public Transform turret;

        [Header("Movement")]
        public float rotationSpeed = 100;

        [Header("Firing")]
        public KeyCode shootKey = KeyCode.Space;
        public GameObject projectilePrefab;
        public Transform projectileMount;

        [Header("Stats")]
        [SyncVar] public int health = 4;

        void Update()
        {
            // always update health bar.
            // (SyncVar hook would only update on clients, not on server)
            healthBar.text = new string('-', health);

            // movement for local player
            if (isLocalPlayer)
            {
                // rotate
                float horizontal = Input.GetAxis("Horizontal");
                transform.Rotate(0, horizontal * rotationSpeed * Time.deltaTime, 0);

                // move
                float vertical = Input.GetAxis("Vertical");
                Vector3 forward = transform.TransformDirection(Vector3.forward);
                agent.velocity = forward * Mathf.Max(vertical, 0) * agent.speed;
                animator.SetBool("Moving", agent.velocity != Vector3.zero);

                // shoot
                if (Input.GetKeyDown(shootKey))
                {
                    CmdFire();
                }

                RotateTurret();
            }
        }

        // this is called on the server
        [Command]
        void CmdFire()
        {
            GameObject projectile = Instantiate(projectilePrefab, projectileMount.position, projectileMount.rotation);
            NetworkServer.Spawn(projectile);
            RpcOnFire();
        }

        // this is called on the tank that fired for all observers
        [ClientRpc]
        void RpcOnFire()
        {
            animator.SetTrigger("Shoot");
        }

在Update方法中,判断 isLocalPlayer 来对tank进行旋转和移动操作,并且当按下开火键时,执行CmdFire命令,该命令是在server上执行的,该命令中会调用 RpcOnFire方法,该方法是在所有客户端上执行的。