Unity3d Network 局域网多人对战之游戏大厅

本文是通过在局域网内进行玩家匹配,需要游戏大厅展示局域网内的服务器列表(房间信息),玩家通过点击列表进入服务器创建的房间,准备好后开始游戏。

由于Unity官方提供NetworkManager HUB只是一个实例,UI丑丑的。而且局域网内匹配也没有可供选择的服务器列表。那服务器在局域网内通过UDP数据传输协议来通知其他客户端生成服务器列表。客户端点击列表进入房间中等待加入游戏。

准备的插件:

1.Network Lobby插件:Asset Store已经下架,我是在下架之前就购买了,所以就导出一份静态资源供大家下载。这个插件主要是让我们的游戏大厅界面好看些。关于Network Lobby插件的教程可以观看此视频,也可以查看Multiplayer and Networking官方文档

2.UnityMainThreadDispatcher插件:子线程中更新UI用的,主要使用在房间列表的更新。如何使用github上有。

 

 首先汉化一下吧,这个不多说。保留 PLAY AND HOST 改成 创建房间。配置LobbyManager

增加UDP脚本,该脚本主要实现在后台运行的接收线程UDP传输信息,需要与NetworkLobby插件结合一下,举个例子当客户端创建房间时,那么就要通过UDP传输告诉其他局域网内的客户端,我创建了一个房间信息。其他客户端在房间列表中加入此信息。看看主要代码。

发送命令代码:这里发送5种消息头,根据不同的消息进行对应处理。

  public void sendMessage(string infoHearder, string serverIp = "")
        {
            UdpClient myUdpClient = new UdpClient();
            try
            {
                //将发送内容转换为字节数组
                byte[] bytes = null;
                //让其自动提供子网中的IP广播地址
                IPEndPoint iep = new IPEndPoint(IPAddress.Broadcast, 8001);

                string tempStr = "";
                string ip = localIp;
                if (infoHearder == "create")
                {
                    isServer = true;
                    bytes = null;
                    if (serverInfo == null)
                    {
                        serverInfo = new mServerInfo();
                        serverInfo.ip = ip;
                        serverInfo.status = "0";
                        serverInfo.currentNum = 1;
                    }
                    tempStr = infoHearder + "-" + ip + "-" + ip + "-" + (lobbyManager._playerNumber == 0 ? 1 : lobbyManager._playerNumber);
                    bytes = Encoding.UTF8.GetBytes(tempStr);
                }
                else if (infoHearder == "exit")
                {
                    bytes = null;
                    serverInfo = null;

                    tempStr = infoHearder + "-" + serverIp + "-" + ip + "-本机退出!";
                    bytes = Encoding.UTF8.GetBytes(tempStr);
                }
                else if (infoHearder == "join")
                {
                    bytes = null;
                    tempStr = infoHearder + "-" + serverIp + "-" + ip + "-客机加入!";
                    bytes = Encoding.UTF8.GetBytes(tempStr);
                }
                else if (infoHearder == "kicked")
                {
                    bytes = null;
                    tempStr = infoHearder + "-" + serverIp + "-" + ip + "-踢了客户端!";
                    bytes = Encoding.UTF8.GetBytes(tempStr);
                }
                else if (infoHearder == "start")
                {
                    bytes = null;
                    tempStr = infoHearder + "-" + serverIp + "-" + ip + "-服务器开始了!";
                    bytes = Encoding.UTF8.GetBytes(tempStr);
                }
                //向子网发送信息
                myUdpClient.Send(bytes, bytes.Length, iep);
                Debug.Log("send " + tempStr);
            }
            catch (Exception err)
            {
                Debug.Log("发送失败" + err.Message);
            }
            finally
            {
                myUdpClient.Close();
            }
        }

创建接收信息的线程:

       void Start()
        {
            monitorThread = new Thread(ReceiveData);
            //将线程设为后台运行
            monitorThread.IsBackground = true;
            monitorThread.Start();
           
        }

