Unity3d Network 局域网多人对战之游戏大厅
本文是通過在局域網(wǎng)內(nèi)進(jìn)行玩家匹配,需要游戲大廳展示局域網(wǎng)內(nèi)的服務(wù)器列表(房間信息),玩家通過點(diǎn)擊列表進(jìn)入服務(wù)器創(chuàng)建的房間,準(zhǔn)備好后開始游戲。
由于Unity官方提供NetworkManager HUB只是一個實(shí)例,UI丑丑的。而且局域網(wǎng)內(nèi)匹配也沒有可供選擇的服務(wù)器列表。那服務(wù)器在局域網(wǎng)內(nèi)通過UDP數(shù)據(jù)傳輸協(xié)議來通知其他客戶端生成服務(wù)器列表。客戶端點(diǎn)擊列表進(jìn)入房間中等待加入游戲。
準(zhǔn)備的插件:
1.Network Lobby插件:Asset Store已經(jīng)下架,我是在下架之前就購買了,所以就導(dǎo)出一份靜態(tài)資源供大家下載。這個插件主要是讓我們的游戲大廳界面好看些。關(guān)于Network Lobby插件的教程可以觀看此視頻,也可以查看Multiplayer and Networking官方文檔
2.UnityMainThreadDispatcher插件:子線程中更新UI用的,主要使用在房間列表的更新。如何使用github上有。
?
?首先漢化一下吧,這個不多說。保留 PLAY AND HOST 改成 創(chuàng)建房間。配置LobbyManager
增加UDP腳本,該腳本主要實(shí)現(xiàn)在后臺運(yùn)行的接收線程UDP傳輸信息,需要與NetworkLobby插件結(jié)合一下,舉個例子當(dāng)客戶端創(chuàng)建房間時,那么就要通過UDP傳輸告訴其他局域網(wǎng)內(nèi)的客戶端,我創(chuàng)建了一個房間信息。其他客戶端在房間列表中加入此信息??纯粗饕a。
發(fā)送命令代碼:這里發(fā)送5種消息頭,根據(jù)不同的消息進(jìn)行對應(yīng)處理。
public void sendMessage(string infoHearder, string serverIp = ""){UdpClient myUdpClient = new UdpClient();try{//將發(fā)送內(nèi)容轉(zhuǎn)換為字節(jié)數(shù)組byte[] bytes = null;//讓其自動提供子網(wǎng)中的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 + "-本機(jī)退出!";bytes = Encoding.UTF8.GetBytes(tempStr);}else if (infoHearder == "join"){bytes = null;tempStr = infoHearder + "-" + serverIp + "-" + ip + "-客機(jī)加入!";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 + "-服務(wù)器開始了!";bytes = Encoding.UTF8.GetBytes(tempStr);}//向子網(wǎng)發(fā)送信息myUdpClient.Send(bytes, bytes.Length, iep);Debug.Log("send " + tempStr);}catch (Exception err){Debug.Log("發(fā)送失敗" + err.Message);}finally{myUdpClient.Close();}}創(chuàng)建接收信息的線程:
void Start(){monitorThread = new Thread(ReceiveData);//將線程設(shè)為后臺運(yùn)行monitorThread.IsBackground = true;monitorThread.Start();}接收信息及更新UI的代碼:這里包涵接收5種消息頭所對應(yīng)的的處理方式。
/// <summary>/// 在后臺運(yùn)行的接收線程/// </summary>private void ReceiveData(){//在本機(jī)指定的端口接收udpClient = new UdpClient(port);IPEndPoint remote = null;//接收從遠(yuǎn)程主機(jī)發(fā)送過來的信息;while (true){try{//關(guān)閉udpClient時此句會產(chǎn)生異常byte[] bytes = udpClient.Receive(ref remote);str = Encoding.UTF8.GetString(bytes, 0, bytes.Length);Debug.Log("rec-" + str);Debug.Log("遠(yuǎn)程主機(jī)IP-" + remote.ToString());//更新UIUnityMainThreadDispatcher.Instance().Enqueue(ThisWillBeExecutedOnTheMainThread(str, remote));}catch (Exception ex){Debug.Log("rec-" + ex.Message);//退出循環(huán),結(jié)束線程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"){//此命令只能是服務(wù)器發(fā)出// 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"){//此命令只能是服務(wù)器發(fā)出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類型的消息頭中進(jìn)行房間列表的更新,LobbyMainMenu主要代碼:roomItemPrefabs是房間信息的預(yù)制體,roomContent是ScrollView中的內(nèi)容,?roomList添加roomItemPrefabs為了ScrollView添加、移除操作的進(jìn)行。這里順便把加入房間按鈕事件也注冊了。
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方法添加一些加入的規(guī)則(人數(shù)的判斷,房間的狀態(tài)等),我這里新增了OnListClickJoin并進(jìn)行傳參,原因是要獲取單擊按鈕上的server ip使客戶端能加入房間。同時發(fā)送join的消息頭及獲取到的server ip,使用udp進(jìn)行局域網(wǎng)內(nèi)廣播那么在其他客戶端接收到j(luò)oin消息頭時,可以通過傳輸?shù)?server ip進(jìn)行判斷哪個客戶端游戲的服務(wù)器。這樣就通過server ip更新列表的人數(shù)信息。
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("主機(jī)信息不存在!", lobbyManager.networkAddress);return;}else{var server = list.Find(p => p.ip == textIp);if (server.status == "1"){lobbyManager.infoPanel.Display("房間已經(jīng)開始,不能加入!", "確定",null);return;}else if (server.status == "2"){lobbyManager.infoPanel.Display("房間人數(shù)已經(jīng)夠了,不能加入!", "確定", 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消息頭增加人數(shù)。
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";}}}}在客戶端/服務(wù)器退出時,需要進(jìn)行udp消息頭的發(fā)送。修改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;}}在局域網(wǎng)內(nèi)接收到exit的消息頭時,如果是服務(wù)器退出時需要移除房間列表中的信息,客戶端退出時需要修改列表中的人數(shù)信息。那么在ReceiveData接收信息后就需要針對性的處理。這里的remote是udp發(fā)送消息的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消息頭的發(fā)送與處理:這個命令是客戶端被房主提出房間。修改LobbyManager腳本中的KickedMessageHandler函數(shù)增加發(fā)送kicked消息頭的命令。
public void KickedMessageHandler(NetworkMessage netMsg){udp.sendMessage("kicked");infoPanel.Display("房主把你請出房間", "關(guān)閉", null);netMsg.conn.Disconnect();}當(dāng)我在做完這些工作后遇到一個問題:就是如何將用戶列表選擇文本信息和顏色顯示在游戲預(yù)制體上呢,網(wǎng)上找了好些資料終于在這個插件的腳本中發(fā)現(xiàn)了一個LobbyHook的腳本。在這里看看因?yàn)樽⒔饩兔靼琢?#xff0c;然后又搜索了一下官方文檔關(guān)于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 prefabpublic 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;}在這里需要注意下官方有個說明這個方法是運(yùn)行在服務(wù)器端的,也就是說還涉及到文本信息及選擇的顏色同步到其他客戶端的問題。上代碼
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;}}?
總結(jié)
以上是生活随笔為你收集整理的Unity3d Network 局域网多人对战之游戏大厅的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: oci mysql_Oracle常用的O
- 下一篇: cgi一键还原 linux分区,cgi