NetworkManager网络通讯_NetworkLobbyManager(三)

时间:2022-09-08 21:51:10

此部分可以先建立游戏大厅,然后进入游戏,此处坑甚多耗费大量时间。国内百度出来的基本没靠谱的,一些专栏作家大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;
    }
}