接收信息及更新UI的代码:这里包涵接收5种消息头所对应的的处理方式。

 /// <summary>
        /// 在后台运行的接收线程
        /// </summary>
        private void ReceiveData()
        {
            //在本机指定的端口接收
            udpClient = new UdpClient(port);
            IPEndPoint remote = null;
            //接收从远程主机发送过来的信息;
            while (true)
            {
                try
                {
                    //关闭udpClient时此句会产生异常
                    byte[] bytes = udpClient.Receive(ref remote);
                    str = Encoding.UTF8.GetString(bytes, 0, bytes.Length);
                    Debug.Log("rec-" + str);
                    Debug.Log("远程主机IP-" + remote.ToString());
                    //更新UI
                    UnityMainThreadDispatcher.Instance().Enqueue(ThisWillBeExecutedOnTheMainThread(str, remote));
                   
                }
                catch (Exception ex)
                {
                    Debug.Log("rec-" + ex.Message);
                    //退出循环,结束线程
                    break;
                }
            }
        }
        public IEnumerator ThisWillBeExecutedOnTheMainThread(string str, IPEndPoint remote)
        {
            try
            {
                sendMessageText.text = str;
                status.text = "";
                var arr = str.Split('-');
                string headMes = arr[0];
                if (headMes == "create")
                {
                    var tempIp = remote.ToString().Split(':')[0];
                    if (!serverList.Exists(p => p.ip == tempIp))
                    {
                        mServerInfo tempInfo = new mServerInfo();
                        tempInfo.status = "0";
                        tempInfo.ip = tempIp;
                        tempInfo.num = 5;
                        tempInfo.currentNum = Convert.ToInt32(arr[arr.Length - 1]);
                        serverList.Add(tempInfo);
                        GameObject _roomListItemGo = Instantiate(roomItemPrefabs);
                        _roomListItemGo.GetComponent<Button>().onClick.AddListener(delegate
                        {
                            mainMenu.OnListClickJoin(_roomListItemGo);
                        });
                        _roomListItemGo.transform.Find("ip").GetComponent<Text>().text = tempInfo.ip;
                        _roomListItemGo.transform.Find("rs").GetComponent<Text>().text = "(" + tempInfo.currentNum + "/" + tempInfo.num + ")";
                        _roomListItemGo.transform.SetParent(roomContent);
                        roomList.Add(_roomListItemGo);
                        ipText.text = tempInfo.ip;
                    }
                }
                else if (headMes == "exit")
                {
                    serverIp = arr[1];
                    string ip = remote.ToString().Split(':')[0];
                    for (int i = 0; i < serverList.Count; i++)
                    {

                        if (serverList[i].ip == ip)
                        {
                            serverList.RemoveAt(i);
                            //列表移除
                            Destroy(roomList[i]);
                            roomList.RemoveAt(i);
                        }
                        if (!isServer)
                        {
                            var temp = serverList[i];
                            if (temp.ip == serverIp)
                            {
                                temp.currentNum -= 1;
                                temp.currentNum = temp.currentNum < 1 ? 1 : temp.currentNum;
                                var _roomListItemGo = roomList[i];
                                _roomListItemGo.transform.Find("rs").GetComponent<Text>().text = "(" + temp.currentNum + "/" + temp.num + ")";
                                if (temp.currentNum < temp.num && temp.status != "1")
                                {
                                    temp.status = "0";
                                }
                            }
                        }
                    }
                    ipText.text = ip;
                }
                else if (headMes == "join")
                {
                    serverIp = arr[1];
                    Debug.Log("join:" + serverIp);
                    for (int i = 0; i < serverList.Count; i++)
                    {
                        var temp = serverList[i];
                        if (temp.ip == serverIp)
                        {
                            temp.currentNum += 1;
                            var _roomListItemGo = roomList[i];
                            _roomListItemGo.transform.Find("rs").GetComponent<Text>().text = "(" + temp.currentNum + "/" + temp.num + ")";
                            if (temp.currentNum == temp.num)
                            {
                                temp.status = "2";
                            }
                        }
                    }
                }
                else if (headMes == "kicked")
                {
                    //此命令只能是服务器发出
                    //  string serverIp = remote.ToString().Split(':')[0];
                    Debug.Log("kicked:" + serverIp);
                    for (int i = 0; i < serverList.Count; i++)
                    {
                        var temp = serverList[i];
                        if (temp.ip == serverIp)
                        {
                            temp.currentNum -= 1;
                            temp.currentNum = temp.currentNum < 1 ? 1 : temp.currentNum;
                            var _roomListItemGo = roomList[i];
                            _roomListItemGo.transform.Find("rs").GetComponent<Text>().text = "(" + temp.currentNum + "/" + temp.num + ")";
                            if (temp.currentNum < temp.num && temp.status != "1")
                            {
                                temp.status = "0";
                            }
                        }
                    }
                }
                else if (headMes == "start")
                {
                    //此命令只能是服务器发出
                    string serverIp = arr[1];
                    Debug.Log("start:" + serverIp);
                    for (int i = 0; i < serverList.Count; i++)
                    {
                        var serverInfo = serverList[i];
                        if (serverInfo.ip == serverIp)
                        {

                            var _roomListItemGo = roomList[i];
                            _roomListItemGo.SetActive(false);
                            serverInfo.status = "1";

                        }
                    }
                }
                else if (headMes == "status")
                {
                    if (serverList.Count == 0)
                    {
                        status.text = "没有新建房间信息!";
                    }
                }
                else
                {
                    Debug.Log("不是有效的消息头");
                }
            }
            catch (Exception ex)
            {
                Debug.Log(" Rec-err" + ex.Message);
            }
            //  Debug.Log("This is executed from the main thread");
            yield return null;
        }

