Mirror/Unity-Technologies-networking/Runtime/NetworkManager.cs

1341 lines
49 KiB
C#
Raw Normal View History

#if ENABLE_UNET
using System;
using System.Collections.Generic;
using System.Net;
using UnityEngine.Networking.Match;
using UnityEngine.Networking.NetworkSystem;
using UnityEngine.Networking.Types;
using UnityEngine.SceneManagement;
namespace UnityEngine.Networking
{
public enum PlayerSpawnMethod
{
Random,
RoundRobin
};
[AddComponentMenu("Network/NetworkManager")]
public class NetworkManager : MonoBehaviour
{
// configuration
[SerializeField] int m_NetworkPort = 7777;
[SerializeField] bool m_ServerBindToIP;
[SerializeField] string m_ServerBindAddress = "";
[SerializeField] string m_NetworkAddress = "localhost";
[SerializeField] bool m_DontDestroyOnLoad = true;
[SerializeField] bool m_RunInBackground = true;
[SerializeField] bool m_ScriptCRCCheck = true;
[SerializeField] float m_MaxDelay = 0.01f;
[SerializeField] LogFilter.FilterLevel m_LogLevel = (LogFilter.FilterLevel)LogFilter.Info;
[SerializeField] GameObject m_PlayerPrefab;
[SerializeField] bool m_AutoCreatePlayer = true;
[SerializeField] PlayerSpawnMethod m_PlayerSpawnMethod;
[SerializeField] string m_OfflineScene = "";
[SerializeField] string m_OnlineScene = "";
[SerializeField] List<GameObject> m_SpawnPrefabs = new List<GameObject>();
[SerializeField] bool m_CustomConfig;
[SerializeField] int m_MaxConnections = 4;
[SerializeField] ConnectionConfig m_ConnectionConfig;
[SerializeField] GlobalConfig m_GlobalConfig;
[SerializeField] List<QosType> m_Channels = new List<QosType>();
[SerializeField] bool m_UseWebSockets;
[SerializeField] bool m_UseSimulator;
[SerializeField] int m_SimulatedLatency = 1;
[SerializeField] float m_PacketLossPercentage;
[SerializeField] int m_MaxBufferedPackets = ChannelBuffer.MaxPendingPacketCount;
[SerializeField] bool m_AllowFragmentation = true;
// matchmaking configuration
[SerializeField] string m_MatchHost = "mm.unet.unity3d.com";
[SerializeField] int m_MatchPort = 443;
[SerializeField] public string matchName = "default";
[SerializeField] public uint matchSize = 4;
#if ENABLE_UNET_HOST_MIGRATION
NetworkMigrationManager m_MigrationManager;
#endif
private EndPoint m_EndPoint;
bool m_ClientLoadedScene;
// properties
public int networkPort { get { return m_NetworkPort; } set { m_NetworkPort = value; } }
public bool serverBindToIP { get { return m_ServerBindToIP; } set { m_ServerBindToIP = value; }}
public string serverBindAddress { get { return m_ServerBindAddress; } set { m_ServerBindAddress = value; }}
public string networkAddress { get { return m_NetworkAddress; } set { m_NetworkAddress = value; } }
public bool dontDestroyOnLoad { get { return m_DontDestroyOnLoad; } set { m_DontDestroyOnLoad = value; } }
public bool runInBackground { get { return m_RunInBackground; } set { m_RunInBackground = value; } }
public bool scriptCRCCheck { get { return m_ScriptCRCCheck; } set { m_ScriptCRCCheck = value; }}
[Obsolete("moved to NetworkMigrationManager")]
public bool sendPeerInfo { get { return false; } set {} }
public float maxDelay { get { return m_MaxDelay; } set { m_MaxDelay = value; } }
public LogFilter.FilterLevel logLevel { get { return m_LogLevel; } set { m_LogLevel = value; LogFilter.currentLogLevel = (int)value; } }
public GameObject playerPrefab { get { return m_PlayerPrefab; } set { m_PlayerPrefab = value; } }
public bool autoCreatePlayer { get { return m_AutoCreatePlayer; } set { m_AutoCreatePlayer = value; } }
public PlayerSpawnMethod playerSpawnMethod { get { return m_PlayerSpawnMethod; } set { m_PlayerSpawnMethod = value; } }
public string offlineScene { get { return m_OfflineScene; } set { m_OfflineScene = value; } }
public string onlineScene { get { return m_OnlineScene; } set { m_OnlineScene = value; } }
public List<GameObject> spawnPrefabs { get { return m_SpawnPrefabs; }}
public List<Transform> startPositions { get { return s_StartPositions; }}
public bool customConfig { get { return m_CustomConfig; } set { m_CustomConfig = value; } }
public ConnectionConfig connectionConfig { get { if (m_ConnectionConfig == null) { m_ConnectionConfig = new ConnectionConfig(); } return m_ConnectionConfig; } }
public GlobalConfig globalConfig { get { if (m_GlobalConfig == null) { m_GlobalConfig = new GlobalConfig(); } return m_GlobalConfig; } }
public int maxConnections { get { return m_MaxConnections; } set { m_MaxConnections = value; } }
public List<QosType> channels { get { return m_Channels; } }
public EndPoint secureTunnelEndpoint { get { return m_EndPoint; } set { m_EndPoint = value; } }
public bool useWebSockets { get { return m_UseWebSockets; } set { m_UseWebSockets = value; } }
public bool useSimulator { get { return m_UseSimulator; } set { m_UseSimulator = value; }}
public int simulatedLatency { get { return m_SimulatedLatency; } set { m_SimulatedLatency = value; } }
public float packetLossPercentage { get { return m_PacketLossPercentage; } set { m_PacketLossPercentage = value; } }
public string matchHost { get { return m_MatchHost; } set { m_MatchHost = value; } }
public int matchPort { get { return m_MatchPort; } set { m_MatchPort = value; } }
public bool clientLoadedScene { get { return m_ClientLoadedScene; } set { m_ClientLoadedScene = value; } }
#if ENABLE_UNET_HOST_MIGRATION
public NetworkMigrationManager migrationManager { get { return m_MigrationManager; }}
#endif
// only really valid on the server
public int numPlayers
{
get
{
int numPlayers = 0;
for (int i = 0; i < NetworkServer.connections.Count; i++)
{
var conn = NetworkServer.connections[i];
if (conn == null)
continue;
for (int ii = 0; ii < conn.playerControllers.Count; ii++)
{
if (conn.playerControllers[ii].IsValid)
{
numPlayers += 1;
}
}
}
return numPlayers;
}
}
// runtime data
static public string networkSceneName = ""; // this is used to make sure that all scene changes are initialized by UNET. loading a scene manually wont set networkSceneName, so UNET would still load it again on start.
public bool isNetworkActive;
public NetworkClient client;
static List<Transform> s_StartPositions = new List<Transform>();
static int s_StartPositionIndex;
// matchmaking runtime data
public MatchInfo matchInfo;
public NetworkMatch matchMaker;
public List<MatchInfoSnapshot> matches;
public static NetworkManager singleton;
// static message objects to avoid runtime-allocations
static AddPlayerMessage s_AddPlayerMessage = new AddPlayerMessage();
static RemovePlayerMessage s_RemovePlayerMessage = new RemovePlayerMessage();
static ErrorMessage s_ErrorMessage = new ErrorMessage();
static AsyncOperation s_LoadingSceneAsync;
static NetworkConnection s_ClientReadyConnection;
// this is used to persist network address between scenes.
static string s_Address;
#if UNITY_EDITOR
static bool s_DomainReload;
static NetworkManager s_PendingSingleton;
internal static void OnDomainReload()
{
s_DomainReload = true;
}
public NetworkManager()
{
s_PendingSingleton = this;
}
#endif
void Awake()
{
Debug.Log("Thank you for using HLAPI Pro! https://forum.unity3d.com/threads/unet-hlapi-pro-taking-unet-to-the-next-level.425437/");
InitializeSingleton();
}
void InitializeSingleton()
{
if (singleton != null && singleton == this)
{
return;
}
// do this early
var logLevel = (int)m_LogLevel;
if (logLevel != LogFilter.SetInScripting)
{
LogFilter.currentLogLevel = logLevel;
}
if (m_DontDestroyOnLoad)
{
if (singleton != null)
{
if (LogFilter.logDev) { Debug.Log("Multiple NetworkManagers detected in the scene. Only one NetworkManager can exist at a time. The duplicate NetworkManager will not be used."); }
Destroy(gameObject);
return;
}
if (LogFilter.logDev) { Debug.Log("NetworkManager created singleton (DontDestroyOnLoad)"); }
singleton = this;
if (Application.isPlaying) DontDestroyOnLoad(gameObject);
}
else
{
if (LogFilter.logDev) { Debug.Log("NetworkManager created singleton (ForScene)"); }
singleton = this;
}
if (m_NetworkAddress != "")
{
s_Address = m_NetworkAddress;
}
else if (s_Address != "")
{
m_NetworkAddress = s_Address;
}
}
void OnValidate()
{
if (m_SimulatedLatency < 1) m_SimulatedLatency = 1;
if (m_SimulatedLatency > 500) m_SimulatedLatency = 500;
if (m_PacketLossPercentage < 0) m_PacketLossPercentage = 0;
if (m_PacketLossPercentage > 99) m_PacketLossPercentage = 99;
if (m_MaxConnections <= 0) m_MaxConnections = 1;
if (m_MaxConnections > 32000) m_MaxConnections = 32000;
if (m_MaxBufferedPackets <= 0) m_MaxBufferedPackets = 0;
if (m_MaxBufferedPackets > ChannelBuffer.MaxBufferedPackets)
{
m_MaxBufferedPackets = ChannelBuffer.MaxBufferedPackets;
if (LogFilter.logError) { Debug.LogError("NetworkManager - MaxBufferedPackets cannot be more than " + ChannelBuffer.MaxBufferedPackets); }
}
if (m_PlayerPrefab != null && m_PlayerPrefab.GetComponent<NetworkIdentity>() == null)
{
if (LogFilter.logError) { Debug.LogError("NetworkManager - playerPrefab must have a NetworkIdentity."); }
m_PlayerPrefab = null;
}
if (m_ConnectionConfig != null && m_ConnectionConfig.MinUpdateTimeout <= 0)
{
if (LogFilter.logError) { Debug.LogError("NetworkManager MinUpdateTimeout cannot be zero or less. The value will be reset to 1 millisecond"); }
m_ConnectionConfig.MinUpdateTimeout = 1;
}
if (m_GlobalConfig != null)
{
if (m_GlobalConfig.ThreadAwakeTimeout <= 0)
{
if (LogFilter.logError) { Debug.LogError("NetworkManager ThreadAwakeTimeout cannot be zero or less. The value will be reset to 1 millisecond"); }
m_GlobalConfig.ThreadAwakeTimeout = 1;
}
}
// vis2k
// Channels.DefaultReliable = 0 and Unreliable = 1, so we have to
// force some kind of reliable to channel 0 and unreliable to 1.
// Otherwise the HLAPI code will assume a wrong channel for a lot of
// built-in functions.
//
// Changing them would result in chaos. Using anything != Fragmented
// would fail to send bigger messages too.
if (channels.Count < 1 || !NetworkConnection.IsReliableQoS(channels[0]))
Debug.LogWarning("NetworkManager: First channel needs to be Reliable because in the code Channels.DefaultReliable is 0.");
if (channels.Count < 2 || !NetworkConnection.IsUnreliableQoS(channels[1]))
Debug.LogWarning("NetworkManager: Second channel needs to be Unreliable because in the code Channels.DefaultReliable is 1.");
}
internal void RegisterServerMessages()
{
NetworkServer.RegisterHandler(MsgType.Connect, OnServerConnectInternal);
NetworkServer.RegisterHandler(MsgType.Disconnect, OnServerDisconnectInternal);
NetworkServer.RegisterHandler(MsgType.Ready, OnServerReadyMessageInternal);
NetworkServer.RegisterHandler(MsgType.AddPlayer, OnServerAddPlayerMessageInternal);
NetworkServer.RegisterHandler(MsgType.RemovePlayer, OnServerRemovePlayerMessageInternal);
NetworkServer.RegisterHandler(MsgType.Error, OnServerErrorInternal);
}
#if ENABLE_UNET_HOST_MIGRATION
public void SetupMigrationManager(NetworkMigrationManager man)
{
m_MigrationManager = man;
}
#endif
public bool StartServer(ConnectionConfig config, int maxConnections)
{
return StartServer(null, config, maxConnections);
}
public bool StartServer()
{
return StartServer(null);
}
public bool StartServer(MatchInfo info)
{
return StartServer(info, null, -1);
}
bool StartServer(MatchInfo info, ConnectionConfig config, int maxConnections)
{
InitializeSingleton();
OnStartServer();
if (m_RunInBackground)
Application.runInBackground = true;
NetworkCRC.scriptCRCCheck = scriptCRCCheck;
NetworkServer.useWebSockets = m_UseWebSockets;
if (m_GlobalConfig != null)
{
NetworkTransport.Init(m_GlobalConfig);
}
// passing a config overrides setting the connectionConfig property
if (m_CustomConfig && m_ConnectionConfig != null && config == null)
{
m_ConnectionConfig.Channels.Clear();
for (int channelId = 0; channelId < m_Channels.Count; channelId++)
{
m_ConnectionConfig.AddChannel(m_Channels[channelId]);
}
NetworkServer.Configure(m_ConnectionConfig, m_MaxConnections);
}
if (config != null)
{
NetworkServer.Configure(config, maxConnections);
}
if (info != null)
{
if (!NetworkServer.Listen(info, m_NetworkPort))
{
if (LogFilter.logError) { Debug.LogError("StartServer listen failed."); }
return false;
}
}
else
{
if (m_ServerBindToIP && !string.IsNullOrEmpty(m_ServerBindAddress))
{
if (!NetworkServer.Listen(m_ServerBindAddress, m_NetworkPort))
{
if (LogFilter.logError) { Debug.LogError("StartServer listen on " + m_ServerBindAddress + " failed."); }
return false;
}
}
else
{
if (!NetworkServer.Listen(m_NetworkPort))
{
if (LogFilter.logError) { Debug.LogError("StartServer listen failed."); }
return false;
}
}
}
// this must be after Listen(), since that registers the default message handlers
RegisterServerMessages();
if (LogFilter.logDebug) { Debug.Log("NetworkManager StartServer port:" + m_NetworkPort); }
isNetworkActive = true;
// Only change scene if the requested online scene is not blank, and is not already loaded
string loadedSceneName = SceneManager.GetSceneAt(0).name;
if (!string.IsNullOrEmpty(m_OnlineScene) && m_OnlineScene != loadedSceneName && m_OnlineScene != m_OfflineScene)
{
ServerChangeScene(m_OnlineScene);
}
else
{
NetworkServer.SpawnObjects();
}
return true;
}
internal void RegisterClientMessages(NetworkClient client)
{
client.RegisterHandler(MsgType.Connect, OnClientConnectInternal);
client.RegisterHandler(MsgType.Disconnect, OnClientDisconnectInternal);
client.RegisterHandler(MsgType.NotReady, OnClientNotReadyMessageInternal);
client.RegisterHandler(MsgType.Error, OnClientErrorInternal);
client.RegisterHandler(MsgType.Scene, OnClientSceneInternal);
if (m_PlayerPrefab != null)
{
ClientScene.RegisterPrefab(m_PlayerPrefab);
}
for (int i = 0; i < m_SpawnPrefabs.Count; i++)
{
var prefab = m_SpawnPrefabs[i];
if (prefab != null)
{
ClientScene.RegisterPrefab(prefab);
}
}
}
public void UseExternalClient(NetworkClient externalClient)
{
if (m_RunInBackground)
Application.runInBackground = true;
if (externalClient != null)
{
client = externalClient;
isNetworkActive = true;
RegisterClientMessages(client);
OnStartClient(client);
}
else
{
OnStopClient();
// this should stop any game-related systems, but not close the connection
ClientScene.DestroyAllClientObjects();
ClientScene.HandleClientDisconnect(client.connection);
client = null;
if (!string.IsNullOrEmpty(m_OfflineScene))
{
ClientChangeScene(m_OfflineScene, false);
}
}
s_Address = m_NetworkAddress;
}
public NetworkClient StartClient(MatchInfo info, ConnectionConfig config, int hostPort)
{
InitializeSingleton();
matchInfo = info;
if (m_RunInBackground)
Application.runInBackground = true;
isNetworkActive = true;
if (m_GlobalConfig != null)
{
NetworkTransport.Init(m_GlobalConfig);
}
client = new NetworkClient();
client.hostPort = hostPort;
if (config != null)
{
if ((config.UsePlatformSpecificProtocols) && (UnityEngine.Application.platform != RuntimePlatform.PS4) && (UnityEngine.Application.platform != RuntimePlatform.PSP2))
throw new ArgumentOutOfRangeException("Platform specific protocols are not supported on this platform");
client.Configure(config, 1);
}
else
{
if (m_CustomConfig && m_ConnectionConfig != null)
{
m_ConnectionConfig.Channels.Clear();
for (int i = 0; i < m_Channels.Count; i++)
{
m_ConnectionConfig.AddChannel(m_Channels[i]);
}
if ((m_ConnectionConfig.UsePlatformSpecificProtocols) && (UnityEngine.Application.platform != RuntimePlatform.PS4) && (UnityEngine.Application.platform != RuntimePlatform.PSP2))
throw new ArgumentOutOfRangeException("Platform specific protocols are not supported on this platform");
client.Configure(m_ConnectionConfig, m_MaxConnections);
}
}
RegisterClientMessages(client);
if (matchInfo != null)
{
if (LogFilter.logDebug) { Debug.Log("NetworkManager StartClient match: " + matchInfo); }
client.Connect(matchInfo);
}
else if (m_EndPoint != null)
{
if (LogFilter.logDebug) { Debug.Log("NetworkManager StartClient using provided SecureTunnel"); }
client.Connect(m_EndPoint);
}
else
{
if (string.IsNullOrEmpty(m_NetworkAddress))
{
if (LogFilter.logError) { Debug.LogError("Must set the Network Address field in the manager"); }
return null;
}
if (LogFilter.logDebug) { Debug.Log("NetworkManager StartClient address:" + m_NetworkAddress + " port:" + m_NetworkPort); }
if (m_UseSimulator)
{
client.ConnectWithSimulator(m_NetworkAddress, m_NetworkPort, m_SimulatedLatency, m_PacketLossPercentage);
}
else
{
client.Connect(m_NetworkAddress, m_NetworkPort);
}
}
#if ENABLE_UNET_HOST_MIGRATION
if (m_MigrationManager != null)
{
m_MigrationManager.Initialize(client, matchInfo);
}
#endif
OnStartClient(client);
s_Address = m_NetworkAddress;
return client;
}
public NetworkClient StartClient(MatchInfo matchInfo)
{
return StartClient(matchInfo, null);
}
public NetworkClient StartClient()
{
return StartClient(null, null);
}
public NetworkClient StartClient(MatchInfo info, ConnectionConfig config)
{
return StartClient(info, config, 0);
}
public virtual NetworkClient StartHost(ConnectionConfig config, int maxConnections)
{
OnStartHost();
if (StartServer(config, maxConnections))
{
var client = ConnectLocalClient();
OnServerConnect(client.connection);
OnStartClient(client);
return client;
}
return null;
}
public virtual NetworkClient StartHost(MatchInfo info)
{
OnStartHost();
matchInfo = info;
if (StartServer(info))
{
var client = ConnectLocalClient();
OnStartClient(client);
return client;
}
return null;
}
public virtual NetworkClient StartHost()
{
OnStartHost();
if (StartServer())
{
var localClient = ConnectLocalClient();
OnStartClient(localClient);
return localClient;
}
return null;
}
NetworkClient ConnectLocalClient()
{
if (LogFilter.logDebug) { Debug.Log("NetworkManager StartHost port:" + m_NetworkPort); }
m_NetworkAddress = "localhost";
client = ClientScene.ConnectLocalServer();
RegisterClientMessages(client);
#if ENABLE_UNET_HOST_MIGRATION
if (m_MigrationManager != null)
{
m_MigrationManager.Initialize(client, matchInfo);
}
#endif
return client;
}
public void StopHost()
{
#if ENABLE_UNET_HOST_MIGRATION
var serverWasActive = NetworkServer.active;
#endif
OnStopHost();
StopServer();
StopClient();
#if ENABLE_UNET_HOST_MIGRATION
if (m_MigrationManager != null)
{
if (serverWasActive)
{
m_MigrationManager.LostHostOnHost();
}
}
#endif
}
public void StopServer()
{
if (!NetworkServer.active)
return;
OnStopServer();
if (LogFilter.logDebug) { Debug.Log("NetworkManager StopServer"); }
isNetworkActive = false;
NetworkServer.Shutdown();
StopMatchMaker();
if (!string.IsNullOrEmpty(m_OfflineScene))
{
ServerChangeScene(m_OfflineScene);
}
CleanupNetworkIdentities();
}
public void StopClient()
{
OnStopClient();
if (LogFilter.logDebug) { Debug.Log("NetworkManager StopClient"); }
isNetworkActive = false;
if (client != null)
{
// only shutdown this client, not ALL clients.
client.Disconnect();
client.Shutdown();
client = null;
}
StopMatchMaker();
ClientScene.DestroyAllClientObjects();
if (!string.IsNullOrEmpty(m_OfflineScene))
{
ClientChangeScene(m_OfflineScene, false);
}
CleanupNetworkIdentities();
}
public virtual void ServerChangeScene(string newSceneName)
{
if (string.IsNullOrEmpty(newSceneName))
{
if (LogFilter.logError) { Debug.LogError("ServerChangeScene empty scene name"); }
return;
}
if (LogFilter.logDebug) { Debug.Log("ServerChangeScene " + newSceneName); }
NetworkServer.SetAllClientsNotReady();
networkSceneName = newSceneName;
s_LoadingSceneAsync = SceneManager.LoadSceneAsync(newSceneName);
StringMessage msg = new StringMessage(networkSceneName);
NetworkServer.SendToAll(MsgType.Scene, msg);
s_StartPositionIndex = 0;
s_StartPositions.Clear();
}
void CleanupNetworkIdentities()
{
foreach (NetworkIdentity netId in Resources.FindObjectsOfTypeAll<NetworkIdentity>())
{
netId.MarkForReset();
}
}
internal void ClientChangeScene(string newSceneName, bool forceReload)
{
if (string.IsNullOrEmpty(newSceneName))
{
if (LogFilter.logError) { Debug.LogError("ClientChangeScene empty scene name"); }
return;
}
if (LogFilter.logDebug) { Debug.Log("ClientChangeScene newSceneName:" + newSceneName + " networkSceneName:" + networkSceneName); }
if (newSceneName == networkSceneName)
{
#if ENABLE_UNET_HOST_MIGRATION
if (m_MigrationManager != null)
{
// special case for rejoining a match after host migration
FinishLoadScene();
return;
}
#endif
if (!forceReload)
{
FinishLoadScene();
return;
}
}
// vis2k: pause message handling while loading scene. otherwise we will process messages and then lose all
// the sate as soon as the load is finishing, causing all kinds of bugs because of missing state.
// (client may be null after StopClient etc.)
if (client != null)
{
if (LogFilter.logDebug) { Debug.Log("ClientChangeScene: pausing handlers while scene is loading to avoid data loss after scene was loaded."); }
client.connection.PauseHandling();
}
s_LoadingSceneAsync = SceneManager.LoadSceneAsync(newSceneName);
networkSceneName = newSceneName;
}
void FinishLoadScene()
{
// NOTE: this cannot use NetworkClient.allClients[0] - that client may be for a completely different purpose.
if (client != null)
{
// process queued messages that we received while loading the scene
if (LogFilter.logDebug) { Debug.Log("FinishLoadScene: resuming handlers after scene was loading."); }
client.connection.ResumeHandling();
if (s_ClientReadyConnection != null)
{
m_ClientLoadedScene = true;
OnClientConnect(s_ClientReadyConnection);
s_ClientReadyConnection = null;
}
}
else
{
if (LogFilter.logDev) { Debug.Log("FinishLoadScene client is null"); }
}
if (NetworkServer.active)
{
NetworkServer.SpawnObjects();
OnServerSceneChanged(networkSceneName);
}
if (IsClientConnected() && client != null)
{
RegisterClientMessages(client);
OnClientSceneChanged(client.connection);
}
}
internal static void UpdateScene()
{
#if UNITY_EDITOR
// In the editor, reloading scripts in play mode causes a Mono Domain Reload.
// This gets the transport layer (C++) and HLAPI (C#) out of sync.
// This check below detects that problem and shuts down the transport layer to bring both systems back in sync.
if (singleton == null && s_PendingSingleton != null && s_DomainReload)
{
if (LogFilter.logWarn) { Debug.LogWarning("NetworkManager detected a script reload in the editor. This has caused the network to be shut down."); }
s_DomainReload = false;
s_PendingSingleton.InitializeSingleton();
// destroy network objects
var uvs = FindObjectsOfType<NetworkIdentity>();
foreach (var uv in uvs)
{
GameObject.Destroy(uv.gameObject);
}
singleton.StopHost();
NetworkTransport.Shutdown();
}
#endif
if (singleton == null)
return;
if (s_LoadingSceneAsync == null)
return;
if (!s_LoadingSceneAsync.isDone)
return;
if (LogFilter.logDebug) { Debug.Log("ClientChangeScene done readyCon:" + s_ClientReadyConnection); }
singleton.FinishLoadScene();
s_LoadingSceneAsync.allowSceneActivation = true;
s_LoadingSceneAsync = null;
}
void OnDestroy()
{
if (LogFilter.logDev) { Debug.Log("NetworkManager destroyed"); }
}
static public void RegisterStartPosition(Transform start)
{
if (LogFilter.logDebug) { Debug.Log("RegisterStartPosition: (" + start.gameObject.name + ") " + start.position); }
s_StartPositions.Add(start);
}
static public void UnRegisterStartPosition(Transform start)
{
if (LogFilter.logDebug) { Debug.Log("UnRegisterStartPosition: (" + start.gameObject.name + ") " + start.position); }
s_StartPositions.Remove(start);
}
public bool IsClientConnected()
{
return client != null && client.isConnected;
}
// this is the only way to clear the singleton, so another instance can be created.
static public void Shutdown()
{
if (singleton == null)
return;
s_StartPositions.Clear();
s_StartPositionIndex = 0;
s_ClientReadyConnection = null;
singleton.StopHost();
singleton = null;
}
// ----------------------------- Server Internal Message Handlers --------------------------------
internal void OnServerConnectInternal(NetworkMessage netMsg)
{
if (LogFilter.logDebug) { Debug.Log("NetworkManager:OnServerConnectInternal"); }
netMsg.conn.SetMaxDelay(m_MaxDelay);
if (m_MaxBufferedPackets != ChannelBuffer.MaxBufferedPackets)
{
for (int channelId = 0; channelId < NetworkServer.numChannels; channelId++)
{
netMsg.conn.SetChannelOption(channelId, ChannelOption.MaxPendingBuffers, m_MaxBufferedPackets);
}
}
if (!m_AllowFragmentation)
{
for (int channelId = 0; channelId < NetworkServer.numChannels; channelId++)
{
netMsg.conn.SetChannelOption(channelId, ChannelOption.AllowFragmentation, 0);
}
}
if (networkSceneName != "" && networkSceneName != m_OfflineScene)
{
StringMessage msg = new StringMessage(networkSceneName);
netMsg.conn.Send(MsgType.Scene, msg);
}
#if ENABLE_UNET_HOST_MIGRATION
if (m_MigrationManager != null)
{
m_MigrationManager.SendPeerInfo();
}
#endif
OnServerConnect(netMsg.conn);
}
internal void OnServerDisconnectInternal(NetworkMessage netMsg)
{
if (LogFilter.logDebug) { Debug.Log("NetworkManager:OnServerDisconnectInternal"); }
#if ENABLE_UNET_HOST_MIGRATION
if (m_MigrationManager != null)
{
m_MigrationManager.SendPeerInfo();
}
#endif
OnServerDisconnect(netMsg.conn);
}
internal void OnServerReadyMessageInternal(NetworkMessage netMsg)
{
if (LogFilter.logDebug) { Debug.Log("NetworkManager:OnServerReadyMessageInternal"); }
OnServerReady(netMsg.conn);
}
internal void OnServerAddPlayerMessageInternal(NetworkMessage netMsg)
{
if (LogFilter.logDebug) { Debug.Log("NetworkManager:OnServerAddPlayerMessageInternal"); }
netMsg.ReadMessage(s_AddPlayerMessage);
if (s_AddPlayerMessage.msgSize != 0)
{
var reader = new NetworkReader(s_AddPlayerMessage.msgData);
OnServerAddPlayer(netMsg.conn, s_AddPlayerMessage.playerControllerId, reader);
}
else
{
OnServerAddPlayer(netMsg.conn, s_AddPlayerMessage.playerControllerId);
}
#if ENABLE_UNET_HOST_MIGRATION
if (m_MigrationManager != null)
{
m_MigrationManager.SendPeerInfo();
}
#endif
}
internal void OnServerRemovePlayerMessageInternal(NetworkMessage netMsg)
{
if (LogFilter.logDebug) { Debug.Log("NetworkManager:OnServerRemovePlayerMessageInternal"); }
netMsg.ReadMessage(s_RemovePlayerMessage);
PlayerController player;
netMsg.conn.GetPlayerController(s_RemovePlayerMessage.playerControllerId, out player);
OnServerRemovePlayer(netMsg.conn, player);
netMsg.conn.RemovePlayerController(s_RemovePlayerMessage.playerControllerId);
#if ENABLE_UNET_HOST_MIGRATION
if (m_MigrationManager != null)
{
m_MigrationManager.SendPeerInfo();
}
#endif
}
internal void OnServerErrorInternal(NetworkMessage netMsg)
{
if (LogFilter.logDebug) { Debug.Log("NetworkManager:OnServerErrorInternal"); }
netMsg.ReadMessage(s_ErrorMessage);
OnServerError(netMsg.conn, s_ErrorMessage.errorCode);
}
// ----------------------------- Client Internal Message Handlers --------------------------------
internal void OnClientConnectInternal(NetworkMessage netMsg)
{
if (LogFilter.logDebug) { Debug.Log("NetworkManager:OnClientConnectInternal"); }
netMsg.conn.SetMaxDelay(m_MaxDelay);
string loadedSceneName = SceneManager.GetSceneAt(0).name;
if (string.IsNullOrEmpty(m_OnlineScene) || (m_OnlineScene == m_OfflineScene) || (loadedSceneName == m_OnlineScene))
{
m_ClientLoadedScene = false;
OnClientConnect(netMsg.conn);
}
else
{
// will wait for scene id to come from the server.
s_ClientReadyConnection = netMsg.conn;
}
}
internal void OnClientDisconnectInternal(NetworkMessage netMsg)
{
if (LogFilter.logDebug) { Debug.Log("NetworkManager:OnClientDisconnectInternal"); }
#if ENABLE_UNET_HOST_MIGRATION
if (m_MigrationManager != null)
{
if (m_MigrationManager.LostHostOnClient(netMsg.conn))
{
// should OnClientDisconnect be called?
return;
}
}
#endif
if (!string.IsNullOrEmpty(m_OfflineScene))
{
ClientChangeScene(m_OfflineScene, false);
}
// If we have a valid connection here drop the client in the matchmaker before shutting down below
if (matchMaker != null && matchInfo != null && matchInfo.networkId != NetworkID.Invalid && matchInfo.nodeId != NodeID.Invalid)
{
matchMaker.DropConnection(matchInfo.networkId, matchInfo.nodeId, matchInfo.domain, OnDropConnection);
}
OnClientDisconnect(netMsg.conn);
}
internal void OnClientNotReadyMessageInternal(NetworkMessage netMsg)
{
if (LogFilter.logDebug) { Debug.Log("NetworkManager:OnClientNotReadyMessageInternal"); }
ClientScene.SetNotReady();
OnClientNotReady(netMsg.conn);
// NOTE: s_ClientReadyConnection is not set here! don't want OnClientConnect to be invoked again after scene changes.
}
internal void OnClientErrorInternal(NetworkMessage netMsg)
{
if (LogFilter.logDebug) { Debug.Log("NetworkManager:OnClientErrorInternal"); }
netMsg.ReadMessage(s_ErrorMessage);
OnClientError(netMsg.conn, s_ErrorMessage.errorCode);
}
internal void OnClientSceneInternal(NetworkMessage netMsg)
{
if (LogFilter.logDebug) { Debug.Log("NetworkManager:OnClientSceneInternal"); }
string newSceneName = netMsg.reader.ReadString();
if (IsClientConnected() && !NetworkServer.active)
{
ClientChangeScene(newSceneName, true);
}
}
// ----------------------------- Server System Callbacks --------------------------------
public virtual void OnServerConnect(NetworkConnection conn)
{
}
public virtual void OnServerDisconnect(NetworkConnection conn)
{
NetworkServer.DestroyPlayersForConnection(conn);
// note: timeouts happen all the time, no need to throw an error there.
if (conn.lastError == NetworkError.Ok || conn.lastError == NetworkError.Timeout)
{
if (LogFilter.logDebug) { Debug.Log("OnServerDisconnect: client disconnected:" + conn); }
}
else
{
// a client disconnected, let's show a message
if (LogFilter.logError) { Debug.LogError("OnServerDisconnect: Client disconnected with error: " + conn.lastError); }
}
}
public virtual void OnServerReady(NetworkConnection conn)
{
if (conn.playerControllers.Count == 0)
{
// this is now allowed (was not for a while)
if (LogFilter.logDebug) { Debug.Log("Ready with no player object"); }
}
NetworkServer.SetClientReady(conn);
}
public virtual void OnServerAddPlayer(NetworkConnection conn, short playerControllerId, NetworkReader extraMessageReader)
{
OnServerAddPlayerInternal(conn, playerControllerId);
}
public virtual void OnServerAddPlayer(NetworkConnection conn, short playerControllerId)
{
OnServerAddPlayerInternal(conn, playerControllerId);
}
void OnServerAddPlayerInternal(NetworkConnection conn, short playerControllerId)
{
if (m_PlayerPrefab == null)
{
if (LogFilter.logError) { Debug.LogError("The PlayerPrefab is empty on the NetworkManager. Please setup a PlayerPrefab object."); }
return;
}
if (m_PlayerPrefab.GetComponent<NetworkIdentity>() == null)
{
if (LogFilter.logError) { Debug.LogError("The PlayerPrefab does not have a NetworkIdentity. Please add a NetworkIdentity to the player prefab."); }
return;
}
if (playerControllerId < conn.playerControllers.Count && conn.playerControllers[playerControllerId].IsValid && conn.playerControllers[playerControllerId].gameObject != null)
{
if (LogFilter.logError) { Debug.LogError("There is already a player at that playerControllerId for this connections."); }
return;
}
GameObject player;
Transform startPos = GetStartPosition();
if (startPos != null)
{
player = (GameObject)Instantiate(m_PlayerPrefab, startPos.position, startPos.rotation);
}
else
{
player = (GameObject)Instantiate(m_PlayerPrefab, Vector3.zero, Quaternion.identity);
}
NetworkServer.AddPlayerForConnection(conn, player, playerControllerId);
}
public Transform GetStartPosition()
{
// first remove any dead transforms
if (s_StartPositions.Count > 0)
{
for (int i = s_StartPositions.Count - 1; i >= 0; i--)
{
if (s_StartPositions[i] == null)
s_StartPositions.RemoveAt(i);
}
}
if (m_PlayerSpawnMethod == PlayerSpawnMethod.Random && s_StartPositions.Count > 0)
{
// try to spawn at a random start location
int index = Random.Range(0, s_StartPositions.Count);
return s_StartPositions[index];
}
if (m_PlayerSpawnMethod == PlayerSpawnMethod.RoundRobin && s_StartPositions.Count > 0)
{
if (s_StartPositionIndex >= s_StartPositions.Count)
{
s_StartPositionIndex = 0;
}
Transform startPos = s_StartPositions[s_StartPositionIndex];
s_StartPositionIndex += 1;
return startPos;
}
return null;
}
public virtual void OnServerRemovePlayer(NetworkConnection conn, PlayerController player)
{
if (player.gameObject != null)
{
NetworkServer.Destroy(player.gameObject);
}
}
public virtual void OnServerError(NetworkConnection conn, int errorCode)
{
}
public virtual void OnServerSceneChanged(string sceneName)
{
}
// ----------------------------- Client System Callbacks --------------------------------
public virtual void OnClientConnect(NetworkConnection conn)
{
if (!clientLoadedScene)
{
// Ready/AddPlayer is usually triggered by a scene load completing. if no scene was loaded, then Ready/AddPlayer it here instead.
ClientScene.Ready(conn);
if (m_AutoCreatePlayer)
{
ClientScene.AddPlayer(0);
}
}
}
public virtual void OnClientDisconnect(NetworkConnection conn)
{
StopClient();
if (conn.lastError != NetworkError.Ok)
{
if (LogFilter.logError) { Debug.LogError("ClientDisconnected due to error: " + conn.lastError); }
}
}
public virtual void OnClientError(NetworkConnection conn, int errorCode)
{
}
public virtual void OnClientNotReady(NetworkConnection conn)
{
}
public virtual void OnClientSceneChanged(NetworkConnection conn)
{
// always become ready.
ClientScene.Ready(conn);
if (!m_AutoCreatePlayer)
{
return;
}
bool addPlayer = (ClientScene.localPlayers.Count == 0);
bool foundPlayer = false;
for (int i = 0; i < ClientScene.localPlayers.Count; i++)
{
if (ClientScene.localPlayers[i].gameObject != null)
{
foundPlayer = true;
break;
}
}
if (!foundPlayer)
{
// there are players, but their game objects have all been deleted
addPlayer = true;
}
if (addPlayer)
{
ClientScene.AddPlayer(0);
}
}
// ----------------------------- Matchmaker --------------------------------
public void StartMatchMaker()
{
if (LogFilter.logDebug) { Debug.Log("NetworkManager StartMatchMaker"); }
SetMatchHost(m_MatchHost, m_MatchPort, m_MatchPort == 443);
}
public void StopMatchMaker()
{
// If we have a valid connection here drop the client in the matchmaker before shutting down below
if (matchMaker != null && matchInfo != null && matchInfo.networkId != NetworkID.Invalid && matchInfo.nodeId != NodeID.Invalid)
{
matchMaker.DropConnection(matchInfo.networkId, matchInfo.nodeId, matchInfo.domain, OnDropConnection);
}
if (matchMaker != null)
{
Destroy(matchMaker);
matchMaker = null;
}
matchInfo = null;
matches = null;
}
public void SetMatchHost(string newHost, int port, bool https)
{
if (matchMaker == null)
{
matchMaker = gameObject.AddComponent<NetworkMatch>();
}
if (newHost == "127.0.0.1")
{
newHost = "localhost";
}
string prefix = "http://";
if (https)
{
prefix = "https://";
}
if (newHost.StartsWith("http://"))
{
newHost = newHost.Replace("http://", "");
}
if (newHost.StartsWith("https://"))
{
newHost = newHost.Replace("https://", "");
}
m_MatchHost = newHost;
m_MatchPort = port;
string fullURI = prefix + m_MatchHost + ":" + m_MatchPort;
if (LogFilter.logDebug) { Debug.Log("SetMatchHost:" + fullURI); }
matchMaker.baseUri = new Uri(fullURI);
}
//------------------------------ Start & Stop callbacks -----------------------------------
// Since there are multiple versions of StartServer, StartClient and StartHost, to reliably customize
// their functionality, users would need override all the versions. Instead these callbacks are invoked
// from all versions, so users only need to implement this one case.
public virtual void OnStartHost()
{
}
public virtual void OnStartServer()
{
}
public virtual void OnStartClient(NetworkClient client)
{
}
public virtual void OnStopServer()
{
}
public virtual void OnStopClient()
{
}
public virtual void OnStopHost()
{
}
//------------------------------ Matchmaker callbacks -----------------------------------
public virtual void OnMatchCreate(bool success, string extendedInfo, MatchInfo matchInfo)
{
if (LogFilter.logDebug) { Debug.LogFormat("NetworkManager OnMatchCreate Success:{0}, ExtendedInfo:{1}, matchInfo:{2}", success, extendedInfo, matchInfo); }
if (success)
StartHost(matchInfo);
}
public virtual void OnMatchList(bool success, string extendedInfo, List<MatchInfoSnapshot> matchList)
{
if (LogFilter.logDebug) { Debug.LogFormat("NetworkManager OnMatchList Success:{0}, ExtendedInfo:{1}, matchList.Count:{2}", success, extendedInfo, matchList.Count); }
matches = matchList;
}
public virtual void OnMatchJoined(bool success, string extendedInfo, MatchInfo matchInfo)
{
if (LogFilter.logDebug) { Debug.LogFormat("NetworkManager OnMatchJoined Success:{0}, ExtendedInfo:{1}, matchInfo:{2}", success, extendedInfo, matchInfo); }
if (success)
StartClient(matchInfo);
}
public virtual void OnDestroyMatch(bool success, string extendedInfo)
{
if (LogFilter.logDebug) { Debug.LogFormat("NetworkManager OnDestroyMatch Success:{0}, ExtendedInfo:{1}", success, extendedInfo); }
}
public virtual void OnDropConnection(bool success, string extendedInfo)
{
if (LogFilter.logDebug) { Debug.LogFormat("NetworkManager OnDropConnection Success:{0}, ExtendedInfo:{1}", success, extendedInfo); }
}
public virtual void OnSetMatchAttributes(bool success, string extendedInfo)
{
if (LogFilter.logDebug) { Debug.LogFormat("NetworkManager OnSetMatchAttributes Success:{0}, ExtendedInfo:{1}", success, extendedInfo); }
}
}
}
#endif //ENABLE_UNET