Mirror Networking
NetworkIdentity.cs
1using System;
2using System.Collections.Generic;
4using UnityEngine;
5using UnityEngine.Serialization;
6
7#if UNITY_EDITOR
8 using UnityEditor;
9
10 #if UNITY_2021_2_OR_NEWER
11 using UnityEditor.SceneManagement;
12 #elif UNITY_2018_3_OR_NEWER
13 using UnityEditor.Experimental.SceneManagement;
14 #endif
15#endif
16
17namespace Mirror
18{
19 // Default = use interest management
20 // ForceHidden = useful to hide monsters while they respawn etc.
21 // ForceShown = useful to have score NetworkIdentities that always broadcast
22 // to everyone etc.
23 public enum Visibility { Default, ForceHidden, ForceShown }
24
26 {
27 // IMPORTANT: int tick avoids floating point inaccuracy over days/weeks
28 public int tick;
29 public NetworkWriter ownerWriter;
30 public NetworkWriter observersWriter;
31 }
32
34 [DisallowMultipleComponent]
35 // NetworkIdentity.Awake initializes all NetworkComponents.
36 // let's make sure it's always called before their Awake's.
37 [DefaultExecutionOrder(-1)]
38 [AddComponentMenu("Network/Network Identity")]
39 [HelpURL("https://mirror-networking.gitbook.io/docs/components/network-identity")]
40 public sealed class NetworkIdentity : MonoBehaviour
41 {
43 //
44 // IMPORTANT:
45 // OnStartClient sets it to true. we NEVER set it to false after.
46 // otherwise components like Skillbars couldn't use OnDestroy()
47 // for saving, etc. since isClient may have been reset before
48 // OnDestroy was called.
49 //
50 // we also DO NOT make it dependent on NetworkClient.active or similar.
51 // we set it, then never change it. that's the user's expectation too.
52 //
53 // => fixes https://github.com/vis2k/Mirror/issues/1475
54 public bool isClient { get; internal set; }
55
57 //
58 // IMPORTANT:
59 // OnStartServer sets it to true. we NEVER set it to false after.
60 // otherwise components like Skillbars couldn't use OnDestroy()
61 // for saving, etc. since isServer may have been reset before
62 // OnDestroy was called.
63 //
64 // we also DO NOT make it dependent on NetworkServer.active or similar.
65 // we set it, then never change it. that's the user's expectation too.
66 //
67 // => fixes https://github.com/vis2k/Mirror/issues/1484
68 // => fixes https://github.com/vis2k/Mirror/issues/2533
69 public bool isServer { get; internal set; }
70
72 //
73 // IMPORTANT:
74 // OnStartLocalPlayer sets it to true. we NEVER set it to false after.
75 // otherwise components like Skillbars couldn't use OnDestroy()
76 // for saving, etc. since isLocalPlayer may have been reset before
77 // OnDestroy was called.
78 //
79 // we also DO NOT make it dependent on NetworkClient.localPlayer or similar.
80 // we set it, then never change it. that's the user's expectation too.
81 //
82 // => fixes https://github.com/vis2k/Mirror/issues/2615
83 public bool isLocalPlayer { get; internal set; }
84
86 public bool isServerOnly => isServer && !isClient;
87
89 public bool isClientOnly => isClient && !isServer;
90
92 public bool hasAuthority { get; internal set; }
93
95 // note: null until OnStartServer was called. this is necessary for
96 // SendTo* to work properly in server-only mode.
97 public Dictionary<int, NetworkConnectionToClient> observers;
98
100 public uint netId { get; internal set; }
101
103 // persistent scene id <sceneHash/32,sceneId/32> (see AssignSceneID comments)
104 [FormerlySerializedAs("m_SceneId"), HideInInspector]
105 public ulong sceneId;
106
108 [FormerlySerializedAs("m_ServerOnly")]
109 [Tooltip("Prevents this object from being spawned / enabled on clients")]
110 public bool serverOnly;
111
112 // Set before Destroy is called so that OnDestroy doesn't try to destroy
113 // the object again
114 internal bool destroyCalled;
115
117 // TODO change to NetworkConnectionToServer, but might cause some breaking
118 public NetworkConnection connectionToServer { get; internal set; }
119
122 {
123 get => _connectionToClient;
124 internal set
125 {
126 _connectionToClient?.RemoveOwnedObject(this);
127 _connectionToClient = value;
128 _connectionToClient?.AddOwnedObject(this);
129 }
130 }
131 NetworkConnectionToClient _connectionToClient;
132
134 // server sees ALL spawned ones.
135 // client sees OBSERVED spawned ones.
136 // => split into NetworkServer.spawned and NetworkClient.spawned to
137 // reduce shared state between server & client.
138 // => prepares for NetworkServer/Client as component & better host mode.
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
141 {
142 get
143 {
144 // server / host mode: use the one from server.
145 // host mode has access to all spawned.
147
148 // client
150
151 // neither: then we are testing.
152 // we could default to NetworkServer.spawned.
153 // but from the outside, that's not obvious.
154 // better to throw an exception to make it obvious.
155 throw new Exception("NetworkIdentity.spawned was accessed before NetworkServer/NetworkClient were active.");
156 }
157 }
158
159 // get all NetworkBehaviour components
160 public NetworkBehaviour[] NetworkBehaviours { get; private set; }
161
162 // current visibility
163 //
164 // Default = use interest management
165 // ForceHidden = useful to hide monsters while they respawn etc.
166 // ForceShown = useful to have score NetworkIdentities that always broadcast
167 // to everyone etc.
168 //
169 // TODO rename to 'visibility' after removing .visibility some day!
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;
172
173 // broadcasting serializes all entities around a player for each player.
174 // we don't want to serialize one entity twice in the same tick.
175 // so we cache the last serialization and remember the timestamp so we
176 // know which Update it was serialized.
177 // (timestamp is the same while inside Update)
178 // => this way we don't need to pool thousands of writers either.
179 // => way easier to store them per object
181 {
182 ownerWriter = new NetworkWriter(),
183 observersWriter = new NetworkWriter()
184 };
185
187 //
188 // The AssetId trick:
189 // Ideally we would have a serialized 'Guid m_AssetId' but Unity can't
190 // serialize it because Guid's internal bytes are private
191 //
192 // UNET used 'NetworkHash128' originally, with byte0, ..., byte16
193 // which works, but it just unnecessary extra code
194 //
195 // Using just the Guid string would work, but it's 32 chars long and
196 // would then be sent over the network as 64 instead of 16 bytes
197 //
198 // => The solution is to serialize the string internally here and then
199 // use the real 'Guid' type for everything else via .assetId
200 public Guid assetId
201 {
202 get
203 {
204#if UNITY_EDITOR
205 // This is important because sometimes OnValidate does not run (like when adding view to prefab with no child links)
206 if (string.IsNullOrWhiteSpace(m_AssetId))
207 SetupIDs();
208#endif
209 // convert string to Guid and use .Empty to avoid exception if
210 // we would use 'new Guid("")'
211 return string.IsNullOrWhiteSpace(m_AssetId) ? Guid.Empty : new Guid(m_AssetId);
212 }
213 internal set
214 {
215 string newAssetIdString = value == Guid.Empty ? string.Empty : value.ToString("N");
216 string oldAssetIdString = m_AssetId;
217
218 // they are the same, do nothing
219 if (oldAssetIdString == newAssetIdString)
220 {
221 return;
222 }
223
224 // new is empty
225 if (string.IsNullOrWhiteSpace(newAssetIdString))
226 {
227 Debug.LogError($"Can not set AssetId to empty guid on NetworkIdentity '{name}', old assetId '{oldAssetIdString}'");
228 return;
229 }
230
231 // old not empty
232 if (!string.IsNullOrWhiteSpace(oldAssetIdString))
233 {
234 Debug.LogError($"Can not Set AssetId on NetworkIdentity '{name}' because it already had an assetId, current assetId '{oldAssetIdString}', attempted new assetId '{newAssetIdString}'");
235 return;
236 }
237
238 // old is empty
239 m_AssetId = newAssetIdString;
240 // Debug.Log($"Settings AssetId on NetworkIdentity '{name}', new assetId '{newAssetIdString}'");
241 }
242 }
243 [SerializeField, HideInInspector] string m_AssetId;
244
245 // Keep track of all sceneIds to detect scene duplicates
246 static readonly Dictionary<ulong, NetworkIdentity> sceneIds =
247 new Dictionary<ulong, NetworkIdentity>();
248
249 // reset only client sided statics.
250 // don't touch server statics when calling StopClient in host mode.
251 // https://github.com/vis2k/Mirror/issues/2954
252 internal static void ResetClientStatics()
253 {
254 previousLocalPlayer = null;
256 }
257
258 internal static void ResetServerStatics()
259 {
260 nextNetworkId = 1;
261 }
262
263 // RuntimeInitializeOnLoadMethod -> fast playmode without domain reload
264 // internal so it can be called from NetworkServer & NetworkClient
265 [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
266 internal static void ResetStatics()
267 {
268 // reset ALL statics
269 ResetClientStatics();
270 ResetServerStatics();
271 }
272
274 public static NetworkIdentity GetSceneIdentity(ulong id) => sceneIds[id];
275
276 // used when adding players
277 internal void SetClientOwner(NetworkConnectionToClient conn)
278 {
279 // do nothing if it already has an owner
280 if (connectionToClient != null && conn != connectionToClient)
281 {
282 Debug.LogError($"Object {this} netId={netId} already has an owner. Use RemoveClientAuthority() first", this);
283 return;
284 }
285
286 // otherwise set the owner connection
287 connectionToClient = conn;
288 }
289
290 static uint nextNetworkId = 1;
291 internal static uint GetNextNetworkId() => nextNetworkId++;
292
294 public static void ResetNextNetworkId() => nextNetworkId = 1;
295
297 public delegate void ClientAuthorityCallback(NetworkConnectionToClient conn, NetworkIdentity identity, bool authorityState);
298
301
302 // hasSpawned should always be false before runtime
303 [SerializeField, HideInInspector] bool hasSpawned;
304 public bool SpawnedFromInstantiate { get; private set; }
305
306 // NetworkBehaviour components are initialized in Awake once.
307 // Changing them at runtime would get client & server out of sync.
308 // BUT internal so tests can add them after creating the NetworkIdentity
309 internal void InitializeNetworkBehaviours()
310 {
311 // Get all NetworkBehaviours
312 // (never null. GetComponents returns [] if none found)
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);
316
317 // initialize each one
318 for (int i = 0; i < NetworkBehaviours.Length; ++i)
319 {
320 NetworkBehaviour component = NetworkBehaviours[i];
321 component.netIdentity = this;
322 component.ComponentIndex = i;
323 }
324 }
325
326 // Awake is only called in Play mode.
327 // internal so we can call it during unit tests too.
328 internal void Awake()
329 {
330 // initialize NetworkBehaviour components.
331 // Awake() is called immediately after initialization.
332 // no one can overwrite it because NetworkIdentity is sealed.
333 // => doing it here is the fastest and easiest solution.
334 InitializeNetworkBehaviours();
335
336 if (hasSpawned)
337 {
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;
340 Destroy(gameObject);
341 }
342 hasSpawned = true;
343 }
344
345 void OnValidate()
346 {
347 // OnValidate is not called when using Instantiate, so we can use
348 // it to make sure that hasSpawned is false
349 hasSpawned = false;
350
351#if UNITY_EDITOR
352 SetupIDs();
353#endif
354 }
355
356#if UNITY_EDITOR
357 void AssignAssetID(string path)
358 {
359 // only set if not empty. fixes https://github.com/vis2k/Mirror/issues/2765
360 if (!string.IsNullOrWhiteSpace(path))
361 m_AssetId = AssetDatabase.AssetPathToGUID(path);
362 }
363
364 void AssignAssetID(GameObject prefab) => AssignAssetID(AssetDatabase.GetAssetPath(prefab));
365
366 // persistent sceneId assignment
367 // (because scene objects have no persistent unique ID in Unity)
368 //
369 // original UNET used OnPostProcessScene to assign an index based on
370 // FindObjectOfType<NetworkIdentity> order.
371 // -> this didn't work because FindObjectOfType order isn't deterministic.
372 // -> one workaround is to sort them by sibling paths, but it can still
373 // get out of sync when we open scene2 in editor and we have
374 // DontDestroyOnLoad objects that messed with the sibling index.
375 //
376 // we absolutely need a persistent id. challenges:
377 // * it needs to be 0 for prefabs
378 // => we set it to 0 in SetupIDs() if prefab!
379 // * it needs to be only assigned in edit time, not at runtime because
380 // only the objects that were in the scene since beginning should have
381 // a scene id.
382 // => Application.isPlaying check solves that
383 // * it needs to detect duplicated sceneIds after duplicating scene
384 // objects
385 // => sceneIds dict takes care of that
386 // * duplicating the whole scene file shouldn't result in duplicate
387 // scene objects
388 // => buildIndex is shifted into sceneId for that.
389 // => if we have no scenes in build index then it doesn't matter
390 // because by definition a build can't switch to other scenes
391 // => if we do have scenes in build index then it will be != -1
392 // note: the duplicated scene still needs to be opened once for it to
393 // be set properly
394 // * scene objects need the correct scene index byte even if the scene's
395 // build index was changed or a duplicated scene wasn't opened yet.
396 // => OnPostProcessScene is the only function that gets called for
397 // each scene before runtime, so this is where we set the scene
398 // byte.
399 // * disabled scenes in build settings should result in same scene index
400 // in editor and in build
401 // => .gameObject.scene.buildIndex filters out disabled scenes by
402 // default
403 // * generated sceneIds absolutely need to set scene dirty and force the
404 // user to resave.
405 // => Undo.RecordObject does that perfectly.
406 // * sceneIds should never be generated temporarily for unopened scenes
407 // when building, otherwise editor and build get out of sync
408 // => BuildPipeline.isBuildingPlayer check solves that
409 void AssignSceneID()
410 {
411 // we only ever assign sceneIds at edit time, never at runtime.
412 // by definition, only the original scene objects should get one.
413 // -> if we assign at runtime then server and client would generate
414 // different random numbers!
415 if (Application.isPlaying)
416 return;
417
418 // no valid sceneId yet, or duplicate?
419 bool duplicate = sceneIds.TryGetValue(sceneId, out NetworkIdentity existing) && existing != null && existing != this;
420 if (sceneId == 0 || duplicate)
421 {
422 // clear in any case, because it might have been a duplicate
423 sceneId = 0;
424
425 // if a scene was never opened and we are building it, then a
426 // sceneId would be assigned to build but not saved in editor,
427 // resulting in them getting out of sync.
428 // => don't ever assign temporary ids. they always need to be
429 // permanent
430 // => throw an exception to cancel the build and let the user
431 // know how to fix it!
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.");
434
435 // if we generate the sceneId then we MUST be sure to set dirty
436 // in order to save the scene object properly. otherwise it
437 // would be regenerated every time we reopen the scene, and
438 // upgrading would be very difficult.
439 // -> Undo.RecordObject is the new EditorUtility.SetDirty!
440 // -> we need to call it before changing.
441 Undo.RecordObject(this, "Generated SceneId");
442
443 // generate random sceneId part (0x00000000FFFFFFFF)
444 uint randomId = Utils.GetTrueRandomUInt();
445
446 // only assign if not a duplicate of an existing scene id
447 // (small chance, but possible)
448 duplicate = sceneIds.TryGetValue(randomId, out existing) && existing != null && existing != this;
449 if (!duplicate)
450 {
451 sceneId = randomId;
452 //Debug.Log($"{name} in scene {gameObject.scene.name} sceneId assigned to:{sceneId:X}");
453 }
454 }
455
456 // add to sceneIds dict no matter what
457 // -> even if we didn't generate anything new, because we still need
458 // existing sceneIds in there to check duplicates
459 sceneIds[sceneId] = this;
460 }
461
462 // copy scene path hash into sceneId for scene objects.
463 // this is the only way for scene file duplication to not contain
464 // duplicate sceneIds as it seems.
465 // -> sceneId before: 0x00000000AABBCCDD
466 // -> then we clear the left 4 bytes, so that our 'OR' uses 0x00000000
467 // -> then we OR the hash into the 0x00000000 part
468 // -> buildIndex is not enough, because Editor and Build have different
469 // build indices if there are disabled scenes in build settings, and
470 // if no scene is in build settings then Editor and Build have
471 // different indices too (Editor=0, Build=-1)
472 // => ONLY USE THIS FROM POSTPROCESSSCENE!
473 public void SetSceneIdSceneHashPartInternal()
474 {
475 // Use `ToLower` to that because BuildPipeline.BuildPlayer is case insensitive but hash is case sensitive
476 // If the scene in the project is `forest.unity` but `Forest.unity` is given to BuildPipeline then the
477 // BuildPipeline will use `Forest.unity` for the build and create a different hash than the editor will.
478 // Using ToLower will mean the hash will be the same for these 2 paths
479 // Assets/Scenes/Forest.unity
480 // Assets/Scenes/forest.unity
481 string scenePath = gameObject.scene.path.ToLower();
482
483 // get deterministic scene hash
484 uint pathHash = (uint)scenePath.GetStableHashCode();
485
486 // shift hash from 0x000000FFFFFFFF to 0xFFFFFFFF00000000
487 ulong shiftedHash = (ulong)pathHash << 32;
488
489 // OR into scene id
490 sceneId = (sceneId & 0xFFFFFFFF) | shiftedHash;
491
492 // log it. this is incredibly useful to debug sceneId issues.
493 //Debug.Log($"{name} in scene {gameObject.scene.name} scene index hash {pathHash:X} copied into sceneId {sceneId:X}");
494 }
495
496 void SetupIDs()
497 {
498 // is this a prefab?
499 if (Utils.IsPrefab(gameObject))
500 {
501 // force 0 for prefabs
502 sceneId = 0;
503 AssignAssetID(gameObject);
504 }
505 // are we currently in prefab editing mode? aka prefab stage
506 // => check prefabstage BEFORE SceneObjectWithPrefabParent
507 // (fixes https://github.com/vis2k/Mirror/issues/976)
508 // => if we don't check GetCurrentPrefabStage and only check
509 // GetPrefabStage(gameObject), then the 'else' case where we
510 // assign a sceneId and clear the assetId would still be
511 // triggered for prefabs. in other words: if we are in prefab
512 // stage, do not bother with anything else ever!
513 else if (PrefabStageUtility.GetCurrentPrefabStage() != null)
514 {
515 // when modifying a prefab in prefab stage, Unity calls
516 // OnValidate for that prefab and for all scene objects based on
517 // that prefab.
518 //
519 // is this GameObject the prefab that we modify, and not just a
520 // scene object based on the prefab?
521 // * GetCurrentPrefabStage = 'are we editing ANY prefab?'
522 // * GetPrefabStage(go) = 'are we editing THIS prefab?'
523 if (PrefabStageUtility.GetPrefabStage(gameObject) != null)
524 {
525 // force 0 for prefabs
526 sceneId = 0;
527 //Debug.Log($"{name} scene:{gameObject.scene.name} sceneid reset to 0 because CurrentPrefabStage={PrefabStageUtility.GetCurrentPrefabStage()} PrefabStage={PrefabStageUtility.GetPrefabStage(gameObject)}");
528
529 // get path from PrefabStage for this prefab
530#if UNITY_2020_1_OR_NEWER
531 string path = PrefabStageUtility.GetPrefabStage(gameObject).assetPath;
532#else
533 string path = PrefabStageUtility.GetPrefabStage(gameObject).prefabAssetPath;
534#endif
535
536 AssignAssetID(path);
537 }
538 }
539 // is this a scene object with prefab parent?
540 else if (Utils.IsSceneObjectWithPrefabParent(gameObject, out GameObject prefab))
541 {
542 AssignSceneID();
543 AssignAssetID(prefab);
544 }
545 else
546 {
547 AssignSceneID();
548
549 // IMPORTANT: DO NOT clear assetId at runtime!
550 // => fixes a bug where clicking any of the NetworkIdentity
551 // properties (like ServerOnly/ForceHidden) at runtime would
552 // call OnValidate
553 // => OnValidate gets into this else case here because prefab
554 // connection isn't known at runtime
555 // => then we would clear the previously assigned assetId
556 // => and NetworkIdentity couldn't be spawned on other clients
557 // anymore because assetId was cleared
558 if (!EditorApplication.isPlaying)
559 {
560 m_AssetId = "";
561 }
562 // don't log. would show a lot when pressing play in uMMORPG/uSurvival/etc.
563 //else Debug.Log($"Avoided clearing assetId at runtime for {name} after (probably) clicking any of the NetworkIdentity properties.");
564 }
565 }
566#endif
567
568 // OnDestroy is called for all SPAWNED NetworkIdentities
569 // => scene objects aren't destroyed. it's not called for them.
570 //
571 // Note: Unity will Destroy all networked objects on Scene Change, so we
572 // have to handle that here silently. That means we cannot have any
573 // warning or logging in this method.
574 void OnDestroy()
575 {
576 // Objects spawned from Instantiate are not allowed so are destroyed right away
577 // we don't want to call NetworkServer.Destroy if this is the case
578 if (SpawnedFromInstantiate)
579 return;
580
581 // If false the object has already been unspawned
582 // if it is still true, then we need to unspawn it
583 // if destroy is already called don't call it again
584 if (isServer && !destroyCalled)
585 {
586 // Do not add logging to this (see above)
587 NetworkServer.Destroy(gameObject);
588 }
589
590 if (isLocalPlayer)
591 {
592 // previously there was a bug where isLocalPlayer was
593 // false in OnDestroy because it was dynamically defined as:
594 // isLocalPlayer => NetworkClient.localPlayer == this
595 // we fixed it by setting isLocalPlayer manually and never
596 // resetting it.
597 //
598 // BUT now we need to be aware of a possible data race like in
599 // our rooms example:
600 // => GamePlayer is in world
601 // => player returns to room
602 // => GamePlayer is destroyed
603 // => NetworkClient.localPlayer is set to RoomPlayer
604 // => GamePlayer.OnDestroy is called 1 frame later
605 // => GamePlayer.OnDestroy 'isLocalPlayer' is true, so here we
606 // are trying to clear NetworkClient.localPlayer
607 // => which would overwrite the new RoomPlayer local player
608 //
609 // FIXED by simply only clearing if NetworkClient.localPlayer
610 // still points to US!
611 // => see also: https://github.com/vis2k/Mirror/issues/2635
612 if (NetworkClient.localPlayer == this)
613 NetworkClient.localPlayer = null;
614 }
615 }
616
617 internal void OnStartServer()
618 {
619 // do nothing if already spawned
620 if (isServer)
621 return;
622
623 // set isServer flag
624 isServer = true;
625
626 // set isLocalPlayer earlier, in case OnStartLocalplayer is called
627 // AFTER OnStartClient, in which case it would still be falsse here.
628 // many projects will check isLocalPlayer in OnStartClient though.
629 // TODO ideally set isLocalPlayer when NetworkClient.localPlayer is set?
630 if (NetworkClient.localPlayer == this)
631 {
632 isLocalPlayer = true;
633 }
634
635 // If the instance/net ID is invalid here then this is an object instantiated from a prefab and the server should assign a valid ID
636 // NOTE: this might not be necessary because the above m_IsServer
637 // check already checks netId. BUT this case here checks only
638 // netId, so it would still check cases where isServer=false
639 // but netId!=0.
640 if (netId != 0)
641 {
642 // This object has already been spawned, this method might be called again
643 // if we try to respawn all objects. This can happen when we add a scene
644 // in that case there is nothing else to do.
645 return;
646 }
647
648 netId = GetNextNetworkId();
649 observers = new Dictionary<int, NetworkConnectionToClient>();
650
651 //Debug.Log($"OnStartServer {this} NetId:{netId} SceneId:{sceneId:X}");
652
653 // add to spawned (note: the original EnableIsServer isn't needed
654 // because we already set m_isServer=true above)
656
657 // in host mode we set isClient true before calling OnStartServer,
658 // otherwise isClient is false in OnStartServer.
660 {
661 isClient = true;
662 }
663
664 foreach (NetworkBehaviour comp in NetworkBehaviours)
665 {
666 // an exception in OnStartServer should be caught, so that one
667 // component's exception doesn't stop all other components from
668 // being initialized
669 // => this is what Unity does for Start() etc. too.
670 // one exception doesn't stop all the other Start() calls!
671 try
672 {
673 comp.OnStartServer();
674 }
675 catch (Exception e)
676 {
677 Debug.LogException(e, comp);
678 }
679 }
680 }
681
682 internal void OnStopServer()
683 {
684 foreach (NetworkBehaviour comp in NetworkBehaviours)
685 {
686 // an exception in OnStartServer should be caught, so that one
687 // component's exception doesn't stop all other components from
688 // being initialized
689 // => this is what Unity does for Start() etc. too.
690 // one exception doesn't stop all the other Start() calls!
691 try
692 {
693 comp.OnStopServer();
694 }
695 catch (Exception e)
696 {
697 Debug.LogException(e, comp);
698 }
699 }
700 }
701
702 bool clientStarted;
703 internal void OnStartClient()
704 {
705 if (clientStarted)
706 return;
707 clientStarted = true;
708
709 isClient = true;
710
711 // set isLocalPlayer earlier, in case OnStartLocalplayer is called
712 // AFTER OnStartClient, in which case it would still be falsse here.
713 // many projects will check isLocalPlayer in OnStartClient though.
714 // TODO ideally set isLocalPlayer when NetworkClient.localPlayer is set?
715 if (NetworkClient.localPlayer == this)
716 {
717 isLocalPlayer = true;
718 }
719
720 // Debug.Log($"OnStartClient {gameObject} netId:{netId}");
721 foreach (NetworkBehaviour comp in NetworkBehaviours)
722 {
723 // an exception in OnStartClient should be caught, so that one
724 // component's exception doesn't stop all other components from
725 // being initialized
726 // => this is what Unity does for Start() etc. too.
727 // one exception doesn't stop all the other Start() calls!
728 try
729 {
730 // user implemented startup
731 comp.OnStartClient();
732 }
733 catch (Exception e)
734 {
735 Debug.LogException(e, comp);
736 }
737 }
738 }
739
740 internal void OnStopClient()
741 {
742 foreach (NetworkBehaviour comp in NetworkBehaviours)
743 {
744 // an exception in OnStopClient should be caught, so that
745 // one component's exception doesn't stop all other components
746 // from being initialized
747 // => this is what Unity does for Start() etc. too.
748 // one exception doesn't stop all the other Start() calls!
749 try
750 {
751 comp.OnStopClient();
752 }
753 catch (Exception e)
754 {
755 Debug.LogException(e, comp);
756 }
757 }
758 }
759
760 // TODO any way to make this not static?
761 // introduced in https://github.com/vis2k/Mirror/commit/c7530894788bb843b0f424e8f25029efce72d8ca#diff-dc8b7a5a67840f75ccc884c91b9eb76ab7311c9ca4360885a7e41d980865bdc2
762 // for PR https://github.com/vis2k/Mirror/pull/1263
763 //
764 // explanation:
765 // we send the spawn message multiple times. Whenever an object changes
766 // authority, we send the spawn message again for the object. This is
767 // necessary because we need to reinitialize all variables when
768 // ownership change due to sync to owner feature.
769 // Without this static, the second time we get the spawn message we
770 // would call OnStartLocalPlayer again on the same object
771 internal static NetworkIdentity previousLocalPlayer = null;
772 internal void OnStartLocalPlayer()
773 {
774 if (previousLocalPlayer == this)
775 return;
776 previousLocalPlayer = this;
777
778 isLocalPlayer = true;
779
780 foreach (NetworkBehaviour comp in NetworkBehaviours)
781 {
782 // an exception in OnStartLocalPlayer should be caught, so that
783 // one component's exception doesn't stop all other components
784 // from being initialized
785 // => this is what Unity does for Start() etc. too.
786 // one exception doesn't stop all the other Start() calls!
787 try
788 {
789 comp.OnStartLocalPlayer();
790 }
791 catch (Exception e)
792 {
793 Debug.LogException(e, comp);
794 }
795 }
796 }
797
798 internal void OnStopLocalPlayer()
799 {
800 foreach (NetworkBehaviour comp in NetworkBehaviours)
801 {
802 // an exception in OnStopLocalPlayer should be caught, so that
803 // one component's exception doesn't stop all other components
804 // from being initialized
805 // => this is what Unity does for Start() etc. too.
806 // one exception doesn't stop all the other Start() calls!
807 try
808 {
809 comp.OnStopLocalPlayer();
810 }
811 catch (Exception e)
812 {
813 Debug.LogException(e, comp);
814 }
815 }
816 }
817
818 bool hadAuthority;
819 internal void NotifyAuthority()
820 {
821 if (!hadAuthority && hasAuthority)
822 OnStartAuthority();
823 if (hadAuthority && !hasAuthority)
824 OnStopAuthority();
825 hadAuthority = hasAuthority;
826 }
827
828 internal void OnStartAuthority()
829 {
830 foreach (NetworkBehaviour comp in NetworkBehaviours)
831 {
832 // an exception in OnStartAuthority should be caught, so that one
833 // component's exception doesn't stop all other components from
834 // being initialized
835 // => this is what Unity does for Start() etc. too.
836 // one exception doesn't stop all the other Start() calls!
837 try
838 {
839 comp.OnStartAuthority();
840 }
841 catch (Exception e)
842 {
843 Debug.LogException(e, comp);
844 }
845 }
846 }
847
848 internal void OnStopAuthority()
849 {
850 foreach (NetworkBehaviour comp in NetworkBehaviours)
851 {
852 // an exception in OnStopAuthority should be caught, so that one
853 // component's exception doesn't stop all other components from
854 // being initialized
855 // => this is what Unity does for Start() etc. too.
856 // one exception doesn't stop all the other Start() calls!
857 try
858 {
859 comp.OnStopAuthority();
860 }
861 catch (Exception e)
862 {
863 Debug.LogException(e, comp);
864 }
865 }
866 }
867
868 // vis2k: readstring bug prevention: https://github.com/vis2k/Mirror/issues/2617
869 // -> OnSerialize writes length,componentData,length,componentData,...
870 // -> OnDeserialize carefully extracts each data, then deserializes each component with separate readers
871 // -> it will be impossible to read too many or too few bytes in OnDeserialize
872 // -> we can properly track down errors
873 bool OnSerializeSafely(NetworkBehaviour comp, NetworkWriter writer, bool initialState)
874 {
875 // write placeholder length bytes
876 // (jumping back later is WAY faster than allocating a temporary
877 // writer for the payload, then writing payload.size, payload)
878 int headerPosition = writer.Position;
879 // no varint because we don't know the final size yet
880 writer.WriteInt(0);
881 int contentPosition = writer.Position;
882
883 // write payload
884 bool result = false;
885 try
886 {
887 result = comp.OnSerialize(writer, initialState);
888 }
889 catch (Exception e)
890 {
891 // show a detailed error and let the user know what went wrong
892 Debug.LogError($"OnSerialize failed for: object={name} component={comp.GetType()} sceneId={sceneId:X}\n\n{e}");
893 }
894 int endPosition = writer.Position;
895
896 // fill in length now
897 writer.Position = headerPosition;
898 writer.WriteInt(endPosition - contentPosition);
899 writer.Position = endPosition;
900
901 //Debug.Log($"OnSerializeSafely written for object {comp.name} component:{comp.GetType()} sceneId:{sceneId:X} header:{headerPosition} content:{contentPosition} end:{endPosition} contentSize:{endPosition - contentPosition}");
902
903 return result;
904 }
905
906 // serialize all components using dirtyComponentsMask
907 // check ownerWritten/observersWritten to know if anything was written
908 // We pass dirtyComponentsMask into this function so that we can check
909 // if any Components are dirty before creating writers
910 internal void OnSerializeAllSafely(bool initialState, NetworkWriter ownerWriter, NetworkWriter observersWriter)
911 {
912 // check if components are in byte.MaxRange just to be 100% sure
913 // that we avoid overflows
914 NetworkBehaviour[] components = NetworkBehaviours;
915 if (components.Length > byte.MaxValue)
916 throw new IndexOutOfRangeException($"{name} has more than {byte.MaxValue} components. This is not supported.");
917
918 // serialize all components
919 for (int i = 0; i < components.Length; ++i)
920 {
921 // is this component dirty?
922 // -> always serialize if initialState so all components are included in spawn packet
923 // -> note: IsDirty() is false if the component isn't dirty or sendInterval isn't elapsed yet
924 NetworkBehaviour comp = components[i];
925 if (initialState || comp.IsDirty())
926 {
927 //Debug.Log($"OnSerializeAllSafely: {name} -> {comp.GetType()} initial:{ initialState}");
928
929 // remember start position in case we need to copy it into
930 // observers writer too
931 int startPosition = ownerWriter.Position;
932
933 // write index as byte [0..255].
934 // necessary because deserialize may only get data for some
935 // components because not dirty, not owner, etc.
936 ownerWriter.WriteByte((byte)i);
937
938 // serialize into ownerWriter first
939 // (owner always gets everything!)
940 OnSerializeSafely(comp, ownerWriter, initialState);
941
942 // copy into observersWriter too if SyncMode.Observers
943 // -> we copy instead of calling OnSerialize again because
944 // we don't know what magic the user does in OnSerialize.
945 // -> it's not guaranteed that calling it twice gets the
946 // same result
947 // -> it's not guaranteed that calling it twice doesn't mess
948 // with the user's OnSerialize timing code etc.
949 // => so we just copy the result without touching
950 // OnSerialize again
951 if (comp.syncMode == SyncMode.Observers)
952 {
953 ArraySegment<byte> segment = ownerWriter.ToArraySegment();
954 int length = ownerWriter.Position - startPosition;
955 observersWriter.WriteBytes(segment.Array, startPosition, length);
956 }
957 }
958 }
959 }
960
961 // get cached serialization for this tick (or serialize if none yet)
962 // IMPORTANT: int tick avoids floating point inaccuracy over days/weeks
963 internal NetworkIdentitySerialization GetSerializationAtTick(int tick)
964 {
965 // only rebuild serialization once per tick. reuse otherwise.
966 // except for tests, where Time.frameCount never increases.
967 // so during tests, we always rebuild.
968 // (otherwise [SyncVar] changes would never be serialized in tests)
969 //
970 // NOTE: != instead of < because int.max+1 overflows at some point.
971 if (lastSerialization.tick != tick || !Application.isPlaying)
972 {
973 // reset
974 lastSerialization.ownerWriter.Position = 0;
975 lastSerialization.observersWriter.Position = 0;
976
977 // serialize
978 OnSerializeAllSafely(false,
979 lastSerialization.ownerWriter,
980 lastSerialization.observersWriter);
981
982 // clear dirty bits for the components that we serialized.
983 // previously we did this in NetworkServer.BroadcastToConnection
984 // for every connection, for every entity.
985 // but we only serialize each entity once, right here in this
986 // 'lastSerialization.tick != tick' scope.
987 // so only do it once.
988 //
989 // NOTE: not in OnSerializeAllSafely as that should only do one
990 // thing: serialize data.
991 //
992 //
993 // NOTE: DO NOT clear ALL component's dirty bits, because
994 // components can have different syncIntervals and we
995 // don't want to reset dirty bits for the ones that were
996 // not synced yet.
997 //
998 // NOTE: this used to be very important to avoid ever growing
999 // SyncList changes if they had no observers, but we've
1000 // added SyncObject.isRecording since.
1001 ClearDirtyComponentsDirtyBits();
1002
1003 // set tick
1004 lastSerialization.tick = tick;
1005 //Debug.Log($"{name} (netId={netId}) serialized for tick={tickTimeStamp}");
1006 }
1007
1008 // return it
1009 return lastSerialization;
1010 }
1011
1012 void OnDeserializeSafely(NetworkBehaviour comp, NetworkReader reader, bool initialState)
1013 {
1014 // read header as 4 bytes and calculate this chunk's start+end
1015 int contentSize = reader.ReadInt();
1016 int chunkStart = reader.Position;
1017 int chunkEnd = reader.Position + contentSize;
1018
1019 // call OnDeserialize and wrap it in a try-catch block so there's no
1020 // way to mess up another component's deserialization
1021 try
1022 {
1023 //Debug.Log($"OnDeserializeSafely: {comp.name} component:{comp.GetType()} sceneId:{sceneId:X} length:{contentSize}");
1024 comp.OnDeserialize(reader, initialState);
1025 }
1026 catch (Exception e)
1027 {
1028 // show a detailed error and let the user know what went wrong
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" +
1034 $"Exception {e}");
1035 }
1036
1037 // now the reader should be EXACTLY at 'before + size'.
1038 // otherwise the component read too much / too less data.
1039 if (reader.Position != chunkEnd)
1040 {
1041 // warn the user
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.");
1044
1045 // fix the position, so the following components don't all fail
1046 reader.Position = chunkEnd;
1047 }
1048 }
1049
1050 internal void OnDeserializeAllSafely(NetworkReader reader, bool initialState)
1051 {
1052 if (NetworkBehaviours == null)
1053 {
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);
1058 return;
1059 }
1060
1061 // deserialize all components that were received
1062 NetworkBehaviour[] components = NetworkBehaviours;
1063 while (reader.Remaining > 0)
1064 {
1065 // read & check index [0..255]
1066 byte index = reader.ReadByte();
1067 if (index < components.Length)
1068 {
1069 // deserialize this component
1070 OnDeserializeSafely(components[index], reader, initialState);
1071 }
1072 }
1073 }
1074
1075 // Helper function to handle Command/Rpc
1076 internal void HandleRemoteCall(byte componentIndex, ushort functionHash, RemoteCallType remoteCallType, NetworkReader reader, NetworkConnectionToClient senderConnection = null)
1077 {
1078 // check if unity object has been destroyed
1079 if (this == null)
1080 {
1081 Debug.LogWarning($"{remoteCallType} [{functionHash}] received for deleted object [netId={netId}]");
1082 return;
1083 }
1084
1085 // find the right component to invoke the function on
1086 if (componentIndex >= NetworkBehaviours.Length)
1087 {
1088 Debug.LogWarning($"Component [{componentIndex}] not found for [netId={netId}]");
1089 return;
1090 }
1091
1092 NetworkBehaviour invokeComponent = NetworkBehaviours[componentIndex];
1093 if (!RemoteProcedureCalls.Invoke(functionHash, remoteCallType, reader, invokeComponent, senderConnection))
1094 {
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}].");
1096 }
1097 }
1098
1099 internal void AddObserver(NetworkConnectionToClient conn)
1100 {
1101 if (observers == null)
1102 {
1103 Debug.LogError($"AddObserver for {gameObject} observer list is null");
1104 return;
1105 }
1106
1107 if (observers.ContainsKey(conn.connectionId))
1108 {
1109 // if we try to add a connectionId that was already added, then
1110 // we may have generated one that was already in use.
1111 return;
1112 }
1113
1114 // Debug.Log($"Added observer: {conn.address} added for {gameObject}");
1115
1116 // if we previously had no observers, then clear all dirty bits once.
1117 // a monster's health may have changed while it had no observers.
1118 // but that change (= the dirty bits) don't matter as soon as the
1119 // first observer comes.
1120 // -> first observer gets full spawn packet
1121 // -> afterwards it gets delta packet
1122 // => if we don't clear previous dirty bits, observer would get
1123 // the health change because the bit was still set.
1124 // => ultimately this happens because spawn doesn't reset dirty
1125 // bits
1126 // => which happens because spawn happens separately, instead of
1127 // in Broadcast() (which will be changed in the future)
1128 //
1129 // NOTE that NetworkServer.Broadcast previously cleared dirty bits
1130 // for ALL SPAWNED that don't have observers. that was super
1131 // expensive. doing it when adding the first observer has the
1132 // same result, without the O(N) iteration in Broadcast().
1133 //
1134 // TODO remove this after moving spawning into Broadcast()!
1135 if (observers.Count == 0)
1136 {
1137 ClearAllComponentsDirtyBits();
1138 }
1139
1140 observers[conn.connectionId] = conn;
1141 conn.AddToObserving(this);
1142 }
1143
1144 // this is used when a connection is destroyed, since the "observers" property is read-only
1145 internal void RemoveObserver(NetworkConnection conn)
1146 {
1147 observers?.Remove(conn.connectionId);
1148 }
1149
1150 // Called when NetworkIdentity is destroyed
1151 internal void ClearObservers()
1152 {
1153 if (observers != null)
1154 {
1155 foreach (NetworkConnectionToClient conn in observers.Values)
1156 {
1157 conn.RemoveFromObserving(this, true);
1158 }
1159 observers.Clear();
1160 }
1161 }
1162
1164 // This causes hasAuthority to be set on the client that owns the object,
1165 // and NetworkBehaviour.OnStartAuthority will be called on that client.
1166 // This object then will be in the NetworkConnection.clientOwnedObjects
1167 // list for the connection.
1168 //
1169 // Authority can be removed with RemoveClientAuthority. Only one client
1170 // can own an object at any time. This does not need to be called for
1171 // player objects, as their authority is setup automatically.
1173 {
1174 if (!isServer)
1175 {
1176 Debug.LogError("AssignClientAuthority can only be called on the server for spawned objects.");
1177 return false;
1178 }
1179
1180 if (conn == null)
1181 {
1182 Debug.LogError($"AssignClientAuthority for {gameObject} owner cannot be null. Use RemoveClientAuthority() instead.");
1183 return false;
1184 }
1185
1186 if (connectionToClient != null && conn != connectionToClient)
1187 {
1188 Debug.LogError($"AssignClientAuthority for {gameObject} already has an owner. Use RemoveClientAuthority() first.");
1189 return false;
1190 }
1191
1192 SetClientOwner(conn);
1193
1194 // The client will match to the existing object
1195 NetworkServer.SendChangeOwnerMessage(this, conn);
1196
1197 clientAuthorityCallback?.Invoke(conn, this, true);
1198
1199 return true;
1200 }
1201
1203 // Applies to objects that had authority set by AssignClientAuthority,
1204 // or NetworkServer.Spawn with a NetworkConnection parameter included.
1205 // Authority cannot be removed for player objects.
1207 {
1208 if (!isServer)
1209 {
1210 Debug.LogError("RemoveClientAuthority can only be called on the server for spawned objects.");
1211 return;
1212 }
1213
1214 if (connectionToClient?.identity == this)
1215 {
1216 Debug.LogError("RemoveClientAuthority cannot remove authority for a player object");
1217 return;
1218 }
1219
1220 if (connectionToClient != null)
1221 {
1222 clientAuthorityCallback?.Invoke(connectionToClient, this, false);
1224 connectionToClient = null;
1225 NetworkServer.SendChangeOwnerMessage(this, previousOwner);
1226 }
1227 }
1228
1229 // Reset is called when the user hits the Reset button in the
1230 // Inspector's context menu or when adding the component the first time.
1231 // This function is only called in editor mode.
1232 //
1233 // Reset() seems to be called only for Scene objects.
1234 // we can't destroy them (they are always in the scene).
1235 // instead we disable them and call Reset().
1236 //
1237 // OLD COMMENT:
1238 // Marks the identity for future reset, this is because we cant reset
1239 // the identity during destroy as people might want to be able to read
1240 // the members inside OnDestroy(), and we have no way of invoking reset
1241 // after OnDestroy is called.
1242 internal void Reset()
1243 {
1244 // make sure to call this before networkBehavioursCache is cleared below
1245 ResetSyncObjects();
1246
1247 hasSpawned = false;
1248 clientStarted = false;
1249 isClient = false;
1250 isServer = false;
1251 //isLocalPlayer = false; <- cleared AFTER ClearLocalPlayer below!
1252
1253 // remove authority flag. This object may be unspawned, not destroyed, on client.
1254 hasAuthority = false;
1255 NotifyAuthority();
1256
1257 netId = 0;
1258 connectionToServer = null;
1259 connectionToClient = null;
1260
1261 ClearObservers();
1262
1263 // clear local player if it was the local player,
1264 // THEN reset isLocalPlayer AFTERWARDS
1265 if (isLocalPlayer)
1266 {
1267 // only clear NetworkClient.localPlayer IF IT POINTS TO US!
1268 // see OnDestroy() comments. it does the same.
1269 // (https://github.com/vis2k/Mirror/issues/2635)
1270 if (NetworkClient.localPlayer == this)
1271 NetworkClient.localPlayer = null;
1272 }
1273
1274 previousLocalPlayer = null;
1275 isLocalPlayer = false;
1276 }
1277
1278 // clear all component's dirty bits no matter what
1279 internal void ClearAllComponentsDirtyBits()
1280 {
1281 foreach (NetworkBehaviour comp in NetworkBehaviours)
1282 {
1283 comp.ClearAllDirtyBits();
1284 }
1285 }
1286
1287 // Clear only dirty component's dirty bits. ignores components which
1288 // may be dirty but not ready to be synced yet (because of syncInterval)
1289 //
1290 // NOTE: this used to be very important to avoid ever
1291 // growing SyncList changes if they had no observers,
1292 // but we've added SyncObject.isRecording since.
1293 internal void ClearDirtyComponentsDirtyBits()
1294 {
1295 foreach (NetworkBehaviour comp in NetworkBehaviours)
1296 {
1297 if (comp.IsDirty())
1298 {
1299 comp.ClearAllDirtyBits();
1300 }
1301 }
1302 }
1303
1304 void ResetSyncObjects()
1305 {
1306 // ResetSyncObjects is called by Reset, which is called by Unity.
1307 // AddComponent() calls Reset().
1308 // AddComponent() is called before Awake().
1309 // so NetworkBehaviours may not be initialized yet.
1310 if (NetworkBehaviours == null)
1311 return;
1312
1313 foreach (NetworkBehaviour comp in NetworkBehaviours)
1314 {
1315 comp.ResetSyncObjects();
1316 }
1317 }
1318 }
1319}
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
Definition: RemoteCalls.cs:32