美文网首页Unity3d相关(M)
Unity新网络Multiplayer

Unity新网络Multiplayer

作者: 欣羽馨予 | 来源:发表于2016-06-14 16:00 被阅读2049次

前言

随着Unity版本的更新,新版的网络系统Multiplayer也渐渐地越来越被重视,5.3.4版本测试很好用。使用这套网络系统可以轻松开发联机网络游戏,而且其中封装的API也针对于开发者的层次作了区分。HighLevelAPI(简称HLAPI)针对于简单的网络系统搭建,封装的比较严重,只需轻松几部即可完成网络环境的搭建;LowLevelAPI(简称LLAPI)偏向底层,网络环境的搭建,需要依靠底层类层层搭建,但较为灵活。根据不同的需求,可以选择不同的API,当然通常HLAPI和LLAPI是混合起来一起用的。本文会简单讲解Multiplayer的API架构,主要通过项目将所有内容串联。

  • HLAPI架构图
    首先给大家看一下UnityAPI中提供的一张Multiplayer的HLAPI架构图,清晰的了解我们常用的类的层次。


    HLAPI架构图
    • Transport/Configuration — 底层API类
    • Connection/Reader/Writer — 消息发送类、序列化与反序列化类
    • NetworkClient/NetworkServer — 网络环境搭建类
    • NetworkIDentity/NetworkBehaviour — 网络对象状态同步
    • NetworkManager — 网络游戏控制(一个组件搞定一个网络)
    • NetworkLobbyManager — 集成了网络游戏大厅功能
    • NetworkTransform/NetworkAnimator — 引擎继承的状态同步组件
  • 使用基础类(NetworkServer/NetworkClient)搭建网络环境

    • 服务器端
      NetworkServer.Listen(7777);//创建服务器监听本机网卡7777端口

    • 客户端
      NetworkClient client;//创建客户端对象
      client.Connect("127.0.0.1",7777);//连接服务器

  • 使用基础类(NetworkServer/NetworkClient)创建网络游戏对象


    服务器创建网络对象卵生到客户端
    • 客户端
      ClientScene.Ready(msg.conn); //通知服务器已准备完毕
      ClientScene.RegisterPrefab(playerPrefab); //注册网络预设体
      ClientScene.AddPlayer(0); //通知服务器实例化预设体
    • 服务器(只有服务器才能创建网络对象)
      GameObject player = (GameObject)Instantiate(playerPrefab);
      //给予该客户端该对象的权限
      NetworkServer.AddPlayerForConnection(netMsg.conn, player, 0);
      //卵生[同步到其他客户端]
      NetworkServer.Spawn(player);
  • 远程过程调用(RPC)


    网络环境下的远程消息发送
    • Command:由客户端发送给服务器[在服务器执行方法]

    • ClientRPC:由服务器发送给客户端[在客户端执行方法]

    • 客户端调服务器方法(Command方法名必须以Cmd开头)
      [Command]
      /// <summary>
      /// 发射炮弹
      /// </summary>
      void CmdFire ()
      {
      GameObject bullet = (GameObject)Instantiate (MyLobbyManager.instance.spawnPrefabs [0],firePoint.position, firePoint.rotation);
      bullet.GetComponent<Rigidbody> ().velocity = bullet.transform.forward * 20;
      NetworkServer.Spawn (bullet);
      }

    • 服务器调客户端方法(ClientRPC方法必须以Rpc开头)
      [ClientRpc]
      /// <summary>
      /// 播放特效稍后销毁
      /// </summary>
      /// <param name="eff">Eff.</param>
      void RpcStopEffect (GameObject eff)
      {
      eff.GetComponent<ParticleSystem> ().Play ();
      Destroy (eff, 1.05f);
      }

当然是用NetworkManager/NetworkLobbyManager组件同样可以搭建网络环境,且更为方便,这里不再赘述,详见项目。

  • 实战项目坦克大战


    坦克大战游戏大厅
    坦克大战主场景
  • 挑几个重点脚本看看
    1.大厅管理

using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;

public class MyLobbyManager : NetworkLobbyManager
{
    //单例
    public static MyLobbyManager instance;
    //是否开启切换场景标志位
    public bool beginChange = false;
    //背景音乐
    public GameObject backAud;
    //玩家位置编号
    private int playerPositionIndex = 0;

    void Awake ()
    {
        instance = this;
    }

    void Start ()
    {
        DontDestroyOnLoad (backAud);
    }

    /// <summary>
    /// 当所有玩家都已准备完毕
    /// </summary>
    public override void OnLobbyServerPlayersReady ()
    {
        //启动协程等待动画播放完毕
        StartCoroutine (PlayProgress ());
        //遍历所有客户端发送播放指令
        foreach (NetworkLobbyPlayer item in lobbySlots) {
            if (item) {
                (item as MyLobbyPlayer).RpcBeginPlay ();
            }
        }
    }

