2using System.Collections.Generic;
5using UnityEngine.Serialization;
10 #if UNITY_2021_2_OR_NEWER
11 using UnityEditor.SceneManagement;
12 #elif UNITY_2018_3_OR_NEWER
13 using UnityEditor.Experimental.SceneManagement;
23 public enum Visibility { Default, ForceHidden, ForceShown }
34 [DisallowMultipleComponent]
37 [DefaultExecutionOrder(-1)]
38 [AddComponentMenu(
"Network/Network Identity")]
39 [HelpURL(
"https://mirror-networking.gitbook.io/docs/components/network-identity")]
97 public Dictionary<int, NetworkConnectionToClient>
observers;
100 public uint
netId {
get;
internal set; }
104 [FormerlySerializedAs(
"m_SceneId"), HideInInspector]
108 [FormerlySerializedAs(
"m_ServerOnly")]
109 [Tooltip(
"Prevents this object from being spawned / enabled on clients")]
114 internal bool destroyCalled;
123 get => _connectionToClient;
126 _connectionToClient?.RemoveOwnedObject(
this);
127 _connectionToClient = value;
128 _connectionToClient?.AddOwnedObject(
this);
139 [Obsolete(
"NetworkIdentity.spawned is now NetworkServer.spawned on server, NetworkClient.spawned on client.\nPrepares for NetworkServer/Client as component, better host mode, better testing.")]
140 public static Dictionary<uint, NetworkIdentity>
spawned
155 throw new Exception(
"NetworkIdentity.spawned was accessed before NetworkServer/NetworkClient were active.");
170 [Tooltip(
"Visibility can overwrite interest management. ForceHidden can be useful to hide monsters while they respawn. ForceShown can be useful for score NetworkIdentities that should always broadcast to everyone in the world.")]
171 public Visibility visible = Visibility.Default;
206 if (
string.IsNullOrWhiteSpace(m_AssetId))
211 return string.IsNullOrWhiteSpace(m_AssetId) ? Guid.Empty :
new Guid(m_AssetId);
215 string newAssetIdString = value == Guid.Empty ? string.Empty : value.ToString(
"N");
216 string oldAssetIdString = m_AssetId;
219 if (oldAssetIdString == newAssetIdString)
225 if (
string.IsNullOrWhiteSpace(newAssetIdString))
227 Debug.LogError($
"Can not set AssetId to empty guid on NetworkIdentity '{name}', old assetId '{oldAssetIdString}'");
232 if (!
string.IsNullOrWhiteSpace(oldAssetIdString))
234 Debug.LogError($
"Can not Set AssetId on NetworkIdentity '{name}' because it already had an assetId, current assetId '{oldAssetIdString}', attempted new assetId '{newAssetIdString}'");
239 m_AssetId = newAssetIdString;
243 [SerializeField, HideInInspector]
string m_AssetId;
246 static readonly Dictionary<ulong, NetworkIdentity> sceneIds =
247 new Dictionary<ulong, NetworkIdentity>();
252 internal static void ResetClientStatics()
254 previousLocalPlayer =
null;
258 internal static void ResetServerStatics()
265 [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
266 internal static void ResetStatics()
269 ResetClientStatics();
270 ResetServerStatics();
282 Debug.LogError($
"Object {this} netId={netId} already has an owner. Use RemoveClientAuthority() first",
this);
290 static uint nextNetworkId = 1;
291 internal static uint GetNextNetworkId() => nextNetworkId++;
303 [SerializeField, HideInInspector]
bool hasSpawned;
304 public bool SpawnedFromInstantiate {
get;
private set; }
309 internal void InitializeNetworkBehaviours()
313 NetworkBehaviours = GetComponents<NetworkBehaviour>();
314 if (NetworkBehaviours.Length >
byte.MaxValue)
315 Debug.LogError($
"Only {byte.MaxValue} NetworkBehaviour components are allowed for NetworkIdentity: {name} because we send the index as byte.",
this);
318 for (
int i = 0; i < NetworkBehaviours.Length; ++i)
321 component.netIdentity =
this;
322 component.ComponentIndex = i;
328 internal void Awake()
334 InitializeNetworkBehaviours();
338 Debug.LogError($
"{name} has already spawned. Don't call Instantiate for NetworkIdentities that were in the scene since the beginning (aka scene objects). Otherwise the client won't know which object to use for a SpawnSceneObject message.");
339 SpawnedFromInstantiate =
true;
357 void AssignAssetID(
string path)
360 if (!
string.IsNullOrWhiteSpace(path))
361 m_AssetId = AssetDatabase.AssetPathToGUID(path);
364 void AssignAssetID(GameObject prefab) => AssignAssetID(AssetDatabase.GetAssetPath(prefab));
415 if (Application.isPlaying)
419 bool duplicate = sceneIds.TryGetValue(
sceneId, out
NetworkIdentity existing) && existing !=
null && existing !=
this;
432 if (BuildPipeline.isBuildingPlayer)
433 throw new InvalidOperationException($
"Scene {gameObject.scene.path} needs to be opened and resaved before building, because the scene object {name} has no valid sceneId yet.");
441 Undo.RecordObject(
this,
"Generated SceneId");
444 uint randomId =
Utils.GetTrueRandomUInt();
448 duplicate = sceneIds.TryGetValue(randomId, out existing) && existing !=
null && existing !=
this;
473 public void SetSceneIdSceneHashPartInternal()
481 string scenePath = gameObject.scene.path.ToLower();
484 uint pathHash = (uint)scenePath.GetStableHashCode();
487 ulong shiftedHash = (ulong)pathHash << 32;
499 if (
Utils.IsPrefab(gameObject))
503 AssignAssetID(gameObject);
513 else if (PrefabStageUtility.GetCurrentPrefabStage() !=
null)
523 if (PrefabStageUtility.GetPrefabStage(gameObject) !=
null)
530#if UNITY_2020_1_OR_NEWER
531 string path = PrefabStageUtility.GetPrefabStage(gameObject).assetPath;
533 string path = PrefabStageUtility.GetPrefabStage(gameObject).prefabAssetPath;
540 else if (
Utils.IsSceneObjectWithPrefabParent(gameObject, out GameObject prefab))
543 AssignAssetID(prefab);
558 if (!EditorApplication.isPlaying)
578 if (SpawnedFromInstantiate)
613 NetworkClient.localPlayer =
null;
617 internal void OnStartServer()
648 netId = GetNextNetworkId();
649 observers =
new Dictionary<int, NetworkConnectionToClient>();
677 Debug.LogException(e, comp);
682 internal void OnStopServer()
697 Debug.LogException(e, comp);
703 internal void OnStartClient()
707 clientStarted =
true;
735 Debug.LogException(e, comp);
740 internal void OnStopClient()
755 Debug.LogException(e, comp);
772 internal void OnStartLocalPlayer()
774 if (previousLocalPlayer ==
this)
776 previousLocalPlayer =
this;
793 Debug.LogException(e, comp);
798 internal void OnStopLocalPlayer()
813 Debug.LogException(e, comp);
819 internal void NotifyAuthority()
828 internal void OnStartAuthority()
843 Debug.LogException(e, comp);
848 internal void OnStopAuthority()
863 Debug.LogException(e, comp);
878 int headerPosition = writer.
Position;
881 int contentPosition = writer.
Position;
892 Debug.LogError($
"OnSerialize failed for: object={name} component={comp.GetType()} sceneId={sceneId:X}\n\n{e}");
897 writer.Position = headerPosition;
898 writer.WriteInt(endPosition - contentPosition);
899 writer.Position = endPosition;
915 if (components.Length >
byte.MaxValue)
916 throw new IndexOutOfRangeException($
"{name} has more than {byte.MaxValue} components. This is not supported.");
919 for (
int i = 0; i < components.Length; ++i)
925 if (initialState || comp.IsDirty())
931 int startPosition = ownerWriter.
Position;
936 ownerWriter.WriteByte((
byte)i);
940 OnSerializeSafely(comp, ownerWriter, initialState);
951 if (comp.
syncMode == SyncMode.Observers)
954 int length = ownerWriter.Position - startPosition;
955 observersWriter.WriteBytes(segment.Array, startPosition, length);
971 if (lastSerialization.tick != tick || !Application.isPlaying)
974 lastSerialization.ownerWriter.Position = 0;
975 lastSerialization.observersWriter.Position = 0;
978 OnSerializeAllSafely(
false,
979 lastSerialization.ownerWriter,
980 lastSerialization.observersWriter);
1001 ClearDirtyComponentsDirtyBits();
1004 lastSerialization.tick = tick;
1009 return lastSerialization;
1015 int contentSize = reader.ReadInt();
1017 int chunkEnd = reader.Position + contentSize;
1029 Debug.LogError($
"OnDeserialize failed Exception={e.GetType()} (see below) object={name} component={comp.GetType()} sceneId={sceneId:X} length={contentSize}. Possible Reasons:\n" +
1030 $
" * Do {comp.GetType()}'s OnSerialize and OnDeserialize calls write the same amount of data({contentSize} bytes)? \n" +
1031 $
" * Was there an exception in {comp.GetType()}'s OnSerialize/OnDeserialize code?\n" +
1032 $
" * Are the server and client the exact same project?\n" +
1033 $
" * Maybe this OnDeserialize call was meant for another GameObject? The sceneIds can easily get out of sync if the Hierarchy was modified only in the client OR the server. Try rebuilding both.\n\n" +
1042 int bytesRead = reader.Position - chunkStart;
1043 Debug.LogWarning($
"OnDeserialize was expected to read {contentSize} instead of {bytesRead} bytes for object:{name} component={comp.GetType()} sceneId={sceneId:X}. Make sure that OnSerialize and OnDeserialize write/read the same amount of data in all cases.");
1046 reader.Position = chunkEnd;
1050 internal void OnDeserializeAllSafely(
NetworkReader reader,
bool initialState)
1052 if (NetworkBehaviours ==
null)
1054 Debug.LogError($
"NetworkBehaviours array is null on {gameObject.name}!\n" +
1055 $
"Typically this can happen when a networked object is a child of a " +
1056 $
"non-networked parent that's disabled, preventing Awake on the networked object " +
1057 $
"from being invoked, where the NetworkBehaviours array is initialized.", gameObject);
1066 byte index = reader.ReadByte();
1067 if (index < components.Length)
1070 OnDeserializeSafely(components[index], reader, initialState);
1081 Debug.LogWarning($
"{remoteCallType} [{functionHash}] received for deleted object [netId={netId}]");
1086 if (componentIndex >= NetworkBehaviours.Length)
1088 Debug.LogWarning($
"Component [{componentIndex}] not found for [netId={netId}]");
1093 if (!
RemoteProcedureCalls.Invoke(functionHash, remoteCallType, reader, invokeComponent, senderConnection))
1095 Debug.LogError($
"Found no receiver for incoming {remoteCallType} [{functionHash}] on {gameObject.name}, the server and client should have the same NetworkBehaviour instances [netId={netId}].");
1103 Debug.LogError($
"AddObserver for {gameObject} observer list is null");
1137 ClearAllComponentsDirtyBits();
1141 conn.AddToObserving(
this);
1151 internal void ClearObservers()
1157 conn.RemoveFromObserving(
this,
true);
1176 Debug.LogError(
"AssignClientAuthority can only be called on the server for spawned objects.");
1182 Debug.LogError($
"AssignClientAuthority for {gameObject} owner cannot be null. Use RemoveClientAuthority() instead.");
1188 Debug.LogError($
"AssignClientAuthority for {gameObject} already has an owner. Use RemoveClientAuthority() first.");
1192 SetClientOwner(conn);
1210 Debug.LogError(
"RemoveClientAuthority can only be called on the server for spawned objects.");
1216 Debug.LogError(
"RemoveClientAuthority cannot remove authority for a player object");
1242 internal void Reset()
1248 clientStarted =
false;
1271 NetworkClient.localPlayer =
null;
1274 previousLocalPlayer =
null;
1279 internal void ClearAllComponentsDirtyBits()
1293 internal void ClearDirtyComponentsDirtyBits()
1304 void ResetSyncObjects()
1310 if (NetworkBehaviours ==
null)
1315 comp.ResetSyncObjects();
Base class for networked components.
void ClearAllDirtyBits()
Clears all the dirty bits that were set by SetDirtyBits()
virtual void OnStartAuthority()
Like Start(), but only called for objects the client has authority over.
virtual void OnDeserialize(NetworkReader reader, bool initialState)
Override to do custom deserialization (instead of SyncVars/SyncLists). Use OnSerialize too.
virtual void OnStopServer()
Stop event, only called on server and host.
virtual void OnStartClient()
Like Start(), but only called on client and host.
virtual void OnStartServer()
Like Start(), but only called on server and host.
virtual void OnStartLocalPlayer()
Like Start(), but only called on client and host for the local player object.
SyncMode syncMode
sync mode for OnSerialize
virtual void OnStopClient()
Stop event, only called on client and host.
virtual void OnStopLocalPlayer()
Stop event, but only called on client and host for the local player object.
virtual bool OnSerialize(NetworkWriter writer, bool initialState)
Override to do custom serialization (instead of SyncVars/SyncLists). Use OnDeserialize too.
virtual void OnStopAuthority()
Stop event, only called for objects the client has authority over.
NetworkClient with connection to server.
static bool active
active is true while a client is connecting/connected
static readonly Dictionary< uint, NetworkIdentity > spawned
All spawned NetworkIdentities by netId.
static NetworkIdentity localPlayer
NetworkIdentity of the localPlayer
Base NetworkConnection class for server-to-client and client-to-server connection.
readonly int connectionId
Unique identifier for this connection that is assigned by the transport layer.
NetworkIdentity identifies objects across the network.
static NetworkIdentity GetSceneIdentity(ulong id)
Gets the NetworkIdentity from the sceneIds dictionary with the corresponding id
bool isClient
Returns true if running as a client and this object was spawned by a server.
bool serverOnly
Make this object only exist when the game is running as a server (or host).
bool hasAuthority
True on client if that component has been assigned to the client. E.g. player, pets,...
uint netId
The unique network Id of this object (unique at runtime).
bool isServer
Returns true if NetworkServer.active and server is not stopped.
Guid assetId
Prefab GUID used to spawn prefabs across the network.
ulong sceneId
Unique identifier for NetworkIdentity objects within a scene, used for spawning scene objects.
bool isLocalPlayer
Return true if this object represents the player on the local machine.
static Dictionary< uint, NetworkIdentity > spawned
All spawned NetworkIdentities by netId. Available on server and client.
Dictionary< int, NetworkConnectionToClient > observers
The set of network connections (players) that can see this object.
bool isServerOnly
True if this object only exists on the server
void RemoveClientAuthority()
Removes ownership for an object.
NetworkConnectionToClient connectionToClient
Server's network connection to the client. This is only valid for client-owned objects (including the...
static ClientAuthorityCallback clientAuthorityCallback
A callback that can be populated to be notified when the client-authority state of objects changes.
bool AssignClientAuthority(NetworkConnectionToClient conn)
Assign control of an object to a client via the client's NetworkConnection.
bool isClientOnly
True if this object exists on a client that is not also acting as a server.
delegate void ClientAuthorityCallback(NetworkConnectionToClient conn, NetworkIdentity identity, bool authorityState)
The delegate type for the clientAuthorityCallback.
static void ResetNextNetworkId()
Resets nextNetworkId = 1
NetworkConnection connectionToServer
Client's network connection to the server. This is only valid for player objects on the client.
Network Reader for most simple types like floats, ints, buffers, structs, etc. Use NetworkReaderPool....
int Position
Next position to read from the buffer
int Remaining
Remaining bytes that can be read, for convenience.
NetworkServer handles remote connections and has a local connection for a local client.
static bool active
active checks if the server has been started
static readonly Dictionary< uint, NetworkIdentity > spawned
All spawned NetworkIdentities by netId.
static void Destroy(GameObject obj)
Destroys this object and corresponding objects on all clients.
Network Writer for most simple types like floats, ints, buffers, structs, etc. Use NetworkWriterPool....
ArraySegment< byte > ToArraySegment()
Returns allocation-free ArraySegment until 'Position'.
int Position
Next position to write to the buffer
Used to help manage remote calls for NetworkBehaviours