mirror of
https://github.com/misternebula/quantum-space-buddies.git
synced 2025-01-01 03:32:38 +00:00
1376 lines
58 KiB
C#
1376 lines
58 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using kcp2k;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
using UnityEngine.Serialization;
|
|
|
|
namespace Mirror
|
|
{
|
|
public enum PlayerSpawnMethod { Random, RoundRobin }
|
|
public enum NetworkManagerMode { Offline, ServerOnly, ClientOnly, Host }
|
|
|
|
[DisallowMultipleComponent]
|
|
[AddComponentMenu("Network/NetworkManager")]
|
|
[HelpURL("https://mirror-networking.gitbook.io/docs/components/network-manager")]
|
|
public class NetworkManager : MonoBehaviour
|
|
{
|
|
/// <summary>Enable to keep NetworkManager alive when changing scenes.</summary>
|
|
// This should be set if your game has a single NetworkManager that exists for the lifetime of the process. If there is a NetworkManager in each scene, then this should not be set.</para>
|
|
[Header("Configuration")]
|
|
[FormerlySerializedAs("m_DontDestroyOnLoad")]
|
|
[Tooltip("Should the Network Manager object be persisted through scene changes?")]
|
|
public bool dontDestroyOnLoad = true;
|
|
|
|
/// <summary>Multiplayer games should always run in the background so the network doesn't time out.</summary>
|
|
[FormerlySerializedAs("m_RunInBackground")]
|
|
[Tooltip("Multiplayer games should always run in the background so the network doesn't time out.")]
|
|
public bool runInBackground = true;
|
|
|
|
/// <summary>Should the server auto-start when 'Server Build' is checked in build settings</summary>
|
|
[Tooltip("Should the server auto-start when 'Server Build' is checked in build settings")]
|
|
[FormerlySerializedAs("startOnHeadless")]
|
|
public bool autoStartServerBuild = true;
|
|
|
|
/// <summary>Server Update frequency, per second. Use around 60Hz for fast paced games like Counter-Strike to minimize latency. Use around 30Hz for games like WoW to minimize computations. Use around 1-10Hz for slow paced games like EVE.</summary>
|
|
[Tooltip("Server Update frequency, per second. Use around 60Hz for fast paced games like Counter-Strike to minimize latency. Use around 30Hz for games like WoW to minimize computations. Use around 1-10Hz for slow paced games like EVE.")]
|
|
public int serverTickRate = 30;
|
|
|
|
/// <summary>Automatically switch to this scene upon going offline (on start / on disconnect / on shutdown).</summary>
|
|
[Header("Scene Management")]
|
|
[Scene]
|
|
[FormerlySerializedAs("m_OfflineScene")]
|
|
[Tooltip("Scene that Mirror will switch to when the client or server is stopped")]
|
|
public string offlineScene = "";
|
|
|
|
/// <summary>Automatically switch to this scene upon going online (after connect/startserver).</summary>
|
|
[Scene]
|
|
[FormerlySerializedAs("m_OnlineScene")]
|
|
[Tooltip("Scene that Mirror will switch to when the server is started. Clients will recieve a Scene Message to load the server's current scene when they connect.")]
|
|
public string onlineScene = "";
|
|
|
|
// transport layer
|
|
[Header("Network Info")]
|
|
[Tooltip("Transport component attached to this object that server and client will use to connect")]
|
|
[SerializeField]
|
|
protected Transport transport;
|
|
|
|
/// <summary>Server's address for clients to connect to.</summary>
|
|
[FormerlySerializedAs("m_NetworkAddress")]
|
|
[Tooltip("Network Address where the client should connect to the server. Server does not use this for anything.")]
|
|
public string networkAddress = "localhost";
|
|
|
|
/// <summary>The maximum number of concurrent network connections to support.</summary>
|
|
[FormerlySerializedAs("m_MaxConnections")]
|
|
[Tooltip("Maximum number of concurrent connections.")]
|
|
public int maxConnections = 100;
|
|
|
|
[Header("Authentication")]
|
|
[Tooltip("Authentication component attached to this object")]
|
|
public NetworkAuthenticator authenticator;
|
|
|
|
/// <summary>The default prefab to be used to create player objects on the server.</summary>
|
|
// Player objects are created in the default handler for AddPlayer() on
|
|
// the server. Implementing OnServerAddPlayer overrides this behaviour.
|
|
[Header("Player Object")]
|
|
[FormerlySerializedAs("m_PlayerPrefab")]
|
|
[Tooltip("Prefab of the player object. Prefab must have a Network Identity component. May be an empty game object or a full avatar.")]
|
|
public GameObject playerPrefab;
|
|
|
|
/// <summary>Enable to automatically create player objects on connect and on scene change.</summary>
|
|
[FormerlySerializedAs("m_AutoCreatePlayer")]
|
|
[Tooltip("Should Mirror automatically spawn the player after scene change?")]
|
|
public bool autoCreatePlayer = true;
|
|
|
|
/// <summary>Where to spawn players.</summary>
|
|
[FormerlySerializedAs("m_PlayerSpawnMethod")]
|
|
[Tooltip("Round Robin or Random order of Start Position selection")]
|
|
public PlayerSpawnMethod playerSpawnMethod;
|
|
|
|
/// <summary>Prefabs that can be spawned over the network need to be registered here.</summary>
|
|
[FormerlySerializedAs("m_SpawnPrefabs"), HideInInspector]
|
|
public List<GameObject> spawnPrefabs = new List<GameObject>();
|
|
|
|
/// <summary>List of transforms populated by NetworkStartPositions</summary>
|
|
public static List<Transform> startPositions = new List<Transform>();
|
|
public static int startPositionIndex;
|
|
|
|
/// <summary>The one and only NetworkManager</summary>
|
|
public static NetworkManager singleton { get; private set; }
|
|
|
|
/// <summary>Number of active player objects across all connections on the server.</summary>
|
|
public int numPlayers => NetworkServer.connections.Count(kv => kv.Value.identity != null);
|
|
|
|
/// <summary>True if the server is running or client is connected/connecting.</summary>
|
|
public bool isNetworkActive => NetworkServer.active || NetworkClient.active;
|
|
|
|
// TODO remove this
|
|
// internal for tests
|
|
internal static NetworkConnection clientReadyConnection;
|
|
|
|
/// <summary>True if the client loaded a new scene when connecting to the server.</summary>
|
|
// This is set before OnClientConnect is called, so it can be checked
|
|
// there to perform different logic if a scene load occurred.
|
|
[NonSerialized]
|
|
public bool clientLoadedScene;
|
|
|
|
// helper enum to know if we started the networkmanager as server/client/host.
|
|
// -> this is necessary because when StartHost changes server scene to
|
|
// online scene, FinishLoadScene is called and the host client isn't
|
|
// connected yet (no need to connect it before server was fully set up).
|
|
// in other words, we need this to know which mode we are running in
|
|
// during FinishLoadScene.
|
|
public NetworkManagerMode mode { get; private set; }
|
|
|
|
// virtual so that inheriting classes' OnValidate() can call base.OnValidate() too
|
|
public virtual void OnValidate()
|
|
{
|
|
// always >= 0
|
|
maxConnections = Mathf.Max(maxConnections, 0);
|
|
|
|
if (playerPrefab != null && playerPrefab.GetComponent<NetworkIdentity>() == null)
|
|
{
|
|
Debug.LogError("NetworkManager - Player Prefab must have a NetworkIdentity.");
|
|
playerPrefab = null;
|
|
}
|
|
|
|
// This avoids the mysterious "Replacing existing prefab with assetId ... Old prefab 'Player', New prefab 'Player'" warning.
|
|
if (playerPrefab != null && spawnPrefabs.Contains(playerPrefab))
|
|
{
|
|
Debug.LogWarning("NetworkManager - Player Prefab should not be added to Registered Spawnable Prefabs list...removed it.");
|
|
spawnPrefabs.Remove(playerPrefab);
|
|
}
|
|
}
|
|
|
|
// virtual so that inheriting classes' Reset() can call base.Reset() too
|
|
// Reset only gets called when the component is added or the user resets the component
|
|
// Thats why we validate these things that only need to be validated on adding the NetworkManager here
|
|
// If we would do it in OnValidate() then it would run this everytime a value changes
|
|
public virtual void Reset()
|
|
{
|
|
// make sure someone doesn't accidentally add another NetworkManager
|
|
// need transform.root because when adding to a child, the parent's
|
|
// Reset isn't called.
|
|
foreach (NetworkManager manager in transform.root.GetComponentsInChildren<NetworkManager>())
|
|
{
|
|
if (manager != this)
|
|
{
|
|
Debug.LogError($"{name} detected another component of type {typeof(NetworkManager)} in its hierarchy on {manager.name}. There can only be one, please remove one of them.");
|
|
// return early so that transport component isn't auto-added
|
|
// to the duplicate NetworkManager.
|
|
return;
|
|
}
|
|
}
|
|
|
|
// add transport if there is none yet. makes upgrading easier.
|
|
if (transport == null)
|
|
{
|
|
#if UNITY_EDITOR
|
|
// RecordObject needs to be called before we make the change
|
|
UnityEditor.Undo.RecordObject(gameObject, "Added default Transport");
|
|
#endif
|
|
|
|
transport = GetComponent<Transport>();
|
|
|
|
// was a transport added yet? if not, add one
|
|
if (transport == null)
|
|
{
|
|
transport = gameObject.AddComponent<KcpTransport>();
|
|
Debug.Log("NetworkManager: added default Transport because there was none yet.");
|
|
}
|
|
}
|
|
}
|
|
|
|
// virtual so that inheriting classes' Awake() can call base.Awake() too
|
|
public virtual void Awake()
|
|
{
|
|
// Don't allow collision-destroyed second instance to continue.
|
|
if (!InitializeSingleton()) return;
|
|
|
|
Debug.Log("Mirror | mirror-networking.com | discord.gg/N9QVxbM");
|
|
|
|
// Set the networkSceneName to prevent a scene reload
|
|
// if client connection to server fails.
|
|
networkSceneName = offlineScene;
|
|
|
|
// setup OnSceneLoaded callback
|
|
SceneManager.sceneLoaded += OnSceneLoaded;
|
|
}
|
|
|
|
// virtual so that inheriting classes' Start() can call base.Start() too
|
|
public virtual void Start()
|
|
{
|
|
// headless mode? then start the server
|
|
// can't do this in Awake because Awake is for initialization.
|
|
// some transports might not be ready until Start.
|
|
//
|
|
// (tick rate is applied in StartServer!)
|
|
#if UNITY_SERVER
|
|
if (autoStartServerBuild)
|
|
{
|
|
StartServer();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// virtual so that inheriting classes' LateUpdate() can call base.LateUpdate() too
|
|
public virtual void LateUpdate()
|
|
{
|
|
UpdateScene();
|
|
}
|
|
|
|
// keep the online scene change check in a separate function
|
|
bool IsServerOnlineSceneChangeNeeded()
|
|
{
|
|
// Only change scene if the requested online scene is not blank, and is not already loaded
|
|
return !string.IsNullOrWhiteSpace(onlineScene) && !IsSceneActive(onlineScene) && onlineScene != offlineScene;
|
|
}
|
|
|
|
public static bool IsSceneActive(string scene)
|
|
{
|
|
Scene activeScene = SceneManager.GetActiveScene();
|
|
return activeScene.path == scene || activeScene.name == scene;
|
|
}
|
|
|
|
// full server setup code, without spawning objects yet
|
|
void SetupServer()
|
|
{
|
|
// Debug.Log("NetworkManager SetupServer");
|
|
InitializeSingleton();
|
|
|
|
if (runInBackground)
|
|
Application.runInBackground = true;
|
|
|
|
if (authenticator != null)
|
|
{
|
|
authenticator.OnStartServer();
|
|
authenticator.OnServerAuthenticated.AddListener(OnServerAuthenticated);
|
|
}
|
|
|
|
ConfigureHeadlessFrameRate();
|
|
|
|
// start listening to network connections
|
|
NetworkServer.Listen(maxConnections);
|
|
|
|
// call OnStartServer AFTER Listen, so that NetworkServer.active is
|
|
// true and we can call NetworkServer.Spawn in OnStartServer
|
|
// overrides.
|
|
// (useful for loading & spawning stuff from database etc.)
|
|
//
|
|
// note: there is no risk of someone connecting after Listen() and
|
|
// before OnStartServer() because this all runs in one thread
|
|
// and we don't start processing connects until Update.
|
|
OnStartServer();
|
|
|
|
// this must be after Listen(), since that registers the default message handlers
|
|
RegisterServerMessages();
|
|
}
|
|
|
|
/// <summary>Starts the server, listening for incoming connections.</summary>
|
|
public void StartServer()
|
|
{
|
|
if (NetworkServer.active)
|
|
{
|
|
Debug.LogWarning("Server already started.");
|
|
return;
|
|
}
|
|
|
|
mode = NetworkManagerMode.ServerOnly;
|
|
|
|
// StartServer is inherently ASYNCHRONOUS (=doesn't finish immediately)
|
|
//
|
|
// Here is what it does:
|
|
// Listen
|
|
// if onlineScene:
|
|
// LoadSceneAsync
|
|
// ...
|
|
// FinishLoadSceneServerOnly
|
|
// SpawnObjects
|
|
// else:
|
|
// SpawnObjects
|
|
//
|
|
// there is NO WAY to make it synchronous because both LoadSceneAsync
|
|
// and LoadScene do not finish loading immediately. as long as we
|
|
// have the onlineScene feature, it will be asynchronous!
|
|
|
|
SetupServer();
|
|
|
|
// scene change needed? then change scene and spawn afterwards.
|
|
if (IsServerOnlineSceneChangeNeeded())
|
|
{
|
|
ServerChangeScene(onlineScene);
|
|
}
|
|
// otherwise spawn directly
|
|
else
|
|
{
|
|
NetworkServer.SpawnObjects();
|
|
}
|
|
}
|
|
|
|
/// <summary>Starts the client, connects it to the server with networkAddress.</summary>
|
|
public void StartClient()
|
|
{
|
|
if (NetworkClient.active)
|
|
{
|
|
Debug.LogWarning("Client already started.");
|
|
return;
|
|
}
|
|
|
|
mode = NetworkManagerMode.ClientOnly;
|
|
|
|
InitializeSingleton();
|
|
|
|
if (runInBackground)
|
|
Application.runInBackground = true;
|
|
|
|
if (authenticator != null)
|
|
{
|
|
authenticator.OnStartClient();
|
|
authenticator.OnClientAuthenticated.AddListener(OnClientAuthenticated);
|
|
}
|
|
|
|
// In case this is a headless client...
|
|
ConfigureHeadlessFrameRate();
|
|
|
|
RegisterClientMessages();
|
|
|
|
if (string.IsNullOrWhiteSpace(networkAddress))
|
|
{
|
|
Debug.LogError("Must set the Network Address field in the manager");
|
|
return;
|
|
}
|
|
// Debug.Log($"NetworkManager StartClient address:{networkAddress}");
|
|
|
|
NetworkClient.Connect(networkAddress);
|
|
|
|
OnStartClient();
|
|
}
|
|
|
|
/// <summary>Starts the client, connects it to the server via Uri</summary>
|
|
public void StartClient(Uri uri)
|
|
{
|
|
if (NetworkClient.active)
|
|
{
|
|
Debug.LogWarning("Client already started.");
|
|
return;
|
|
}
|
|
|
|
mode = NetworkManagerMode.ClientOnly;
|
|
|
|
InitializeSingleton();
|
|
|
|
if (runInBackground)
|
|
Application.runInBackground = true;
|
|
|
|
if (authenticator != null)
|
|
{
|
|
authenticator.OnStartClient();
|
|
authenticator.OnClientAuthenticated.AddListener(OnClientAuthenticated);
|
|
}
|
|
|
|
RegisterClientMessages();
|
|
|
|
// Debug.Log($"NetworkManager StartClient address:{uri}");
|
|
networkAddress = uri.Host;
|
|
|
|
NetworkClient.Connect(uri);
|
|
|
|
OnStartClient();
|
|
}
|
|
|
|
/// <summary>Starts a network "host" - a server and client in the same application.</summary>
|
|
public void StartHost()
|
|
{
|
|
if (NetworkServer.active || NetworkClient.active)
|
|
{
|
|
Debug.LogWarning("Server or Client already started.");
|
|
return;
|
|
}
|
|
|
|
mode = NetworkManagerMode.Host;
|
|
|
|
// StartHost is inherently ASYNCHRONOUS (=doesn't finish immediately)
|
|
//
|
|
// Here is what it does:
|
|
// Listen
|
|
// ConnectHost
|
|
// if onlineScene:
|
|
// LoadSceneAsync
|
|
// ...
|
|
// FinishLoadSceneHost
|
|
// FinishStartHost
|
|
// SpawnObjects
|
|
// StartHostClient <= not guaranteed to happen after SpawnObjects if onlineScene is set!
|
|
// ClientAuth
|
|
// success: server sends changescene msg to client
|
|
// else:
|
|
// FinishStartHost
|
|
//
|
|
// there is NO WAY to make it synchronous because both LoadSceneAsync
|
|
// and LoadScene do not finish loading immediately. as long as we
|
|
// have the onlineScene feature, it will be asynchronous!
|
|
|
|
// setup server first
|
|
SetupServer();
|
|
|
|
// call OnStartHost AFTER SetupServer. this way we can use
|
|
// NetworkServer.Spawn etc. in there too. just like OnStartServer
|
|
// is called after the server is actually properly started.
|
|
OnStartHost();
|
|
|
|
// scene change needed? then change scene and spawn afterwards.
|
|
// => BEFORE host client connects. if client auth succeeds then the
|
|
// server tells it to load 'onlineScene'. we can't do that if
|
|
// server is still in 'offlineScene'. so load on server first.
|
|
if (IsServerOnlineSceneChangeNeeded())
|
|
{
|
|
// call FinishStartHost after changing scene.
|
|
finishStartHostPending = true;
|
|
ServerChangeScene(onlineScene);
|
|
}
|
|
// otherwise call FinishStartHost directly
|
|
else
|
|
{
|
|
FinishStartHost();
|
|
}
|
|
}
|
|
|
|
// This may be set true in StartHost and is evaluated in FinishStartHost
|
|
bool finishStartHostPending;
|
|
|
|
// FinishStartHost is guaranteed to be called after the host server was
|
|
// fully started and all the asynchronous StartHost magic is finished
|
|
// (= scene loading), or immediately if there was no asynchronous magic.
|
|
//
|
|
// note: we don't really need FinishStartClient/FinishStartServer. the
|
|
// host version is enough.
|
|
void FinishStartHost()
|
|
{
|
|
// ConnectHost needs to be called BEFORE SpawnObjects:
|
|
// https://github.com/vis2k/Mirror/pull/1249/
|
|
// -> this sets NetworkServer.localConnection.
|
|
// -> localConnection needs to be set before SpawnObjects because:
|
|
// -> SpawnObjects calls OnStartServer in all NetworkBehaviours
|
|
// -> OnStartServer might spawn an object and set [SyncVar(hook="OnColorChanged")] object.color = green;
|
|
// -> this calls SyncVar.set (generated by Weaver), which has
|
|
// a custom case for host mode (because host mode doesn't
|
|
// get OnDeserialize calls, where SyncVar hooks are usually
|
|
// called):
|
|
//
|
|
// if (!SyncVarEqual(value, ref color))
|
|
// {
|
|
// if (NetworkServer.localClientActive && !getSyncVarHookGuard(1uL))
|
|
// {
|
|
// setSyncVarHookGuard(1uL, value: true);
|
|
// OnColorChangedHook(value);
|
|
// setSyncVarHookGuard(1uL, value: false);
|
|
// }
|
|
// SetSyncVar(value, ref color, 1uL);
|
|
// }
|
|
//
|
|
// -> localClientActive needs to be true, otherwise the hook
|
|
// isn't called in host mode!
|
|
//
|
|
// TODO call this after spawnobjects and worry about the syncvar hook fix later?
|
|
NetworkClient.ConnectHost();
|
|
|
|
// server scene was loaded. now spawn all the objects
|
|
NetworkServer.SpawnObjects();
|
|
|
|
// connect client and call OnStartClient AFTER server scene was
|
|
// loaded and all objects were spawned.
|
|
// DO NOT do this earlier. it would cause race conditions where a
|
|
// client will do things before the server is even fully started.
|
|
//Debug.Log("StartHostClient called");
|
|
StartHostClient();
|
|
}
|
|
|
|
void StartHostClient()
|
|
{
|
|
//Debug.Log("NetworkManager ConnectLocalClient");
|
|
|
|
if (authenticator != null)
|
|
{
|
|
authenticator.OnStartClient();
|
|
authenticator.OnClientAuthenticated.AddListener(OnClientAuthenticated);
|
|
}
|
|
|
|
networkAddress = "localhost";
|
|
NetworkServer.ActivateHostScene();
|
|
RegisterClientMessages();
|
|
|
|
// ConnectLocalServer needs to be called AFTER RegisterClientMessages
|
|
// (https://github.com/vis2k/Mirror/pull/1249/)
|
|
NetworkClient.ConnectLocalServer();
|
|
|
|
OnStartClient();
|
|
}
|
|
|
|
/// <summary>This stops both the client and the server that the manager is using.</summary>
|
|
public void StopHost()
|
|
{
|
|
OnStopHost();
|
|
|
|
// calling OnTransportDisconnected was needed to fix
|
|
// https://github.com/vis2k/Mirror/issues/1515
|
|
// so that the host client receives a DisconnectMessage
|
|
// TODO reevaluate if this is still needed after all the disconnect
|
|
// fixes, and try to put this into LocalConnection.Disconnect!
|
|
NetworkServer.OnTransportDisconnected(NetworkConnection.LocalConnectionId);
|
|
|
|
StopClient();
|
|
StopServer();
|
|
}
|
|
|
|
/// <summary>Stops the server from listening and simulating the game.</summary>
|
|
public void StopServer()
|
|
{
|
|
// return if already stopped to avoid recursion deadlock
|
|
if (!NetworkServer.active)
|
|
return;
|
|
|
|
if (authenticator != null)
|
|
{
|
|
authenticator.OnServerAuthenticated.RemoveListener(OnServerAuthenticated);
|
|
authenticator.OnStopServer();
|
|
}
|
|
|
|
// Get Network Manager out of DDOL before going to offline scene
|
|
// to avoid collision and let a fresh Network Manager be created.
|
|
// IMPORTANT: .gameObject can be null if StopClient is called from
|
|
// OnApplicationQuit or from tests!
|
|
if (gameObject != null
|
|
&& gameObject.scene.name == "DontDestroyOnLoad"
|
|
&& !string.IsNullOrWhiteSpace(offlineScene)
|
|
&& SceneManager.GetActiveScene().path != offlineScene)
|
|
SceneManager.MoveGameObjectToScene(gameObject, SceneManager.GetActiveScene());
|
|
|
|
OnStopServer();
|
|
|
|
//Debug.Log("NetworkManager StopServer");
|
|
NetworkServer.Shutdown();
|
|
|
|
// set offline mode BEFORE changing scene so that FinishStartScene
|
|
// doesn't think we need initialize anything.
|
|
mode = NetworkManagerMode.Offline;
|
|
|
|
if (!string.IsNullOrWhiteSpace(offlineScene))
|
|
{
|
|
ServerChangeScene(offlineScene);
|
|
}
|
|
|
|
startPositionIndex = 0;
|
|
|
|
networkSceneName = "";
|
|
}
|
|
|
|
/// <summary>Stops and disconnects the client.</summary>
|
|
public void StopClient()
|
|
{
|
|
if (mode == NetworkManagerMode.Offline)
|
|
return;
|
|
|
|
if (authenticator != null)
|
|
{
|
|
authenticator.OnClientAuthenticated.RemoveListener(OnClientAuthenticated);
|
|
authenticator.OnStopClient();
|
|
}
|
|
|
|
// Get Network Manager out of DDOL before going to offline scene
|
|
// to avoid collision and let a fresh Network Manager be created.
|
|
// IMPORTANT: .gameObject can be null if StopClient is called from
|
|
// OnApplicationQuit or from tests!
|
|
if (gameObject != null
|
|
&& gameObject.scene.name == "DontDestroyOnLoad"
|
|
&& !string.IsNullOrWhiteSpace(offlineScene)
|
|
&& SceneManager.GetActiveScene().path != offlineScene)
|
|
SceneManager.MoveGameObjectToScene(gameObject, SceneManager.GetActiveScene());
|
|
|
|
OnStopClient();
|
|
|
|
//Debug.Log("NetworkManager StopClient");
|
|
|
|
// set offline mode BEFORE changing scene so that FinishStartScene
|
|
// doesn't think we need initialize anything.
|
|
// set offline mode BEFORE NetworkClient.Disconnect so StopClient
|
|
// only runs once.
|
|
mode = NetworkManagerMode.Offline;
|
|
|
|
// shutdown client
|
|
NetworkClient.Disconnect();
|
|
NetworkClient.Shutdown();
|
|
|
|
// If this is the host player, StopServer will already be changing scenes.
|
|
// Check loadingSceneAsync to ensure we don't double-invoke the scene change.
|
|
// Check if NetworkServer.active because we can get here via Disconnect before server has started to change scenes.
|
|
if (!string.IsNullOrWhiteSpace(offlineScene) && !IsSceneActive(offlineScene) && loadingSceneAsync == null && !NetworkServer.active)
|
|
{
|
|
ClientChangeScene(offlineScene, SceneOperation.Normal);
|
|
}
|
|
|
|
networkSceneName = "";
|
|
}
|
|
|
|
// called when quitting the application by closing the window / pressing
|
|
// stop in the editor. virtual so that inheriting classes'
|
|
// OnApplicationQuit() can call base.OnApplicationQuit() too
|
|
public virtual void OnApplicationQuit()
|
|
{
|
|
// stop client first
|
|
// (we want to send the quit packet to the server instead of waiting
|
|
// for a timeout)
|
|
if (NetworkClient.isConnected)
|
|
{
|
|
StopClient();
|
|
//Debug.Log("OnApplicationQuit: stopped client");
|
|
}
|
|
|
|
// stop server after stopping client (for proper host mode stopping)
|
|
if (NetworkServer.active)
|
|
{
|
|
StopServer();
|
|
//Debug.Log("OnApplicationQuit: stopped server");
|
|
}
|
|
|
|
// Call ResetStatics to reset statics and singleton
|
|
ResetStatics();
|
|
}
|
|
|
|
/// <summary>Set the frame rate for a headless builds. Override to disable or modify.</summary>
|
|
// useful for dedicated servers.
|
|
// useful for headless benchmark clients.
|
|
public virtual void ConfigureHeadlessFrameRate()
|
|
{
|
|
#if UNITY_SERVER
|
|
Application.targetFrameRate = serverTickRate;
|
|
// Debug.Log($"Server Tick Rate set to {Application.targetFrameRate} Hz.");
|
|
#endif
|
|
}
|
|
|
|
bool InitializeSingleton()
|
|
{
|
|
if (singleton != null && singleton == this)
|
|
return true;
|
|
|
|
if (dontDestroyOnLoad)
|
|
{
|
|
if (singleton != null)
|
|
{
|
|
Debug.LogWarning("Multiple NetworkManagers detected in the scene. Only one NetworkManager can exist at a time. The duplicate NetworkManager will be destroyed.");
|
|
Destroy(gameObject);
|
|
|
|
// Return false to not allow collision-destroyed second instance to continue.
|
|
return false;
|
|
}
|
|
//Debug.Log("NetworkManager created singleton (DontDestroyOnLoad)");
|
|
singleton = this;
|
|
if (Application.isPlaying)
|
|
{
|
|
// Force the object to scene root, in case user made it a child of something
|
|
// in the scene since DDOL is only allowed for scene root objects
|
|
transform.SetParent(null);
|
|
DontDestroyOnLoad(gameObject);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Debug.Log("NetworkManager created singleton (ForScene)");
|
|
singleton = this;
|
|
}
|
|
|
|
// set active transport AFTER setting singleton.
|
|
// so only if we didn't destroy ourselves.
|
|
Transport.activeTransport = transport;
|
|
return true;
|
|
}
|
|
|
|
void RegisterServerMessages()
|
|
{
|
|
NetworkServer.OnConnectedEvent = OnServerConnectInternal;
|
|
NetworkServer.OnDisconnectedEvent = OnServerDisconnect;
|
|
NetworkServer.OnErrorEvent = OnServerError;
|
|
NetworkServer.RegisterHandler<AddPlayerMessage>(OnServerAddPlayerInternal);
|
|
|
|
// Network Server initially registers its own handler for this, so we replace it here.
|
|
NetworkServer.ReplaceHandler<ReadyMessage>(OnServerReadyMessageInternal);
|
|
}
|
|
|
|
void RegisterClientMessages()
|
|
{
|
|
NetworkClient.OnConnectedEvent = OnClientConnectInternal;
|
|
NetworkClient.OnDisconnectedEvent = OnClientDisconnectInternal;
|
|
NetworkClient.OnErrorEvent = OnClientError;
|
|
NetworkClient.RegisterHandler<NotReadyMessage>(OnClientNotReadyMessageInternal);
|
|
NetworkClient.RegisterHandler<SceneMessage>(OnClientSceneInternal, false);
|
|
|
|
if (playerPrefab != null)
|
|
NetworkClient.RegisterPrefab(playerPrefab);
|
|
|
|
foreach (GameObject prefab in spawnPrefabs.Where(t => t != null))
|
|
NetworkClient.RegisterPrefab(prefab);
|
|
}
|
|
|
|
// This is the only way to clear the singleton, so another instance can be created.
|
|
// RuntimeInitializeOnLoadMethod -> fast playmode without domain reload
|
|
[RuntimeInitializeOnLoadMethod]
|
|
public static void ResetStatics()
|
|
{
|
|
// call StopHost if we have a singleton
|
|
if (singleton)
|
|
singleton.StopHost();
|
|
|
|
// reset all statics
|
|
startPositions.Clear();
|
|
startPositionIndex = 0;
|
|
clientReadyConnection = null;
|
|
loadingSceneAsync = null;
|
|
networkSceneName = string.Empty;
|
|
|
|
// and finally (in case it isn't null already)...
|
|
singleton = null;
|
|
}
|
|
|
|
// virtual so that inheriting classes' OnDestroy() can call base.OnDestroy() too
|
|
public virtual void OnDestroy()
|
|
{
|
|
//Debug.Log("NetworkManager destroyed");
|
|
}
|
|
|
|
/// <summary>The name of the current network scene.</summary>
|
|
// set by NetworkManager when changing the scene.
|
|
// new clients will automatically load this scene.
|
|
// Loading a scene manually won't set it.
|
|
public static string networkSceneName { get; protected set; } = "";
|
|
|
|
public static AsyncOperation loadingSceneAsync;
|
|
|
|
/// <summary>Change the server scene and all client's scenes across the network.</summary>
|
|
// Called automatically if onlineScene or offlineScene are set, but it
|
|
// can be called from user code to switch scenes again while the game is
|
|
// in progress. This automatically sets clients to be not-ready during
|
|
// the change and ready again to participate in the new scene.
|
|
public virtual void ServerChangeScene(string newSceneName)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(newSceneName))
|
|
{
|
|
Debug.LogError("ServerChangeScene empty scene name");
|
|
return;
|
|
}
|
|
|
|
if (NetworkServer.isLoadingScene && newSceneName == networkSceneName)
|
|
{
|
|
Debug.LogError($"Scene change is already in progress for {newSceneName}");
|
|
return;
|
|
}
|
|
|
|
// Debug.Log($"ServerChangeScene {newSceneName}");
|
|
NetworkServer.SetAllClientsNotReady();
|
|
networkSceneName = newSceneName;
|
|
|
|
// Let server prepare for scene change
|
|
OnServerChangeScene(newSceneName);
|
|
|
|
// set server flag to stop processing messages while changing scenes
|
|
// it will be re-enabled in FinishLoadScene.
|
|
NetworkServer.isLoadingScene = true;
|
|
|
|
loadingSceneAsync = SceneManager.LoadSceneAsync(newSceneName);
|
|
|
|
// ServerChangeScene can be called when stopping the server
|
|
// when this happens the server is not active so does not need to tell clients about the change
|
|
if (NetworkServer.active)
|
|
{
|
|
// notify all clients about the new scene
|
|
NetworkServer.SendToAll(new SceneMessage { sceneName = newSceneName });
|
|
}
|
|
|
|
startPositionIndex = 0;
|
|
startPositions.Clear();
|
|
}
|
|
|
|
// This is only set in ClientChangeScene below...never on server.
|
|
// We need to check this in OnClientSceneChanged called from FinishLoadSceneClientOnly
|
|
// to prevent AddPlayer message after loading/unloading additive scenes
|
|
SceneOperation clientSceneOperation = SceneOperation.Normal;
|
|
|
|
internal void ClientChangeScene(string newSceneName, SceneOperation sceneOperation = SceneOperation.Normal, bool customHandling = false)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(newSceneName))
|
|
{
|
|
Debug.LogError("ClientChangeScene empty scene name");
|
|
return;
|
|
}
|
|
|
|
//Debug.Log($"ClientChangeScene newSceneName: {newSceneName} networkSceneName{networkSceneName}");
|
|
|
|
// Let client prepare for scene change
|
|
OnClientChangeScene(newSceneName, sceneOperation, customHandling);
|
|
|
|
// After calling OnClientChangeScene, exit if server since server is already doing
|
|
// the actual scene change, and we don't need to do it for the host client
|
|
if (NetworkServer.active)
|
|
return;
|
|
|
|
// set client flag to stop processing messages while loading scenes.
|
|
// otherwise we would process messages and then lose all the state
|
|
// as soon as the load is finishing, causing all kinds of bugs
|
|
// because of missing state.
|
|
// (client may be null after StopClient etc.)
|
|
// Debug.Log("ClientChangeScene: pausing handlers while scene is loading to avoid data loss after scene was loaded.");
|
|
NetworkClient.isLoadingScene = true;
|
|
|
|
// Cache sceneOperation so we know what was requested by the
|
|
// Scene message in OnClientChangeScene and OnClientSceneChanged
|
|
clientSceneOperation = sceneOperation;
|
|
|
|
// scene handling will happen in overrides of OnClientChangeScene and/or OnClientSceneChanged
|
|
// Do not call FinishLoadScene here. Custom handler will assign loadingSceneAsync and we need
|
|
// to wait for that to finish. UpdateScene already checks for that to be not null and isDone.
|
|
if (customHandling)
|
|
return;
|
|
|
|
switch (sceneOperation)
|
|
{
|
|
case SceneOperation.Normal:
|
|
loadingSceneAsync = SceneManager.LoadSceneAsync(newSceneName);
|
|
break;
|
|
case SceneOperation.LoadAdditive:
|
|
// Ensure additive scene is not already loaded on client by name or path
|
|
// since we don't know which was passed in the Scene message
|
|
if (!SceneManager.GetSceneByName(newSceneName).IsValid() && !SceneManager.GetSceneByPath(newSceneName).IsValid())
|
|
loadingSceneAsync = SceneManager.LoadSceneAsync(newSceneName, LoadSceneMode.Additive);
|
|
else
|
|
{
|
|
Debug.LogWarning($"Scene {newSceneName} is already loaded");
|
|
|
|
// Reset the flag that we disabled before entering this switch
|
|
NetworkClient.isLoadingScene = false;
|
|
}
|
|
break;
|
|
case SceneOperation.UnloadAdditive:
|
|
// Ensure additive scene is actually loaded on client by name or path
|
|
// since we don't know which was passed in the Scene message
|
|
if (SceneManager.GetSceneByName(newSceneName).IsValid() || SceneManager.GetSceneByPath(newSceneName).IsValid())
|
|
loadingSceneAsync = SceneManager.UnloadSceneAsync(newSceneName, UnloadSceneOptions.UnloadAllEmbeddedSceneObjects);
|
|
else
|
|
{
|
|
Debug.LogWarning($"Cannot unload {newSceneName} with UnloadAdditive operation");
|
|
|
|
// Reset the flag that we disabled before entering this switch
|
|
NetworkClient.isLoadingScene = false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// don't change the client's current networkSceneName when loading additive scene content
|
|
if (sceneOperation == SceneOperation.Normal)
|
|
networkSceneName = newSceneName;
|
|
}
|
|
|
|
// support additive scene loads:
|
|
// NetworkScenePostProcess disables all scene objects on load, and
|
|
// * NetworkServer.SpawnObjects enables them again on the server when
|
|
// calling OnStartServer
|
|
// * NetworkClient.PrepareToSpawnSceneObjects enables them again on the
|
|
// client after the server sends ObjectSpawnStartedMessage to client
|
|
// in SpawnObserversForConnection. this is only called when the
|
|
// client joins, so we need to rebuild scene objects manually again
|
|
// TODO merge this with FinishLoadScene()?
|
|
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
|
{
|
|
if (mode == LoadSceneMode.Additive)
|
|
{
|
|
if (NetworkServer.active)
|
|
{
|
|
// TODO only respawn the server objects from that scene later!
|
|
NetworkServer.SpawnObjects();
|
|
// Debug.Log($"Respawned Server objects after additive scene load: {scene.name}");
|
|
}
|
|
if (NetworkClient.active)
|
|
{
|
|
NetworkClient.PrepareToSpawnSceneObjects();
|
|
// Debug.Log($"Rebuild Client spawnableObjects after additive scene load: {scene.name}");
|
|
}
|
|
}
|
|
}
|
|
|
|
void UpdateScene()
|
|
{
|
|
if (loadingSceneAsync != null && loadingSceneAsync.isDone)
|
|
{
|
|
//Debug.Log($"ClientChangeScene done readyConn {clientReadyConnection}");
|
|
|
|
// try-finally to guarantee loadingSceneAsync being cleared.
|
|
// fixes https://github.com/vis2k/Mirror/issues/2517 where if
|
|
// FinishLoadScene throws an exception, loadingSceneAsync would
|
|
// never be cleared and this code would run every Update.
|
|
try
|
|
{
|
|
FinishLoadScene();
|
|
}
|
|
finally
|
|
{
|
|
loadingSceneAsync.allowSceneActivation = true;
|
|
loadingSceneAsync = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void FinishLoadScene()
|
|
{
|
|
// NOTE: this cannot use NetworkClient.allClients[0] - that client may be for a completely different purpose.
|
|
|
|
// process queued messages that we received while loading the scene
|
|
//Debug.Log("FinishLoadScene: resuming handlers after scene was loading.");
|
|
NetworkServer.isLoadingScene = false;
|
|
NetworkClient.isLoadingScene = false;
|
|
|
|
// host mode?
|
|
if (mode == NetworkManagerMode.Host)
|
|
{
|
|
FinishLoadSceneHost();
|
|
}
|
|
// server-only mode?
|
|
else if (mode == NetworkManagerMode.ServerOnly)
|
|
{
|
|
FinishLoadSceneServerOnly();
|
|
}
|
|
// client-only mode?
|
|
else if (mode == NetworkManagerMode.ClientOnly)
|
|
{
|
|
FinishLoadSceneClientOnly();
|
|
}
|
|
// otherwise we called it after stopping when loading offline scene.
|
|
// do nothing then.
|
|
}
|
|
|
|
// finish load scene part for host mode. makes code easier and is
|
|
// necessary for FinishStartHost later.
|
|
// (the 3 things have to happen in that exact order)
|
|
void FinishLoadSceneHost()
|
|
{
|
|
// debug message is very important. if we ever break anything then
|
|
// it's very obvious to notice.
|
|
//Debug.Log("Finished loading scene in host mode.");
|
|
|
|
if (clientReadyConnection != null)
|
|
{
|
|
#pragma warning disable 618
|
|
// obsolete method calls new method because it's not empty
|
|
OnClientConnect(clientReadyConnection);
|
|
#pragma warning restore 618
|
|
clientLoadedScene = true;
|
|
clientReadyConnection = null;
|
|
}
|
|
|
|
// do we need to finish a StartHost() call?
|
|
// then call FinishStartHost and let it take care of spawning etc.
|
|
if (finishStartHostPending)
|
|
{
|
|
finishStartHostPending = false;
|
|
FinishStartHost();
|
|
|
|
// call OnServerSceneChanged
|
|
OnServerSceneChanged(networkSceneName);
|
|
|
|
// DO NOT call OnClientSceneChanged here.
|
|
// the scene change happened because StartHost loaded the
|
|
// server's online scene. it has nothing to do with the client.
|
|
// this was not meant as a client scene load, so don't call it.
|
|
//
|
|
// otherwise AddPlayer would be called twice:
|
|
// -> once for client OnConnected
|
|
// -> once in OnClientSceneChanged
|
|
}
|
|
// otherwise we just changed a scene in host mode
|
|
else
|
|
{
|
|
// spawn server objects
|
|
NetworkServer.SpawnObjects();
|
|
|
|
// call OnServerSceneChanged
|
|
OnServerSceneChanged(networkSceneName);
|
|
|
|
if (NetworkClient.isConnected)
|
|
{
|
|
// let client know that we changed scene
|
|
#pragma warning disable 618
|
|
// obsolete method calls new method because it's not empty
|
|
OnClientSceneChanged(NetworkClient.connection);
|
|
#pragma warning restore 618
|
|
}
|
|
}
|
|
}
|
|
|
|
// finish load scene part for server-only. . makes code easier and is
|
|
// necessary for FinishStartServer later.
|
|
void FinishLoadSceneServerOnly()
|
|
{
|
|
// debug message is very important. if we ever break anything then
|
|
// it's very obvious to notice.
|
|
//Debug.Log("Finished loading scene in server-only mode.");
|
|
|
|
NetworkServer.SpawnObjects();
|
|
OnServerSceneChanged(networkSceneName);
|
|
}
|
|
|
|
// finish load scene part for client-only. makes code easier and is
|
|
// necessary for FinishStartClient later.
|
|
void FinishLoadSceneClientOnly()
|
|
{
|
|
// debug message is very important. if we ever break anything then
|
|
// it's very obvious to notice.
|
|
//Debug.Log("Finished loading scene in client-only mode.");
|
|
|
|
if (clientReadyConnection != null)
|
|
{
|
|
#pragma warning disable 618
|
|
// obsolete method calls new method because it's not empty
|
|
OnClientConnect(clientReadyConnection);
|
|
#pragma warning restore 618
|
|
clientLoadedScene = true;
|
|
clientReadyConnection = null;
|
|
}
|
|
|
|
if (NetworkClient.isConnected)
|
|
{
|
|
#pragma warning disable 618
|
|
// obsolete method calls new method because it's not empty
|
|
OnClientSceneChanged(NetworkClient.connection);
|
|
#pragma warning restore 618
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers the transform of a game object as a player spawn location.
|
|
/// <para>This is done automatically by NetworkStartPosition components, but can be done manually from user script code.</para>
|
|
/// </summary>
|
|
/// <param name="start">Transform to register.</param>
|
|
// Static because it's called from NetworkStartPosition::Awake
|
|
// and singleton may not exist yet
|
|
public static void RegisterStartPosition(Transform start)
|
|
{
|
|
// Debug.Log($"RegisterStartPosition: {start.gameObject.name} {start.position}");
|
|
startPositions.Add(start);
|
|
|
|
// reorder the list so that round-robin spawning uses the start positions
|
|
// in hierarchy order. This assumes all objects with NetworkStartPosition
|
|
// component are siblings, either in the scene root or together as children
|
|
// under a single parent in the scene.
|
|
startPositions = startPositions.OrderBy(transform => transform.GetSiblingIndex()).ToList();
|
|
}
|
|
|
|
/// <summary>Unregister a Transform from start positions.</summary>
|
|
// Static because it's called from NetworkStartPosition::OnDestroy
|
|
// and singleton may not exist yet
|
|
public static void UnRegisterStartPosition(Transform start)
|
|
{
|
|
//Debug.Log($"UnRegisterStartPosition: {start.name} {start.position}");
|
|
startPositions.Remove(start);
|
|
}
|
|
|
|
/// <summary>Get the next NetworkStartPosition based on the selected PlayerSpawnMethod.</summary>
|
|
public Transform GetStartPosition()
|
|
{
|
|
// first remove any dead transforms
|
|
startPositions.RemoveAll(t => t == null);
|
|
|
|
if (startPositions.Count == 0)
|
|
return null;
|
|
|
|
if (playerSpawnMethod == PlayerSpawnMethod.Random)
|
|
{
|
|
return startPositions[UnityEngine.Random.Range(0, startPositions.Count)];
|
|
}
|
|
else
|
|
{
|
|
Transform startPosition = startPositions[startPositionIndex];
|
|
startPositionIndex = (startPositionIndex + 1) % startPositions.Count;
|
|
return startPosition;
|
|
}
|
|
}
|
|
|
|
void OnServerConnectInternal(NetworkConnection conn)
|
|
{
|
|
//Debug.Log("NetworkManager.OnServerConnectInternal");
|
|
|
|
if (authenticator != null)
|
|
{
|
|
// we have an authenticator - let it handle authentication
|
|
authenticator.OnServerAuthenticate(conn);
|
|
}
|
|
else
|
|
{
|
|
// authenticate immediately
|
|
OnServerAuthenticated(conn);
|
|
}
|
|
}
|
|
|
|
// called after successful authentication
|
|
// TODO do the NetworkServer.OnAuthenticated thing from x branch
|
|
void OnServerAuthenticated(NetworkConnection conn)
|
|
{
|
|
//Debug.Log("NetworkManager.OnServerAuthenticated");
|
|
|
|
// set connection to authenticated
|
|
conn.isAuthenticated = true;
|
|
|
|
// proceed with the login handshake by calling OnServerConnect
|
|
if (networkSceneName != "" && networkSceneName != offlineScene)
|
|
{
|
|
SceneMessage msg = new SceneMessage() { sceneName = networkSceneName };
|
|
conn.Send(msg);
|
|
}
|
|
|
|
OnServerConnect(conn);
|
|
}
|
|
|
|
void OnServerReadyMessageInternal(NetworkConnection conn, ReadyMessage msg)
|
|
{
|
|
//Debug.Log("NetworkManager.OnServerReadyMessageInternal");
|
|
OnServerReady(conn);
|
|
}
|
|
|
|
void OnServerAddPlayerInternal(NetworkConnection conn, AddPlayerMessage msg)
|
|
{
|
|
//Debug.Log("NetworkManager.OnServerAddPlayer");
|
|
|
|
if (autoCreatePlayer && playerPrefab == null)
|
|
{
|
|
Debug.LogError("The PlayerPrefab is empty on the NetworkManager. Please setup a PlayerPrefab object.");
|
|
return;
|
|
}
|
|
|
|
if (autoCreatePlayer && playerPrefab.GetComponent<NetworkIdentity>() == null)
|
|
{
|
|
Debug.LogError("The PlayerPrefab does not have a NetworkIdentity. Please add a NetworkIdentity to the player prefab.");
|
|
return;
|
|
}
|
|
|
|
if (conn.identity != null)
|
|
{
|
|
Debug.LogError("There is already a player for this connection.");
|
|
return;
|
|
}
|
|
|
|
OnServerAddPlayer(conn);
|
|
}
|
|
|
|
void OnClientConnectInternal()
|
|
{
|
|
//Debug.Log("NetworkManager.OnClientConnectInternal");
|
|
|
|
if (authenticator != null)
|
|
{
|
|
// we have an authenticator - let it handle authentication
|
|
authenticator.OnClientAuthenticate();
|
|
}
|
|
else
|
|
{
|
|
// authenticate immediately
|
|
OnClientAuthenticated();
|
|
}
|
|
}
|
|
|
|
// called after successful authentication
|
|
void OnClientAuthenticated()
|
|
{
|
|
//Debug.Log("NetworkManager.OnClientAuthenticated");
|
|
|
|
// set connection to authenticated
|
|
NetworkClient.connection.isAuthenticated = true;
|
|
|
|
// proceed with the login handshake by calling OnClientConnect
|
|
if (string.IsNullOrWhiteSpace(onlineScene) || onlineScene == offlineScene || IsSceneActive(onlineScene))
|
|
{
|
|
clientLoadedScene = false;
|
|
#pragma warning disable 618
|
|
// obsolete method calls new method because it's not empty
|
|
OnClientConnect(NetworkClient.connection);
|
|
#pragma warning restore 618
|
|
}
|
|
else
|
|
{
|
|
// will wait for scene id to come from the server.
|
|
clientLoadedScene = true;
|
|
clientReadyConnection = NetworkClient.connection;
|
|
}
|
|
}
|
|
|
|
void OnClientDisconnectInternal()
|
|
{
|
|
//Debug.Log("NetworkManager.OnClientDisconnectInternal");
|
|
#pragma warning disable 618
|
|
// obsolete method calls new method because it's not empty
|
|
OnClientDisconnect(NetworkClient.connection);
|
|
#pragma warning restore 618
|
|
}
|
|
|
|
void OnClientNotReadyMessageInternal(NotReadyMessage msg)
|
|
{
|
|
//Debug.Log("NetworkManager.OnClientNotReadyMessageInternal");
|
|
NetworkClient.ready = false;
|
|
#pragma warning disable 618
|
|
OnClientNotReady(NetworkClient.connection);
|
|
#pragma warning restore 618
|
|
OnClientNotReady();
|
|
|
|
// NOTE: clientReadyConnection is not set here! don't want OnClientConnect to be invoked again after scene changes.
|
|
}
|
|
|
|
void OnClientSceneInternal(SceneMessage msg)
|
|
{
|
|
//Debug.Log("NetworkManager.OnClientSceneInternal");
|
|
|
|
// This needs to run for host client too. NetworkServer.active is checked there
|
|
if (NetworkClient.isConnected)
|
|
{
|
|
ClientChangeScene(msg.sceneName, msg.sceneOperation, msg.customHandling);
|
|
}
|
|
}
|
|
|
|
/// <summary>Called on the server when a new client connects.</summary>
|
|
public virtual void OnServerConnect(NetworkConnection conn) {}
|
|
|
|
/// <summary>Called on the server when a client disconnects.</summary>
|
|
// Called by NetworkServer.OnTransportDisconnect!
|
|
public virtual void OnServerDisconnect(NetworkConnection conn)
|
|
{
|
|
// by default, this function destroys the connection's player.
|
|
// can be overwritten for cases like delayed logouts in MMOs to
|
|
// avoid players escaping from PvP situations by logging out.
|
|
NetworkServer.DestroyPlayerForConnection(conn);
|
|
//Debug.Log("OnServerDisconnect: Client disconnected.");
|
|
}
|
|
|
|
/// <summary>Called on the server when a client is ready (= loaded the scene)</summary>
|
|
public virtual void OnServerReady(NetworkConnection conn)
|
|
{
|
|
if (conn.identity == null)
|
|
{
|
|
// this is now allowed (was not for a while)
|
|
//Debug.Log("Ready with no player object");
|
|
}
|
|
NetworkServer.SetClientReady(conn);
|
|
}
|
|
|
|
/// <summary>Called on server when a client requests to add the player. Adds playerPrefab by default. Can be overwritten.</summary>
|
|
// The default implementation for this function creates a new player object from the playerPrefab.
|
|
public virtual void OnServerAddPlayer(NetworkConnection conn)
|
|
{
|
|
Transform startPos = GetStartPosition();
|
|
GameObject player = startPos != null
|
|
? Instantiate(playerPrefab, startPos.position, startPos.rotation)
|
|
: Instantiate(playerPrefab);
|
|
|
|
// instantiating a "Player" prefab gives it the name "Player(clone)"
|
|
// => appending the connectionId is WAY more useful for debugging!
|
|
player.name = $"{playerPrefab.name} [connId={conn.connectionId}]";
|
|
NetworkServer.AddPlayerForConnection(conn, player);
|
|
}
|
|
|
|
/// <summary>Called on server when transport raises an exception. NetworkConnection may be null.</summary>
|
|
public virtual void OnServerError(NetworkConnection conn, Exception exception) {}
|
|
|
|
/// <summary>Called from ServerChangeScene immediately before SceneManager.LoadSceneAsync is executed</summary>
|
|
public virtual void OnServerChangeScene(string newSceneName) {}
|
|
|
|
/// <summary>Called on server after a scene load with ServerChangeScene() is completed.</summary>
|
|
public virtual void OnServerSceneChanged(string sceneName) {}
|
|
|
|
/// <summary>Called on the client when connected to a server. By default it sets client as ready and adds a player.</summary>
|
|
public virtual void OnClientConnect()
|
|
{
|
|
// OnClientConnect by default calls AddPlayer but it should not do
|
|
// that when we have online/offline scenes. so we need the
|
|
// clientLoadedScene flag to prevent it.
|
|
if (!clientLoadedScene)
|
|
{
|
|
// Ready/AddPlayer is usually triggered by a scene load completing.
|
|
// if no scene was loaded, then Ready/AddPlayer it here instead.
|
|
if (!NetworkClient.ready)
|
|
NetworkClient.Ready();
|
|
|
|
if (autoCreatePlayer)
|
|
NetworkClient.AddPlayer();
|
|
}
|
|
}
|
|
|
|
// Deprecated 2021-12-11
|
|
[Obsolete("Remove the NetworkConnection parameter in your override and use NetworkClient.connection instead.")]
|
|
public virtual void OnClientConnect(NetworkConnection conn) => OnClientConnect();
|
|
|
|
/// <summary>Called on clients when disconnected from a server.</summary>
|
|
public virtual void OnClientDisconnect()
|
|
{
|
|
if (mode == NetworkManagerMode.Offline)
|
|
return;
|
|
|
|
StopClient();
|
|
}
|
|
|
|
// Deprecated 2021-12-11
|
|
[Obsolete("Remove the NetworkConnection parameter in your override and use NetworkClient.connection instead.")]
|
|
public virtual void OnClientDisconnect(NetworkConnection conn) => OnClientDisconnect();
|
|
|
|
/// <summary>Called on client when transport raises an exception.</summary>
|
|
public virtual void OnClientError(Exception exception) {}
|
|
|
|
/// <summary>Called on clients when a servers tells the client it is no longer ready, e.g. when switching scenes.</summary>
|
|
public virtual void OnClientNotReady() {}
|
|
|
|
// Deprecated 2021-12-11
|
|
[Obsolete("Remove the NetworkConnection parameter in your override and use NetworkClient.connection instead.")]
|
|
public virtual void OnClientNotReady(NetworkConnection conn) {}
|
|
|
|
/// <summary>Called from ClientChangeScene immediately before SceneManager.LoadSceneAsync is executed</summary>
|
|
// customHandling: indicates if scene loading will be handled through overrides
|
|
public virtual void OnClientChangeScene(string newSceneName, SceneOperation sceneOperation, bool customHandling) {}
|
|
|
|
/// <summary>Called on clients when a scene has completed loaded, when the scene load was initiated by the server.</summary>
|
|
// Scene changes can cause player objects to be destroyed. The default
|
|
// implementation of OnClientSceneChanged in the NetworkManager is to
|
|
// add a player object for the connection if no player object exists.
|
|
public virtual void OnClientSceneChanged()
|
|
{
|
|
// always become ready.
|
|
if (!NetworkClient.ready) NetworkClient.Ready();
|
|
|
|
// Only call AddPlayer for normal scene changes, not additive load/unload
|
|
if (clientSceneOperation == SceneOperation.Normal && autoCreatePlayer && NetworkClient.localPlayer == null)
|
|
{
|
|
// add player if existing one is null
|
|
NetworkClient.AddPlayer();
|
|
}
|
|
}
|
|
|
|
// Deprecated 2021-12-11
|
|
[Obsolete("Remove the NetworkConnection parameter in your override and use NetworkClient.connection instead.")]
|
|
public virtual void OnClientSceneChanged(NetworkConnection conn) => OnClientSceneChanged();
|
|
|
|
// 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.
|
|
|
|
/// <summary>This is invoked when a host is started.</summary>
|
|
public virtual void OnStartHost() {}
|
|
|
|
/// <summary>This is invoked when a server is started - including when a host is started.</summary>
|
|
public virtual void OnStartServer() {}
|
|
|
|
/// <summary>This is invoked when the client is started.</summary>
|
|
public virtual void OnStartClient() {}
|
|
|
|
/// <summary>This is called when a server is stopped - including when a host is stopped.</summary>
|
|
public virtual void OnStopServer() {}
|
|
|
|
/// <summary>This is called when a client is stopped.</summary>
|
|
public virtual void OnStopClient() {}
|
|
|
|
/// <summary>This is called when a host is stopped.</summary>
|
|
public virtual void OnStopHost() {}
|
|
}
|
|
}
|