在上面的代码中,在create类型的消息头中进行房间列表的更新,LobbyMainMenu主要代码:roomItemPrefabs是房间信息的预制体,roomContent是ScrollView中的内容, roomList添加roomItemPrefabs为了ScrollView添加、移除操作的进行。这里顺便把加入房间按钮事件也注册了。

         GameObject _roomListItemGo = Instantiate(roomItemPrefabs);
                        _roomListItemGo.GetComponent<Button>().onClick.AddListener(delegate
                        {
                            mainMenu.OnListClickJoin(_roomListItemGo);
                        });
                        _roomListItemGo.transform.Find("ip").GetComponent<Text>().text = tempInfo.ip;
                        _roomListItemGo.transform.Find("rs").GetComponent<Text>().text = "(" + tempInfo.currentNum + "/" + tempInfo.num + ")";
                        _roomListItemGo.transform.SetParent(roomContent);
                        roomList.Add(_roomListItemGo);

在原来LobbyMainMenu脚本中的改写OnClickJoin方法添加一些加入的规则(人数的判断,房间的状态等),我这里新增了OnListClickJoin并进行传参,原因是要获取单击按钮上的server ip使客户端能加入房间。同时发送join的消息头及获取到的server ip,使用udp进行局域网内广播那么在其他客户端接收到join消息头时,可以通过传输的 server ip进行判断哪个客户端游戏的服务器。这样就通过server ip更新列表的人数信息。

 public void OnListClickJoin(GameObject myGO)
        {
            var list = udp.getServerList();
            var textIp = myGO.transform.Find("ip").GetComponent<Text>().text;
            Debug.Log(textIp);
            if (!list.Exists(p => p.ip == textIp))
            {
                lobbyManager.SetServerInfo("主机信息不存在!", lobbyManager.networkAddress);
                return;
            }
            else
            {
                var server = list.Find(p => p.ip == textIp);
                if (server.status == "1")
                {
                    lobbyManager.infoPanel.Display("房间已经开始,不能加入!", "确定",null);
                    return;
                }
                else if (server.status == "2")
                {
                    lobbyManager.infoPanel.Display("房间人数已经够了,不能加入!", "确定", null);
                    return;
                }
                else if (server.status == "-1")
                {
                    lobbyManager.infoPanel.Display("房间不能加入!", "确定", null);
                    return;
                }
               
            }

            lobbyManager.ChangeTo(lobbyPanel);
            lobbyManager.networkAddress = textIp;
            lobbyManager.StartClient();
            lobbyManager.backDelegate = lobbyManager.StopClientClbk;
            lobbyManager.DisplayIsConnecting();
            lobbyManager.SetServerInfo("正在连接...", lobbyManager.networkAddress);
            udp.sendMessage("join", textIp);
        }

在ReceiveData脚本中join消息头增加人数。

           if (headMes == "join")
                {
                    serverIp = arr[1];
                    Debug.Log("join:" + serverIp);
                    for (int i = 0; i < serverList.Count; i++)
                    {
                        var temp = serverList[i];
                        if (temp.ip == serverIp)
                        {
                            temp.currentNum += 1;
                            var _roomListItemGo = roomList[i];
                            _roomListItemGo.transform.Find("rs").GetComponent<Text>().text = "(" + temp.currentNum + "/" + temp.num + ")";
                            if (temp.currentNum == temp.num)
                            {
                                temp.status = "2";
                            }
                        }
                    }
                }

