zoukankan      html  css  js  c++  java
  • NetworkManager网络通讯_NetworkLobbyManager(三)

    此部分可以先建立游戏大厅,然后进入游戏,此处坑甚多耗费大量时间。国内百度出来的基本没靠谱的,一些专栏作家大V也不过是基本翻译了一下用户手册(坑啊),只能通过看youtube视频以及不停的翻阅用户手册解决(很多demo根本没踩到这些坑)。废话不说了,开始(此部分是在上一篇基础上开始的,一些上一篇解释过的操作就不再重复),建议看完此部分后可以看一下下一篇,有助于理解此部分的代码:

    1)自定义NetworkLobbyManager

    自定义脚本LobbyManager并重写NetworkLobbyManager的虚方法,惯例脚本在文末。

    2)挂载脚本LobbyManager

    挂载脚本是有讲究的,此脚本继承自NetworkLobbyManager。NetworkLobbyManager需要两个场景,即lobby场景和游戏场景,前者obby场景为准备场景(可以是像王者荣耀开始前队友列表UI,可以像吃鸡游戏开始之前的小岛场景),后者game场景即为真实游戏进行的场景。同时,对应的 需要两个参数lobby Player和gamePlayer,lobbyPlayer为进入lobby的游戏物体,game Player为真是游戏时的游戏物体。NetworkLobbyManager根据lobby场景中的lobbyPlayer在游戏场景中生成gamePlayer,但是当加载游戏场景后如果lobbyPlayer被销毁,则无法生成gamePlayer,所以加载场景时不能销毁。 而NetworkLobbyManager继承自NetworkManager,所以有DontDestroyOnLoad选项(自定义脚本不需要再继续添加DontDestroyOnLoad选项。而且在某些非常态lobby场景重新加载时(稍后会说到一些可能的重新加载场景)会有多个NetworkLobbyManager),所以生成的lobby Player最好是NetworkLobbyManager的子游戏物体。

    3)游戏列表 

    游戏列表其实为了把unityService中建立的游戏进行列表,可供玩家选择加入。不同的游戏是通过LobbyMatch脚本控制,在有些列表中把每一个LobbyMatch实例化出来

    4)Player定义

    gamePlayer与上一篇(其实是本系列第一篇)中NetworkManager的game Player定义相同,必须包含NetworkIdentity和NetworkTransform(如果需要),在增加其他诸如移动,血量控制的脚本。lobbyPlayer则必须继承自Network LobbyPlayer。本篇通过自定义的LobbyPlayer(继承自Network LobbyPlayer)以及Lobby PlayerUI(lobby场景中LobbyPlayer为ui形态),两者脚本在文末。

    PS:通过对NetworkLobbyManager应用发现很多坑,所幸基本坑都填上,坑实在太多,但是大部分均在代码中注释清楚,更加详细解释单独在下篇文章中解释。

    //———————————————————————脚本————————————————————————//

    using UnityEngine.Networking;
    using UnityEngine.Networking.Match;
    using System;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class LobbyManager : NetworkLobbyManager
    {
        public static LobbyManager theLobbyManager;
    
        #region OUTER ACITONS
        public Action<MatchInfo> matchCreatedAction;
    
        public Action<List<MatchInfoSnapshot>> matchListedAction;
    
        public Action destroyMatchAction;
    
        public Action dropConnAction;
    
        public Action clientSceneChangedAction;
        #endregion
    
        #region HOST CALLBACKS
        public override void OnStartHost()
        {
            LogInfo.theLogger.Log("Host create");
    
            base.OnStartHost();
        }
    
        public override void OnStopHost()
        {
            LogInfo.theLogger.Log("Host stop");
    
            base.OnStopHost();
        }
    
        public override void OnStopClient()
        {
            LogInfo.theLogger.Log("Host client");
    
            base.OnStopClient();
        }
    
        public override void OnStartClient(NetworkClient lobbyClient)
        {
            LogInfo.theLogger.Log("Client create");
    
            base.OnStartClient(lobbyClient);
        }
    
        public override GameObject OnLobbyServerCreateLobbyPlayer(NetworkConnection conn, short playerControllerId)
        {
            int num = 0;
            foreach (var p in lobbySlots)
            {
                if (p != null)
                {
                    num++;
                }
            }
    
            LogInfo.theLogger.Log("ServerCreateLobbyPlayer:" + num);
    
            return base.OnLobbyServerCreateLobbyPlayer(conn, playerControllerId); ;
        }
    
        public override GameObject OnLobbyServerCreateGamePlayer(NetworkConnection conn, short playerControllerId)
        {
            LogInfo.theLogger.Log("ServerCreateGamePlayer:" + lobbySlots.Length);
    
            return base.OnLobbyServerCreateGamePlayer(conn, playerControllerId);
        }
    
        public override void OnLobbyServerPlayerRemoved(NetworkConnection conn, short playerControllerId)
        {
            LogInfo.theLogger.Log("LobbyServerPlayerRemoved");
    
            base.OnLobbyServerPlayerRemoved(conn, playerControllerId);
        }
    
        public override void OnServerRemovePlayer(NetworkConnection conn, PlayerController player)
        {
            LogInfo.theLogger.Log("ServerRemovePlayer");
    
            base.OnServerRemovePlayer(conn, player);
        }
    
        public override void OnLobbyClientSceneChanged(NetworkConnection conn)
        {
            LogInfo.theLogger.Log("LobbyClientSceneChanged");
    
            base.OnLobbyClientSceneChanged(conn);
        }
    
        public override void OnLobbyServerPlayersReady()//当所有当前加入的player均准备后调用此方法,但加入的玩家数不一定定于maxPlayer
        {
            LogInfo.theLogger.Log("LobbyServerPlayersReady:"+lobbySlots.Length);
    
            //base方法当所有加入的player均准备后进入游戏场景
            //base.OnLobbyServerPlayersReady();
    
            int readyPlayersNum = 0;
    
            foreach(var player in lobbySlots)
            {
                if (player != null && player.readyToBegin) readyPlayersNum++;
            }
    
            if (readyPlayersNum == maxPlayers) BeginGame();
        }
        #endregion
    
        #region MATCH CALLBACKS
        public override void OnMatchCreate(bool success, string extendedInfo, MatchInfo matchInfo)
        {
            LogInfo.theLogger.Log("Match create");
    
            base.OnMatchCreate(success, extendedInfo, matchInfo);
    
            if (matchCreatedAction != null)
            {
                matchCreatedAction(matchInfo);
            }
        }
    
        public override void OnMatchJoined(bool success, string extendedInfo, MatchInfo matchInfo)
        {
            LogInfo.theLogger.Log("Match joined_" + matchInfo.address);
    
            Debug.Log("Before callback Manager==null:" + (theLobbyManager == null));
    
            base.OnMatchJoined(success, extendedInfo, matchInfo);
    
            Debug.Log("After callback Manager==null:" + (theLobbyManager == null));
        }
    
        public override void OnMatchList(bool success, string extendedInfo, List<MatchInfoSnapshot> matchList)
        {
            base.OnMatchList(success, extendedInfo, matchList);
    
            LogInfo.theLogger.Log("Match list:" + matchList.Count);
    
            if (matchListedAction != null)
            {
                matchListedAction(matchList);
            }
        }
    
        public override void OnDestroyMatch(bool success, string extendedInfo)
        {
            LogInfo.theLogger.Log("DestroyMatch");
    
            base.OnDestroyMatch(success, extendedInfo);
    
            StopMatchMaker();
            StopHost();//Local Connection already exists
    
            if (destroyMatchAction != null)
            {
                destroyMatchAction();
            }
        }
    
        public override void OnDropConnection(bool success, string extendedInfo)
        {
            LogInfo.theLogger.Log("DropConnection");
    
            base.OnDropConnection(success, extendedInfo);
    
            if (dropConnAction != null)
            {
                dropConnAction();
            }
        }
        #endregion
    
        #region CLIENT CALLBACKS
        public override void OnClientConnect(NetworkConnection conn)
        {
            LogInfo.theLogger.Log("Client connect");
    
            base.OnClientConnect(conn);
            LogInfo.theLogger.Log("ClientScene players:"+ClientScene.localPlayers.Count);
    
            conn.RegisterHandler(LobbyPlayer.playerMsg, HandlePlayMsg);
        }
    
        public override void OnClientDisconnect(NetworkConnection conn)
        {
            LogInfo.theLogger.Log("Client disconnect");
    
            base.OnClientDisconnect(conn);
        }
    
        public override void OnClientSceneChanged(NetworkConnection conn)
        {
            LogInfo.theLogger.Log("ClientSceneChanged");
            Debug.Log("ClientSceneChanged");
    
            if(clientSceneChangedAction!=null)
            {
                clientSceneChangedAction();
            }
            //base中回调执行ClientScene.AddPlayer(conn, 0, msg);
            //A connection has already been set as ready.There can only be one.
            //UnityEngine.Networking.NetworkLobbyManager:OnClientSceneChanged(NetworkConnection)
            //base.OnClientSceneChanged(conn); 
    
            //if (string.IsNullOrEmpty(this.onlineScene) || this.onlineScene == this.offlineScene)
            //{
            //    ClientScene.Ready(conn);
            //    if (this.autoCreatePlayer)
            //    {
            //        ClientScene.AddPlayer(conn, 0, new PlayerMsg());
            //    }
            //}
        }
    
        //class PlayerMsg : MessageBase { }
    
        #endregion
    
        private void HandlePlayMsg(NetworkMessage netMsg)
        {
            netMsg.conn.Disconnect();
        }
    
        private void BeginGame()
        {
            LogInfo.theLogger.Log("开始");
            ServerChangeScene(playScene);
        }
        //private void Awake()
        //{
            //DontDestroyOnLoad(gameObject);
            //theLobbyManager = this;
        //}
    
        //private void Update()
        //{
            //Debug.Log("this==null:" + (this == null));
            //if(theLobbyManager==null)
            //{
            //    theLobbyManager = this;
            //    Debug.Log("theManager Reset");
            //}
        //}
    }
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Networking.Match;
    using UnityEngine.UI;
    
    public class LobbyMainMenu : MonoBehaviour
    {
        public LobbyManager lobbyManager;
        public Button createMatchBtn;
        public Button playersBackToMain;
        public Button serversBackToMain;
        public Button listServerBtn;
        public InputField matchName;
        public GameObject gameListPanel;
        public GameObject serverListPanel;
        public GameObject lobbyMatch;
    
        public static LobbyMainMenu mainMenu;
    
        public void AddPlayer(LobbyPlayer player)
        {
            gameListPanel.SetActive(true);
            serverListPanel.SetActive(false);
            player.transform.SetParent(gameListPanel.transform.Find("PlayerListPanel"));
        }
    
        public void DropConnection()
        {
            //lobbyManager = LobbyManager.theLobbyManager;
            Debug.Log("Before drop_MM_" + (lobbyManager == null) + "_MK_" + (lobbyManager.matchMaker == null));
    
            if (lobbyManager.matchMaker == null) lobbyManager.StartMatchMaker();//此语句为了测试增加,此处可以去掉
    
            lobbyManager.matchMaker.DropConnection(lobbyManager.matchInfo.networkId,
                lobbyManager.matchInfo.nodeId, 0, lobbyManager.OnDropConnection);
    
            Debug.Log("After drop_MM_" + (lobbyManager == null) + "_MK_" + (lobbyManager.matchMaker == null));
        }
    
        public void DestroyMatch()
        {
            //lobbyManager.matchMaker.DropConnection(lobbyManager.matchInfo.networkId,
            //    lobbyManager.matchInfo.nodeId, 0, lobbyManager.OnDropConnection);
    
            lobbyManager.matchMaker.DestroyMatch(lobbyManager.matchInfo.networkId, 0,
                lobbyManager.OnDestroyMatch);
        }
    
        private void CreateMatch()
        {
            if (lobbyManager.matchMaker == null) lobbyManager.StartMatchMaker();
    
            lobbyManager.matchMaker.CreateMatch(matchName.text, 5, true, "", "", "", 0, 0,
                lobbyManager.OnMatchCreate);
        }
    
        private void ListServers()
        {
            if (lobbyManager.matchMaker == null) lobbyManager.StartMatchMaker();
    
            lobbyManager.matchMaker.ListMatches(0, 5, "", true, 0, 0,
                lobbyManager.OnMatchList);
        }
    
        private void PlayerListBackToMainMenu()
        {
            //lobbyManager.matchMaker.DestroyMatch(lobbyManager.matchInfo.networkId, 0,
            //lobbyManager.OnDestroyMatch);
    
            //lobbyManager.matchMaker.DropConnection(lobbyManager.matchInfo.networkId,
            //lobbyManager.matchInfo.nodeId, 0, lobbyManager.OnDestroyMatch);
            gameListPanel.SetActive(false);
            gameObject.SetActive(true);
        }
    
        private void ServerListBackToMainMenu()
        {
            serverListPanel.SetActive(false);
            gameObject.SetActive(true);
        }
    
        private void OnMatchCreate(MatchInfo obj)
        {
            gameListPanel.SetActive(true);
            gameObject.SetActive(false);
        }
    
        private void OnMatchList(List<MatchInfoSnapshot> matchList)
        {
            foreach(Transform tt in serverListPanel.transform.Find("ServerListPanel"))
            {
                Destroy(tt.gameObject);
            }
    
            foreach(var match in matchList)
            {
                GameObject matchGo = Instantiate(lobbyMatch, serverListPanel.transform.Find("ServerListPanel"));
                matchGo.GetComponent<LobbyMatch>().Populate(match, lobbyManager);
            }
    
            serverListPanel.SetActive(true);
            gameObject.SetActive(false);
        }
    
        private void OnDestroyMatch()
        {
            gameListPanel.SetActive(false);
            gameObject.SetActive(true);
        }
    
        private void OnClientSceneChanged()
        {
            //加载游戏场景后,将主场景ui关掉
            //后续此部分还要处理,返回时还需重新打开ui
            gameObject.SetActive(false);
            gameListPanel.SetActive(false);
        }
    
        private void Start()
        {
            mainMenu = this;
            //lobbyManager = LobbyManager.theLobbyManager;
    
            createMatchBtn.onClick.AddListener(CreateMatch);
            listServerBtn.onClick.AddListener(ListServers);
            playersBackToMain.onClick.AddListener(PlayerListBackToMainMenu);
            serversBackToMain.onClick.AddListener(ServerListBackToMainMenu);
    
            lobbyManager.matchCreatedAction += OnMatchCreate;
            lobbyManager.matchListedAction += OnMatchList;
            lobbyManager.destroyMatchAction += OnDestroyMatch;
        }
    
    }
    using System;
    using UnityEngine;
    using UnityEngine.Networking;
    
    /// <summary>
    /// isLocalPayer,isServer,isClient区别:
    /// OnClientEnterLobby回调时只是数据层面已经客户端连接进来,但是还没有建立client的gameobject,所以此时isLocalPlayer并未赋值(默认false)
    /// 三者的区别从定义以及测试得知如下:
    /// isServer:在服务端的活动的player,此值均为true,即不管是server端StartHost时自己建立的client还是Server Spawn产生,只要在服务端显示,此值均为true
    /// isClient:由Server端spawn产生,并作为client运行的,此值均为true,即只要是Spawn产生的,不管是在服务端还是在客户端,此值均为true
    /// 所以,服务端存在的player,isServer和isClient均为true,客户端isclient均为true,isServer均为false。(isServer与isClient并没有对立关系)
    /// isLocalPayer在执行OnStartLocalPlayer回调时赋值为true,即生成本地的player时赋值,且当Server端spawn的client加入到场景中时只执行OnClientEnterLobby回调,
    /// 不执行OnStartLocalPlayer回调,它的作用是识别玩家控制的游戏物体
    /// </summary>
    public class LobbyPlayer : NetworkLobbyPlayer
    {
        public Action<bool> clientEnterAction;
        public Action<bool,bool> localPlayerAction;
        public Action<bool> clientReadyAction;
    
        public static short playerMsg = MsgType.Highest + 11;
    
        #region METHODS
        public void RemovePlayer_()
        {
            if (isLocalPlayer)
            {
                //如果调用RemovePlayer以及ClientScene的RemovePlayer时只会删除服务端与客户端的player
                //而客户端与服务端的NetworkConnection不会销毁(RemovePlayer亲测,ClientScene的RemovePlayer未测试,只是看释义)
                //所以当离开游戏继续加入游戏时会报Error:A connection has already set as ready....
                //因为每次重新连接时,connection是肯定存在,没有被销毁,所以不确定后续的风险
                //所以此处调用matchMaker.DropConnection
                //但是用DropConnection方法会引发Error:ClientDisconnected due to error: Timeout(不确定是否为本身bug)
                //但是此问题与Error:ServerDisconnected due to error: Timeout(本身bug,有说此bug已修复,但是作者2017版本依然存在,但为下载补丁(下载补丁也许可以))相似
                //此问题可控,不会有后续风险,所以在此采用后者,但不论哪一种方法,只要忽略Error,均可以运行。
                //RemovePlayer();
                if(isServer)
                {
                    RemovePlayer();//先移除player,否则Error:ClientScene::AddPlayer: playerControllerId of 0 already in use.
                    //connectionToClient.Disconnect();
                    //connectionToClient.Dispose();
                    LobbyMainMenu.mainMenu.DestroyMatch();
                    Debug.Log("destroy match");
                }
                else
                {
                    LobbyMainMenu.mainMenu.DropConnection();
                    Debug.Log("drop match");
                }
            }
            else if(isServer)//此处是根据connectionToClient定义做的判断,去掉if(isServer)判断也可以,因为在初始化时已经做了限制,非isserver的不会调用
            {
                //采用Send方法是为了验证消息注册的用法(现在方法简洁明了)
                //每一个player中的connection(connectionToClient)为客户端连接到主机时建立的
                //connectionToClient.Send(playerMsg, new PlayerMsg());
                //connectionToClient为服务端与客户端具有相同identity的player的NetworkConnection,即通过一个玩家,客户端与服务端的连接,并且只在server端有效
                //所以只要断开连接即可    
                connectionToClient.Disconnect();
                connectionToClient.Dispose();
                Debug.Log("disconnect match");
            }
        }
    
        public void SendReadyToBeginMessage_()
        {
            SendReadyToBeginMessage();
        }
    
        #endregion
    
        #region CALLBACKS
        public override void OnClientEnterLobby()
        {
            base.OnClientEnterLobby();
            LobbyMainMenu.mainMenu.AddPlayer(this);
    
            if(clientEnterAction!=null)
            {            
                clientEnterAction(isServer);
            }
    
            //LogInfo.theLogger.Log("Client enter lobby_"+isLocalPlayer+"_"+isServer);
            LogInfo.theLogger.Log("ClientEnterLobby_"+isClient+"_"+isServer);
        }
    
        public override void OnStartLocalPlayer()
        {
            LogInfo.theLogger.Log("StartLocalPlayer_" + isLocalPlayer + "_" + isServer);
    
            if(localPlayerAction!=null)
            {
                localPlayerAction(isLocalPlayer,isServer);
            }
    
            base.OnStartLocalPlayer();
        }
    
        public override void OnClientExitLobby()
        {
            base.OnClientExitLobby();
        }
    
        public override void OnClientReady(bool readyState)
        {
            LogInfo.theLogger.Log("OnClientReady_" + readyState);
    
            base.OnClientReady(readyState);
    
            if(clientReadyAction!=null)
            {
                clientReadyAction(readyState);
            }
        }
        #endregion
    
        class PlayerMsg : MessageBase { }
    }
    using UnityEngine;
    using UnityEngine.Networking;
    using UnityEngine.UI;
    
    public class LobbyPlayerUI : NetworkBehaviour
    {
        public LobbyPlayer thePlayer;
        public Button removeBtn;
        public Toggle readyBtn;
        public GameObject readyStateImage;
    
        private void OnRemove()
        {
            thePlayer.RemovePlayer_();
        }
    
        private void OnReady(bool isReady)
        {
            if (isReady)
            {
                thePlayer.SendReadyToBeginMessage();
            }
            else
            {
                thePlayer.SendNotReadyToBeginMessage();
            }
        }
    
        private void OnClientReady(bool isReady)
        {
            RpcOnClientReady(isReady);
        }
    
        [ClientRpc]
        private void RpcOnClientReady(bool isReady)
        {
            readyStateImage.SetActive(isReady);
        }
    
        private void OnClientEnter(bool isServer)
        {
            if(!isServer)
            {
                //这段是为了对不同的客户端进行权限操作,此处代码完全可以作为初始值,不用设置
                removeBtn.GetComponentInChildren<Text>().text = "Leave";
                removeBtn.interactable = false;
                readyBtn.interactable = false;
            }
            else
            {
                readyBtn.interactable = false;
                removeBtn.GetComponentInChildren<Text>().text = "Kick";
    
                //通过isLocalPlayer进行主机客户端进行单独设置,但是此时islocalplayer==false(未初始化)
                //if (isLocalPlayer)
                //{
                //    removeBtn.GetComponentInChildren<Text>().text = "Leave";
                //    readyBtn.interactable = true;
                //}    
            }
        }
    
        private void LocalPlayerIdentity(bool isLocalPlayer, bool isServer)
        {
            if(isLocalPlayer)//此处可以不用判断,因为当create local player时才会调用此方法,而create local player时isLocalPlayer肯定为true
            {
                removeBtn.interactable = true;
                readyBtn.interactable = true;
                GetComponent<Image>().color = Color.red;
    
                if(isServer) removeBtn.GetComponentInChildren<Text>().text = "Leave";
            }
        }
    
        private void Awake()
        {
            removeBtn.onClick.AddListener(OnRemove);
            readyBtn.onValueChanged.AddListener(OnReady);
    
            thePlayer.clientEnterAction += OnClientEnter;
            thePlayer.localPlayerAction += LocalPlayerIdentity;
            thePlayer.clientReadyAction += OnClientReady;
        }
    }
    using UnityEngine;
    
    public class PlayerList : MonoBehaviour
    {
        public static PlayerList theList;
    
        public void AddPlayer(LobbyPlayer player)
        {
            player.transform.SetParent(transform);
        }
    
        private void Start()
        {
            theList = this;
        }
    }
  • 相关阅读:
    C++课程学习笔记第六周:多态
    C++课程学习笔记第五周:继承
    C++课程学习笔记第四周:运算符的重载
    C++课程学习笔记第三周:类和对象提高
    C++课程学习笔记第一周:从C到C++
    C++课程学习笔记第二周:类和对象基础
    PyTorch的安装及学习资料
    PyTorch练手项目一:训练一个简单的线性回归
    PyTorch练手项目二:MNIST手写数字识别
    PyTorch练手项目四:孪生网络(Siamese Network)
  • 原文地址:https://www.cnblogs.com/llstart-new0201/p/9281980.html
Copyright © 2011-2022 走看看