防止Unity网络大厅中的空游戏室被破坏
Prevent Empty Game Room in Unity Network Lobby from Destroying
软件:Unity 2018.1
语言:C# 4
插件:网络大厅
Problem/Question:
我的 Unity Network Lobby 已设置并正常运行。当所有玩家离开房间或断开连接时,我试图防止游戏室被破坏,以便以后始终可以返回。
尝试:
我尝试将 int i = 0
更改为 i = -1
(没有用,没有影响)
我尝试将 if (p != null)
更改为 if (p == null)
(没有用,没有影响)
我也尝试使用 'Command, /' 使代码静音。 (没用,没影响)
代码如下:
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);
}
}
}
完整代码:
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 : NetworkLobbyManager
{
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()
{
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
{
StopHost();
}
ChangeTo(mainMenuPanel);
}
public void StopClientClbk()
{
StopClient();
if (_isMatchmaking)
{
StopMatchMaker();
}
ChangeTo(mainMenuPanel);
}
public void StopServerClbk()
{
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 OnMatchCreate(bool success, string extendedInfo, MatchInfo matchInfo)
{
base.OnMatchCreate(success, extendedInfo, matchInfo);
_currentMatchID = (System.UInt64)matchInfo.networkId;
}
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)
{
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)
{
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);
}
}
}
我认为你必须重新考虑这个概念。
如果您想 "reference" 保留到最后一个场景,您应该以某种静态方式(json?)或至少在可变游戏对象中保存场景状态。
您不会保留 "OnDestroyOnLoad" 您的场景,因为这是一种资源浪费。
但是,如果您仍然想要,我会建议您使用附加方式加载场景。所以你以加法的方式加载新场景,并禁用上一个场景的对象。
加法模式: https://docs.unity3d.com/ScriptReference/SceneManagement.LoadSceneMode.Additive.html
禁用游戏对象: https://docs.unity3d.com/ScriptReference/GameObject.SetActive.html
软件:Unity 2018.1 语言:C# 4 插件:网络大厅
Problem/Question:
我的 Unity Network Lobby 已设置并正常运行。当所有玩家离开房间或断开连接时,我试图防止游戏室被破坏,以便以后始终可以返回。
尝试:
我尝试将 int i = 0
更改为 i = -1
(没有用,没有影响)
我尝试将 if (p != null)
更改为 if (p == null)
(没有用,没有影响)
我也尝试使用 'Command, /' 使代码静音。 (没用,没影响)
代码如下:
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);
}
}
}
完整代码:
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 : NetworkLobbyManager
{
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()
{
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
{
StopHost();
}
ChangeTo(mainMenuPanel);
}
public void StopClientClbk()
{
StopClient();
if (_isMatchmaking)
{
StopMatchMaker();
}
ChangeTo(mainMenuPanel);
}
public void StopServerClbk()
{
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 OnMatchCreate(bool success, string extendedInfo, MatchInfo matchInfo)
{
base.OnMatchCreate(success, extendedInfo, matchInfo);
_currentMatchID = (System.UInt64)matchInfo.networkId;
}
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)
{
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)
{
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);
}
}
}
我认为你必须重新考虑这个概念。
如果您想 "reference" 保留到最后一个场景,您应该以某种静态方式(json?)或至少在可变游戏对象中保存场景状态。
您不会保留 "OnDestroyOnLoad" 您的场景,因为这是一种资源浪费。
但是,如果您仍然想要,我会建议您使用附加方式加载场景。所以你以加法的方式加载新场景,并禁用上一个场景的对象。
加法模式: https://docs.unity3d.com/ScriptReference/SceneManagement.LoadSceneMode.Additive.html
禁用游戏对象: https://docs.unity3d.com/ScriptReference/GameObject.SetActive.html