在客户端/服务器退出时,需要进行udp消息头的发送。修改LobbyManager脚本中的GoBackButton方法,加入 udp.sendMessage("exit", udp.serverIp)

    public delegate void BackButtonDelegate();
        public BackButtonDelegate backDelegate;
        public void GoBackButton()
        {

            backDelegate();
            topPanel.isInGame = false;
            udp.sendMessage("exit", udp.serverIp);
            if (mainMenu.serverThread != null)
            {
                mainMenu.serverThread.Abort();
                mainMenu.serverThread = null;
            }

        }

在局域网内接收到exit的消息头时,如果是服务器退出时需要移除房间列表中的信息,客户端退出时需要修改列表中的人数信息。那么在ReceiveData接收信息后就需要针对性的处理。这里的remote是udp发送消息的IPEndPoint信息。

            if (headMes == "exit")
                {
                    serverIp = arr[1];
                    string ip = remote.ToString().Split(':')[0];
                    for (int i = 0; i < serverList.Count; i++)
                    {

                        if (serverList[i].ip == ip)
                        {
                            serverList.RemoveAt(i);
                            //列表移除
                            Destroy(roomList[i]);
                            roomList.RemoveAt(i);
                        }
                        if (!isServer)
                        {
                            var temp = serverList[i];
                            if (temp.ip == serverIp)
                            {
                                temp.currentNum -= 1;
                                temp.currentNum = temp.currentNum < 1 ? 1 : temp.currentNum;
                                var _roomListItemGo = roomList[i];
                                _roomListItemGo.transform.Find("rs").GetComponent<Text>().text = "(" + temp.currentNum + "/" + temp.num + ")";
                                if (temp.currentNum < temp.num && temp.status != "1")
                                {
                                    temp.status = "0";
                                }
                            }
                        }
                    }
                    ipText.text = ip;
                }

kicked消息头的发送与处理:这个命令是客户端被房主提出房间。修改LobbyManager脚本中的KickedMessageHandler函数增加发送kicked消息头的命令。

      public void KickedMessageHandler(NetworkMessage netMsg)
        {
            udp.sendMessage("kicked");
            infoPanel.Display("房主把你请出房间", "关闭", null);
            netMsg.conn.Disconnect();
        }

当我在做完这些工作后遇到一个问题:就是如何将用户列表选择文本信息和颜色显示在游戏预制体上呢,网上找了好些资料终于在这个插件的脚本中发现了一个LobbyHook的脚本。在这里看看因为注解就明白了,然后又搜索了一下官方文档关于OnLobbyServerSceneLoadedForPlayer 的说明。

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


namespace Prototype.NetworkLobby
{
    // Subclass this and redefine the function you want
    // then add it to the lobby prefab
    public abstract class LobbyHook : MonoBehaviour
    {
        public virtual void OnLobbyServerSceneLoadedForPlayer(NetworkManager manager, GameObject lobbyPlayer, GameObject gamePlayer) {
            //gamePlayer.transform.Find("nameCvs/playerName").GetComponent<Text>().text = "2222";
        }
    }

}

那么我们新建一个脚本myLobbyHook继承LobbyHook脚本。重写此方法:

  public override void OnLobbyServerSceneLoadedForPlayer(NetworkManager manager, GameObject lobbyPlayer, GameObject gamePlayer)
        {
            TankNameController tankName = gamePlayer.GetComponent<TankNameController>();
            tankName.playName = lobbyPlayer.GetComponent<LobbyPlayer>().playerDropName;
            tankName.PlaryColor = lobbyPlayer.GetComponent<LobbyPlayer>().playerColor;


        }

在这里需要注意下官方有个说明这个方法是运行在服务器端的,也就是说还涉及到文本信息及选择的颜色同步到其他客户端的问题。上代码


using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
public class TankNameController : NetworkBehaviour
{
    public Text name;
    [SyncVar]
    public string playName;
    [SyncVar]
    public Color PlaryColor = Color.white;
    void Update()
    {
        name.color = PlaryColor;
        name.text = playName;
    }

}

 

  • 3
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yxlalm

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值