    IEnumerator PlayProgress ()
    {
        //如果还没有开始切换场景,继续播放动画,保持等待
        while (!beginChange) {
            yield return null;
        }
        base.OnLobbyServerPlayersReady ();
    }

    //当服务器添加玩家对象时调用
    public override void OnServerAddPlayer (NetworkConnection conn, short playerControllerId)
    {
        base.OnServerAddPlayer (conn, playerControllerId);
        //判断是否在游戏场景而非游戏大厅
        if (beginChange) {
            //创建坦克
            GameObject player = Instantiate (gamePlayerPrefab) as GameObject;
            //通过新场景的NetworkStartPosition确定坦克的创建位置
            player.transform.position = startPositions [playerPositionIndex++].position;
            //设置坦克脚本中的网络变量--坦克编号
            player.GetComponent<MyPlayer> ().tankNum = playerPositionIndex - 1;
            //给予客户端该坦克的使用权限
            NetworkServer.AddPlayerForConnection (conn, player, playerControllerId);
            //卵生坦克
            NetworkServer.Spawn (player);
        }
    }
}

2.大厅玩家

using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
using UnityEngine.UI;

public class MyLobbyPlayer : NetworkLobbyPlayer
{
    //玩家大厅名称显示
    private Transform content;
    //玩家大厅准备按钮
    private Button readyButton;
    //单例LobbyManager
    private MyLobbyManager manager;
    //倒计时进度条
    private GameObject progress;

    void Awake ()
    {
        manager = MyLobbyManager.instance;
        content = GameController.instance.content.transform;
        readyButton = transform.GetChild (0).GetComponent<Button> ();
    }

    void Start ()
    {
        //设置当前对象到UI中显示
        GameController.instance.SetParent (this.transform);
        //获取进度条
        progress = transform.parent.parent.parent.GetChild (3).gameObject;
        //重置缩放
        transform.localScale = Vector3.one;
        //非主机客户端更新玩家名称
        if (!isServer) {
            CmdUpdateItemName ();
        }
    }

    [Command]
    public void CmdUpdateItemName ()
    {
        //服务器开始下发指令
        RpcUpdateItemName ();
    }

    [ClientRpc]
    public void RpcUpdateItemName ()
    {
        //非主机客户端设置字体颜色
        content.GetChild (1).GetComponent<Image> ().color = Color.red;
        //非主机客户端设置玩家名称
        content.GetChild (1).GetChild (1).GetComponent<Text> ().text = "Player2";
    }

    /// <summary>
    /// 本地玩家执行
    /// </summary>
    public override void OnStartLocalPlayer ()
    {
        base.OnStartLocalPlayer ();
        //设置准备按钮可用
        readyButton.interactable = true;
        //移除所有监听
        readyButton.onClick.RemoveAllListeners ();
        //设置准备按钮事件监听
        readyButton.onClick.AddListener (OnReadyButtonClick);
    }

    /// <summary>
    /// 玩家准备按钮点击事件
    /// </summary>
    public void OnReadyButtonClick ()
    {
        //向服务器发送准备指令
        SendReadyToBeginMessage ();
        //移除该按钮所有事件监听
        readyButton.onClick.RemoveAllListeners ();
        //设置该按钮取消准备的事件监听
        readyButton.onClick.AddListener (OnNotReadyButtonClick);
    }

    /// <summary>
    /// 玩家取消准备按钮点击事件
    /// </summary>
    public void OnNotReadyButtonClick ()
    {
        //向服务器发送取消准备的指令
        SendNotReadyToBeginMessage ();
        //取消该按钮的所有事件监听
        readyButton.onClick.RemoveAllListeners ();
        //添加该按钮准备的事件监听
        readyButton.onClick.AddListener (OnReadyButtonClick);
    }

    /// <summary>
    /// 当客户端主播完毕后调用
    /// </summary>
    /// <param name="readyState">If set to <c>true</c> ready state.</param>
    public override void OnClientReady (bool readyState)
    {
        base.OnClientReady (readyState);
        //如果准备好了
        if (readyState) {
            //按钮文字显示为Done
            readyButton.GetComponentInChildren<Text> ().text = "Done";
        } else {
            //否则显示Ready
            readyButton.GetComponentInChildren<Text> ().text = "Ready";
        }
    }

    [ClientRpc]
    /// <summary>
    /// 客户端开始播放切换场景动画
    /// </summary>
    public void RpcBeginPlay ()
    {
        progress.SetActive (true);
    }
}

3.主场景玩家(坦克)

using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
using UnityEngine.UI;

public class MyPlayer : NetworkBehaviour
{
    [SyncVar]
    //坦克编号
    public int tankNum = 0;

    [SyncVar]
    //坦克血量
    public int health = 100;
    //坦克移动速度
    public float tankMoveSpeed = 3f;
    //坦克旋转速度
    public float tankTurnSpeed = 10f;
    //坦克发射的炮弹飞行速度
    public float fireSpeed = 20f;
    //声音片段
    public AudioClip idle;
    public AudioClip run;

