I am using both the NAT Traversal plugin and Unity’s new Network Lobby system in my project. Individually I am able to use the examples and I understand how to make each plugin work by themselves. But my problem is that I can not figure out how to make them work together.
I am new to networking in Unity and I have been reading a lot about the subject (trying examples too). However this seems to be becoming a very large task (4 days worth of attempts) and currently feels to be beyond my abilities.
I was wondering and really hoping someone knowledgeable could help me understand how to make Unity’s network lobby inherit from NATTraversal.NetworkManager instead of UnityEngine.NetworkManager and use NAT Punchthrough?
After importing the Network Lobby plugin into a new project I noticed in the provided example LobbyManager prefab it uses a script called LobbyManager.cs which inherits from UnityEngine.NetworkLobbyManager and ultimately inherits from UnityEngine.NetworkManager, so the UnityEngine.NetworkLobbyManager is an assembly that I can not modify to change it’s inheritance to the desired NATTraversal.NetworkManager.
My attempt (mostly just shooting in the dark) was to change the inheritance in LobbyManager.cs from UnityEngine.NetworkLobbyManager to NATTraversal.NATLobbyManager which ultimately inherits from NATTraversal.NetworkManager. I am not sure if this is even remotely correct but I just wanted to see how things went.
Upon doing the above I noticed some errors associated with missing references, I tried to fill in the blanks the best I could and correct any errors. The code now compiles and does “seem” to launch LAN matches, but whenever I try to create an internet match, the server’s lobby window hangs forever on the “Connecting…” dialog. But I do see the server listed on the client after clicking List Server. So some things work. However when I attempt to join I get a NullReferenceException error for OnMatchJoined()
Script attached to LobbyManager prefab (controls networking and the visibility of menu panels):
C# Code: LobbyManager
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using UnityEngine.Networking;
using UnityEngine.Networking.Types;
using UnityEngine.Networking.Match;
using System.Collections;
namespace Prototype.NetworkLobby
{
public class LobbyManager : NATTraversal.NATLobbyManager
{
static short MsgKicked = MsgType.Highest + 1;
static public LobbyManager s_Singleton;
[Header("Unity UI Lobby")]
[Tooltip("Time in second between all players ready & match start")]
public float prematchCountdown = 5.0f;
[Space]
[Header("UI Reference")]
public LobbyTopPanel topPanel;
public RectTransform mainMenuPanel;
public RectTransform lobbyPanel;
public LobbyInfoPanel infoPanel;
public LobbyCountdownPanel countdownPanel;
public GameObject addPlayerButton;
protected RectTransform currentPanel;
public Button backButton;
public Text statusInfo;
public Text hostInfo;
//Client numPlayers from NetworkManager is always 0, so we count (throught connect/destroy in LobbyPlayer) the number
//of players, so that even client know how many player there is.
[HideInInspector]
public int _playerNumber = 0;
//used to disconnect a client properly when exiting the matchmaker
[HideInInspector]
public bool _isMatchmaking = false;
protected bool _disconnectServer = false;
protected ulong _currentMatchID;
protected LobbyHook _lobbyHooks;
void Start()
{
base.Start ();
s_Singleton = this;
_lobbyHooks = GetComponent<Prototype.NetworkLobby.LobbyHook>();
currentPanel = mainMenuPanel;
backButton.gameObject.SetActive(false);
GetComponent<Canvas>().enabled = true;
DontDestroyOnLoad(gameObject);
SetServerInfo("Offline", "None");
}
public override void OnLobbyClientSceneChanged(NetworkConnection conn)
{
if (SceneManager.GetSceneAt(0).name == lobbyScene)
{
if (topPanel.isInGame)
{
ChangeTo(lobbyPanel);
if (_isMatchmaking)
{
if (conn.playerControllers[0].unetView.isServer)
{
backDelegate = StopHostClbk;
}
else
{
backDelegate = StopClientClbk;
}
}
else
{
if (conn.playerControllers[0].unetView.isClient)
{
backDelegate = StopHostClbk;
}
else
{
backDelegate = StopClientClbk;
}
}
}
else
{
ChangeTo(mainMenuPanel);
}
topPanel.ToggleVisibility(true);
topPanel.isInGame = false;
}
else
{
ChangeTo(null);
Destroy(GameObject.Find("MainMenuUI(Clone)"));
//backDelegate = StopGameClbk;
topPanel.isInGame = true;
topPanel.ToggleVisibility(false);
}
}
public void ChangeTo(RectTransform newPanel)
{
if (currentPanel != null)
{
currentPanel.gameObject.SetActive(false);
}
if (newPanel != null)
{
newPanel.gameObject.SetActive(true);
}
currentPanel = newPanel;
if (currentPanel != mainMenuPanel)
{
backButton.gameObject.SetActive(true);
}
else
{
backButton.gameObject.SetActive(false);
SetServerInfo("Offline", "None");
_isMatchmaking = false;
}
}
public void DisplayIsConnecting()
{
var _this = this;
infoPanel.Display("Connecting...", "Cancel", () => {
_this.backDelegate();
});
}
public void SetServerInfo(string status, string host)
{
statusInfo.text = status;
hostInfo.text = host;
}
public delegate void BackButtonDelegate();
public BackButtonDelegate backDelegate;
public void GoBackButton()
{
backDelegate();
topPanel.isInGame = false;
}
// ----------------- Server management
public void AddLocalPlayer()
{
TryToAddPlayer();
}
public void RemovePlayer(LobbyPlayer player)
{
player.RemovePlayer();
}
public void SimpleBackClbk()
{
ChangeTo(mainMenuPanel);
}
public void StopHostClbk()
{
if (_isMatchmaking)
{
matchMaker.DestroyMatch((NetworkID)_currentMatchID, 0, OnDestroyMatch);
_disconnectServer = true;
}
else
{
Debug.LogError ("STOPPING NOW");
NetworkManager manager = this.GetComponent<NetworkManager>();
manager.StopHost();
}
ChangeTo(mainMenuPanel);
}
public void StopClientClbk()
{
NetworkManager manager = this.GetComponent<NetworkManager>();
manager.StopClient();
if (_isMatchmaking)
{
StopMatchMaker();
}
ChangeTo(mainMenuPanel);
}
public void StopServerClbk()
{
NetworkManager manager = this.GetComponent<NetworkManager>();
manager.StopServer();
ChangeTo(mainMenuPanel);
}
class KickMsg : MessageBase { }
public void KickPlayer(NetworkConnection conn)
{
conn.Send(MsgKicked, new KickMsg());
}
public void KickedMessageHandler(NetworkMessage netMsg)
{
infoPanel.Display("Kicked by Server", "Close", null);
netMsg.conn.Disconnect();
}
//===================
public override void OnStartHost()
{
base.OnStartHost();
ChangeTo(lobbyPanel);
backDelegate = StopHostClbk;
SetServerInfo("Hosting", networkAddress);
}
public override void OnMatchJoined(bool success, string extendedInfo, MatchInfo matchInfo)
{
//base.OnMatchJoined (success, extendedInfo, matchInfo);
}
public override void OnMatchCreate(bool success, string extendedInfo, MatchInfo matchInfo)
{
_currentMatchID = (System.UInt64)matchInfo.networkId;
base.OnMatchCreate(success, extendedInfo, matchInfo);
}
public override void OnDestroyMatch(bool success, string extendedInfo)
{
base.OnDestroyMatch(success, extendedInfo);
if (_disconnectServer)
{
StopMatchMaker();
StopHost();
}
}
//allow to handle the (+) button to add/remove player
public void OnPlayersNumberModified(int count)
{
_playerNumber += count;
int localPlayerCount = 0;
foreach (PlayerController p in ClientScene.localPlayers)
localPlayerCount += (p == null || p.playerControllerId == -1) ? 0 : 1;
addPlayerButton.SetActive(localPlayerCount < maxPlayersPerConnection && _playerNumber < maxPlayers);
}
// ----------------- Server callbacks ------------------
//we want to disable the button JOIN if we don't have enough player
//But OnLobbyClientConnect isn't called on hosting player. So we override the lobbyPlayer creation
public override GameObject OnLobbyServerCreateLobbyPlayer(NetworkConnection conn, short playerControllerId)
{
Debug.LogError ("CREATE LOBBY PLAYER");
GameObject obj = Instantiate(lobbyPlayerPrefab.gameObject) as GameObject;
LobbyPlayer newPlayer = obj.GetComponent<LobbyPlayer>();
newPlayer.ToggleJoinButton(numPlayers + 1 >= minPlayers);
for (int i = 0; i < lobbySlots.Length; ++i)
{
LobbyPlayer p = lobbySlots[i] as LobbyPlayer;
if (p != null)
{
p.RpcUpdateRemoveButton();
p.ToggleJoinButton(numPlayers + 1 >= minPlayers);
}
}
return obj;
}
public override void OnLobbyServerPlayerRemoved(NetworkConnection conn, short playerControllerId)
{
for (int i = 0; i < lobbySlots.Length; ++i)
{
LobbyPlayer p = lobbySlots[i] as LobbyPlayer;
if (p != null)
{
p.RpcUpdateRemoveButton();
p.ToggleJoinButton(numPlayers + 1 >= minPlayers);
}
}
}
public override void OnLobbyServerDisconnect(NetworkConnection conn)
{
for (int i = 0; i < lobbySlots.Length; ++i)
{
LobbyPlayer p = lobbySlots[i] as LobbyPlayer;
if (p != null)
{
p.RpcUpdateRemoveButton();
p.ToggleJoinButton(numPlayers >= minPlayers);
}
}
}
public override bool OnLobbyServerSceneLoadedForPlayer(GameObject lobbyPlayer, GameObject gamePlayer)
{
//This hook allows you to apply state data from the lobby-player to the game-player
//just subclass "LobbyHook" and add it to the lobby object.
if (_lobbyHooks)
_lobbyHooks.OnLobbyServerSceneLoadedForPlayer(this, lobbyPlayer, gamePlayer);
return true;
}
// --- Countdown management
public override void OnLobbyServerPlayersReady()
{
bool allready = true;
for(int i = 0; i < lobbySlots.Length; ++i)
{
if(lobbySlots[i] != null)
allready &= lobbySlots[i].readyToBegin;
}
if(allready)
StartCoroutine(ServerCountdownCoroutine());
}
public IEnumerator ServerCountdownCoroutine()
{
float remainingTime = prematchCountdown;
int floorTime = Mathf.FloorToInt(remainingTime);
while (remainingTime > 0)
{
yield return null;
remainingTime -= Time.deltaTime;
int newFloorTime = Mathf.FloorToInt(remainingTime);
if (newFloorTime != floorTime)
{//to avoid flooding the network of message, we only send a notice to client when the number of plain seconds change.
floorTime = newFloorTime;
for (int i = 0; i < lobbySlots.Length; ++i)
{
if (lobbySlots[i] != null)
{//there is maxPlayer slots, so some could be == null, need to test it before accessing!
(lobbySlots[i] as LobbyPlayer).RpcUpdateCountdown(floorTime);
}
}
}
}
for (int i = 0; i < lobbySlots.Length; ++i)
{
if (lobbySlots[i] != null)
{
(lobbySlots[i] as LobbyPlayer).RpcUpdateCountdown(0);
}
}
ServerChangeScene(playScene);
}
// ----------------- Client callbacks ------------------
public override void OnClientConnect(NetworkConnection conn)
{
Debug.LogError ("OnClientConnect");
base.OnClientConnect(conn);
infoPanel.gameObject.SetActive(false);
conn.RegisterHandler(MsgKicked, KickedMessageHandler);
if (!NetworkServer.active)
{//only to do on pure client (not self hosting client)
ChangeTo(lobbyPanel);
backDelegate = StopClientClbk;
SetServerInfo("Client", networkAddress);
}
}
public override void OnClientDisconnect(NetworkConnection conn)
{
base.OnClientDisconnect(conn);
ChangeTo(mainMenuPanel);
}
public override void OnClientError(NetworkConnection conn, int errorCode)
{
ChangeTo(mainMenuPanel);
infoPanel.Display("Cient error : " + (errorCode == 6 ? "timeout" : errorCode.ToString()), "Close", null);
}
}
}
Script component attached to PlayerInfo prefab (inheriting from NATTraversal.NATLobbyPlayer):
C# Code: LobbyPlayer
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Prototype.NetworkLobby
{
//Player entry in the lobby. Handle selecting color/setting name & getting ready for the game
//Any LobbyHook can then grab it and pass those value to the game player prefab (see the Pong Example in the Samples Scenes)
public class LobbyPlayer : NATTraversal.NATLobbyPlayer
{
static Color[] Colors = new Color[] { Color.magenta, Color.red, Color.cyan, Color.blue, Color.green, Color.yellow };
//used on server to avoid assigning the same color to two player
static List<int> _colorInUse = new List<int>();
public Button colorButton;
public InputField nameInput;
public Button readyButton;
public Button waitingPlayerButton;
public Button removePlayerButton;
public GameObject localIcone;
public GameObject remoteIcone;
//OnMyName function will be invoked on clients when server change the value of playerName
[SyncVar(hook = "OnMyName")]
public string playerName = "";
[SyncVar(hook = "OnMyColor")]
public Color playerColor = Color.white;
public Color OddRowColor = new Color(250.0f / 255.0f, 250.0f / 255.0f, 250.0f / 255.0f, 1.0f);
public Color EvenRowColor = new Color(180.0f / 255.0f, 180.0f / 255.0f, 180.0f / 255.0f, 1.0f);
static Color JoinColor = new Color(255.0f/255.0f, 0.0f, 101.0f/255.0f,1.0f);
static Color NotReadyColor = new Color(34.0f / 255.0f, 44 / 255.0f, 55.0f / 255.0f, 1.0f);
static Color ReadyColor = new Color(0.0f, 204.0f / 255.0f, 204.0f / 255.0f, 1.0f);
static Color TransparentColor = new Color(0, 0, 0, 0);
//static Color OddRowColor = new Color(250.0f / 255.0f, 250.0f / 255.0f, 250.0f / 255.0f, 1.0f);
//static Color EvenRowColor = new Color(180.0f / 255.0f, 180.0f / 255.0f, 180.0f / 255.0f, 1.0f);
public override void OnClientEnterLobby()
{
base.OnClientEnterLobby();
if (LobbyManager.s_Singleton != null) LobbyManager.s_Singleton.OnPlayersNumberModified(1);
LobbyPlayerList._instance.AddPlayer(this);
LobbyPlayerList._instance.DisplayDirectServerWarning(isServer && LobbyManager.s_Singleton.matchMaker == null);
if (isLocalPlayer)
{
SetupLocalPlayer();
}
else
{
SetupOtherPlayer();
}
//setup the player data on UI. The value are SyncVar so the player
//will be created with the right value currently on server
OnMyName(playerName);
OnMyColor(playerColor);
}
public override void OnStartAuthority()
{
base.OnStartAuthority();
//if we return from a game, color of text can still be the one for "Ready"
readyButton.transform.GetChild(0).GetComponent<Text>().color = Color.white;
SetupLocalPlayer();
}
void ChangeReadyButtonColor(Color c)
{
ColorBlock b = readyButton.colors;
b.normalColor = c;
b.pressedColor = c;
b.highlightedColor = c;
b.disabledColor = c;
readyButton.colors = b;
}
void SetupOtherPlayer()
{
nameInput.interactable = false;
removePlayerButton.interactable = NetworkServer.active;
ChangeReadyButtonColor(NotReadyColor);
readyButton.transform.GetChild(0).GetComponent<Text>().text = "...";
readyButton.interactable = false;
OnClientReady(false);
}
void SetupLocalPlayer()
{
nameInput.interactable = true;
remoteIcone.gameObject.SetActive(false);
localIcone.gameObject.SetActive(true);
CheckRemoveButton();
if (playerColor == Color.white)
CmdColorChange();
ChangeReadyButtonColor(JoinColor);
readyButton.transform.GetChild(0).GetComponent<Text>().text = "JOIN";
readyButton.interactable = true;
//have to use child count of player prefab already setup as "this.slot" is not set yet
if (playerName == "")
CmdNameChanged("Player" + (LobbyPlayerList._instance.playerListContentTransform.childCount-1));
//we switch from simple name display to name input
colorButton.interactable = true;
nameInput.interactable = true;
nameInput.onEndEdit.RemoveAllListeners();
nameInput.onEndEdit.AddListener(OnNameChanged);
colorButton.onClick.RemoveAllListeners();
colorButton.onClick.AddListener(OnColorClicked);
readyButton.onClick.RemoveAllListeners();
readyButton.onClick.AddListener(OnReadyClicked);
//when OnClientEnterLobby is called, the loval PlayerController is not yet created, so we need to redo that here to disable
//the add button if we reach maxLocalPlayer. We pass 0, as it was already counted on OnClientEnterLobby
if (LobbyManager.s_Singleton != null) LobbyManager.s_Singleton.OnPlayersNumberModified(0);
}
//This enable/disable the remove button depending on if that is the only local player or not
public void CheckRemoveButton()
{
if (!isLocalPlayer)
return;
int localPlayerCount = 0;
foreach (PlayerController p in ClientScene.localPlayers)
localPlayerCount += (p == null || p.playerControllerId == -1) ? 0 : 1;
removePlayerButton.interactable = localPlayerCount > 1;
}
public override void OnClientReady(bool readyState)
{
if (readyState)
{
ChangeReadyButtonColor(TransparentColor);
Text textComponent = readyButton.transform.GetChild(0).GetComponent<Text>();
textComponent.text = "READY";
textComponent.color = ReadyColor;
readyButton.interactable = false;
colorButton.interactable = false;
nameInput.interactable = false;
}
else
{
ChangeReadyButtonColor(isLocalPlayer ? JoinColor : NotReadyColor);
Text textComponent = readyButton.transform.GetChild(0).GetComponent<Text>();
textComponent.text = isLocalPlayer ? "JOIN" : "...";
textComponent.color = Color.white;
readyButton.interactable = isLocalPlayer;
colorButton.interactable = isLocalPlayer;
nameInput.interactable = isLocalPlayer;
}
}
public void OnPlayerListChanged(int idx)
{
GetComponent<Image>().color = (idx % 2 == 0) ? EvenRowColor : OddRowColor;
}
///===== callback from sync var
public void OnMyName(string newName)
{
playerName = newName;
nameInput.text = playerName;
}
public void OnMyColor(Color newColor)
{
playerColor = newColor;
colorButton.GetComponent<Image>().color = newColor;
}
//===== UI Handler
//Note that those handler use Command function, as we need to change the value on the server not locally
//so that all client get the new value throught syncvar
public void OnColorClicked()
{
CmdColorChange();
}
public void OnReadyClicked()
{
SendReadyToBeginMessage();
}
public void OnNameChanged(string str)
{
CmdNameChanged(str);
}
public void OnRemovePlayerClick()
{
if (isLocalPlayer)
{
RemovePlayer();
}
else if (isServer)
LobbyManager.s_Singleton.KickPlayer(connectionToClient);
}
public void ToggleJoinButton(bool enabled)
{
readyButton.gameObject.SetActive(enabled);
waitingPlayerButton.gameObject.SetActive(!enabled);
}
[ClientRpc]
public void RpcUpdateCountdown(int countdown)
{
LobbyManager.s_Singleton.countdownPanel.UIText.text = "Match Starting in " + countdown;
LobbyManager.s_Singleton.countdownPanel.gameObject.SetActive(countdown != 0);
}
[ClientRpc]
public void RpcUpdateRemoveButton()
{
CheckRemoveButton();
}
//====== Server Command
[Command]
public void CmdColorChange()
{
int idx = System.Array.IndexOf(Colors, playerColor);
int inUseIdx = _colorInUse.IndexOf(idx);
if (idx < 0) idx = 0;
idx = (idx + 1) % Colors.Length;
bool alreadyInUse = false;
do
{
alreadyInUse = false;
for (int i = 0; i < _colorInUse.Count; ++i)
{
if (_colorInUse[i] == idx)
{//that color is already in use
alreadyInUse = true;
idx = (idx + 1) % Colors.Length;
}
}
}
while (alreadyInUse);
if (inUseIdx >= 0)
{//if we already add an entry in the colorTabs, we change it
_colorInUse[inUseIdx] = idx;
}
else
{//else we add it
_colorInUse.Add(idx);
}
playerColor = Colors[idx];
}
[Command]
public void CmdNameChanged(string name)
{
playerName = name;
}
//Cleanup thing when get destroy (which happen when client kick or disconnect)
public void OnDestroy()
{
LobbyPlayerList._instance.RemovePlayer(this);
if (LobbyManager.s_Singleton != null) LobbyManager.s_Singleton.OnPlayersNumberModified(-1);
int idx = System.Array.IndexOf(Colors, playerColor);
if (idx < 0)
return;
for (int i = 0; i < _colorInUse.Count; ++i)
{
if (_colorInUse[i] == idx)
{//that color is already in use
_colorInUse.RemoveAt(i);
break;
}
}
}
}
}
The base class of the above LobbyPlayer:
C# Code: NATLobbyPlayer
using System;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Networking.NetworkSystem;
using UnityEngine.SceneManagement;
namespace NATTraversal
{
[DisallowMultipleComponent]
public class NATLobbyPlayer : NetworkBehaviour
{
[SerializeField]
public bool ShowLobbyGUI = true;
byte m_Slot;
bool m_ReadyToBegin;
public byte slot { get { return m_Slot; } set { m_Slot = value; } }
public bool readyToBegin { get { return m_ReadyToBegin; } set { m_ReadyToBegin = value; } }
void Start()
{
DontDestroyOnLoad(gameObject);
}
void OnEnable()
{
SceneManager.sceneLoaded += OnSceneLoaded;
}
void OnDisable()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
}
public override void OnStartClient()
{
var lobby = NetworkManager.singleton as NATLobbyManager;
if (lobby)
{
lobby.lobbySlots[m_Slot] = this;
m_ReadyToBegin = false;
OnClientEnterLobby();
}
else
{
Debug.LogError("LobbyPlayer could not find a NATLobbyManager. The LobbyPlayer requires a NATLobbyManager object to function. Make sure that there is one in the scene.");
}
}
public void SendReadyToBeginMessage()
{
if (LogFilter.logDebug) { Debug.Log("NATLobbyPlayer SendReadyToBeginMessage"); }
var lobby = NetworkManager.singleton as NATLobbyManager;
if (lobby)
{
var msg = new NATLobbyManager.LobbyReadyToBeginMessage();
msg.slotId = (byte)playerControllerId;
msg.readyState = true;
lobby.client.Send(MsgType.LobbyReadyToBegin, msg);
}
}
public void SendNotReadyToBeginMessage()
{
if (LogFilter.logDebug) { Debug.Log("NATLobbyPlayer SendReadyToBeginMessage"); }
var lobby = NetworkManager.singleton as NATLobbyManager;
if (lobby)
{
var msg = new NATLobbyManager.LobbyReadyToBeginMessage();
msg.slotId = (byte)playerControllerId;
msg.readyState = false;
lobby.client.Send(MsgType.LobbyReadyToBegin, msg);
}
}
public void SendSceneLoadedMessage()
{
if (LogFilter.logDebug) { Debug.Log("NATLobbyPlayer SendSceneLoadedMessage"); }
var lobby = NetworkManager.singleton as NATLobbyManager;
if (lobby)
{
var msg = new IntegerMessage(playerControllerId);
lobby.client.Send(MsgType.LobbySceneLoaded, msg);
}
}
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
var lobby = NetworkManager.singleton as NATLobbyManager;
if (lobby)
{
// dont even try this in the startup scene
// Should we check if the LoadSceneMode is Single or Additive??
// Can the lobby scene be loaded Additively??
string loadedSceneName = scene.name;
if (loadedSceneName == lobby.lobbyScene)
{
return;
}
}
if (isLocalPlayer)
{
SendSceneLoadedMessage();
}
}
public void RemovePlayer()
{
if (isLocalPlayer && !m_ReadyToBegin)
{
if (LogFilter.logDebug) { Debug.Log("NATLobbyPlayer RemovePlayer"); }
ClientScene.RemovePlayer(GetComponent<NetworkIdentity>().playerControllerId);
}
}
// ------------------------ callbacks ------------------------
public virtual void OnClientEnterLobby()
{
}
public virtual void OnClientExitLobby()
{
}
public virtual void OnClientReady(bool readyState)
{
}
// ------------------------ Custom Serialization ------------------------
public override bool OnSerialize(NetworkWriter writer, bool initialState)
{
// dirty flag
writer.WritePackedUInt32(1);
writer.Write(m_Slot);
writer.Write(m_ReadyToBegin);
return true;
}
public override void OnDeserialize(NetworkReader reader, bool initialState)
{
var dirty = reader.ReadPackedUInt32();
if (dirty == 0)
return;
m_Slot = reader.ReadByte();
m_ReadyToBegin = reader.ReadBoolean();
}
// ------------------------ optional UI ------------------------
void OnGUI()
{
if (!ShowLobbyGUI)
return;
var lobby = NetworkManager.singleton as NATLobbyManager;
if (lobby)
{
if (!lobby.showLobbyGUI)
return;
string loadedSceneName = SceneManager.GetSceneAt(0).name;
if (loadedSceneName != lobby.lobbyScene)
return;
}
Rect rec = new Rect(100 + m_Slot * 100, 200, 90, 20);
if (isLocalPlayer)
{
string youStr;
if (m_ReadyToBegin)
{
youStr = "(Ready)";
}
else
{
youStr = "(Not Ready)";
}
GUI.Label(rec, youStr);
if (m_ReadyToBegin)
{
rec.y += 25;
if (GUI.Button(rec, "STOP"))
{
SendNotReadyToBeginMessage();
}
}
else
{
rec.y += 25;
if (GUI.Button(rec, "START"))
{
SendReadyToBeginMessage();
}
rec.y += 25;
if (GUI.Button(rec, "Remove"))
{
ClientScene.RemovePlayer(GetComponent<NetworkIdentity>().playerControllerId);
}
}
}
else
{
GUI.Label(rec, "Player [" + netId + "]");
rec.y += 25;
GUI.Label(rec, "Ready [" + m_ReadyToBegin + "]");
}
}
}
}
The call to StartHostAll() is contained in this script and is fired when the join button is clicked:
C# Code: LobbyMainMenu
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
namespace Prototype.NetworkLobby
{
//Main menu, mainly only a bunch of callback called by the UI (setup throught the Inspector)
public class LobbyMainMenu : MonoBehaviour
{
public LobbyManager lobbyManager;
public RectTransform lobbyServerList;
public RectTransform lobbyPanel;
public InputField ipInput;
public InputField matchNameInput;
public void OnEnable()
{
lobbyManager.topPanel.ToggleVisibility(true);
ipInput.onEndEdit.RemoveAllListeners();
ipInput.onEndEdit.AddListener(onEndEditIP);
matchNameInput.onEndEdit.RemoveAllListeners();
matchNameInput.onEndEdit.AddListener(onEndEditGameName);
}
public void OnClickHost()
{
lobbyManager.StartHost();
}
public void OnClickJoin()
{
lobbyManager.ChangeTo(lobbyPanel);
lobbyManager.networkAddress = ipInput.text;
lobbyManager.StartClient();
lobbyManager.backDelegate = lobbyManager.StopClientClbk;
lobbyManager.DisplayIsConnecting();
lobbyManager.SetServerInfo("Connecting...", lobbyManager.networkAddress);
}
public void OnClickDedicated()
{
lobbyManager.ChangeTo(null);
lobbyManager.StartServer();
lobbyManager.backDelegate = lobbyManager.StopServerClbk;
lobbyManager.SetServerInfo("Dedicated Server", lobbyManager.networkAddress);
}
public void OnClickCreateMatchmakingGame()
{
lobbyManager.StartMatchMaker();
/*lobbyManager.matchMaker.CreateMatch(
matchNameInput.text,
(uint)lobbyManager.maxPlayers,
true,
"", "", "", 0, 0,
lobbyManager.OnMatchCreate);
*/
lobbyManager.StartHostAll(
matchNameInput.text,
(uint)lobbyManager.maxPlayers,
true,
"", 0, 0,
lobbyManager.OnMatchCreate);
lobbyManager.backDelegate = lobbyManager.StopHost;
lobbyManager._isMatchmaking = true;
lobbyManager.DisplayIsConnecting();
lobbyManager.SetServerInfo("Matchmaker Host", lobbyManager.matchHost);
}
public void OnClickOpenServerList()
{
lobbyManager.StartMatchMaker();
lobbyManager.backDelegate = lobbyManager.SimpleBackClbk;
lobbyManager.ChangeTo(lobbyServerList);
}
void onEndEditIP(string text)
{
if (Input.GetKeyDown(KeyCode.Return))
{
OnClickJoin();
}
}
void onEndEditGameName(string text)
{
if (Input.GetKeyDown(KeyCode.Return))
{
OnClickCreateMatchmakingGame();
}
}
}
}
[EDIT]
I went ahead and added the override method to my LobbyManager.cs:
public override void OnMatchJoined(bool success, string extendedInfo, MatchInfo matchInfo) {
Debug.Log("JUST CALLED: OnMatchJoined");
//What should I do here?
}
Now the NullReferenceException error for OnMatchJoined() is no longer thrown and that function is called right after pressing “Join”, however it only contains a Debug line and I do not know what else to put inside of it?
[EDIT]
I just tried adding a call to the base function but I keep getting a NullReferenceException:
public override void OnMatchJoined(bool success, string extendedInfo, MatchInfo matchInfo) {
Debug.Log("JUST CALLED: OnMatchJoined");
base.OnMatchJoined (success, extendedInfo, matchInfo);
}
/*PRODUCES ERROR:
NullReferenceException: Object reference not set to an instance of an object
NATTraversal.NetworkManager.OnMatchJoined (Boolean success, System.String extendedInfo, UnityEngine.Networking.Match.MatchInfo info)
Prototype.NetworkLobby.LobbyManager.OnMatchJoined (Boolean success, System.String extendedInfo, UnityEngine.Networking.Match.MatchInfo matchInfo) (at Assets/Lobby/Scripts/Lobby/LobbyManager.cs:255)
UnityEngine.Networking.Match.NetworkMatch.OnMatchJoined (UnityEngine.Networking.Match.JoinMatchResponse response, UnityEngine.Networking.Match.DataResponseDelegate`1 userCallback) (at C:/buildslave/unity/build/Runtime/Networking/Managed/MatchMakingClient.cs:226)
UnityEngine.Networking.Match.NetworkMatch+<ProcessMatchResponse>c__Iterator0`2[UnityEngine.Networking.Match.JoinMatchResponse,UnityEngine.Networking.Match.NetworkMatch+DataResponseDelegate`1[UnityEngine.Networking.Match.MatchInfo]].MoveNext () (at C:/buildslave/unity/build/Runtime/Networking/Managed/MatchMakingClient.cs:438)
UnityEngine.SetupCoroutine.InvokeMoveNext (IEnumerator enumerator, IntPtr returnValueAddress) (at C:/buildslave/unity/build/Runtime/Export/Coroutines.cs:17)
*/