    private Rigidbody rig;
    //观察点
    private Transform targetPoint;
    //坦克炮头
    private Transform gun;
    //发射点
    private Transform firePoint;
    //操纵轴
    private float hor, ver, gunDir;
    //坦克血条颜色
    private Color[] colors = new Color[]{ Color.red, Color.green };
    //坦克血条背景图片
    private Image healthColor;
    //坦克血条
    private Slider healthSlider;
    //结果UI
    private GameObject resultUI;
    //声音片段
    private AudioSource aud;

    void Awake ()
    {
        rig = GetComponent<Rigidbody> ();
        aud = GetComponent<AudioSource> ();
        targetPoint = transform.Find ("TargetPoint");
        gun = transform.Find ("TankTurret");
        firePoint = transform.Find ("TankTurret/FirePoint");
        healthColor = transform.Find ("HealthCanvas/Slider/Fill Area/Fill").GetComponent<Image> ();
        healthSlider = transform.Find ("HealthCanvas/Slider").GetComponent<Slider> ();
        resultUI = GameObject.FindWithTag ("UI");
    }

    /// <summary>
    /// 本地玩家Start触发
    /// </summary>
    public override void OnStartLocalPlayer ()
    {
        //如果是本地玩家
        if (isLocalPlayer) {
            //设置摄像机跟踪点
            Camera.main.GetComponent<MyCameraFollow> ().SetTarget (targetPoint);
        }
    }

    [ClientCallback]
    void Update ()
    {
        //设置血条背景颜色
        healthColor.color = colors [tankNum];
        //设置血条值
        healthSlider.value = health;
        //如果是本地玩家
        if (isLocalPlayer) {
            //操纵坦克
            hor = Input.GetAxis ("Horizontal");
            ver = Input.GetAxis ("Vertical");
            gunDir = Input.GetAxis ("GunDirection");
            rig.MovePosition (transform.position + transform.forward * ver * Time.deltaTime * tankMoveSpeed);
            transform.eulerAngles += Vector3.up * hor * tankTurnSpeed;
            gun.transform.eulerAngles += Vector3.up * gunDir * tankTurnSpeed;
            //如果坦克移动
            if (hor != 0 || ver != 0) {
                if (aud.clip == idle) {
                    aud.Stop ();
                    aud.clip = run;
                } else {
                    if (!aud.isPlaying) {
                        aud.Play ();
                    }
                }
            } else {
                if (aud.clip == run) {
                    aud.Stop ();
                    aud.clip = idle;
                } else {
                    if (!aud.isPlaying) {
                        aud.Play ();
                    }
                }
            }
            //发射炮弹
            if (Input.GetKeyDown (KeyCode.Space)) {
                CmdFire ();
            }
        }
        //如果血量见底
        if (health <= 0) {
            //本地玩家失败
            if (isLocalPlayer) {
                resultUI.transform.GetChild (0).gameObject.SetActive (true);
                resultUI.transform.GetChild (0).GetChild (0).GetComponent<Text> ().text = "GameOver";
                resultUI.transform.GetChild (0).GetChild (1).GetComponent<Text> ().text = "GameOver";
            }
            //非本地玩家胜利
            else {
                resultUI.transform.GetChild (0).gameObject.SetActive (true);
                resultUI.transform.GetChild (0).GetChild (0).GetComponent<Text> ().text = "Victory";
                resultUI.transform.GetChild (0).GetChild (1).GetComponent<Text> ().text = "Victory";
            }
        }
    }

    [Command]
    /// <summary>
    /// 发射炮弹
    /// </summary>
    void CmdFire ()
    {
        GameObject bullet = (GameObject)Instantiate (MyLobbyManager.instance.spawnPrefabs [0],
                                firePoint.position, firePoint.rotation);
        bullet.GetComponent<Rigidbody> ().velocity = bullet.transform.forward * 20;
        NetworkServer.Spawn (bullet);
    }
}
玩家准备界面
双方玩家都已准备完毕倒计时
主场景开炮射击

结束语

相比从前的老网络系统,新版网络解决了很多Bug,也进一步做了优化,没有出现老网络的尴尬问题,只是新网络同步帧速率有些低,有时候会出现延迟较大的情况,这方面还有待改进。新网络类多内容也多,感兴趣的同学还需要多去看API,关于新网络今后还会有续集喔,敬请期待。本次项目链接: http://pan.baidu.com/s/1jHMN9zS 密码: wh3n

相关文章

网友评论

  • 为你征服全世界:请问这个代码是不是用到了StandardAssets文件夹中的部分,我按着这个做后显示
    NetworkLobbyManager can't accept new connection [hostId: -1 connectionId: 0 isReady: False channel count: 0], not in lobby and game already in progress.
    是不是需要引用StandardAssets文件夹中的NetWork部分?
    欣羽馨予:不需要的
  • TobyStark:🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂🐂
  • Keropok:沙发

本文标题:Unity新网络Multiplayer

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