From 43906be1af85d046cdf2453ad9e8707fc22b5a46 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 22 May 2025 18:14:47 -0500 Subject: [PATCH 01/43] update Adding AttachableBehaviour and ObjectController. --- .../Components/Helpers/AttachableBehaviour.cs | 146 ++++++++++++++++++ .../Helpers/AttachableBehaviour.cs.meta | 2 + .../Components/Helpers/ObjectController.cs | 119 ++++++++++++++ .../Helpers/ObjectController.cs.meta | 2 + 4 files changed, 269 insertions(+) create mode 100644 com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Components/Helpers/ObjectController.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Components/Helpers/ObjectController.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs new file mode 100644 index 0000000000..db21ea110c --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs @@ -0,0 +1,146 @@ +using System; +using UnityEngine; + +namespace Unity.Netcode.Components +{ + /// + /// Handles parenting the that is a child under a root network prefab + /// without having to use the same parenting rules. + /// + public class AttachableBehaviour : NetworkBehaviour + { + /// + /// Invoked just prior to the parent being applied. + /// + /// + /// The parameter passed into a susbcriber callback will be either null or a valid.
+ /// When null, the parent is being unapplied/removed from the this instance is attached to. + ///
+ public event Action ParentIsBeingApplied; + + private NetworkVariable m_AppliedParent = new NetworkVariable(new NetworkBehaviourReference(null)); + private GameObject m_DefaultParent; + private Vector3 m_OriginalLocalPosition; + private Quaternion m_OriginalLocalRotation; + + /// + /// Will be true when this instance has a parent applied to it.
+ /// Will be false when this instance does not have a parent applied to it.
+ ///
+ public bool ParentIsApplied { get; private set; } + + /// + protected virtual void Awake() + { + m_DefaultParent = transform.parent == null ? gameObject : transform.parent.gameObject; + m_OriginalLocalPosition = transform.localPosition; + m_OriginalLocalRotation = transform.localRotation; + } + + /// + protected override void OnNetworkPostSpawn() + { + base.OnNetworkPostSpawn(); + if (HasAuthority) + { + m_AppliedParent.Value = new NetworkBehaviourReference(null); + } + UpdateParent(); + m_AppliedParent.OnValueChanged += OnAppliedParentChanged; + } + + /// + public override void OnNetworkDespawn() + { + ResetToDefault(); + base.OnNetworkDespawn(); + } + + private void OnAppliedParentChanged(NetworkBehaviourReference previous, NetworkBehaviourReference current) + { + UpdateParent(); + } + + private void UpdateParent() + { + var parent = (NetworkBehaviour)null; + if (m_AppliedParent.Value.TryGet(out parent)) + { + ParentIsApplied = true; + transform.SetParent(parent.gameObject.transform, false); + } + else + { + ParentIsApplied = false; + ResetToDefault(); + } + + OnParentUpdated(parent); + } + + private void ResetToDefault() + { + if (m_DefaultParent != null) + { + transform.SetParent(m_DefaultParent.transform, false); + } + transform.localPosition = m_OriginalLocalPosition; + transform.localRotation = m_OriginalLocalRotation; + } + + /// + /// Invoked after the parent has been applied.
+ ///
+ /// + /// The can be either null or a valid .
+ /// When null, the parent is being unapplied/removed from the this instance is attached to. + ///
+ /// The that is applied or null if it is no longer applied. + protected virtual void OnParentUpdated(NetworkBehaviour parent) + { + + } + + /// + /// Invoked just prior to the parent being applied.
+ /// This is a good time to handle disabling or enabling s using an . + ///
+ /// + /// The can be either null or a valid .
+ /// When null, the parent is being unapplied/removed from the this instance is attached to. + ///
+ /// The that is applied or null if it is no longer applied. + protected virtual void OnParentBeingApplied(NetworkBehaviour parent) + { + + } + + /// + /// Applies a parent to a nested and all children + /// of the nested . + /// + /// The to be applied or null to reparent under its original when spawned. + public void ApplyParent(NetworkBehaviour parent) + { + if (!IsSpawned) + { + Debug.LogError($"[{name}][Not Spawned] Can only have a parent applied when it is spawned!"); + return; + } + + if (!HasAuthority) + { + Debug.LogError($"[{name}][Not Authority] Client-{NetworkManager.LocalClientId} is not the authority!"); + return; + } + // Notify any subscriptions + ParentIsBeingApplied?.Invoke(parent); + + // Invoke for any overrides + OnParentBeingApplied(parent); + + // Once everything has been notified that we are applying a parent...apply the parent. + m_AppliedParent.Value = new NetworkBehaviourReference(parent); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs.meta b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs.meta new file mode 100644 index 0000000000..ade010ae67 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7aa87489bfc51d448940c66c2a2cf840 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ObjectController.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ObjectController.cs new file mode 100644 index 0000000000..10871b4b71 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ObjectController.cs @@ -0,0 +1,119 @@ +using System.Collections.Generic; +using System.Reflection; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace Unity.Netcode.Components +{ + /// + /// Handles enabling or disabling commonly used components, behaviours, RenderMeshes, etc.
+ /// Anything that derives from and has an enabled property can be added + /// to the list of objects.
+ /// This also synchronizes the enabling or disabling of the objects with connected and late + /// joining clients. + ///
+ public class ObjectController : NetworkBehaviour + { + /// + /// Determines whether the selected s will start out enabled or disabled. + /// + public bool InitialState; + + /// + /// The list of s to be enabled and disabled. + /// + public List Objects; + + private Dictionary m_ValidObjects = new Dictionary(); + private NetworkVariable m_IsEnabled = new NetworkVariable(); + + /// + protected virtual void Awake() + { + var emptyEntries = 0; + foreach (var someObject in Objects) + { + if (someObject == null) + { + emptyEntries++; + continue; + } + var propertyInfo = someObject.GetType().GetProperty("enabled", BindingFlags.Instance | BindingFlags.Public); + if (propertyInfo != null && propertyInfo.PropertyType == typeof(bool)) + { + m_ValidObjects.Add(someObject, propertyInfo); + } + else + { + Debug.LogWarning($"{name} does not contain a public enable property! (Ignoring)"); + } + } + if (emptyEntries > 0) + { + Debug.LogWarning($"{name} has {emptyEntries} emtpy(null) entries in the Objects list!"); + } + else + { + Debug.Log($"{name} has {m_ValidObjects.Count} valid object entries."); + } + } + + /// + public override void OnNetworkSpawn() + { + if (HasAuthority) + { + m_IsEnabled.Value = InitialState; + } + base.OnNetworkSpawn(); + } + + /// + protected override void OnNetworkPostSpawn() + { + m_IsEnabled.OnValueChanged += OnEnabledChanged; + ApplyEnabled(m_IsEnabled.Value); + base.OnNetworkPostSpawn(); + } + + /// + public override void OnNetworkDespawn() + { + m_IsEnabled.OnValueChanged -= OnEnabledChanged; + base.OnNetworkDespawn(); + } + + private void OnEnabledChanged(bool previous, bool current) + { + ApplyEnabled(current); + } + + private void ApplyEnabled(bool enabled) + { + foreach (var entry in m_ValidObjects) + { + entry.Value.SetValue(entry.Key, enabled); + } + } + + /// + /// Invoke on the authority side to enable or disable the . + /// + /// true = enabled | false = disabled + public void SetEnabled(bool isEnabled) + { + if (!IsSpawned) + { + Debug.Log($"[{name}] Must be spawned to use {nameof(SetEnabled)}!"); + return; + } + + if (!HasAuthority) + { + Debug.Log($"[Client-{NetworkManager.LocalClientId}] Attempting to invoke {nameof(SetEnabled)} without authority!"); + return; + } + m_IsEnabled.Value = isEnabled; + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ObjectController.cs.meta b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ObjectController.cs.meta new file mode 100644 index 0000000000..e7482640fa --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ObjectController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4d0b25b3f95e5324abc80c09cb29f271 \ No newline at end of file From 6267a3e5700101b65a66a62736410aa774f6c811 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 28 May 2025 11:20:38 -0500 Subject: [PATCH 02/43] update Renaming ObjectController to ComponentController. Added some additional validation checking and handling. Updated XML API. --- .../Components/Helpers/AttachableBehaviour.cs | 41 +++- .../Components/Helpers/ComponentController.cs | 197 ++++++++++++++++++ ...er.cs.meta => ComponentController.cs.meta} | 0 .../Components/Helpers/ObjectController.cs | 119 ----------- 4 files changed, 236 insertions(+), 121 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs rename com.unity.netcode.gameobjects/Runtime/Components/Helpers/{ObjectController.cs.meta => ComponentController.cs.meta} (100%) delete mode 100644 com.unity.netcode.gameobjects/Runtime/Components/Helpers/ObjectController.cs diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs index db21ea110c..ae423c5c57 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs @@ -1,14 +1,50 @@ using System; +#if UNITY_EDITOR +using UnityEditor; +#endif using UnityEngine; namespace Unity.Netcode.Components { /// - /// Handles parenting the that is a child under a root network prefab - /// without having to use the same parenting rules. + /// Handles parenting of the this component is attached to and is a nested .
+ /// The can reside under a parent or some higher generational parent. ///
public class AttachableBehaviour : NetworkBehaviour { +#if UNITY_EDITOR + /// + /// + /// In the event an is placed on the same + /// as the , this will automatically create a child and add an + /// to that. + /// + protected virtual void OnValidate() + { + var networkObject = gameObject.GetComponentInParent(); + if (!networkObject) + { + networkObject = gameObject.GetComponent(); + } + if (networkObject && networkObject.gameObject == gameObject) + { + Debug.LogWarning($"[{name}][{nameof(AttachableBehaviour)}] Cannot be placed on the same {nameof(GameObject)} as the {nameof(NetworkObject)}!"); + // Wait for the next editor update to create a nested child and add the AttachableBehaviour + EditorApplication.update += CreatedNestedChild; + } + } + + private void CreatedNestedChild() + { + EditorApplication.update -= CreatedNestedChild; + var childGameObject = new GameObject($"{name}-Child"); + childGameObject.transform.parent = transform; + childGameObject.AddComponent(); + Debug.Log($"[{name}][Created Child] Adding {nameof(AttachableBehaviour)} to newly created child {childGameObject.name}."); + DestroyImmediate(this); + } +#endif + /// /// Invoked just prior to the parent being applied. /// @@ -133,6 +169,7 @@ public void ApplyParent(NetworkBehaviour parent) Debug.LogError($"[{name}][Not Authority] Client-{NetworkManager.LocalClientId} is not the authority!"); return; } + // Notify any subscriptions ParentIsBeingApplied?.Invoke(parent); diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs new file mode 100644 index 0000000000..2952f13139 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs @@ -0,0 +1,197 @@ +using System.Collections.Generic; +using System.Reflection; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace Unity.Netcode.Components +{ + /// + /// Handles enabling or disabling commonly used components, behaviours, RenderMeshes, etc.
+ /// Anything that derives from and has an enabled property can be added + /// to the list of objects.
+ /// derived components are not allowed and will be automatically removed. + ///
+ /// + /// This will synchronize the enabled or disabled state of the s with + /// connected and late joining clients. + /// + public class ComponentController : NetworkBehaviour + { + /// + /// Determines whether the selected s will start enabled or disabled when spawned. + /// + [Tooltip("The initial state of the components when spawned.")] + public bool InitialState = true; + + /// + /// The list of s to be enabled and disabled. + /// + [Tooltip("The list of components to control. You can drag and drop an entire GameObject on this to include all components.")] + public List Components; + + private Dictionary m_ValidComponents = new Dictionary(); + private NetworkVariable m_IsEnabled = new NetworkVariable(); + +#if UNITY_EDITOR + /// + /// + /// Checks for invalid entries. + /// + protected virtual void OnValidate() + { + if (Components == null || Components.Count == 0) + { + return; + } + + var gameObjectsToScan = new List(); + for (int i = Components.Count - 1; i >= 0; i--) + { + if (Components[i] == null) + { + continue; + } + var componentType = Components[i].GetType(); + if (componentType == typeof(GameObject)) + { + gameObjectsToScan.Add(Components[i] as GameObject); + Components.RemoveAt(i); + continue; + } + + if (componentType.IsSubclassOf(typeof(NetworkBehaviour))) + { + Debug.LogWarning($"Removing {Components[i].name} since {nameof(NetworkBehaviour)}s are not allowed to be controlled by this component."); + Components.RemoveAt(i); + continue; + } + + var propertyInfo = Components[i].GetType().GetProperty("enabled", BindingFlags.Instance | BindingFlags.Public); + if (propertyInfo == null && propertyInfo.PropertyType != typeof(bool)) + { + Debug.LogWarning($"{Components[i].name} does not contain a public enabled property! (Removing)"); + Components.RemoveAt(i); + } + } + + foreach (var entry in gameObjectsToScan) + { + var components = entry.GetComponents(); + foreach (var component in components) + { + // Ignore any NetworkBehaviour derived components + if (component.GetType().IsSubclassOf(typeof(NetworkBehaviour))) + { + continue; + } + + var propertyInfo = component.GetType().GetProperty("enabled", BindingFlags.Instance | BindingFlags.Public); + if (propertyInfo != null && propertyInfo.PropertyType == typeof(bool)) + { + Components.Add(component); + } + } + } + gameObjectsToScan.Clear(); + } +#endif + + /// + /// + /// Also checks to assure all entries are valid and creates a final table of + /// s paired to their . + /// + protected virtual void Awake() + { + var emptyEntries = 0; + foreach (var someObject in Components) + { + if (someObject == null) + { + emptyEntries++; + continue; + } + var propertyInfo = someObject.GetType().GetProperty("enabled", BindingFlags.Instance | BindingFlags.Public); + if (propertyInfo != null && propertyInfo.PropertyType == typeof(bool)) + { + m_ValidComponents.Add(someObject as Component, propertyInfo); + } + else + { + Debug.LogWarning($"{name} does not contain a public enable property! (Ignoring)"); + } + } + if (emptyEntries > 0) + { + Debug.LogWarning($"{name} has {emptyEntries} emtpy(null) entries in the {nameof(Components)} list!"); + } + else + { + Debug.Log($"{name} has {m_ValidComponents.Count} valid {nameof(Component)} entries."); + } + } + + /// + public override void OnNetworkSpawn() + { + if (HasAuthority) + { + m_IsEnabled.Value = InitialState; + } + base.OnNetworkSpawn(); + } + + /// + /// + /// Assures all instances subscribe to the internal of type + /// that synchronizes all instances when s are enabled + /// or disabled. + /// + protected override void OnNetworkPostSpawn() + { + m_IsEnabled.OnValueChanged += OnEnabledChanged; + ApplyEnabled(m_IsEnabled.Value); + base.OnNetworkPostSpawn(); + } + + /// + public override void OnNetworkDespawn() + { + m_IsEnabled.OnValueChanged -= OnEnabledChanged; + base.OnNetworkDespawn(); + } + + private void OnEnabledChanged(bool previous, bool current) + { + ApplyEnabled(current); + } + + private void ApplyEnabled(bool enabled) + { + foreach (var entry in m_ValidComponents) + { + entry.Value.SetValue(entry.Key, enabled); + } + } + + /// + /// Invoke on the authority side to enable or disable the s. + /// + /// true = enabled | false = disabled + public void SetEnabled(bool isEnabled) + { + if (!IsSpawned) + { + Debug.Log($"[{name}] Must be spawned to use {nameof(SetEnabled)}!"); + return; + } + + if (!HasAuthority) + { + Debug.Log($"[Client-{NetworkManager.LocalClientId}] Attempting to invoke {nameof(SetEnabled)} without authority!"); + return; + } + m_IsEnabled.Value = isEnabled; + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ObjectController.cs.meta b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Runtime/Components/Helpers/ObjectController.cs.meta rename to com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ObjectController.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ObjectController.cs deleted file mode 100644 index 10871b4b71..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ObjectController.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System.Collections.Generic; -using System.Reflection; -using UnityEngine; -using Object = UnityEngine.Object; - -namespace Unity.Netcode.Components -{ - /// - /// Handles enabling or disabling commonly used components, behaviours, RenderMeshes, etc.
- /// Anything that derives from and has an enabled property can be added - /// to the list of objects.
- /// This also synchronizes the enabling or disabling of the objects with connected and late - /// joining clients. - ///
- public class ObjectController : NetworkBehaviour - { - /// - /// Determines whether the selected s will start out enabled or disabled. - /// - public bool InitialState; - - /// - /// The list of s to be enabled and disabled. - /// - public List Objects; - - private Dictionary m_ValidObjects = new Dictionary(); - private NetworkVariable m_IsEnabled = new NetworkVariable(); - - /// - protected virtual void Awake() - { - var emptyEntries = 0; - foreach (var someObject in Objects) - { - if (someObject == null) - { - emptyEntries++; - continue; - } - var propertyInfo = someObject.GetType().GetProperty("enabled", BindingFlags.Instance | BindingFlags.Public); - if (propertyInfo != null && propertyInfo.PropertyType == typeof(bool)) - { - m_ValidObjects.Add(someObject, propertyInfo); - } - else - { - Debug.LogWarning($"{name} does not contain a public enable property! (Ignoring)"); - } - } - if (emptyEntries > 0) - { - Debug.LogWarning($"{name} has {emptyEntries} emtpy(null) entries in the Objects list!"); - } - else - { - Debug.Log($"{name} has {m_ValidObjects.Count} valid object entries."); - } - } - - /// - public override void OnNetworkSpawn() - { - if (HasAuthority) - { - m_IsEnabled.Value = InitialState; - } - base.OnNetworkSpawn(); - } - - /// - protected override void OnNetworkPostSpawn() - { - m_IsEnabled.OnValueChanged += OnEnabledChanged; - ApplyEnabled(m_IsEnabled.Value); - base.OnNetworkPostSpawn(); - } - - /// - public override void OnNetworkDespawn() - { - m_IsEnabled.OnValueChanged -= OnEnabledChanged; - base.OnNetworkDespawn(); - } - - private void OnEnabledChanged(bool previous, bool current) - { - ApplyEnabled(current); - } - - private void ApplyEnabled(bool enabled) - { - foreach (var entry in m_ValidObjects) - { - entry.Value.SetValue(entry.Key, enabled); - } - } - - /// - /// Invoke on the authority side to enable or disable the . - /// - /// true = enabled | false = disabled - public void SetEnabled(bool isEnabled) - { - if (!IsSpawned) - { - Debug.Log($"[{name}] Must be spawned to use {nameof(SetEnabled)}!"); - return; - } - - if (!HasAuthority) - { - Debug.Log($"[Client-{NetworkManager.LocalClientId}] Attempting to invoke {nameof(SetEnabled)} without authority!"); - return; - } - m_IsEnabled.Value = isEnabled; - } - } -} From 83ee6509b9ff175d735f73e5572aa63412366e29 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 28 May 2025 11:21:30 -0500 Subject: [PATCH 03/43] update Adding helpers meta. --- .../Runtime/Components/Helpers.meta | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 com.unity.netcode.gameobjects/Runtime/Components/Helpers.meta diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers.meta b/com.unity.netcode.gameobjects/Runtime/Components/Helpers.meta new file mode 100644 index 0000000000..28b2944c3d --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fbc8738cd4ff119499520131b7aa7232 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: From 1832d21e2dd83a5a73baedaae1e2bc3c50e34cc2 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 23 Jun 2025 19:36:07 -0500 Subject: [PATCH 04/43] refactor Adding an AttachableNode as the target for AttachableBehaviour. Refactoring AttachableBehaviour. --- .../Components/Helpers/AttachableBehaviour.cs | 313 ++++++++++++++---- .../Components/Helpers/AttachableNode.cs | 77 +++++ .../Components/Helpers/AttachableNode.cs.meta | 2 + 3 files changed, 331 insertions(+), 61 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs index ae423c5c57..57afe67707 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs @@ -7,9 +7,19 @@ namespace Unity.Netcode.Components { /// - /// Handles parenting of the this component is attached to and is a nested .
- /// The can reside under a parent or some higher generational parent. + /// Attachable NetworkBehaviours
+ /// This component handles the parenting synchronization of the that this component is attached to.
+ /// under another 's .
+ /// The to be parented must have this component attached to it and must be nested on any child under the 's .
+ /// The target parent must have an component attached to it and must belong to a + /// different than that of the 's. ///
+ /// + /// The term "attach" is used in place of parenting in order to distinguish between parenting and + /// parenting ("attaching" and "detaching").
+ /// This component can be used along with one or more in order to enable or disable different components depending + /// upon the instance's current state. + ///
public class AttachableBehaviour : NetworkBehaviour { #if UNITY_EDITOR @@ -46,138 +56,319 @@ private void CreatedNestedChild() #endif /// - /// Invoked just prior to the parent being applied. + /// Invoked when the of this instance has changed. /// - /// - /// The parameter passed into a susbcriber callback will be either null or a valid.
- /// When null, the parent is being unapplied/removed from the this instance is attached to. - ///
- public event Action ParentIsBeingApplied; + public event Action AttachStateChange; - private NetworkVariable m_AppliedParent = new NetworkVariable(new NetworkBehaviourReference(null)); - private GameObject m_DefaultParent; - private Vector3 m_OriginalLocalPosition; - private Quaternion m_OriginalLocalRotation; + /// + /// The various states of . + /// + public enum AttachState + { + /// + /// The instance is not attached to anything. + /// When not attached to anything, the instance will be parented under the original + /// . + /// + Detached, + /// + /// The instance is attaching to an . + /// + /// + /// One example usage:
+ /// When using an with one or more component(s), + /// this would be a good time to enable or disable components. + ///
+ Attaching, + /// + /// The instance is attached to an . + /// + /// + /// This would be a good time to apply any additional local position or rotation values to this instance. + /// + Attached, + /// + /// The instance is detaching from an . + /// + /// + /// One example usage:
+ /// When using an with one or more component(s), + /// this would be a good time to enable or disable components. + ///
+ Detaching + } + + /// + /// The current instance's . + /// + protected AttachState m_AttachState { get; private set; } /// - /// Will be true when this instance has a parent applied to it.
- /// Will be false when this instance does not have a parent applied to it.
+ /// The original parent of this instance. ///
- public bool ParentIsApplied { get; private set; } + protected GameObject m_DefaultParent { get; private set; } + + /// + /// If attached, attaching, or detaching this will be the this instance is attached to. + /// + protected AttachableNode m_AttachableNode { get; private set; } + + private NetworkVariable m_AttachedNodeReference = new NetworkVariable(new NetworkBehaviourReference(null)); + private Vector3 m_OriginalLocalPosition; + private Quaternion m_OriginalLocalRotation; /// + /// + /// If you create a custom and override this method, you must invoke + /// this base instance of . + /// protected virtual void Awake() { m_DefaultParent = transform.parent == null ? gameObject : transform.parent.gameObject; m_OriginalLocalPosition = transform.localPosition; m_OriginalLocalRotation = transform.localRotation; + m_AttachState = AttachState.Detached; + m_AttachableNode = null; } /// + /// + /// If you create a custom and override this method, you must invoke + /// this base instance of . + /// protected override void OnNetworkPostSpawn() { - base.OnNetworkPostSpawn(); if (HasAuthority) { - m_AppliedParent.Value = new NetworkBehaviourReference(null); + m_AttachedNodeReference.Value = new NetworkBehaviourReference(null); } - UpdateParent(); - m_AppliedParent.OnValueChanged += OnAppliedParentChanged; + m_AttachedNodeReference.OnValueChanged += OnAttachedNodeReferenceChanged; + base.OnNetworkPostSpawn(); + } + + /// + /// + /// If you create a custom and override this method, you will want to + /// invoke this base instance of if you want the current + /// state to have been applied before executing the derived class's + /// script. + /// + protected override void OnNetworkSessionSynchronized() + { + UpdateAttachedState(); + base.OnNetworkSessionSynchronized(); } /// public override void OnNetworkDespawn() { - ResetToDefault(); + m_AttachedNodeReference.OnValueChanged -= OnAttachedNodeReferenceChanged; + InternalDetach(); + if (NetworkManager && !NetworkManager.ShutdownInProgress) + { + // Notify of the changed attached state + UpdateAttachState(m_AttachState, m_AttachableNode); + } base.OnNetworkDespawn(); } - private void OnAppliedParentChanged(NetworkBehaviourReference previous, NetworkBehaviourReference current) + private void OnAttachedNodeReferenceChanged(NetworkBehaviourReference previous, NetworkBehaviourReference current) { - UpdateParent(); + UpdateAttachedState(); } - private void UpdateParent() + private void UpdateAttachedState() { - var parent = (NetworkBehaviour)null; - if (m_AppliedParent.Value.TryGet(out parent)) + var attachableNode = (AttachableNode)null; + var shouldParent = m_AttachedNodeReference.Value.TryGet(out attachableNode, NetworkManager); + var preState = shouldParent ? AttachState.Attaching : AttachState.Detaching; + var preNode = shouldParent ? attachableNode : m_AttachableNode; + shouldParent = shouldParent && attachableNode != null; + + if (shouldParent && m_AttachableNode != null && m_AttachState == AttachState.Attached) { - ParentIsApplied = true; - transform.SetParent(parent.gameObject.transform, false); + // If we are attached to some other AttachableNode, then detach from that before attaching to a new one. + if (m_AttachableNode != attachableNode) + { + // Run through the same process without being triggerd by a NetVar update. + UpdateAttachState(AttachState.Detaching, m_AttachableNode); + m_AttachableNode.Detach(this); + transform.parent = null; + UpdateAttachState(AttachState.Detached, null); + } + } + + // Change the state to attaching or detaching + UpdateAttachState(preState, preNode); + + if (shouldParent) + { + InternalAttach(attachableNode); } else { - ParentIsApplied = false; - ResetToDefault(); + InternalDetach(); + } + + // Notify of the changed attached state + UpdateAttachState(m_AttachState, m_AttachableNode); + } + + /// + /// For customized/derived s, override this method to receive notifications + /// when the has changed. + /// + /// the new . + /// + protected virtual void OnAttachStateChanged(AttachState attachState, AttachableNode attachableNode) + { + + } + + /// + /// Update the attached state. + /// + private void UpdateAttachState(AttachState attachState, AttachableNode attachableNode) + { + try + { + AttachStateChange?.Invoke(attachState, attachableNode); + } + catch (Exception ex) + { + Debug.LogException(ex); } - OnParentUpdated(parent); + try + { + OnAttachStateChanged(attachState, attachableNode); + } + catch (Exception ex) + { + Debug.LogException(ex); + } } - private void ResetToDefault() + /// + /// Internal attach method that just handles changing state, parenting, and sending the a + /// notification that an has attached. + /// + internal void InternalAttach(AttachableNode attachableNode) { - if (m_DefaultParent != null) + if (attachableNode.NetworkManager != NetworkManager) { - transform.SetParent(m_DefaultParent.transform, false); + Debug.Log("Blam!"); } - transform.localPosition = m_OriginalLocalPosition; - transform.localRotation = m_OriginalLocalRotation; + m_AttachState = AttachState.Attached; + m_AttachableNode = attachableNode; + // Attachables are always local space relative + transform.SetParent(m_AttachableNode.transform, false); + m_AttachableNode.Attach(this); } /// - /// Invoked after the parent has been applied.
+ /// Attaches the of this instance to the of the . ///
/// - /// The can be either null or a valid .
- /// When null, the parent is being unapplied/removed from the this instance is attached to. + /// This effectively applies a new parent to a nested and all children + /// of the nested .
+ /// Both the and this instances should be in the spawned state before this + /// is invoked. ///
- /// The that is applied or null if it is no longer applied. - protected virtual void OnParentUpdated(NetworkBehaviour parent) + /// The to be applied or null to reparent under its original when spawned. + public void Attach(AttachableNode attachableNode) { + if (!IsSpawned) + { + NetworkLog.LogError($"[{name}][Attach][Not Spawned] Cannot attach before being spawned!"); + return; + } + if (!HasAuthority) + { + NetworkLog.LogError($"[{name}][Attach][Not Authority] Client-{NetworkManager.LocalClientId} is not the authority!"); + return; + } + + if (attachableNode.NetworkObject == NetworkObject) + { + NetworkLog.LogError($"[{name}][Attach] Cannot attach to the original {NetworkObject} instance!"); + return; + } + + if (m_AttachableNode != null && m_AttachState == AttachState.Attached && m_AttachableNode == attachableNode) + { + NetworkLog.LogError($"[{name}][Attach] Cannot attach! {name} is already attached to {attachableNode.name}!"); + return; + } + + // Update the attached node reference to the new attachable node. + m_AttachedNodeReference.Value = new NetworkBehaviourReference(attachableNode); } /// - /// Invoked just prior to the parent being applied.
- /// This is a good time to handle disabling or enabling s using an . + /// Internal detach method that just handles changing state, parenting, and sending the a + /// notification that an has detached. ///
- /// - /// The can be either null or a valid .
- /// When null, the parent is being unapplied/removed from the this instance is attached to. - ///
- /// The that is applied or null if it is no longer applied. - protected virtual void OnParentBeingApplied(NetworkBehaviour parent) + internal void InternalDetach() { - + if (m_AttachableNode) + { + m_AttachableNode.Detach(this); + m_AttachableNode = null; + if (m_DefaultParent) + { + // Set the original parent and origianl local position and rotation + transform.SetParent(m_DefaultParent.transform, false); + transform.localPosition = m_OriginalLocalPosition; + transform.localRotation = m_OriginalLocalRotation; + } + m_AttachState = AttachState.Detached; + } } /// - /// Applies a parent to a nested and all children - /// of the nested . + /// Invoke to detach from a . /// - /// The to be applied or null to reparent under its original when spawned. - public void ApplyParent(NetworkBehaviour parent) + public void Detach() { if (!IsSpawned) { - Debug.LogError($"[{name}][Not Spawned] Can only have a parent applied when it is spawned!"); + NetworkLog.LogError($"[{name}][Detach][Not Spawned] Cannot detach if not spawned!"); return; } if (!HasAuthority) { - Debug.LogError($"[{name}][Not Authority] Client-{NetworkManager.LocalClientId} is not the authority!"); + NetworkLog.LogError($"[{name}][Detach][Not Authority] Client-{NetworkManager.LocalClientId} is not the authority!"); return; } - // Notify any subscriptions - ParentIsBeingApplied?.Invoke(parent); + if (m_AttachState != AttachState.Attached || m_AttachableNode == null) + { + // Check for the unlikely scenario that an instance has mismatch between the state and assigned attachable node. + if (!m_AttachableNode && m_AttachState == AttachState.Attached) + { + NetworkLog.LogError($"[{name}][Detach] Invalid state detected! {name}'s state is still {m_AttachState} but has no {nameof(AttachableNode)} assigned!"); + } + + // Developer only notification for the most likely scenario where this method is invoked but the instance is not attached to anything. + if (NetworkManager && NetworkManager.LogLevel <= LogLevel.Developer) + { + NetworkLog.LogWarning($"[{name}][Detach] Cannot detach! {name} is not attached to anything!"); + } - // Invoke for any overrides - OnParentBeingApplied(parent); + // If we have the attachable node set and we are not in the middle of detaching, then log an error and note + // this could potentially occur if inoked more than once for the same instance in the same frame. + if (m_AttachableNode && m_AttachState != AttachState.Detaching) + { + NetworkLog.LogError($"[{name}][Detach] Invalid state detected! {name} is still referencing {nameof(AttachableNode)} {m_AttachableNode.name}! Could {nameof(AttachableBehaviour.Detach)} be getting invoked more than once for the same instance?"); + } + return; + } - // Once everything has been notified that we are applying a parent...apply the parent. - m_AppliedParent.Value = new NetworkBehaviourReference(parent); + // Update the attached node reference to nothing-null. + m_AttachedNodeReference.Value = new NetworkBehaviourReference(null); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs new file mode 100644 index 0000000000..8c5ad47cf5 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using Unity.Netcode; +using Unity.Netcode.Components; + + +/// +/// This component is used in conjunction with and is used to +/// denote a specific child that an +/// can attach itself to. +/// +/// +/// Primarily, the can be used as it is or can be extended to perform additional +/// logical operations when something attaches to or detaches from the instance. +/// +public class AttachableNode : NetworkBehaviour +{ + /// + /// A of the currently attached s. + /// + protected readonly List m_AttachedBehaviours = new List(); + + /// + /// + /// If the this belongs to is despawned, + /// then any attached will be detached during . + /// + public override void OnNetworkDespawn() + { + for (int i = m_AttachedBehaviours.Count - 1; i > 0; i--) + { + m_AttachedBehaviours[i].InternalDetach(); + } + base.OnNetworkDespawn(); + } + + /// + /// Override this method to be notified when an has attached to this node. + /// + /// The that has been attached. + protected virtual void OnAttached(AttachableBehaviour attachableBehaviour) + { + + } + + internal void Attach(AttachableBehaviour attachableBehaviour) + { + if (m_AttachedBehaviours.Contains(attachableBehaviour)) + { + NetworkLog.LogError($"[{nameof(AttachableNode)}][{name}][Attach] {nameof(AttachableBehaviour)} {attachableBehaviour.name} is already attached!"); + return; + } + + m_AttachedBehaviours.Add(attachableBehaviour); + OnAttached(attachableBehaviour); + } + + /// + /// Override this method to be notified when an has detached from this node. + /// + /// The that has been detached. + protected virtual void OnDetached(AttachableBehaviour attachableBehaviour) + { + + } + + internal void Detach(AttachableBehaviour attachableBehaviour) + { + if (!m_AttachedBehaviours.Contains(attachableBehaviour)) + { + NetworkLog.LogError($"[{nameof(AttachableNode)}][{name}][Detach] {nameof(AttachableBehaviour)} {attachableBehaviour.name} is not attached!"); + return; + } + + m_AttachedBehaviours.Remove(attachableBehaviour); + OnDetached(attachableBehaviour); + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs.meta b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs.meta new file mode 100644 index 0000000000..dda530aca5 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b870dfbc4cccf6244b4ed1a9b379c701 \ No newline at end of file From d08d4966fcc2527891558784befcf396fe72684f Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 23 Jun 2025 19:36:46 -0500 Subject: [PATCH 05/43] test Adding new test for attachables. --- .../Tests/Runtime/AttachableBehaviourTests.cs | 381 ++++++++++++++++++ .../Runtime/AttachableBehaviourTests.cs.meta | 2 + 2 files changed, 383 insertions(+) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/AttachableBehaviourTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/AttachableBehaviourTests.cs.meta diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/AttachableBehaviourTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/AttachableBehaviourTests.cs new file mode 100644 index 0000000000..0efc6b6654 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/AttachableBehaviourTests.cs @@ -0,0 +1,381 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; +using Unity.Netcode.Components; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + [TestFixture(HostOrServer.DAHost)] + internal class AttachableBehaviourTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 2; + + public AttachableBehaviourTests(HostOrServer hostOrServer) : base(hostOrServer) { } + + private GameObject m_SourcePrefab; + private GameObject m_TargetPrefabA; + private GameObject m_TargetPrefabB; + + /// + /// All of the below instances belong to the authority + /// + private NetworkObject m_SourceInstance; + private NetworkObject m_TargetInstance; + private NetworkObject m_TargetInstanceB; + private TestAttachable m_AttachableBehaviourInstance; + private TestNode m_AttachableNodeInstance; + private TestNode m_AttachableNodeInstanceB; + + private bool m_UseTargetB; + + private StringBuilder m_ErrorLog = new StringBuilder(); + + protected override IEnumerator OnSetup() + { + m_ErrorLog.Clear(); + return base.OnSetup(); + } + + protected override void OnServerAndClientsCreated() + { + // The source prefab contains the nested NetworkBehaviour that + // will be parented under the target prefab. + m_SourcePrefab = CreateNetworkObjectPrefab("Source"); + // The target prefab that the source prefab will attach + // will be parented under the target prefab. + m_TargetPrefabA = CreateNetworkObjectPrefab("TargetA"); + m_TargetPrefabB = CreateNetworkObjectPrefab("TargetB"); + var sourceChild = new GameObject("SourceChild"); + var targetChildA = new GameObject("TargetChildA"); + var targetChildB = new GameObject("TargetChildB"); + sourceChild.transform.parent = m_SourcePrefab.transform; + targetChildA.transform.parent = m_TargetPrefabA.transform; + targetChildB.transform.parent = m_TargetPrefabB.transform; + + sourceChild.AddComponent(); + targetChildA.AddComponent(); + targetChildB.AddComponent(); + base.OnServerAndClientsCreated(); + } + + private NetworkObject GetTargetInstance() + { + return m_UseTargetB ? m_TargetInstanceB : m_TargetInstance; + } + + private bool AllClientsSpawnedInstances() + { + m_ErrorLog.Clear(); + foreach (var networkManager in m_NetworkManagers) + { + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_SourceInstance.NetworkObjectId)) + { + m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Has not spawned {m_SourceInstance.name} yet!"); + } + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_TargetInstance.NetworkObjectId)) + { + m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Has not spawned {m_TargetInstance.name} yet!"); + } + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_TargetInstanceB.NetworkObjectId)) + { + m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Has not spawned {m_TargetInstanceB.name} yet!"); + } + } + return m_ErrorLog.Length == 0; + } + + private bool ResetAllStates() + { + m_ErrorLog.Clear(); + var target = GetTargetInstance(); + // The attachable can move between the two spawned instances. + var currentAttachableRoot = m_AttachableBehaviourInstance.State == AttachableBehaviour.AttachState.Attached ? target : m_SourceInstance; + foreach (var networkManager in m_NetworkManagers) + { + // Source + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_SourceInstance.NetworkObjectId)) + { + m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Has no spawned instance of {currentAttachableRoot.name}!"); + } + else + { + var attachable = networkManager.SpawnManager.SpawnedObjects[currentAttachableRoot.NetworkObjectId].GetComponentInChildren(); + attachable.ResetStates(); + } + + // Target + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_TargetInstance.NetworkObjectId)) + { + m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Has no spawned instance of {m_TargetInstance.name}!"); + } + else + { + var node = networkManager.SpawnManager.SpawnedObjects[m_TargetInstance.NetworkObjectId].GetComponentInChildren(); + node.ResetStates(); + } + + // Target B + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_TargetInstanceB.NetworkObjectId)) + { + m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Has no spawned instance of {m_TargetInstanceB.name}!"); + } + else + { + var node = networkManager.SpawnManager.SpawnedObjects[m_TargetInstanceB.NetworkObjectId].GetComponentInChildren(); + node.ResetStates(); + } + } + return m_ErrorLog.Length == 0; + } + + private bool AllInstancesAttachedStateChanged(bool checkAttached) + { + m_ErrorLog.Clear(); + var target = GetTargetInstance(); + // The attachable can move between the two spawned instances so we have to use the appropriate one depending upon the authority's current state. + var currentAttachableRoot = m_AttachableBehaviourInstance.State == AttachableBehaviour.AttachState.Attached ? target : m_SourceInstance; + var attachable = (TestAttachable)null; + var node = (TestNode)null; + foreach (var networkManager in m_NetworkManagers) + { + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(currentAttachableRoot.NetworkObjectId)) + { + m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Has no spawned instance of {currentAttachableRoot.name}!"); + continue; + } + else + { + attachable = networkManager.SpawnManager.SpawnedObjects[currentAttachableRoot.NetworkObjectId].GetComponentInChildren(); + } + + if (!attachable) + { + attachable = networkManager.SpawnManager.SpawnedObjects[m_TargetInstance.NetworkObjectId].GetComponentInChildren(); + if (!attachable) + { + attachable = networkManager.SpawnManager.SpawnedObjects[m_TargetInstanceB.NetworkObjectId].GetComponentInChildren(); + if (!attachable) + { + m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}][Attachable] Attachable was not found!"); + } + } + continue; + } + + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(target.NetworkObjectId)) + { + m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Has no spawned instance of {target.name}!"); + continue; + } + else + { + node = networkManager.SpawnManager.SpawnedObjects[target.NetworkObjectId].GetComponentInChildren(); + } + + if (!attachable.CheckStateChangedOverride(checkAttached, false, node)) + { + m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}][{attachable.name}] Did not have its override invoked!"); + } + if (!attachable.CheckStateChangedOverride(checkAttached, true, node)) + { + m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}][{attachable.name}] Did not have its event invoked!"); + } + if ((checkAttached && !node.OnAttachedInvoked) || (!checkAttached && !node.OnDetachedInvoked)) + { + m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}][{node.name}] Did not have its override invoked!"); + } + if (checkAttached && attachable.transform.parent != node.transform) + { + m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}][{attachable.name}] {node.name} is not the parent of {attachable.name}!"); + } + else if (!checkAttached && attachable.transform.parent != attachable.DefaultParent.transform) + { + m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}][{attachable.name}] {attachable.DefaultParent.name} is not the parent of {attachable.name}!"); + } + } + return m_ErrorLog.Length == 0; + } + + [UnityTest] + public IEnumerator AttachAndDetachTests() + { + var authority = GetAuthorityNetworkManager(); + m_SourceInstance = SpawnObject(m_SourcePrefab, authority).GetComponent(); + m_TargetInstance = SpawnObject(m_TargetPrefabA, authority).GetComponent(); + m_TargetInstanceB = SpawnObject(m_TargetPrefabB, authority).GetComponent(); + yield return WaitForConditionOrTimeOut(AllClientsSpawnedInstances); + AssertOnTimeout($"Timed out waiting for all clients to spawn {m_SourceInstance.name} and {m_TargetInstance.name}!\n {m_ErrorLog}"); + + m_AttachableBehaviourInstance = m_SourceInstance.GetComponentInChildren(); + Assert.NotNull(m_AttachableBehaviourInstance, $"{m_SourceInstance.name} does not have a nested child {nameof(AttachableBehaviour)}!"); + + m_AttachableNodeInstance = m_TargetInstance.GetComponentInChildren(); + Assert.NotNull(m_AttachableNodeInstance, $"{m_TargetInstance.name} does not have a nested child {nameof(AttachableNode)}!"); + + m_AttachableNodeInstanceB = m_TargetInstanceB.GetComponentInChildren(); + Assert.NotNull(m_AttachableNodeInstanceB, $"{m_TargetInstanceB.name} does not have a nested child {nameof(AttachableNode)}!"); + + Assert.True(ResetAllStates(), $"Failed to reset all states!\n {m_ErrorLog}"); + m_AttachableBehaviourInstance.Attach(m_AttachableNodeInstance); + + yield return WaitForConditionOrTimeOut(() => AllInstancesAttachedStateChanged(true)); + AssertOnTimeout($"Timed out waiting for all clients to attach {m_AttachableBehaviourInstance.name} to {m_AttachableNodeInstance.name}!\n {m_ErrorLog}"); + + // Wait a brief period of time + yield return s_DefaultWaitForTick; + + // Now late join a client to make sure it synchronizes properly + yield return CreateAndStartNewClient(); + yield return WaitForConditionOrTimeOut(() => AllInstancesAttachedStateChanged(true)); + AssertOnTimeout($"Timed out waiting for all clients to attach {m_AttachableBehaviourInstance.name} to {m_AttachableNodeInstance.name}!\n {m_ErrorLog}"); + + // Wait a brief period of time + yield return s_DefaultWaitForTick; + + // Reset all states and prepare for 2nd attach test + Assert.True(ResetAllStates(), $"Failed to reset all states!\n {m_ErrorLog}"); + + // Now, while attached, attach to another attachable node which should detach from the current and attach to the new. + m_AttachableBehaviourInstance.Attach(m_AttachableNodeInstanceB); + + // The attachable should detach from the current AttachableNode first + yield return WaitForConditionOrTimeOut(() => AllInstancesAttachedStateChanged(false)); + AssertOnTimeout($"Timed out waiting for all clients to detach {m_AttachableBehaviourInstance.name} from {m_AttachableNodeInstance.name}!\n {m_ErrorLog}"); + + // Switch the conditional to check the target B attachable node + m_UseTargetB = true; + + // Then the attachable should attach to the target B attachable node + yield return WaitForConditionOrTimeOut(() => AllInstancesAttachedStateChanged(true)); + AssertOnTimeout($"Timed out waiting for all clients to attach {m_AttachableBehaviourInstance.name} to {m_AttachableNodeInstanceB.name}!\n {m_ErrorLog}"); + + // Reset all states and prepare for final detach test + Assert.True(ResetAllStates(), $"Failed to reset all states!\n {m_ErrorLog}"); + + // Now verify complete detaching works + m_AttachableBehaviourInstance.Detach(); + yield return WaitForConditionOrTimeOut(() => AllInstancesAttachedStateChanged(false)); + AssertOnTimeout($"Timed out waiting for all clients to detach {m_AttachableBehaviourInstance.name} from {m_AttachableNodeInstance.name}!\n {m_ErrorLog}"); + } + + /// + /// Helps to validate that the overrides and events are invoked when an attachable attaches or detaches from the instance. + /// This also helps to validate that the appropriate instance is passed in as a parameter. + /// + public class TestAttachable : AttachableBehaviour + { + private Dictionary m_StateUpdates = new Dictionary(); + + private Dictionary m_StateUpdateEvents = new Dictionary(); + + public GameObject DefaultParent => m_DefaultParent; + public AttachState State => m_AttachState; + + public override void OnNetworkSpawn() + { + AttachStateChange += OnAttachStateChangeEvent; + name = $"{name}-{NetworkManager.LocalClientId}"; + base.OnNetworkSpawn(); + } + + public override void OnNetworkDespawn() + { + AttachStateChange -= OnAttachStateChangeEvent; + base.OnNetworkDespawn(); + } + + private void OnAttachStateChangeEvent(AttachState attachState, AttachableNode attachableNode) + { + m_StateUpdateEvents.Add(attachState, attachableNode); + } + + protected override void OnAttachStateChanged(AttachState attachState, AttachableNode attachableNode) + { + m_StateUpdates.Add(attachState, attachableNode); + base.OnAttachStateChanged(attachState, attachableNode); + } + + public void ResetStates() + { + m_StateUpdates.Clear(); + m_StateUpdateEvents.Clear(); + } + + private void Log(string message) + { + Debug.Log($"[{name}] {message}"); + } + + public bool CheckStateChangedOverride(bool checkAttached, bool checkEvent, AttachableNode attachableNode) + { + var tableToCheck = checkEvent ? m_StateUpdateEvents : m_StateUpdates; + var checkStatus = checkAttached ? (tableToCheck.ContainsKey(AttachState.Attaching) && tableToCheck.ContainsKey(AttachState.Attached)) : + (tableToCheck.ContainsKey(AttachState.Detaching) && tableToCheck.ContainsKey(AttachState.Detached)); + + if (checkStatus) + { + foreach (var entry in tableToCheck) + { + // Ignore any states that don't match what is being checked + if ((checkStatus && (entry.Key == AttachState.Detaching || entry.Key == AttachState.Detached)) || + (!checkStatus && (entry.Key == AttachState.Attaching || entry.Key == AttachState.Attached))) + { + continue; + } + + // Special case for completely detached + if (entry.Key == AttachState.Detached) + { + if (entry.Value != null) + { + Log($"[Value] The value {entry.Value.name} is not null!"); + checkStatus = false; + break; + } + } + else if (entry.Value != attachableNode) + { + Log($"[{entry.Key}][Value] The value {entry.Value.name} is not the same as {attachableNode.name}!"); + checkStatus = false; + break; + } + } + } + return checkStatus; + } + } + + /// + /// Helps to validate that the overrides are invoked when an attachable attaches or detaches from the instance. + /// + public class TestNode : AttachableNode + { + public bool OnAttachedInvoked { get; private set; } + public bool OnDetachedInvoked { get; private set; } + + public void ResetStates() + { + OnAttachedInvoked = false; + OnDetachedInvoked = false; + } + + protected override void OnAttached(AttachableBehaviour attachableBehaviour) + { + OnAttachedInvoked = true; + base.OnAttached(attachableBehaviour); + } + + protected override void OnDetached(AttachableBehaviour attachableBehaviour) + { + OnDetachedInvoked = true; + base.OnDetached(attachableBehaviour); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/AttachableBehaviourTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/AttachableBehaviourTests.cs.meta new file mode 100644 index 0000000000..5e7eb6db85 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/AttachableBehaviourTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 016a03eb97e603345a44bca4defacf24 \ No newline at end of file From 4a9775aeb46b25f25f2836a018feef06b94d0e97 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 23 Jun 2025 19:38:02 -0500 Subject: [PATCH 06/43] style Replacing any improperly spelled "detatch" with "detach". XML API and private methods. --- .../Runtime/Components/NetworkRigidBodyBase.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs index 96cd6b79e0..d368465cf3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs @@ -1121,7 +1121,7 @@ private void ApplyFixedJoint(NetworkRigidbodyBase bodyToConnectTo, Vector3 posit /// - This instance can be viewed as the child. /// - The can be viewed as the parent. ///
- /// This is the recommended way, as opposed to parenting, to attached/detatch two rigid bodies to one another when is enabled. + /// This is the recommended way, as opposed to parenting, to attached/detach two rigid bodies to one another when is enabled. /// For more details on using and . ///
/// This provides a simple joint solution between two rigid bodies and serves as an example. You can add different joint types by creating a customized/derived @@ -1187,7 +1187,7 @@ private void RemoveFromParentBody() #if COM_UNITY_MODULES_PHYSICS2D [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DetatchFromFixedJoint2D() + private void DetachFromFixedJoint2D() { if (FixedJoint2D == null) { @@ -1206,7 +1206,7 @@ private void DetatchFromFixedJoint2D() #endif #if COM_UNITY_MODULES_PHYSICS [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DetatchFromFixedJoint3D() + private void DetachFromFixedJoint3D() { if (FixedJoint == null) { @@ -1227,7 +1227,7 @@ private void DetatchFromFixedJoint3D() /// this will detach from the fixed joint and destroy the fixed joint component. /// /// - /// This is the recommended way, as opposed to parenting, to attached/detatch two rigid bodies to one another when is enabled. + /// This is the recommended way, as opposed to parenting, to attached/detach two rigid bodies to one another when is enabled. /// public void DetachFromFixedJoint() { @@ -1240,18 +1240,18 @@ public void DetachFromFixedJoint() #if COM_UNITY_MODULES_PHYSICS && COM_UNITY_MODULES_PHYSICS2D if (m_IsRigidbody2D) { - DetatchFromFixedJoint2D(); + DetachFromFixedJoint2D(); } else { - DetatchFromFixedJoint3D(); + DetachFromFixedJoint3D(); } #endif #if COM_UNITY_MODULES_PHYSICS && !COM_UNITY_MODULES_PHYSICS2D - DetatchFromFixedJoint3D(); + DetachFromFixedJoint3D(); #endif #if !COM_UNITY_MODULES_PHYSICS && COM_UNITY_MODULES_PHYSICS2D - DetatchFromFixedJoint2D(); + DetachFromFixedJoint2D(); #endif } } From 1f81ebd625e6e0c4badf30323ac3431bedd37890 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 23 Jun 2025 19:51:44 -0500 Subject: [PATCH 07/43] style - PVP Minor XML API fixes. --- .../Runtime/Components/Helpers/AttachableBehaviour.cs | 11 +++++------ .../Runtime/Components/Helpers/ComponentController.cs | 5 ++--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs index 57afe67707..fbd9acc1cd 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs @@ -117,11 +117,10 @@ public enum AttachState private Vector3 m_OriginalLocalPosition; private Quaternion m_OriginalLocalRotation; - /// - /// + /// /// If you create a custom and override this method, you must invoke /// this base instance of . - /// + /// protected virtual void Awake() { m_DefaultParent = transform.parent == null ? gameObject : transform.parent.gameObject; @@ -218,8 +217,8 @@ private void UpdateAttachedState() /// For customized/derived s, override this method to receive notifications /// when the has changed. /// - /// the new . - /// + /// The new . + /// The being attached to or from. Will be null when completely detatched. protected virtual void OnAttachStateChanged(AttachState attachState, AttachableNode attachableNode) { @@ -275,7 +274,7 @@ internal void InternalAttach(AttachableNode attachableNode) /// Both the and this instances should be in the spawned state before this /// is invoked. /// - /// The to be applied or null to reparent under its original when spawned. + /// The to attach this instance to. public void Attach(AttachableNode attachableNode) { if (!IsSpawned) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs index 2952f13139..5de25bd9b7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs @@ -96,11 +96,10 @@ protected virtual void OnValidate() } #endif - /// - /// + /// /// Also checks to assure all entries are valid and creates a final table of /// s paired to their . - /// + /// protected virtual void Awake() { var emptyEntries = 0; From 4148bfe590f1178cce433366f3c34cc1fa508a2c Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 23 Jun 2025 21:42:37 -0500 Subject: [PATCH 08/43] style - standards Simplified the nameof AttachableBehaviour.Detach to just Detach. --- .../Runtime/Components/Helpers/AttachableBehaviour.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs index fbd9acc1cd..260257bfa4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs @@ -361,7 +361,7 @@ public void Detach() // this could potentially occur if inoked more than once for the same instance in the same frame. if (m_AttachableNode && m_AttachState != AttachState.Detaching) { - NetworkLog.LogError($"[{name}][Detach] Invalid state detected! {name} is still referencing {nameof(AttachableNode)} {m_AttachableNode.name}! Could {nameof(AttachableBehaviour.Detach)} be getting invoked more than once for the same instance?"); + NetworkLog.LogError($"[{name}][Detach] Invalid state detected! {name} is still referencing {nameof(AttachableNode)} {m_AttachableNode.name}! Could {nameof(Detach)} be getting invoked more than once for the same instance?"); } return; } From 1fce513b6615d515d62987e0416b5bd6c0d2aa23 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 24 Jun 2025 11:12:36 -0500 Subject: [PATCH 09/43] refactor Refactoring the ComponentController to provide more flexibility as well as being able to have component entries that will apply the inverse of the current ComponentController's current state....which allows for switching between different sets of components depending upon the controller's state. --- .../Components/Helpers/ComponentController.cs | 115 ++++++++++++++---- 1 file changed, 94 insertions(+), 21 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs index 5de25bd9b7..a86c6a9aac 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Reflection; using UnityEngine; @@ -5,6 +6,31 @@ namespace Unity.Netcode.Components { + /// + /// This is a serializable contianer class for entries. + /// + [Serializable] + public class ComponentControllerEntry + { + /// + /// When true, this component's enabled state will be the inverse of + /// the value passed into . + /// + public bool InvertEnabled; + + /// + /// The component to control. + /// + /// + /// You can assign an entire to this property which will + /// add all components attached to the . The + /// and properties will be applied to all components found on the . + /// + public Object Component; + + internal PropertyInfo PropertyInfo; + } + /// /// Handles enabling or disabling commonly used components, behaviours, RenderMeshes, etc.
/// Anything that derives from and has an enabled property can be added @@ -20,16 +46,21 @@ public class ComponentController : NetworkBehaviour /// /// Determines whether the selected s will start enabled or disabled when spawned. /// - [Tooltip("The initial state of the components when spawned.")] - public bool InitialState = true; + [Tooltip("The initial state of the component controllers enabled status when instnatiated.")] + public bool StartEnabled = true; /// /// The list of s to be enabled and disabled. /// [Tooltip("The list of components to control. You can drag and drop an entire GameObject on this to include all components.")] - public List Components; + public List Components; - private Dictionary m_ValidComponents = new Dictionary(); + /// + /// Returns the current enabled state of the . + /// + public bool EnabledState => m_IsEnabled.Value; + + private List m_ValidComponents = new List(); private NetworkVariable m_IsEnabled = new NetworkVariable(); #if UNITY_EDITOR @@ -44,39 +75,45 @@ protected virtual void OnValidate() return; } - var gameObjectsToScan = new List(); + var gameObjectsToScan = new List(); for (int i = Components.Count - 1; i >= 0; i--) { if (Components[i] == null) { continue; } - var componentType = Components[i].GetType(); + + if (Components[i].Component == null) + { + continue; + } + var componentType = Components[i].Component.GetType(); if (componentType == typeof(GameObject)) { - gameObjectsToScan.Add(Components[i] as GameObject); + gameObjectsToScan.Add(Components[i]); Components.RemoveAt(i); continue; } if (componentType.IsSubclassOf(typeof(NetworkBehaviour))) { - Debug.LogWarning($"Removing {Components[i].name} since {nameof(NetworkBehaviour)}s are not allowed to be controlled by this component."); + Debug.LogWarning($"Removing {Components[i].Component.name} since {nameof(NetworkBehaviour)}s are not allowed to be controlled by this component."); Components.RemoveAt(i); continue; } - var propertyInfo = Components[i].GetType().GetProperty("enabled", BindingFlags.Instance | BindingFlags.Public); + var propertyInfo = Components[i].Component.GetType().GetProperty("enabled", BindingFlags.Instance | BindingFlags.Public); if (propertyInfo == null && propertyInfo.PropertyType != typeof(bool)) { - Debug.LogWarning($"{Components[i].name} does not contain a public enabled property! (Removing)"); + Debug.LogWarning($"{Components[i].Component.name} does not contain a public enabled property! (Removing)"); Components.RemoveAt(i); } } foreach (var entry in gameObjectsToScan) { - var components = entry.GetComponents(); + var asGameObject = entry.Component as GameObject; + var components = asGameObject.GetComponents(); foreach (var component in components) { // Ignore any NetworkBehaviour derived components @@ -88,7 +125,12 @@ protected virtual void OnValidate() var propertyInfo = component.GetType().GetProperty("enabled", BindingFlags.Instance | BindingFlags.Public); if (propertyInfo != null && propertyInfo.PropertyType == typeof(bool)) { - Components.Add(component); + var componentEntry = new ComponentControllerEntry() + { + Component = component, + PropertyInfo = propertyInfo, + }; + Components.Add(componentEntry); } } } @@ -97,23 +139,24 @@ protected virtual void OnValidate() #endif /// - /// Also checks to assure all entries are valid and creates a final table of - /// s paired to their . + /// This checks to make sure that all entries are valid and will create a final + /// list of valid entries. /// protected virtual void Awake() { var emptyEntries = 0; - foreach (var someObject in Components) + foreach (var entry in Components) { - if (someObject == null) + if (entry == null) { emptyEntries++; continue; } - var propertyInfo = someObject.GetType().GetProperty("enabled", BindingFlags.Instance | BindingFlags.Public); + var propertyInfo = entry.Component.GetType().GetProperty("enabled", BindingFlags.Instance | BindingFlags.Public); if (propertyInfo != null && propertyInfo.PropertyType == typeof(bool)) { - m_ValidComponents.Add(someObject as Component, propertyInfo); + entry.PropertyInfo = propertyInfo; + m_ValidComponents.Add(entry); } else { @@ -128,6 +171,9 @@ protected virtual void Awake() { Debug.Log($"{name} has {m_ValidComponents.Count} valid {nameof(Component)} entries."); } + + // Apply the initial state of all components this instance is controlling. + InitializeComponents(); } /// @@ -135,7 +181,7 @@ public override void OnNetworkSpawn() { if (HasAuthority) { - m_IsEnabled.Value = InitialState; + m_IsEnabled.Value = StartEnabled; } base.OnNetworkSpawn(); } @@ -165,17 +211,44 @@ private void OnEnabledChanged(bool previous, bool current) ApplyEnabled(current); } + /// + /// Initializes each component entry to its initial state. + /// + private void InitializeComponents() + { + foreach (var entry in m_ValidComponents) + { + // If invert enabled is true, then use the inverted value passed in. + // Otherwise, directly apply the value passed in. + var isEnabled = entry.InvertEnabled ? !StartEnabled : StartEnabled; + entry.PropertyInfo.SetValue(entry.Component, isEnabled); + } + } + + /// + /// Applies states changes to all components being controlled by this instance. + /// + /// the state update to apply private void ApplyEnabled(bool enabled) { foreach (var entry in m_ValidComponents) { - entry.Value.SetValue(entry.Key, enabled); + // If invert enabled is true, then use the inverted value passed in. + // Otherwise, directly apply the value passed in. + var isEnabled = entry.InvertEnabled ? !enabled : enabled; + entry.PropertyInfo.SetValue(entry.Component, isEnabled); } } /// - /// Invoke on the authority side to enable or disable the s. + /// Invoke on the authority side to enable or disable components assigned to this instance. /// + /// + /// If any component entry has the set to true, + /// then the inverse of the isEnabled property passed in will be used. If the component entry has the + /// set to false (default), then the value of the + /// isEnabled property will be applied. + /// /// true = enabled | false = disabled public void SetEnabled(bool isEnabled) { From 1ceb76c8fecc1a4f3ab53e72a3d7e2d195de97b4 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 24 Jun 2025 12:33:43 -0500 Subject: [PATCH 10/43] update Made some minor adjustments while writing the base test for ComponentController. --- .../Components/Helpers/ComponentController.cs | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs index a86c6a9aac..cc87d6f732 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs @@ -39,7 +39,10 @@ public class ComponentControllerEntry /// /// /// This will synchronize the enabled or disabled state of the s with - /// connected and late joining clients. + /// connected and late joining clients.
+ /// This class provides the basic functionality to synchronizing components' enabled state.
+ /// It is encouraged to create custom derived versions of this class to provide any additional + /// functionality required for your project specific needs. ///
public class ComponentController : NetworkBehaviour { @@ -60,7 +63,7 @@ public class ComponentController : NetworkBehaviour /// public bool EnabledState => m_IsEnabled.Value; - private List m_ValidComponents = new List(); + internal List ValidComponents = new List(); private NetworkVariable m_IsEnabled = new NetworkVariable(); #if UNITY_EDITOR @@ -144,7 +147,16 @@ protected virtual void OnValidate() /// protected virtual void Awake() { + ValidComponents.Clear(); + + // If no components then don't try to initialize. + if (Components == null) + { + return; + } + var emptyEntries = 0; + foreach (var entry in Components) { if (entry == null) @@ -156,20 +168,16 @@ protected virtual void Awake() if (propertyInfo != null && propertyInfo.PropertyType == typeof(bool)) { entry.PropertyInfo = propertyInfo; - m_ValidComponents.Add(entry); + ValidComponents.Add(entry); } else { - Debug.LogWarning($"{name} does not contain a public enable property! (Ignoring)"); + NetworkLog.LogWarning($"{name} does not contain a public enable property! (Ignoring)"); } } if (emptyEntries > 0) { - Debug.LogWarning($"{name} has {emptyEntries} emtpy(null) entries in the {nameof(Components)} list!"); - } - else - { - Debug.Log($"{name} has {m_ValidComponents.Count} valid {nameof(Component)} entries."); + NetworkLog.LogWarning($"{name} has {emptyEntries} emtpy(null) entries in the {nameof(Components)} list!"); } // Apply the initial state of all components this instance is controlling. @@ -216,7 +224,7 @@ private void OnEnabledChanged(bool previous, bool current) /// private void InitializeComponents() { - foreach (var entry in m_ValidComponents) + foreach (var entry in ValidComponents) { // If invert enabled is true, then use the inverted value passed in. // Otherwise, directly apply the value passed in. @@ -231,7 +239,7 @@ private void InitializeComponents() /// the state update to apply private void ApplyEnabled(bool enabled) { - foreach (var entry in m_ValidComponents) + foreach (var entry in ValidComponents) { // If invert enabled is true, then use the inverted value passed in. // Otherwise, directly apply the value passed in. From db05292d4be127d0821ef765738911b9823f1681 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 24 Jun 2025 12:34:15 -0500 Subject: [PATCH 11/43] test Adding the base ComponentController test. --- .../Tests/Runtime/ComponentControllerTests.cs | 191 ++++++++++++++++++ .../Runtime/ComponentControllerTests.cs.meta | 2 + 2 files changed, 193 insertions(+) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/ComponentControllerTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/ComponentControllerTests.cs.meta diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ComponentControllerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/ComponentControllerTests.cs new file mode 100644 index 0000000000..f5f0be06cc --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ComponentControllerTests.cs @@ -0,0 +1,191 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; +using Unity.Netcode.Components; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + [TestFixture(HostOrServer.DAHost)] + internal class ComponentControllerTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 2; + + private StringBuilder m_ErrorLog = new StringBuilder(); + private GameObject m_TestPrefab; + + private NetworkManager m_Authority; + private ComponentController m_AuthorityController; + + public ComponentControllerTests(HostOrServer hostOrServer) : base(hostOrServer) { } + + protected override IEnumerator OnSetup() + { + m_ErrorLog.Clear(); + yield return base.OnSetup(); + } + + protected override void OnServerAndClientsCreated() + { + // The source prefab contains the nested NetworkBehaviour that + // will be parented under the target prefab. + m_TestPrefab = CreateNetworkObjectPrefab("TestObject"); + var sourceChild = new GameObject("Child"); + sourceChild.transform.parent = m_TestPrefab.transform; + var meshRenderer = sourceChild.AddComponent(); + var boxCollider = sourceChild.AddComponent(); + var controller = m_TestPrefab.AddComponent(); + controller.Components = new List + { + new ComponentControllerEntry() + { + Component = meshRenderer, + }, + new ComponentControllerEntry() + { + InvertEnabled = true, + Component = boxCollider, + } + }; + base.OnServerAndClientsCreated(); + } + + private bool AllClientsSpawnedInstances() + { + m_ErrorLog.Clear(); + foreach (var networkManager in m_NetworkManagers) + { + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_AuthorityController.NetworkObjectId)) + { + m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Has not spawned {m_AuthorityController.name} yet!"); + } + } + return m_ErrorLog.Length == 0; + } + + private void ControllerStateMatches(ComponentController controller) + { + if (m_AuthorityController.EnabledState != controller.EnabledState) + { + m_ErrorLog.AppendLine($"[Client-{controller.NetworkManager.LocalClientId}] The authority controller state ({m_AuthorityController.EnabledState})" + + $" does not match the local controller state ({controller.EnabledState})!"); + return; + } + + if (m_AuthorityController.ValidComponents.Count != controller.ValidComponents.Count) + { + m_ErrorLog.AppendLine($"[Client-{controller.NetworkManager.LocalClientId}] The authority controller has {m_AuthorityController.ValidComponents.Count} valid components but " + + $"the local instance has {controller.ValidComponents.Count}!"); + return; + } + + for (int i = 0; i < m_AuthorityController.ValidComponents.Count; i++) + { + var authorityEntry = m_AuthorityController.ValidComponents[i]; + var nonAuthorityEntry = controller.ValidComponents[i]; + if (authorityEntry.InvertEnabled != nonAuthorityEntry.InvertEnabled) + { + m_ErrorLog.AppendLine($"[Client-{controller.NetworkManager.LocalClientId}] The authority controller's component entry ({i}) " + + $"has an inverted state of {authorityEntry.InvertEnabled} but the local instance has a value of " + + $"{nonAuthorityEntry.InvertEnabled}!"); + } + + var authorityIsEnabled = (bool)authorityEntry.PropertyInfo.GetValue(authorityEntry.Component); + var nonAuthorityIsEnabled = (bool)nonAuthorityEntry.PropertyInfo.GetValue(authorityEntry.Component); + if (authorityIsEnabled != nonAuthorityIsEnabled) + { + m_ErrorLog.AppendLine($"[Client-{controller.NetworkManager.LocalClientId}] The authority controller's component ({i}) " + + $"entry's enabled state is {authorityIsEnabled} but the local instance's value is {nonAuthorityIsEnabled}!"); + } + } + } + + private bool AllComponentStatesMatch() + { + m_ErrorLog.Clear(); + foreach (var networkManager in m_NetworkManagers) + { + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_AuthorityController.NetworkObjectId)) + { + m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Does not have a spawned instance of {m_AuthorityController.name}!"); + } + var controller = networkManager.SpawnManager.SpawnedObjects[m_AuthorityController.NetworkObjectId].GetComponent(); + ControllerStateMatches(controller); + } + return m_ErrorLog.Length == 0; + } + + private bool AllComponentStatesAreCorrect(bool isEnabled) + { + m_ErrorLog.Clear(); + foreach (var networkManager in m_NetworkManagers) + { + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_AuthorityController.NetworkObjectId)) + { + m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Does not have a spawned instance of {m_AuthorityController.name}!"); + } + var controller = networkManager.SpawnManager.SpawnedObjects[m_AuthorityController.NetworkObjectId].GetComponent(); + for (int i = 0; i < controller.ValidComponents.Count; i++) + { + var componentEntry = controller.ValidComponents[i]; + + var componentEntryIsEnabled = (bool)componentEntry.PropertyInfo.GetValue(componentEntry.Component); + var valueToCheck = componentEntry.InvertEnabled ? !isEnabled : isEnabled; + + if (valueToCheck != componentEntryIsEnabled) + { + m_ErrorLog.AppendLine($"[Client-{controller.NetworkManager.LocalClientId}] The enabled state for entry ({i}) " + + $"should be {valueToCheck} but is {componentEntryIsEnabled}!"); + } + } + } + return m_ErrorLog.Length == 0; + } + + [UnityTest] + public IEnumerator EnabledDisabledSynchronizationTests() + { + m_Authority = GetAuthorityNetworkManager(); + + m_AuthorityController = SpawnObject(m_TestPrefab, m_Authority).GetComponent(); + + yield return WaitForConditionOrTimeOut(AllClientsSpawnedInstances); + AssertOnTimeout($"All clients did not spawn an instance of {m_AuthorityController.name}!\n {m_ErrorLog}"); + + // Validate that clients start off with matching states. + yield return WaitForConditionOrTimeOut(AllComponentStatesMatch); + AssertOnTimeout($"Not all client instances matched the authority instance {m_AuthorityController.name}! \n {m_ErrorLog}"); + + // Validate that all controllers have the correct enabled value for the current authority controller instance's value. + yield return WaitForConditionOrTimeOut(() => AllComponentStatesAreCorrect(m_AuthorityController.EnabledState)); + AssertOnTimeout($"Not all client instances have the correct enabled state!\n {m_ErrorLog}"); + + // Toggle the enabled state of the authority controller + m_AuthorityController.SetEnabled(!m_AuthorityController.EnabledState); + + // Validate that all controllers' states match + yield return WaitForConditionOrTimeOut(AllComponentStatesMatch); + AssertOnTimeout($"Not all client instances matched the authority instance {m_AuthorityController.name}! \n {m_ErrorLog}"); + + // Validate that all controllers have the correct enabled value for the current authority controller instance's value. + yield return WaitForConditionOrTimeOut(() => AllComponentStatesAreCorrect(m_AuthorityController.EnabledState)); + AssertOnTimeout($"Not all client instances have the correct enabled state!\n {m_ErrorLog}"); + + // Late join a client to assure the late joining client's values are synchronized properly + yield return CreateAndStartNewClient(); + + // Validate that all controllers' states match + yield return WaitForConditionOrTimeOut(AllComponentStatesMatch); + AssertOnTimeout($"Not all client instances matched the authority instance {m_AuthorityController.name}! \n {m_ErrorLog}"); + + // Validate that all controllers have the correct enabled value for the current authority controller instance's value. + yield return WaitForConditionOrTimeOut(() => AllComponentStatesAreCorrect(m_AuthorityController.EnabledState)); + AssertOnTimeout($"Not all client instances have the correct enabled state!\n {m_ErrorLog}"); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ComponentControllerTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/ComponentControllerTests.cs.meta new file mode 100644 index 0000000000..c5cb8ed883 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ComponentControllerTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 22e1625a8e8c9a24ab0408e95a5250a9 \ No newline at end of file From 2d391ed7e93578cb5ed1ee3d24793967d6b4cd87 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 24 Jun 2025 13:13:13 -0500 Subject: [PATCH 12/43] test - update switching to a Light component as opposed to BoxCollider as BoxCollider requires the physics package and we are just testing the functionality of ComponentController and not specifically any one other type of component. --- .../Tests/Runtime/ComponentControllerTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ComponentControllerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/ComponentControllerTests.cs index f5f0be06cc..f1a537901e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/ComponentControllerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ComponentControllerTests.cs @@ -5,6 +5,7 @@ using Unity.Netcode.Components; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; +using UnityEngine.Experimental.GlobalIllumination; using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests @@ -38,7 +39,7 @@ protected override void OnServerAndClientsCreated() var sourceChild = new GameObject("Child"); sourceChild.transform.parent = m_TestPrefab.transform; var meshRenderer = sourceChild.AddComponent(); - var boxCollider = sourceChild.AddComponent(); + var light = sourceChild.AddComponent(); var controller = m_TestPrefab.AddComponent(); controller.Components = new List { @@ -49,7 +50,7 @@ protected override void OnServerAndClientsCreated() new ComponentControllerEntry() { InvertEnabled = true, - Component = boxCollider, + Component = light, } }; base.OnServerAndClientsCreated(); From ae2987301845d5653592201ff9c25ca49793189a Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 24 Jun 2025 13:13:42 -0500 Subject: [PATCH 13/43] style Removing using directive. --- .../Tests/Runtime/ComponentControllerTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ComponentControllerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/ComponentControllerTests.cs index f1a537901e..7d2d6e86cb 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/ComponentControllerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ComponentControllerTests.cs @@ -5,7 +5,6 @@ using Unity.Netcode.Components; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; -using UnityEngine.Experimental.GlobalIllumination; using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests From 043b0e72dbc5fb2439533f57541ef652ca1306f2 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 10 Jul 2025 14:32:06 -0500 Subject: [PATCH 14/43] update Updating the change log entries. --- com.unity.netcode.gameobjects/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 9788b12a69..1f3ea5a441 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,6 +10,9 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Added `AttachableBehaviour` helper component to provide an alternate approach to parenting items without using the `NetworkObject` parenting. (#3518) +- Added `AttachableNode` helper component that is used by `AttachableBehaviour` as the target node for parenting. (#3518) +- Added `ComponentController` helper component that can be used to synchronize the enabling and disabling of components and can be used in conjunction with `AttachableBehaviour`. (#3518) - Added methods `GetDefaultNetworkSettings` and `GetDefaultPipelineConfigurations` to `UnityTransport`. These can be used to retrieve the default settings and pipeline stages that are used by `UnityTransport`. This is useful when providing a custom driver constructor through `UnityTransport.s_DriverConstructor`, since it allows reusing or tuning the existing configuration instead of trying to recreate it. This means a transport with a custom driver can now easily benefit from most of the features of `UnityTransport`, like integration with the Network Simulator and Network Profiler from the multiplayer tools package. (#3501) - Added mappings between `ClientId` and `TransportId`. (#3516) - Added `NetworkPrefabInstanceHandlerWithData`, a variant of `INetworkPrefabInstanceHandler` that provides access to custom instantiation data directly within the `Instantiate()` method. (#3430) From abd75d0bda1d843db73ae1b3020abc5f3197b894 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 10 Jul 2025 15:33:04 -0500 Subject: [PATCH 15/43] update Removing debug log info. --- .../Runtime/Components/Helpers/AttachableBehaviour.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs index 260257bfa4..91739f5d1b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs @@ -254,10 +254,6 @@ private void UpdateAttachState(AttachState attachState, AttachableNode attachabl /// internal void InternalAttach(AttachableNode attachableNode) { - if (attachableNode.NetworkManager != NetworkManager) - { - Debug.Log("Blam!"); - } m_AttachState = AttachState.Attached; m_AttachableNode = attachableNode; // Attachables are always local space relative From e098e31405687e3b986dc46d9037a4097b5c41ff Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sat, 12 Jul 2025 19:37:20 -0500 Subject: [PATCH 16/43] update Work in progress adjustments for usability under various scenarios. --- .../Components/Helpers/AttachableBehaviour.cs | 21 ++- .../Components/Helpers/AttachableNode.cs | 5 + .../Components/Helpers/ComponentController.cs | 140 +++++++++++++++++- 3 files changed, 156 insertions(+), 10 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs index 91739f5d1b..378146f7ca 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs @@ -18,7 +18,10 @@ namespace Unity.Netcode.Components /// The term "attach" is used in place of parenting in order to distinguish between parenting and /// parenting ("attaching" and "detaching").
/// This component can be used along with one or more in order to enable or disable different components depending - /// upon the instance's current state. + /// upon the instance's current state.
+ /// invocation order: + /// - When attaching, the 's is invoked just before the is invoked with the state.
+ /// - When detaching, the 's is invoked right after the is invoked with the notification.
/// public class AttachableBehaviour : NetworkBehaviour { @@ -191,9 +194,11 @@ private void UpdateAttachedState() { // Run through the same process without being triggerd by a NetVar update. UpdateAttachState(AttachState.Detaching, m_AttachableNode); + InternalDetach(); + UpdateAttachState(AttachState.Detached, m_AttachableNode); + m_AttachableNode.Detach(this); - transform.parent = null; - UpdateAttachState(AttachState.Detached, null); + m_AttachableNode = null; } } @@ -211,6 +216,14 @@ private void UpdateAttachedState() // Notify of the changed attached state UpdateAttachState(m_AttachState, m_AttachableNode); + + // When detatching, we want to make our final action + // the invocation of the AttachableNode's Detatch method. + if (!shouldParent && m_AttachableNode) + { + m_AttachableNode.Detach(this); + m_AttachableNode = null; + } } /// @@ -309,8 +322,6 @@ internal void InternalDetach() { if (m_AttachableNode) { - m_AttachableNode.Detach(this); - m_AttachableNode = null; if (m_DefaultParent) { // Set the original parent and origianl local position and rotation diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs index 8c5ad47cf5..e732b413dd 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs @@ -14,6 +14,11 @@ /// public class AttachableNode : NetworkBehaviour { + /// + /// Returns true if the instance has one or more attached components. + /// + public bool HasAttachments => m_AttachedBehaviours.Count > 0; + /// /// A of the currently attached s. /// diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs index cc87d6f732..6aa8d60f84 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Reflection; using UnityEngine; @@ -16,8 +17,19 @@ public class ComponentControllerEntry /// When true, this component's enabled state will be the inverse of /// the value passed into . /// + [Tooltip("When enabled, this component will inversely mirror the currently applied enable or disable state.")] public bool InvertEnabled; + /// + /// The amount of time to delay enabling this component when the has just transitioned from a disabled to enabled state. + /// + public float EnableDelay; + + /// + /// The amount of time to delay disabling this component when the has just transitioned from an enabled to disabled state. + /// + public float DisableDelay; + /// /// The component to control. /// @@ -27,8 +39,63 @@ public class ComponentControllerEntry /// and properties will be applied to all components found on the . /// public Object Component; - internal PropertyInfo PropertyInfo; + + + internal bool TimeDeltaDelayInProgress; + internal bool PendingState; + internal float EnableDelayTimeDelta; + internal float DisableDelayTimeDelta; + + private bool GetRelativeEnabled(bool enabled) + { + return InvertEnabled ? !enabled : enabled; + } + + internal bool HasDelay(bool enabled) + { + return GetRelativeEnabled(enabled) ? EnableDelay > 0.0f : DisableDelay > 0.0f; + } + + internal void StartTimeDeltaTracking(bool isEnabled) + { + if (GetRelativeEnabled(isEnabled)) + { + EnableDelayTimeDelta = Time.realtimeSinceStartup + EnableDelay; + } + else + { + DisableDelayTimeDelta = Time.realtimeSinceStartup + DisableDelay; + } + TimeDeltaDelayInProgress = true; + PendingState = isEnabled; + } + + + internal void SetValue(bool isEnabled) + { + // If invert enabled is true, then use the inverted value passed in. + // Otherwise, directly apply the value passed in. + PropertyInfo.SetValue(Component, GetRelativeEnabled(isEnabled)); + } + + internal bool CheckTimeDeltaDelay() + { + if (!TimeDeltaDelayInProgress) + { + return false; + } + + var isDeltaDelayInProgress = ((EnableDelayTimeDelta > Time.realtimeSinceStartup) + || (DisableDelayTimeDelta > Time.realtimeSinceStartup)); + + if (!isDeltaDelayInProgress) + { + SetValue(PendingState); + } + TimeDeltaDelayInProgress = isDeltaDelayInProgress; + return TimeDeltaDelayInProgress; + } } /// @@ -52,6 +119,17 @@ public class ComponentController : NetworkBehaviour [Tooltip("The initial state of the component controllers enabled status when instnatiated.")] public bool StartEnabled = true; + /// + /// The coroutine yield time used to check on any pending delayed state transitions. + /// + /// + /// If there are any delays (enable, disable, or both), then upon changing the state of a a coroutine will be started + /// (if not already started) that will monitor any pending delayed transitions ( or ) and will + /// handle changing each delayed until all instances have transitioned their value to the pending state change. + /// When there are no more pending delayed s, the coroutine will stop. + /// + public float PendingDelayYieldTime = 0.032f; + /// /// The list of s to be enabled and disabled. /// @@ -214,6 +292,17 @@ public override void OnNetworkDespawn() base.OnNetworkDespawn(); } + /// + public override void OnDestroy() + { + if (m_CoroutineObject.IsRunning) + { + StopCoroutine(m_CoroutineObject.Coroutine); + m_CoroutineObject.IsRunning = false; + } + base.OnDestroy(); + } + private void OnEnabledChanged(bool previous, bool current) { ApplyEnabled(current); @@ -239,13 +328,54 @@ private void InitializeComponents() /// the state update to apply private void ApplyEnabled(bool enabled) { + foreach (var entry in ValidComponents) { - // If invert enabled is true, then use the inverted value passed in. - // Otherwise, directly apply the value passed in. - var isEnabled = entry.InvertEnabled ? !enabled : enabled; - entry.PropertyInfo.SetValue(entry.Component, isEnabled); + if (entry.HasDelay(enabled)) + { + if (!m_CoroutineObject.IsRunning) + { + m_CoroutineObject.Coroutine = StartCoroutine(PendingAppliedState()); + m_CoroutineObject.IsRunning = true; + } + entry.StartTimeDeltaTracking(enabled); + } + else + { + entry.SetValue(enabled); + } + } + } + + private class CoroutineObject + { + public Coroutine Coroutine; + public bool IsRunning; + } + + private CoroutineObject m_CoroutineObject = new CoroutineObject(); + + + private IEnumerator PendingAppliedState() + { + var checkPendingDeltaDelays = true; + var waitTime = new WaitForSeconds(PendingDelayYieldTime); + + while (checkPendingDeltaDelays) + { + yield return waitTime; + checkPendingDeltaDelays = false; + foreach (var entry in ValidComponents) + { + if (!entry.CheckTimeDeltaDelay()) + { + continue; + } + checkPendingDeltaDelays = true; + } } + m_CoroutineObject.IsRunning = false; + yield break; } /// From cab19832cc21d566262a5318258de0204340c7f1 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sat, 12 Jul 2025 22:31:27 -0500 Subject: [PATCH 17/43] refactor Using RPCs and synchronizing the property being set using NetworkBehaviour.OnSynchronize in order to assure order of operations when it comes to messages. Updated the ComponentController to be able to stagger state updates in the event this occurs (wip and I might remove this part and not allow changes to state until any pending state is finished). --- .../Components/Helpers/AttachableBehaviour.cs | 83 ++++--- .../Components/Helpers/ComponentController.cs | 215 ++++++++++++------ 2 files changed, 195 insertions(+), 103 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs index 378146f7ca..131b7e7bea 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs @@ -116,10 +116,26 @@ public enum AttachState /// protected AttachableNode m_AttachableNode { get; private set; } - private NetworkVariable m_AttachedNodeReference = new NetworkVariable(new NetworkBehaviourReference(null)); + private NetworkBehaviourReference m_AttachedNodeReference = new NetworkBehaviourReference(null); private Vector3 m_OriginalLocalPosition; private Quaternion m_OriginalLocalRotation; + /// + protected override void OnSynchronize(ref BufferSerializer serializer) + { + // Example of how to synchronize late joining clients when using an RPC to update + // a local property's state. + if (serializer.IsWriter) + { + serializer.SerializeValue(ref m_AttachedNodeReference); + } + else + { + serializer.SerializeValue(ref m_AttachedNodeReference); + } + base.OnSynchronize(ref serializer); + } + /// /// If you create a custom and override this method, you must invoke /// this base instance of . @@ -133,21 +149,6 @@ protected virtual void Awake() m_AttachableNode = null; } - /// - /// - /// If you create a custom and override this method, you must invoke - /// this base instance of . - /// - protected override void OnNetworkPostSpawn() - { - if (HasAuthority) - { - m_AttachedNodeReference.Value = new NetworkBehaviourReference(null); - } - m_AttachedNodeReference.OnValueChanged += OnAttachedNodeReferenceChanged; - base.OnNetworkPostSpawn(); - } - /// /// /// If you create a custom and override this method, you will want to @@ -164,25 +165,20 @@ protected override void OnNetworkSessionSynchronized() /// public override void OnNetworkDespawn() { - m_AttachedNodeReference.OnValueChanged -= OnAttachedNodeReferenceChanged; InternalDetach(); if (NetworkManager && !NetworkManager.ShutdownInProgress) { // Notify of the changed attached state UpdateAttachState(m_AttachState, m_AttachableNode); } + m_AttachedNodeReference = new NetworkBehaviourReference(null); base.OnNetworkDespawn(); } - private void OnAttachedNodeReferenceChanged(NetworkBehaviourReference previous, NetworkBehaviourReference current) - { - UpdateAttachedState(); - } - private void UpdateAttachedState() { var attachableNode = (AttachableNode)null; - var shouldParent = m_AttachedNodeReference.Value.TryGet(out attachableNode, NetworkManager); + var shouldParent = m_AttachedNodeReference.TryGet(out attachableNode, NetworkManager); var preState = shouldParent ? AttachState.Attaching : AttachState.Detaching; var preNode = shouldParent ? attachableNode : m_AttachableNode; shouldParent = shouldParent && attachableNode != null; @@ -310,8 +306,7 @@ public void Attach(AttachableNode attachableNode) return; } - // Update the attached node reference to the new attachable node. - m_AttachedNodeReference.Value = new NetworkBehaviourReference(attachableNode); + ChangeReference(new NetworkBehaviourReference(attachableNode)); } /// @@ -344,7 +339,7 @@ public void Detach() return; } - if (!HasAuthority) + if (!OnHasAuthority()) { NetworkLog.LogError($"[{name}][Detach][Not Authority] Client-{NetworkManager.LocalClientId} is not the authority!"); return; @@ -373,8 +368,40 @@ public void Detach() return; } - // Update the attached node reference to nothing-null. - m_AttachedNodeReference.Value = new NetworkBehaviourReference(null); + ChangeReference(new NetworkBehaviourReference(null)); + } + + /// + /// Override this method to change how the instance determines the authority.
+ /// The default is to use the method. + ///
+ /// + /// Useful when using a network topology and you would like + /// to have the owner be the authority of this instance. + /// + /// true = has authoriy | false = does not have authority + protected virtual bool OnHasAuthority() + { + return HasAuthority; + } + + private void ChangeReference(NetworkBehaviourReference networkBehaviourReference) + { + // Update the attached node reference to the new attachable node. + m_AttachedNodeReference = networkBehaviourReference; + UpdateAttachedState(); + + if (OnHasAuthority()) + { + // Send notification of the change in this property's state. + UpdateAttachStateRpc(m_AttachedNodeReference); + } + } + + [Rpc(SendTo.NotMe)] + private void UpdateAttachStateRpc(NetworkBehaviourReference attachedNodeReference) + { + ChangeReference(attachedNodeReference); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs index 6aa8d60f84..03a3dfb16d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs @@ -23,11 +23,13 @@ public class ComponentControllerEntry /// /// The amount of time to delay enabling this component when the has just transitioned from a disabled to enabled state. /// + [Range(0.001f, 2.0f)] public float EnableDelay; /// /// The amount of time to delay disabling this component when the has just transitioned from an enabled to disabled state. /// + [Range(0.001f, 2.0f)] public float DisableDelay; /// @@ -41,37 +43,38 @@ public class ComponentControllerEntry public Object Component; internal PropertyInfo PropertyInfo; - - internal bool TimeDeltaDelayInProgress; - internal bool PendingState; - internal float EnableDelayTimeDelta; - internal float DisableDelayTimeDelta; - - private bool GetRelativeEnabled(bool enabled) + internal bool GetRelativeEnabled(bool enabled) { return InvertEnabled ? !enabled : enabled; } - internal bool HasDelay(bool enabled) - { - return GetRelativeEnabled(enabled) ? EnableDelay > 0.0f : DisableDelay > 0.0f; - } + private List m_PendingStateUpdates = new List(); - internal void StartTimeDeltaTracking(bool isEnabled) + /// + /// Invoke prior to setting the state + /// + internal bool QueueForDelay(bool enabled) { - if (GetRelativeEnabled(isEnabled)) - { - EnableDelayTimeDelta = Time.realtimeSinceStartup + EnableDelay; - } - else + var relativeEnabled = GetRelativeEnabled(enabled); + + if (relativeEnabled ? EnableDelay > 0.0f : DisableDelay > 0.0f) { - DisableDelayTimeDelta = Time.realtimeSinceStartup + DisableDelay; + // Start with no relative time offset + var relativeTimeOffset = 0.0f; + // If we have pending state updates, then get that time of the last state update + // and use that as the time to add this next state update. + if (m_PendingStateUpdates.Count > 0) + { + relativeTimeOffset = m_PendingStateUpdates[m_PendingStateUpdates.Count - 1].DelayTimeDelta; + } + + // We process backwards, so insert new entries at the front + m_PendingStateUpdates.Insert(0, new PendingStateUpdate(this, enabled, relativeTimeOffset)); + return true; } - TimeDeltaDelayInProgress = true; - PendingState = isEnabled; + return false; } - internal void SetValue(bool isEnabled) { // If invert enabled is true, then use the inverted value passed in. @@ -79,25 +82,66 @@ internal void SetValue(bool isEnabled) PropertyInfo.SetValue(Component, GetRelativeEnabled(isEnabled)); } - internal bool CheckTimeDeltaDelay() + internal bool HasPendingStateUpdates() { - if (!TimeDeltaDelayInProgress) + for (int i = m_PendingStateUpdates.Count - 1; i >= 0; i--) { - return false; + if (!m_PendingStateUpdates[i].CheckTimeDeltaDelay()) + { + m_PendingStateUpdates.RemoveAt(i); + continue; + } } + return m_PendingStateUpdates.Count > 0; + } + + private class PendingStateUpdate + { + internal bool TimeDeltaDelayInProgress; + internal bool PendingState; + internal float DelayTimeDelta; - var isDeltaDelayInProgress = ((EnableDelayTimeDelta > Time.realtimeSinceStartup) - || (DisableDelayTimeDelta > Time.realtimeSinceStartup)); + internal ComponentControllerEntry ComponentControllerEntry; - if (!isDeltaDelayInProgress) + internal bool CheckTimeDeltaDelay() { - SetValue(PendingState); + if (!TimeDeltaDelayInProgress) + { + return false; + } + + var isDeltaDelayInProgress = DelayTimeDelta > Time.realtimeSinceStartup; + + if (!isDeltaDelayInProgress) + { + ComponentControllerEntry.SetValue(PendingState); + } + TimeDeltaDelayInProgress = isDeltaDelayInProgress; + return TimeDeltaDelayInProgress; + } + + internal PendingStateUpdate(ComponentControllerEntry componentControllerEntry, bool isEnabled, float relativeTimeOffset) + { + ComponentControllerEntry = componentControllerEntry; + // If there is a pending state, then add the delay to the end of the last pending state's. + var referenceTime = relativeTimeOffset > 0.0f ? relativeTimeOffset : Time.realtimeSinceStartup; + + if (ComponentControllerEntry.GetRelativeEnabled(isEnabled)) + { + DelayTimeDelta = referenceTime + ComponentControllerEntry.EnableDelay; + } + else + { + DelayTimeDelta = referenceTime + ComponentControllerEntry.DisableDelay; + } + TimeDeltaDelayInProgress = true; + PendingState = isEnabled; } - TimeDeltaDelayInProgress = isDeltaDelayInProgress; - return TimeDeltaDelayInProgress; } } + + /// /// Handles enabling or disabling commonly used components, behaviours, RenderMeshes, etc.
/// Anything that derives from and has an enabled property can be added @@ -119,17 +163,6 @@ public class ComponentController : NetworkBehaviour [Tooltip("The initial state of the component controllers enabled status when instnatiated.")] public bool StartEnabled = true; - /// - /// The coroutine yield time used to check on any pending delayed state transitions. - /// - /// - /// If there are any delays (enable, disable, or both), then upon changing the state of a a coroutine will be started - /// (if not already started) that will monitor any pending delayed transitions ( or ) and will - /// handle changing each delayed until all instances have transitioned their value to the pending state change. - /// When there are no more pending delayed s, the coroutine will stop. - /// - public float PendingDelayYieldTime = 0.032f; - /// /// The list of s to be enabled and disabled. /// @@ -139,10 +172,10 @@ public class ComponentController : NetworkBehaviour /// /// Returns the current enabled state of the . /// - public bool EnabledState => m_IsEnabled.Value; + public bool EnabledState => m_IsEnabled; internal List ValidComponents = new List(); - private NetworkVariable m_IsEnabled = new NetworkVariable(); + private bool m_IsEnabled; #if UNITY_EDITOR /// @@ -219,6 +252,22 @@ protected virtual void OnValidate() } #endif + /// + protected override void OnSynchronize(ref BufferSerializer serializer) + { + // Example of how to synchronize late joining clients when using an RPC to update + // a local property's state. + if (serializer.IsWriter) + { + serializer.SerializeValue(ref m_IsEnabled); + } + else + { + serializer.SerializeValue(ref m_IsEnabled); + } + base.OnSynchronize(ref serializer); + } + /// /// This checks to make sure that all entries are valid and will create a final /// list of valid entries. @@ -265,9 +314,9 @@ protected virtual void Awake() /// public override void OnNetworkSpawn() { - if (HasAuthority) + if (OnHasAuthority()) { - m_IsEnabled.Value = StartEnabled; + m_IsEnabled = StartEnabled; } base.OnNetworkSpawn(); } @@ -280,18 +329,10 @@ public override void OnNetworkSpawn() /// protected override void OnNetworkPostSpawn() { - m_IsEnabled.OnValueChanged += OnEnabledChanged; - ApplyEnabled(m_IsEnabled.Value); + ApplyEnabled(); base.OnNetworkPostSpawn(); } - /// - public override void OnNetworkDespawn() - { - m_IsEnabled.OnValueChanged -= OnEnabledChanged; - base.OnNetworkDespawn(); - } - /// public override void OnDestroy() { @@ -303,11 +344,6 @@ public override void OnDestroy() base.OnDestroy(); } - private void OnEnabledChanged(bool previous, bool current) - { - ApplyEnabled(current); - } - /// /// Initializes each component entry to its initial state. /// @@ -326,23 +362,21 @@ private void InitializeComponents() /// Applies states changes to all components being controlled by this instance. /// /// the state update to apply - private void ApplyEnabled(bool enabled) + private void ApplyEnabled() { - foreach (var entry in ValidComponents) { - if (entry.HasDelay(enabled)) + if (entry.QueueForDelay(m_IsEnabled)) { if (!m_CoroutineObject.IsRunning) { m_CoroutineObject.Coroutine = StartCoroutine(PendingAppliedState()); m_CoroutineObject.IsRunning = true; } - entry.StartTimeDeltaTracking(enabled); } else { - entry.SetValue(enabled); + entry.SetValue(m_IsEnabled); } } } @@ -358,24 +392,24 @@ private class CoroutineObject private IEnumerator PendingAppliedState() { - var checkPendingDeltaDelays = true; - var waitTime = new WaitForSeconds(PendingDelayYieldTime); + var continueProcessing = true; - while (checkPendingDeltaDelays) + while (continueProcessing) { - yield return waitTime; - checkPendingDeltaDelays = false; + continueProcessing = false; foreach (var entry in ValidComponents) { - if (!entry.CheckTimeDeltaDelay()) + if (entry.HasPendingStateUpdates()) { - continue; + continueProcessing = true; } - checkPendingDeltaDelays = true; + } + if (continueProcessing) + { + yield return null; } } m_CoroutineObject.IsRunning = false; - yield break; } /// @@ -396,12 +430,43 @@ public void SetEnabled(bool isEnabled) return; } - if (!HasAuthority) + if (!OnHasAuthority()) { Debug.Log($"[Client-{NetworkManager.LocalClientId}] Attempting to invoke {nameof(SetEnabled)} without authority!"); return; } - m_IsEnabled.Value = isEnabled; + ChangeEnabled(isEnabled); + } + + private void ChangeEnabled(bool isEnabled) + { + m_IsEnabled = isEnabled; + ApplyEnabled(); + + if (OnHasAuthority()) + { + ToggleEnabledRpc(m_IsEnabled); + } + } + + /// + /// Override this method to change how the instance determines the authority.
+ /// The default is to use the method. + ///
+ /// + /// Useful when using a network topology and you would like + /// to have the owner be the authority of this instance. + /// + /// true = has authoriy | false = does not have authority + protected virtual bool OnHasAuthority() + { + return HasAuthority; + } + + [Rpc(SendTo.NotMe)] + private void ToggleEnabledRpc(bool enabled) + { + ChangeEnabled(enabled); } } } From 1341aa085ddad47d47a90e569daa5c255cc76b63 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sat, 12 Jul 2025 22:34:17 -0500 Subject: [PATCH 18/43] style --- .../Runtime/Components/Helpers/ComponentController.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs index 03a3dfb16d..110644a206 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs @@ -51,7 +51,7 @@ internal bool GetRelativeEnabled(bool enabled) private List m_PendingStateUpdates = new List(); /// - /// Invoke prior to setting the state + /// Invoke prior to setting the state. /// internal bool QueueForDelay(bool enabled) { @@ -140,8 +140,6 @@ internal PendingStateUpdate(ComponentControllerEntry componentControllerEntry, b } } - - /// /// Handles enabling or disabling commonly used components, behaviours, RenderMeshes, etc.
/// Anything that derives from and has an enabled property can be added From 13d9a03ed6eeb7b298173928a9024f84dae11250 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sun, 13 Jul 2025 17:01:41 -0500 Subject: [PATCH 19/43] update & style Minor adjustment to the range for delays allow it to be zero. Fixing spelling issues. --- .../Runtime/Components/Helpers/AttachableBehaviour.cs | 6 +++--- .../Runtime/Components/Helpers/ComponentController.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs index 131b7e7bea..cd1cedc09f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs @@ -213,8 +213,8 @@ private void UpdateAttachedState() // Notify of the changed attached state UpdateAttachState(m_AttachState, m_AttachableNode); - // When detatching, we want to make our final action - // the invocation of the AttachableNode's Detatch method. + // When detaching, we want to make our final action + // the invocation of the AttachableNode's Detach method. if (!shouldParent && m_AttachableNode) { m_AttachableNode.Detach(this); @@ -227,7 +227,7 @@ private void UpdateAttachedState() /// when the has changed. ///
/// The new . - /// The being attached to or from. Will be null when completely detatched. + /// The being attached to or from. Will be null when completely detached. protected virtual void OnAttachStateChanged(AttachState attachState, AttachableNode attachableNode) { diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs index 110644a206..8093e6ef44 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs @@ -23,13 +23,13 @@ public class ComponentControllerEntry /// /// The amount of time to delay enabling this component when the has just transitioned from a disabled to enabled state. /// - [Range(0.001f, 2.0f)] + [Range(0.0f, 2.0f)] public float EnableDelay; /// /// The amount of time to delay disabling this component when the has just transitioned from an enabled to disabled state. /// - [Range(0.001f, 2.0f)] + [Range(0f, 2.0f)] public float DisableDelay; /// From 8bd712cffe1d72666cf3ff8daefc2a541c4fdfa7 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 14 Jul 2025 20:28:28 -0500 Subject: [PATCH 20/43] refactor Had to make some minor adjustments in order to assure that users could handle sending any last micro-second tasks on any spawned instances prior to them despawning. Added NetworkBehaviour.OnNetworkPreDespawn. Did a slight order of operations on NetworkManager.Shutdown internal in order to assure sending RPCs during despawn would still work. Minor adjustments to the new helper components associated with this PR. --- .../Components/Helpers/AttachableBehaviour.cs | 6 +++- .../Components/Helpers/AttachableNode.cs | 21 ++++++++++-- .../Components/Helpers/ComponentController.cs | 2 +- .../Runtime/Core/NetworkBehaviour.cs | 17 ++++++++++ .../Runtime/Core/NetworkManager.cs | 32 ++++++++++++------- .../Runtime/Core/NetworkObject.cs | 6 ++++ .../Runtime/Spawning/NetworkSpawnManager.cs | 12 ++----- 7 files changed, 70 insertions(+), 26 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs index cd1cedc09f..1962ca6222 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs @@ -333,6 +333,10 @@ internal void InternalDetach() /// public void Detach() { + if (!gameObject) + { + return; + } if (!IsSpawned) { NetworkLog.LogError($"[{name}][Detach][Not Spawned] Cannot detach if not spawned!"); @@ -399,7 +403,7 @@ private void ChangeReference(NetworkBehaviourReference networkBehaviourReference } [Rpc(SendTo.NotMe)] - private void UpdateAttachStateRpc(NetworkBehaviourReference attachedNodeReference) + private void UpdateAttachStateRpc(NetworkBehaviourReference attachedNodeReference, RpcParams rpcParams = default) { ChangeReference(attachedNodeReference); } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs index e732b413dd..c62e16085b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs @@ -19,6 +19,11 @@ public class AttachableNode : NetworkBehaviour ///
public bool HasAttachments => m_AttachedBehaviours.Count > 0; + /// + /// When enabled, any attached s will be automatically detached and re-parented under its original parent. + /// + public bool DetachOnDespawn = true; + /// /// A of the currently attached s. /// @@ -29,12 +34,22 @@ public class AttachableNode : NetworkBehaviour /// If the this belongs to is despawned, /// then any attached will be detached during . /// - public override void OnNetworkDespawn() + public override void OnNetworkPreDespawn() { - for (int i = m_AttachedBehaviours.Count - 1; i > 0; i--) + if (IsSpawned && HasAuthority && DetachOnDespawn) { - m_AttachedBehaviours[i].InternalDetach(); + for (int i = m_AttachedBehaviours.Count - 1; i >= 0; i--) + { + m_AttachedBehaviours[i]?.Detach(); + } } + base.OnNetworkPreDespawn(); + } + + /// + public override void OnNetworkDespawn() + { + m_AttachedBehaviours.Clear(); base.OnNetworkDespawn(); } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs index 8093e6ef44..8a7e1710a3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs @@ -462,7 +462,7 @@ protected virtual bool OnHasAuthority() } [Rpc(SendTo.NotMe)] - private void ToggleEnabledRpc(bool enabled) + private void ToggleEnabledRpc(bool enabled, RpcParams rpcParams = default) { ChangeEnabled(enabled); } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 62e60fb436..73696fa0a4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -737,6 +737,11 @@ protected virtual void OnInSceneObjectsSpawned() { } ///
public virtual void OnNetworkDespawn() { } + /// + /// Gets called before has been invoked for all s associated with the currently spawned instance. + /// + public virtual void OnNetworkPreDespawn() { } + internal void NetworkPreSpawn(ref NetworkManager networkManager) { try @@ -816,6 +821,18 @@ internal void InSceneNetworkObjectsSpawned() } } + internal void InternalOnNetworkPreDespawn() + { + try + { + OnNetworkPreDespawn(); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + internal void InternalOnNetworkDespawn() { IsSpawned = false; diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index cff2768405..3c6662b7be 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1552,12 +1552,27 @@ internal void ShutdownInternal() DeferredMessageManager?.CleanupAllTriggers(); CustomMessagingManager = null; - RpcTarget?.Dispose(); - RpcTarget = null; - BehaviourUpdater?.Shutdown(); BehaviourUpdater = null; + /// Despawning upon shutdown + + // We need to clean up NetworkObjects before we reset the IsServer + // and IsClient properties. This provides consistency of these two + // property values for NetworkObjects that are still spawned when + // the shutdown cycle begins. + + // We need to handle despawning prior to shutting down the connection + // manager or disposing of the RpcTarget so any final updates can take + // place (i.e. sending any last state updates or the like). + + SpawnManager?.DespawnAndDestroyNetworkObjects(); + SpawnManager?.ServerResetShudownStateForSceneObjects(); + //// + + RpcTarget?.Dispose(); + RpcTarget = null; + // Shutdown connection manager last which shuts down transport ConnectionManager.Shutdown(); @@ -1567,17 +1582,12 @@ internal void ShutdownInternal() MessageManager = null; } - // We need to clean up NetworkObjects before we reset the IsServer - // and IsClient properties. This provides consistency of these two - // property values for NetworkObjects that are still spawned when - // the shutdown cycle begins. - SpawnManager?.DespawnAndDestroyNetworkObjects(); - SpawnManager?.ServerResetShudownStateForSceneObjects(); - SpawnManager = null; - // Let the NetworkSceneManager clean up its two SceneEvenData instances SceneManager?.Dispose(); SceneManager = null; + + SpawnManager = null; + IsListening = false; m_ShuttingDown = false; diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index b8c5c5b551..c6e2228819 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -2604,6 +2604,12 @@ internal void InternalInSceneNetworkObjectsSpawned() internal void InvokeBehaviourNetworkDespawn() { + // Invoke OnNetworkPreDespawn on all child behaviours + for (int i = 0; i < ChildNetworkBehaviours.Count; i++) + { + ChildNetworkBehaviours[i].InternalOnNetworkPreDespawn(); + } + NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId, true); NetworkManager.SpawnManager.RemoveNetworkObjectFromSceneChangedUpdates(this); diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 92b90972a7..d9a571b97b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1474,16 +1474,8 @@ internal void DespawnAndDestroyNetworkObjects() } } - // If spawned, then despawn and potentially destroy. - if (networkObjects[i].IsSpawned) - { - OnDespawnObject(networkObjects[i], shouldDestroy); - } - else // Otherwise, if we are not spawned and we should destroy...then destroy. - if (shouldDestroy) - { - UnityEngine.Object.Destroy(networkObjects[i].gameObject); - } + //Despawn and potentially destroy. + OnDespawnObject(networkObjects[i], shouldDestroy); } } } From a63de0b462fcb33d1c30259615bf5bed7371fdba Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 14 Jul 2025 20:30:26 -0500 Subject: [PATCH 21/43] test Updating the AttachableBehaviourTests to include testing that an attachable will be automatically detatched upon despawning the AttachableNode it is attached to. --- .../Tests/Runtime/AttachableBehaviourTests.cs | 72 ++++++++++++++++--- 1 file changed, 62 insertions(+), 10 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/AttachableBehaviourTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/AttachableBehaviourTests.cs index 0efc6b6654..af0568ab7f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/AttachableBehaviourTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/AttachableBehaviourTests.cs @@ -25,6 +25,7 @@ public AttachableBehaviourTests(HostOrServer hostOrServer) : base(hostOrServer) /// /// All of the below instances belong to the authority /// + private ulong m_TargetInstanceId; private NetworkObject m_SourceInstance; private NetworkObject m_TargetInstance; private NetworkObject m_TargetInstanceB; @@ -47,6 +48,7 @@ protected override void OnServerAndClientsCreated() // The source prefab contains the nested NetworkBehaviour that // will be parented under the target prefab. m_SourcePrefab = CreateNetworkObjectPrefab("Source"); + m_SourcePrefab.GetComponent().DontDestroyWithOwner = true; // The target prefab that the source prefab will attach // will be parented under the target prefab. m_TargetPrefabA = CreateNetworkObjectPrefab("TargetA"); @@ -94,8 +96,11 @@ private bool ResetAllStates() { m_ErrorLog.Clear(); var target = GetTargetInstance(); + + // The attachable can move between the two spawned instances. var currentAttachableRoot = m_AttachableBehaviourInstance.State == AttachableBehaviour.AttachState.Attached ? target : m_SourceInstance; + foreach (var networkManager in m_NetworkManagers) { // Source @@ -110,7 +115,7 @@ private bool ResetAllStates() } // Target - if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_TargetInstance.NetworkObjectId)) + if (m_TargetInstance && !networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_TargetInstance.NetworkObjectId)) { m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Has no spawned instance of {m_TargetInstance.name}!"); } @@ -121,7 +126,7 @@ private bool ResetAllStates() } // Target B - if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_TargetInstanceB.NetworkObjectId)) + if (m_TargetInstanceB && !networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_TargetInstanceB.NetworkObjectId)) { m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Has no spawned instance of {m_TargetInstanceB.name}!"); } @@ -134,10 +139,11 @@ private bool ResetAllStates() return m_ErrorLog.Length == 0; } - private bool AllInstancesAttachedStateChanged(bool checkAttached) + private bool AllInstancesAttachedStateChanged(bool checkAttached, bool ignoreIfDespawned = false) { m_ErrorLog.Clear(); var target = GetTargetInstance(); + var targetId = target == null ? m_TargetInstanceId : target.NetworkObjectId; // The attachable can move between the two spawned instances so we have to use the appropriate one depending upon the authority's current state. var currentAttachableRoot = m_AttachableBehaviourInstance.State == AttachableBehaviour.AttachState.Attached ? target : m_SourceInstance; var attachable = (TestAttachable)null; @@ -146,7 +152,10 @@ private bool AllInstancesAttachedStateChanged(bool checkAttached) { if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(currentAttachableRoot.NetworkObjectId)) { - m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Has no spawned instance of {currentAttachableRoot.name}!"); + if (!ignoreIfDespawned) + { + m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Has no spawned instance of {currentAttachableRoot.name}!"); + } continue; } else @@ -156,7 +165,7 @@ private bool AllInstancesAttachedStateChanged(bool checkAttached) if (!attachable) { - attachable = networkManager.SpawnManager.SpawnedObjects[m_TargetInstance.NetworkObjectId].GetComponentInChildren(); + attachable = networkManager.SpawnManager.SpawnedObjects[m_TargetInstanceId].GetComponentInChildren(); if (!attachable) { attachable = networkManager.SpawnManager.SpawnedObjects[m_TargetInstanceB.NetworkObjectId].GetComponentInChildren(); @@ -168,14 +177,23 @@ private bool AllInstancesAttachedStateChanged(bool checkAttached) continue; } - if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(target.NetworkObjectId)) + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(targetId)) { - m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Has no spawned instance of {target.name}!"); + if (!ignoreIfDespawned) + { + m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Has no spawned instance of {target.name}!"); + } continue; } else { - node = networkManager.SpawnManager.SpawnedObjects[target.NetworkObjectId].GetComponentInChildren(); + node = networkManager.SpawnManager.SpawnedObjects[targetId].GetComponentInChildren(); + } + + if (!node && ignoreIfDespawned) + { + VerboseDebug("Skipping check during despawn."); + continue; } if (!attachable.CheckStateChangedOverride(checkAttached, false, node)) @@ -202,6 +220,18 @@ private bool AllInstancesAttachedStateChanged(bool checkAttached) return m_ErrorLog.Length == 0; } + private bool AllInstancesDespawned() + { + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager.SpawnManager != null && networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_TargetInstanceId)) + { + return false; + } + } + return true; + } + [UnityTest] public IEnumerator AttachAndDetachTests() { @@ -209,6 +239,7 @@ public IEnumerator AttachAndDetachTests() m_SourceInstance = SpawnObject(m_SourcePrefab, authority).GetComponent(); m_TargetInstance = SpawnObject(m_TargetPrefabA, authority).GetComponent(); m_TargetInstanceB = SpawnObject(m_TargetPrefabB, authority).GetComponent(); + m_TargetInstanceId = m_TargetInstance.NetworkObjectId; yield return WaitForConditionOrTimeOut(AllClientsSpawnedInstances); AssertOnTimeout($"Timed out waiting for all clients to spawn {m_SourceInstance.name} and {m_TargetInstance.name}!\n {m_ErrorLog}"); @@ -262,6 +293,26 @@ public IEnumerator AttachAndDetachTests() m_AttachableBehaviourInstance.Detach(); yield return WaitForConditionOrTimeOut(() => AllInstancesAttachedStateChanged(false)); AssertOnTimeout($"Timed out waiting for all clients to detach {m_AttachableBehaviourInstance.name} from {m_AttachableNodeInstance.name}!\n {m_ErrorLog}"); + + // Finally, re-attach to the original spawned instance + Assert.True(ResetAllStates(), $"Failed to reset all states!\n {m_ErrorLog}"); + m_AttachableBehaviourInstance.Attach(m_AttachableNodeInstance); + + // Switch back to using the first target attachable node + m_UseTargetB = false; + + yield return WaitForConditionOrTimeOut(() => AllInstancesAttachedStateChanged(true)); + AssertOnTimeout($"[Despawn Detach Phase] Timed out waiting for all clients to attach {m_AttachableBehaviourInstance.name} to {m_AttachableNodeInstance.name}!\n {m_ErrorLog}"); + + var targetInstanceName = m_TargetInstance.name; + VerboseDebug("======== DESPAWN & DETACH ========"); + m_TargetInstance.Despawn(); + m_TargetInstance = null; + yield return WaitForConditionOrTimeOut(() => AllInstancesAttachedStateChanged(false, true)); + AssertOnTimeout($"[Despawn Detach Phase] Timed out waiting for all clients to detach {m_AttachableBehaviourInstance.name} from {targetInstanceName}!\n {m_ErrorLog}"); + + yield return WaitForConditionOrTimeOut(AllInstancesDespawned); + AssertOnTimeout($"[Despawn Detach Phase] Timed out waiting for all clients to despawn {targetInstanceName}!"); } /// @@ -317,7 +368,6 @@ public bool CheckStateChangedOverride(bool checkAttached, bool checkEvent, Attac var tableToCheck = checkEvent ? m_StateUpdateEvents : m_StateUpdates; var checkStatus = checkAttached ? (tableToCheck.ContainsKey(AttachState.Attaching) && tableToCheck.ContainsKey(AttachState.Attached)) : (tableToCheck.ContainsKey(AttachState.Detaching) && tableToCheck.ContainsKey(AttachState.Detached)); - if (checkStatus) { foreach (var entry in tableToCheck) @@ -341,7 +391,9 @@ public bool CheckStateChangedOverride(bool checkAttached, bool checkEvent, Attac } else if (entry.Value != attachableNode) { - Log($"[{entry.Key}][Value] The value {entry.Value.name} is not the same as {attachableNode.name}!"); + var attachableName = attachableNode == null ? "null" : attachableNode.name; + var entryName = entry.Value == null ? "null" : entry.Value.name; + Log($"[{entry.Key}][Value] The value {entryName} is not the same as {attachableName}!"); checkStatus = false; break; } From 912b26ff97d58b2730f85943f1468a484555943b Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 14 Jul 2025 20:52:36 -0500 Subject: [PATCH 22/43] update Updating changelog entry. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 36f925353f..6d7e8ae4e5 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -13,6 +13,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Added `AttachableBehaviour` helper component to provide an alternate approach to parenting items without using the `NetworkObject` parenting. (#3518) - Added `AttachableNode` helper component that is used by `AttachableBehaviour` as the target node for parenting. (#3518) - Added `ComponentController` helper component that can be used to synchronize the enabling and disabling of components and can be used in conjunction with `AttachableBehaviour`. (#3518) +- Added `NetworkBehaviour.OnNetworkPreDespawn` that is invoked before running through the despawn sequence for the `NetworkObject` and all `NetworkBehaviour` children of the `NetworkObject` being despawned. (#3518) - Added methods `GetDefaultNetworkSettings` and `GetDefaultPipelineConfigurations` to `UnityTransport`. These can be used to retrieve the default settings and pipeline stages that are used by `UnityTransport`. This is useful when providing a custom driver constructor through `UnityTransport.s_DriverConstructor`, since it allows reusing or tuning the existing configuration instead of trying to recreate it. This means a transport with a custom driver can now easily benefit from most of the features of `UnityTransport`, like integration with the Network Simulator and Network Profiler from the multiplayer tools package. (#3501) - Added mappings between `ClientId` and `TransportId`. (#3516) - Added `NetworkPrefabInstanceHandlerWithData`, a variant of `INetworkPrefabInstanceHandler` that provides access to custom instantiation data directly within the `Instantiate()` method. (#3430) From 9dd32d5084a3730e5882e532a09e17f73504aea6 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 15 Jul 2025 12:18:28 -0500 Subject: [PATCH 23/43] update Doing a second inspector UI pass to include tool tips and rename each element item to the component's standard inspector view naming where it is the GameObject's name followed by the class name that is separated by capitalization and contained within parenthesis. --- .../Components/Helpers/ComponentController.cs | 95 +++++++++++++++---- 1 file changed, 76 insertions(+), 19 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs index 8a7e1710a3..b3bda2261a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs @@ -2,9 +2,14 @@ using System.Collections; using System.Collections.Generic; using System.Reflection; +using System.Runtime.CompilerServices; +#if UNITY_EDITOR +using System.Text.RegularExpressions; +#endif using UnityEngine; using Object = UnityEngine.Object; + namespace Unity.Netcode.Components { /// @@ -13,33 +18,48 @@ namespace Unity.Netcode.Components [Serializable] public class ComponentControllerEntry { + [HideInInspector] + // Ignoring the naming convention in order to auto-assign element names +#pragma warning disable IDE1006 + public string name = "Component"; +#pragma warning restore IDE1006 + /// /// When true, this component's enabled state will be the inverse of /// the value passed into . /// - [Tooltip("When enabled, this component will inversely mirror the currently applied enable or disable state.")] + [Tooltip("When enabled, this component will inversely mirror the currently applied ComponentController's enabled state.")] public bool InvertEnabled; /// /// The amount of time to delay enabling this component when the has just transitioned from a disabled to enabled state. /// + /// + /// This can be useful under scenarios where you might want to prevent a component from being enabled too early prior to making any adjustments.
+ /// As an example, you might find that delaying the enabling of a until at least the next frame will avoid any single frame + /// rendering anomalies until the has updated the . + ///
[Range(0.0f, 2.0f)] + [Tooltip("The amount of time to delay when transitioning this component from disabled to enabled. When 0, the change is immediate.")] public float EnableDelay; /// /// The amount of time to delay disabling this component when the has just transitioned from an enabled to disabled state. /// + /// + /// This can be useful under scenarios where you might want to prevent a component from being disabled too early prior to making any adjustments.
+ ///
+ [Tooltip("The amount of time to delay when transitioning this component from enabled to disabled. When 0, the change is immediate.")] [Range(0f, 2.0f)] public float DisableDelay; /// - /// The component to control. + /// The component that will have its enabled property synchronized. /// /// - /// You can assign an entire to this property which will - /// add all components attached to the . The - /// and properties will be applied to all components found on the . + /// You can assign an entire to this property which will add all components attached to the and its children. /// + [Tooltip("The component that will have its enabled status synchonized. You can drop a GameObject onto this field and all valid components will be added to the list.")] public Object Component; internal PropertyInfo PropertyInfo; @@ -141,17 +161,16 @@ internal PendingStateUpdate(ComponentControllerEntry componentControllerEntry, b } /// - /// Handles enabling or disabling commonly used components, behaviours, RenderMeshes, etc.
- /// Anything that derives from and has an enabled property can be added - /// to the list of objects.
- /// derived components are not allowed and will be automatically removed. + /// Handles enabling or disabling commonly used components like , , , etc.
+ /// Anything that derives from and has an enabled property can be added to the list of objects.
+ /// NOTE: derived components are not allowed and will be automatically removed. ///
/// - /// This will synchronize the enabled or disabled state of the s with - /// connected and late joining clients.
- /// This class provides the basic functionality to synchronizing components' enabled state.
- /// It is encouraged to create custom derived versions of this class to provide any additional - /// functionality required for your project specific needs. + /// This will synchronize the enabled or disabled state of the s with connected and late joining clients.
+ /// - Use to determine the current synchronized enabled state.
+ /// - Use to change the enabled state and have the change applied to all components this is synchronizing.
+ /// + /// It is encouraged to create custom derived versions of this class to provide any additional functionality required for your project specific needs. ///
public class ComponentController : NetworkBehaviour { @@ -176,6 +195,21 @@ public class ComponentController : NetworkBehaviour private bool m_IsEnabled; #if UNITY_EDITOR + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsValidComponentType(Object component) + { + return !(component.GetType().IsSubclassOf(typeof(NetworkBehaviour)) || component.GetType() == typeof(NetworkObject) || component.GetType() == typeof(NetworkManager)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private string GetComponentNameFormatted(Object component) + { + // Split the class name up based on capitalization + var classNameDisplay = Regex.Replace(component.GetType().Name, "([A-Z])", " $1", RegexOptions.Compiled).Trim(); + return $"{component.name} ({classNameDisplay})"; + } + /// /// /// Checks for invalid entries. @@ -188,6 +222,7 @@ protected virtual void OnValidate() } var gameObjectsToScan = new List(); + // First pass is to verify all entries are valid and look for any GameObjects added as an entry to process next for (int i = Components.Count - 1; i >= 0; i--) { if (Components[i] == null) @@ -207,9 +242,9 @@ protected virtual void OnValidate() continue; } - if (componentType.IsSubclassOf(typeof(NetworkBehaviour))) + if (!IsValidComponentType(Components[i].Component)) { - Debug.LogWarning($"Removing {Components[i].Component.name} since {nameof(NetworkBehaviour)}s are not allowed to be controlled by this component."); + Debug.LogWarning($"Removing {GetComponentNameFormatted(Components[i].Component)} since {Components[i].Component.GetType().Name} is not an allowed component type."); Components.RemoveAt(i); continue; } @@ -222,14 +257,16 @@ protected virtual void OnValidate() } } + // Second pass is to process any GameObjects added. + // Scan the GameObject and all of its children and add all valid components to the list. foreach (var entry in gameObjectsToScan) { var asGameObject = entry.Component as GameObject; - var components = asGameObject.GetComponents(); + var components = asGameObject.GetComponentsInChildren(); foreach (var component in components) { - // Ignore any NetworkBehaviour derived components - if (component.GetType().IsSubclassOf(typeof(NetworkBehaviour))) + // Ignore any NetworkBehaviour derived, NetworkObject, or NetworkManager components + if (!IsValidComponentType(component)) { continue; } @@ -247,6 +284,16 @@ protected virtual void OnValidate() } } gameObjectsToScan.Clear(); + + // Final (third) pass is to name each list element item as the component is normally viewed in the inspector view. + for (int i = 0; i < Components.Count; i++) + { + if (!Components[i].Component) + { + continue; + } + Components[i].name = GetComponentNameFormatted(Components[i].Component); + } } #endif @@ -270,6 +317,9 @@ protected override void OnSynchronize(ref BufferSerializer serializer) /// This checks to make sure that all entries are valid and will create a final /// list of valid entries. ///
+ /// + /// If overriding this method, it is required that you invoke this base method. + /// protected virtual void Awake() { ValidComponents.Clear(); @@ -310,6 +360,9 @@ protected virtual void Awake() } /// + /// + /// If overriding this method, it is required that you invoke this base method. + /// public override void OnNetworkSpawn() { if (OnHasAuthority()) @@ -321,6 +374,7 @@ public override void OnNetworkSpawn() /// /// + /// If overriding this method, it is required that you invoke this base method.
/// Assures all instances subscribe to the internal of type /// that synchronizes all instances when s are enabled /// or disabled. @@ -332,6 +386,9 @@ protected override void OnNetworkPostSpawn() } /// + /// + /// If overriding this method, it is required that you invoke this base method. + /// public override void OnDestroy() { if (m_CoroutineObject.IsRunning) From 793cdb67aa82e2697e7de6c1603b2b14bc2c5447 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 15 Jul 2025 12:19:27 -0500 Subject: [PATCH 24/43] fix Fixing an exception that can occur when you have a network prefab opened for editing and then you delete the prefab asset before exiting the prefab edit mode. --- com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 560b280a02..754b83653d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -218,7 +218,7 @@ private static void CheckPrefabStage(PrefabStage prefabStage) s_PrefabAsset = AssetDatabase.LoadAssetAtPath(s_PrefabStage.assetPath); } - if (s_PrefabInstance.GlobalObjectIdHash != s_PrefabAsset.GlobalObjectIdHash) + if (s_PrefabAsset && s_PrefabInstance && s_PrefabInstance.GlobalObjectIdHash != s_PrefabAsset.GlobalObjectIdHash) { s_PrefabInstance.GlobalObjectIdHash = s_PrefabAsset.GlobalObjectIdHash; // For InContext mode, we don't want to record these modifications (the in-scene GlobalObjectIdHash is serialized with the scene). From fb73b8c4bbfd75e8e6101211bad6e58583e1469a Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 15 Jul 2025 12:29:49 -0500 Subject: [PATCH 25/43] style - PVP Fixing some PVP related issues. --- .../Components/Helpers/ComponentController.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs index b3bda2261a..9c7cc43d5d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs @@ -18,15 +18,18 @@ namespace Unity.Netcode.Components [Serializable] public class ComponentControllerEntry { - [HideInInspector] + // Ignoring the naming convention in order to auto-assign element names #pragma warning disable IDE1006 - public string name = "Component"; + /// + /// Used for naming each element entry. + /// + [HideInInspector] + public string name; #pragma warning restore IDE1006 /// - /// When true, this component's enabled state will be the inverse of - /// the value passed into . + /// When true, this component's enabled state will be the inverse of the value passed into . /// [Tooltip("When enabled, this component will inversely mirror the currently applied ComponentController's enabled state.")] public bool InvertEnabled; @@ -167,9 +170,8 @@ internal PendingStateUpdate(ComponentControllerEntry componentControllerEntry, b ///
/// /// This will synchronize the enabled or disabled state of the s with connected and late joining clients.
- /// - Use to determine the current synchronized enabled state.
+ /// - Use to determine the current synchronized enabled state.
/// - Use to change the enabled state and have the change applied to all components this is synchronizing.
- /// /// It is encouraged to create custom derived versions of this class to provide any additional functionality required for your project specific needs. ///
public class ComponentController : NetworkBehaviour @@ -374,7 +376,7 @@ public override void OnNetworkSpawn() /// /// - /// If overriding this method, it is required that you invoke this base method.
+ /// If overriding this method, it is required that you invoke this base method.
/// Assures all instances subscribe to the internal of type /// that synchronizes all instances when s are enabled /// or disabled. From 4b4c8ca618b0386947078a01ec9ed9d3f7d3ec13 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 18 Jul 2025 18:18:56 -0500 Subject: [PATCH 26/43] update Several improvements on the attach and detach processing. Added the ability to tie ComponentControllers to AttachableBehaviours in order to auto-notify when something is attaching and detaching. Several adjustments to fix the issue with ungraceful disconnects and re-synchronizing attachables. Added forced change based on flags applied, things like when an AttachableNode is despawning, changing ownership, or being destroyed then local instances, whether authority or not, will all force the attach or detach state. Added an internal virtual destroy method on NetworkBehaviour to allow for helper components to assure on destroy script is invoked. --- .../Components/Helpers/AttachableBehaviour.cs | 190 ++++++++-- .../Components/Helpers/AttachableNode.cs | 41 ++- .../Components/Helpers/ComponentController.cs | 324 +++++++++--------- .../Runtime/Core/NetworkBehaviour.cs | 9 + .../Tests/Runtime/ComponentControllerTests.cs | 6 +- 5 files changed, 386 insertions(+), 184 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs index 1962ca6222..9f509fb37b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; + #if UNITY_EDITOR using UnityEditor; #endif @@ -25,6 +27,49 @@ namespace Unity.Netcode.Components ///
public class AttachableBehaviour : NetworkBehaviour { + [Serializable] + internal class ComponentControllerEntry + { + // Ignoring the naming convention in order to auto-assign element names +#pragma warning disable IDE1006 + /// + /// Used for naming each element entry. + /// + [HideInInspector] + public string name; +#pragma warning restore IDE1006 + + +#if UNITY_EDITOR + + internal void OnValidate() + { + if (!HasInitialized) + { + AutoTrigger = TriggerTypes.OnAttach | TriggerTypes.OnDetach; + HasInitialized = true; + } + name = ComponentController != null ? ComponentController.GetComponentNameFormatted(ComponentController) : "Component Controller"; + } +#endif + + [Flags] + public enum TriggerTypes : byte + { + Nothing, + OnAttach, + OnDetach, + } + + public TriggerTypes AutoTrigger; + public bool EnableOnAttach = true; + public ComponentController ComponentController; + + [HideInInspector] + [SerializeField] + internal bool HasInitialized; + } + #if UNITY_EDITOR /// /// @@ -45,6 +90,15 @@ protected virtual void OnValidate() // Wait for the next editor update to create a nested child and add the AttachableBehaviour EditorApplication.update += CreatedNestedChild; } + + foreach (var componentController in ComponentControllers) + { + if (componentController == null) + { + continue; + } + componentController.OnValidate(); + } } private void CreatedNestedChild() @@ -57,6 +111,34 @@ private void CreatedNestedChild() DestroyImmediate(this); } #endif + /// + /// Flags to determine if the will automatically detatch. + /// + [Flags] + public enum AutoDetatchTypes + { + None, + /// + /// Detatch on ownership change. + /// + OnOwnershipChange, + /// + /// Detatch on despawn. + /// + OnDespawn, + /// + /// Detatch on destroy. + /// + OnAttachNodeDestroy, + } + + /// + /// Determines if this will automatically detatch on all instances if it has one of the flags. + /// + public AutoDetatchTypes AutoDetach = AutoDetatchTypes.OnDespawn | AutoDetatchTypes.OnOwnershipChange | AutoDetatchTypes.OnAttachNodeDestroy; + + [SerializeField] + internal List ComponentControllers; /// /// Invoked when the of this instance has changed. @@ -115,6 +197,7 @@ public enum AttachState /// If attached, attaching, or detaching this will be the this instance is attached to. /// protected AttachableNode m_AttachableNode { get; private set; } + internal AttachableNode AttachableNode => m_AttachableNode; private NetworkBehaviourReference m_AttachedNodeReference = new NetworkBehaviourReference(null); private Vector3 m_OriginalLocalPosition; @@ -125,17 +208,20 @@ protected override void OnSynchronize(ref BufferSerializer serializer) { // Example of how to synchronize late joining clients when using an RPC to update // a local property's state. - if (serializer.IsWriter) - { - serializer.SerializeValue(ref m_AttachedNodeReference); - } - else - { - serializer.SerializeValue(ref m_AttachedNodeReference); - } + serializer.SerializeValue(ref m_AttachedNodeReference); base.OnSynchronize(ref serializer); } + /// + /// Override this method in place of Awake. This method is invoked during Awake. + /// + /// + /// The 's Awake method is protected to assure it initializes itself at this point in time. + /// + protected virtual void OnAwake() + { + } + /// /// If you create a custom and override this method, you must invoke /// this base instance of . @@ -147,6 +233,7 @@ protected virtual void Awake() m_OriginalLocalRotation = transform.localRotation; m_AttachState = AttachState.Detached; m_AttachableNode = null; + OnAwake(); } /// @@ -162,9 +249,14 @@ protected override void OnNetworkSessionSynchronized() base.OnNetworkSessionSynchronized(); } - /// - public override void OnNetworkDespawn() + internal void ForceDetatch() { + if (m_AttachState == AttachState.Detached || m_AttachState == AttachState.Detaching) + { + return; + } + + ForceComponentChange(false, true); InternalDetach(); if (NetworkManager && !NetworkManager.ShutdownInProgress) { @@ -172,18 +264,37 @@ public override void OnNetworkDespawn() UpdateAttachState(m_AttachState, m_AttachableNode); } m_AttachedNodeReference = new NetworkBehaviourReference(null); + } + + /// + public override void OnNetworkPreDespawn() + { + if (AutoDetach.HasFlag(AutoDetatchTypes.OnDespawn)) + { + ForceDetatch(); + } base.OnNetworkDespawn(); } private void UpdateAttachedState() { var attachableNode = (AttachableNode)null; - var shouldParent = m_AttachedNodeReference.TryGet(out attachableNode, NetworkManager); - var preState = shouldParent ? AttachState.Attaching : AttachState.Detaching; - var preNode = shouldParent ? attachableNode : m_AttachableNode; - shouldParent = shouldParent && attachableNode != null; + var isAttaching = m_AttachedNodeReference.TryGet(out attachableNode, NetworkManager); + var preState = isAttaching ? AttachState.Attaching : AttachState.Detaching; + + // Exit early if we are already in the correct attached state and the incoming + // AttachableNode reference is the same as the local AttachableNode property. + if (attachableNode == m_AttachableNode && + ((isAttaching && m_AttachState == AttachState.Attached) || + (!isAttaching && m_AttachState == AttachState.Detached))) + { + return; + } + + var preNode = isAttaching ? attachableNode : m_AttachableNode; + isAttaching = isAttaching && attachableNode != null; - if (shouldParent && m_AttachableNode != null && m_AttachState == AttachState.Attached) + if (isAttaching && m_AttachableNode != null && m_AttachState == AttachState.Attached) { // If we are attached to some other AttachableNode, then detach from that before attaching to a new one. if (m_AttachableNode != attachableNode) @@ -201,7 +312,8 @@ private void UpdateAttachedState() // Change the state to attaching or detaching UpdateAttachState(preState, preNode); - if (shouldParent) + ForceComponentChange(isAttaching, false); + if (isAttaching) { InternalAttach(attachableNode); } @@ -215,7 +327,7 @@ private void UpdateAttachedState() // When detaching, we want to make our final action // the invocation of the AttachableNode's Detach method. - if (!shouldParent && m_AttachableNode) + if (!isAttaching && m_AttachableNode) { m_AttachableNode.Detach(this); m_AttachableNode = null; @@ -257,6 +369,29 @@ private void UpdateAttachState(AttachState attachState, AttachableNode attachabl } } + /// + protected override void OnOwnershipChanged(ulong previous, ulong current) + { + if (AutoDetach.HasFlag(AutoDetatchTypes.OnOwnershipChange)) + { + ForceDetatch(); + } + base.OnOwnershipChanged(previous, current); + } + + internal void ForceComponentChange(bool isAttaching, bool forcedChange) + { + var triggerType = isAttaching ? ComponentControllerEntry.TriggerTypes.OnAttach : ComponentControllerEntry.TriggerTypes.OnDetach; + + foreach (var componentControllerEntry in ComponentControllers) + { + if (componentControllerEntry.AutoTrigger.HasFlag(triggerType)) + { + componentControllerEntry.ComponentController.ForceChangeEnabled(componentControllerEntry.EnableOnAttach ? isAttaching : !isAttaching, forcedChange); + } + } + } + /// /// Internal attach method that just handles changing state, parenting, and sending the a /// notification that an has attached. @@ -349,23 +484,23 @@ public void Detach() return; } - if (m_AttachState != AttachState.Attached || m_AttachableNode == null) + if (m_AttachState == AttachState.Detached || m_AttachState == AttachState.Detaching || m_AttachableNode == null) { // Check for the unlikely scenario that an instance has mismatch between the state and assigned attachable node. - if (!m_AttachableNode && m_AttachState == AttachState.Attached) + if (!m_AttachableNode) { NetworkLog.LogError($"[{name}][Detach] Invalid state detected! {name}'s state is still {m_AttachState} but has no {nameof(AttachableNode)} assigned!"); } // Developer only notification for the most likely scenario where this method is invoked but the instance is not attached to anything. - if (NetworkManager && NetworkManager.LogLevel <= LogLevel.Developer) + if (!m_AttachableNode && NetworkManager && NetworkManager.LogLevel <= LogLevel.Developer) { NetworkLog.LogWarning($"[{name}][Detach] Cannot detach! {name} is not attached to anything!"); } // If we have the attachable node set and we are not in the middle of detaching, then log an error and note // this could potentially occur if inoked more than once for the same instance in the same frame. - if (m_AttachableNode && m_AttachState != AttachState.Detaching) + if (m_AttachableNode) { NetworkLog.LogError($"[{name}][Detach] Invalid state detected! {name} is still referencing {nameof(AttachableNode)} {m_AttachableNode.name}! Could {nameof(Detach)} be getting invoked more than once for the same instance?"); } @@ -407,5 +542,18 @@ private void UpdateAttachStateRpc(NetworkBehaviourReference attachedNodeReferenc { ChangeReference(attachedNodeReference); } + + /// + /// Notification that the is being destroyed + /// + internal void OnAttachNodeDestroy() + { + // If this instance should force a detatch on destroy + if (AutoDetach.HasFlag(AutoDetatchTypes.OnAttachNodeDestroy)) + { + // Force a detatch + ForceDetatch(); + } + } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs index c62e16085b..bdd2fc47b6 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs @@ -29,6 +29,35 @@ public class AttachableNode : NetworkBehaviour /// protected readonly List m_AttachedBehaviours = new List(); + /// + protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) + { + m_AttachedBehaviours.Clear(); + base.OnNetworkPreSpawn(ref networkManager); + } + + /// + /// + /// When the ownership of an changes, it will find all currently attached components + /// that are registered as being attached to this instance. + /// + protected override void OnOwnershipChanged(ulong previous, ulong current) + { + if (current == NetworkManager.LocalClientId) + { + m_AttachedBehaviours.Clear(); + var attachables = NetworkObject.transform.GetComponentsInChildren(); + foreach (var attachable in attachables) + { + if (attachable.AttachableNode == this) + { + m_AttachedBehaviours.Add(attachable); + } + } + } + base.OnOwnershipChanged(previous, current); + } + /// /// /// If the this belongs to is despawned, @@ -46,11 +75,15 @@ public override void OnNetworkPreDespawn() base.OnNetworkPreDespawn(); } - /// - public override void OnNetworkDespawn() + internal override void InternalOnDestroy() { + // Notify any attached behaviours that this node is being destroyed. + for (int i = m_AttachedBehaviours.Count - 1; i >= 0; i--) + { + m_AttachedBehaviours[i]?.OnAttachNodeDestroy(); + } m_AttachedBehaviours.Clear(); - base.OnNetworkDespawn(); + base.InternalOnDestroy(); } /// @@ -69,7 +102,6 @@ internal void Attach(AttachableBehaviour attachableBehaviour) NetworkLog.LogError($"[{nameof(AttachableNode)}][{name}][Attach] {nameof(AttachableBehaviour)} {attachableBehaviour.name} is already attached!"); return; } - m_AttachedBehaviours.Add(attachableBehaviour); OnAttached(attachableBehaviour); } @@ -90,7 +122,6 @@ internal void Detach(AttachableBehaviour attachableBehaviour) NetworkLog.LogError($"[{nameof(AttachableNode)}][{name}][Detach] {nameof(AttachableBehaviour)} {attachableBehaviour.name} is not attached!"); return; } - m_AttachedBehaviours.Remove(attachableBehaviour); OnDetached(attachableBehaviour); } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs index 9c7cc43d5d..f135446184 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs @@ -13,169 +13,168 @@ namespace Unity.Netcode.Components { /// - /// This is a serializable contianer class for entries. + /// Handles enabling or disabling commonly used components like , , , etc.
+ /// Anything that derives from and has an enabled property can be added to the list of objects.
+ /// NOTE: derived components are not allowed and will be automatically removed. ///
- [Serializable] - public class ComponentControllerEntry + /// + /// This will synchronize the enabled or disabled state of the s with connected and late joining clients.
+ /// - Use to determine the current synchronized enabled state.
+ /// - Use to change the enabled state and have the change applied to all components this is synchronizing.
+ /// It is encouraged to create custom derived versions of this class to provide any additional functionality required for your project specific needs. + ///
+ public class ComponentController : NetworkBehaviour { - - // Ignoring the naming convention in order to auto-assign element names -#pragma warning disable IDE1006 - /// - /// Used for naming each element entry. - /// - [HideInInspector] - public string name; -#pragma warning restore IDE1006 - - /// - /// When true, this component's enabled state will be the inverse of the value passed into . - /// - [Tooltip("When enabled, this component will inversely mirror the currently applied ComponentController's enabled state.")] - public bool InvertEnabled; - - /// - /// The amount of time to delay enabling this component when the has just transitioned from a disabled to enabled state. - /// - /// - /// This can be useful under scenarios where you might want to prevent a component from being enabled too early prior to making any adjustments.
- /// As an example, you might find that delaying the enabling of a until at least the next frame will avoid any single frame - /// rendering anomalies until the has updated the . - ///
- [Range(0.0f, 2.0f)] - [Tooltip("The amount of time to delay when transitioning this component from disabled to enabled. When 0, the change is immediate.")] - public float EnableDelay; - - /// - /// The amount of time to delay disabling this component when the has just transitioned from an enabled to disabled state. - /// - /// - /// This can be useful under scenarios where you might want to prevent a component from being disabled too early prior to making any adjustments.
- ///
- [Tooltip("The amount of time to delay when transitioning this component from enabled to disabled. When 0, the change is immediate.")] - [Range(0f, 2.0f)] - public float DisableDelay; - /// - /// The component that will have its enabled property synchronized. + /// This is a serializable contianer class for entries. /// - /// - /// You can assign an entire to this property which will add all components attached to the and its children. - /// - [Tooltip("The component that will have its enabled status synchonized. You can drop a GameObject onto this field and all valid components will be added to the list.")] - public Object Component; - internal PropertyInfo PropertyInfo; - - internal bool GetRelativeEnabled(bool enabled) + [Serializable] + internal class ComponentEntry { - return InvertEnabled ? !enabled : enabled; - } - private List m_PendingStateUpdates = new List(); - - /// - /// Invoke prior to setting the state. - /// - internal bool QueueForDelay(bool enabled) - { - var relativeEnabled = GetRelativeEnabled(enabled); + // Ignoring the naming convention in order to auto-assign element names +#pragma warning disable IDE1006 + /// + /// Used for naming each element entry. + /// + [HideInInspector] + public string name; +#pragma warning restore IDE1006 - if (relativeEnabled ? EnableDelay > 0.0f : DisableDelay > 0.0f) + /// + /// When true, this component's enabled state will be the inverse of the value passed into . + /// + [Tooltip("When enabled, this component will inversely mirror the currently applied ComponentController's enabled state.")] + public bool InvertEnabled; + + /// + /// The amount of time to delay enabling this component when the has just transitioned from a disabled to enabled state. + /// + /// + /// This can be useful under scenarios where you might want to prevent a component from being enabled too early prior to making any adjustments.
+ /// As an example, you might find that delaying the enabling of a until at least the next frame will avoid any single frame + /// rendering anomalies until the has updated the . + ///
+ [Range(0.0f, 2.0f)] + [Tooltip("The amount of time to delay when transitioning this component from disabled to enabled. When 0, the change is immediate.")] + public float EnableDelay; + + /// + /// The amount of time to delay disabling this component when the has just transitioned from an enabled to disabled state. + /// + /// + /// This can be useful under scenarios where you might want to prevent a component from being disabled too early prior to making any adjustments.
+ ///
+ [Tooltip("The amount of time to delay when transitioning this component from enabled to disabled. When 0, the change is immediate.")] + [Range(0f, 2.0f)] + public float DisableDelay; + + /// + /// The component that will have its enabled property synchronized. + /// + /// + /// You can assign an entire to this property which will add all components attached to the and its children. + /// + [Tooltip("The component that will have its enabled status synchonized. You can drop a GameObject onto this field and all valid components will be added to the list.")] + public Object Component; + internal PropertyInfo PropertyInfo; + + internal bool GetRelativeEnabled(bool enabled) { - // Start with no relative time offset - var relativeTimeOffset = 0.0f; - // If we have pending state updates, then get that time of the last state update - // and use that as the time to add this next state update. - if (m_PendingStateUpdates.Count > 0) - { - relativeTimeOffset = m_PendingStateUpdates[m_PendingStateUpdates.Count - 1].DelayTimeDelta; - } - - // We process backwards, so insert new entries at the front - m_PendingStateUpdates.Insert(0, new PendingStateUpdate(this, enabled, relativeTimeOffset)); - return true; + return InvertEnabled ? !enabled : enabled; } - return false; - } - internal void SetValue(bool isEnabled) - { - // If invert enabled is true, then use the inverted value passed in. - // Otherwise, directly apply the value passed in. - PropertyInfo.SetValue(Component, GetRelativeEnabled(isEnabled)); - } + private List m_PendingStateUpdates = new List(); - internal bool HasPendingStateUpdates() - { - for (int i = m_PendingStateUpdates.Count - 1; i >= 0; i--) + /// + /// Invoke prior to setting the state. + /// + internal bool QueueForDelay(bool enabled) { - if (!m_PendingStateUpdates[i].CheckTimeDeltaDelay()) + var relativeEnabled = GetRelativeEnabled(enabled); + + if (relativeEnabled ? EnableDelay > 0.0f : DisableDelay > 0.0f) { - m_PendingStateUpdates.RemoveAt(i); - continue; + // Start with no relative time offset + var relativeTimeOffset = 0.0f; + // If we have pending state updates, then get that time of the last state update + // and use that as the time to add this next state update. + if (m_PendingStateUpdates.Count > 0) + { + relativeTimeOffset = m_PendingStateUpdates[m_PendingStateUpdates.Count - 1].DelayTimeDelta; + } + + // We process backwards, so insert new entries at the front + m_PendingStateUpdates.Insert(0, new PendingStateUpdate(this, enabled, relativeTimeOffset)); + return true; } + return false; } - return m_PendingStateUpdates.Count > 0; - } - - private class PendingStateUpdate - { - internal bool TimeDeltaDelayInProgress; - internal bool PendingState; - internal float DelayTimeDelta; - internal ComponentControllerEntry ComponentControllerEntry; - - internal bool CheckTimeDeltaDelay() + internal void SetValue(bool isEnabled) { - if (!TimeDeltaDelayInProgress) - { - return false; - } - - var isDeltaDelayInProgress = DelayTimeDelta > Time.realtimeSinceStartup; + // If invert enabled is true, then use the inverted value passed in. + // Otherwise, directly apply the value passed in. + PropertyInfo.SetValue(Component, GetRelativeEnabled(isEnabled)); + } - if (!isDeltaDelayInProgress) + internal bool HasPendingStateUpdates() + { + for (int i = m_PendingStateUpdates.Count - 1; i >= 0; i--) { - ComponentControllerEntry.SetValue(PendingState); + if (!m_PendingStateUpdates[i].CheckTimeDeltaDelay()) + { + m_PendingStateUpdates.RemoveAt(i); + continue; + } } - TimeDeltaDelayInProgress = isDeltaDelayInProgress; - return TimeDeltaDelayInProgress; + return m_PendingStateUpdates.Count > 0; } - internal PendingStateUpdate(ComponentControllerEntry componentControllerEntry, bool isEnabled, float relativeTimeOffset) + private class PendingStateUpdate { - ComponentControllerEntry = componentControllerEntry; - // If there is a pending state, then add the delay to the end of the last pending state's. - var referenceTime = relativeTimeOffset > 0.0f ? relativeTimeOffset : Time.realtimeSinceStartup; + internal bool TimeDeltaDelayInProgress; + internal bool PendingState; + internal float DelayTimeDelta; + + internal ComponentEntry ComponentEntry; - if (ComponentControllerEntry.GetRelativeEnabled(isEnabled)) + internal bool CheckTimeDeltaDelay() { - DelayTimeDelta = referenceTime + ComponentControllerEntry.EnableDelay; + if (!TimeDeltaDelayInProgress) + { + return false; + } + + var isDeltaDelayInProgress = DelayTimeDelta > Time.realtimeSinceStartup; + + if (!isDeltaDelayInProgress) + { + ComponentEntry.SetValue(PendingState); + } + TimeDeltaDelayInProgress = isDeltaDelayInProgress; + return TimeDeltaDelayInProgress; } - else + + internal PendingStateUpdate(ComponentEntry componentControllerEntry, bool isEnabled, float relativeTimeOffset) { - DelayTimeDelta = referenceTime + ComponentControllerEntry.DisableDelay; + ComponentEntry = componentControllerEntry; + // If there is a pending state, then add the delay to the end of the last pending state's. + var referenceTime = relativeTimeOffset > 0.0f ? relativeTimeOffset : Time.realtimeSinceStartup; + + if (ComponentEntry.GetRelativeEnabled(isEnabled)) + { + DelayTimeDelta = referenceTime + ComponentEntry.EnableDelay; + } + else + { + DelayTimeDelta = referenceTime + ComponentEntry.DisableDelay; + } + TimeDeltaDelayInProgress = true; + PendingState = isEnabled; } - TimeDeltaDelayInProgress = true; - PendingState = isEnabled; } } - } - - /// - /// Handles enabling or disabling commonly used components like , , , etc.
- /// Anything that derives from and has an enabled property can be added to the list of objects.
- /// NOTE: derived components are not allowed and will be automatically removed. - ///
- /// - /// This will synchronize the enabled or disabled state of the s with connected and late joining clients.
- /// - Use to determine the current synchronized enabled state.
- /// - Use to change the enabled state and have the change applied to all components this is synchronizing.
- /// It is encouraged to create custom derived versions of this class to provide any additional functionality required for your project specific needs. - ///
- public class ComponentController : NetworkBehaviour - { /// /// Determines whether the selected s will start enabled or disabled when spawned. /// @@ -186,14 +185,15 @@ public class ComponentController : NetworkBehaviour /// The list of s to be enabled and disabled. ///
[Tooltip("The list of components to control. You can drag and drop an entire GameObject on this to include all components.")] - public List Components; + [SerializeField] + internal List Components; /// /// Returns the current enabled state of the . /// public bool EnabledState => m_IsEnabled; - internal List ValidComponents = new List(); + internal List ValidComponents = new List(); private bool m_IsEnabled; #if UNITY_EDITOR @@ -205,7 +205,7 @@ private bool IsValidComponentType(Object component) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private string GetComponentNameFormatted(Object component) + internal static string GetComponentNameFormatted(Object component) { // Split the class name up based on capitalization var classNameDisplay = Regex.Replace(component.GetType().Name, "([A-Z])", " $1", RegexOptions.Compiled).Trim(); @@ -223,7 +223,7 @@ protected virtual void OnValidate() return; } - var gameObjectsToScan = new List(); + var gameObjectsToScan = new List(); // First pass is to verify all entries are valid and look for any GameObjects added as an entry to process next for (int i = Components.Count - 1; i >= 0; i--) { @@ -236,10 +236,13 @@ protected virtual void OnValidate() { continue; } - var componentType = Components[i].Component.GetType(); - if (componentType == typeof(GameObject)) + var objectType = Components[i].Component.GetType(); + if (objectType == typeof(GameObject)) { - gameObjectsToScan.Add(Components[i]); + if (!gameObjectsToScan.Contains(Components[i])) + { + gameObjectsToScan.Add(Components[i]); + } Components.RemoveAt(i); continue; } @@ -276,7 +279,7 @@ protected virtual void OnValidate() var propertyInfo = component.GetType().GetProperty("enabled", BindingFlags.Instance | BindingFlags.Public); if (propertyInfo != null && propertyInfo.PropertyType == typeof(bool)) { - var componentEntry = new ComponentControllerEntry() + var componentEntry = new ComponentEntry() { Component = component, PropertyInfo = propertyInfo, @@ -304,25 +307,21 @@ protected override void OnSynchronize(ref BufferSerializer serializer) { // Example of how to synchronize late joining clients when using an RPC to update // a local property's state. - if (serializer.IsWriter) - { - serializer.SerializeValue(ref m_IsEnabled); - } - else - { - serializer.SerializeValue(ref m_IsEnabled); - } + serializer.SerializeValue(ref m_IsEnabled); base.OnSynchronize(ref serializer); } /// - /// This checks to make sure that all entries are valid and will create a final - /// list of valid entries. + /// Override this method in place of Awake. This method is invoked during Awake. /// /// - /// If overriding this method, it is required that you invoke this base method. + /// The 's Awake method is protected to assure it is invoked in the correct order. /// - protected virtual void Awake() + protected virtual void OnAwake() + { + } + + private void Awake() { ValidComponents.Clear(); @@ -359,6 +358,15 @@ protected virtual void Awake() // Apply the initial state of all components this instance is controlling. InitializeComponents(); + + try + { + OnAwake(); + } + catch (Exception ex) + { + Debug.LogException(ex); + } } /// @@ -419,11 +427,11 @@ private void InitializeComponents() /// Applies states changes to all components being controlled by this instance. ///
/// the state update to apply - private void ApplyEnabled() + private void ApplyEnabled(bool ignoreDelays = false) { foreach (var entry in ValidComponents) { - if (entry.QueueForDelay(m_IsEnabled)) + if (!ignoreDelays && entry.QueueForDelay(m_IsEnabled)) { if (!m_CoroutineObject.IsRunning) { @@ -506,6 +514,12 @@ private void ChangeEnabled(bool isEnabled) } } + internal void ForceChangeEnabled(bool isEnabled, bool ignoreDelays = false) + { + m_IsEnabled = isEnabled; + ApplyEnabled(ignoreDelays); + } + /// /// Override this method to change how the instance determines the authority.
/// The default is to use the method. diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 73696fa0a4..307068214c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -1525,6 +1525,14 @@ internal bool Synchronize(ref BufferSerializer serializer, ulong targetCli } } + /// + /// Use to assure a helper component invokes script during destroy in the + /// event that a derived class does not invoke base.OnDestroy. + /// + internal virtual void InternalOnDestroy() + { + + } /// /// Invoked when the the is attached to is destroyed. @@ -1532,6 +1540,7 @@ internal bool Synchronize(ref BufferSerializer serializer, ulong targetCli /// public virtual void OnDestroy() { + InternalOnDestroy(); if (NetworkObject != null && NetworkObject.IsSpawned && IsSpawned) { // If the associated NetworkObject is still spawned then this diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/ComponentControllerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/ComponentControllerTests.cs index 7d2d6e86cb..8262614cde 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/ComponentControllerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/ComponentControllerTests.cs @@ -40,13 +40,13 @@ protected override void OnServerAndClientsCreated() var meshRenderer = sourceChild.AddComponent(); var light = sourceChild.AddComponent(); var controller = m_TestPrefab.AddComponent(); - controller.Components = new List + controller.Components = new List { - new ComponentControllerEntry() + new ComponentController.ComponentEntry() { Component = meshRenderer, }, - new ComponentControllerEntry() + new ComponentController.ComponentEntry() { InvertEnabled = true, Component = light, From 42ac2a8362b495f63937703944dabf90dd015ea7 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 18 Jul 2025 21:56:41 -0500 Subject: [PATCH 27/43] sty;e - PvP Adding documentation to an undocumented enum value. --- .../Runtime/Components/Helpers/AttachableBehaviour.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs index 9f509fb37b..b8abd7980f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs @@ -117,6 +117,9 @@ private void CreatedNestedChild() [Flags] public enum AutoDetatchTypes { + /// + /// Disables auto detach. + /// None, /// /// Detatch on ownership change. From efcc40449669d188987eccc74061e59fadff7e8d Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sun, 20 Jul 2025 21:18:42 -0500 Subject: [PATCH 28/43] update and test additions Updating the core components based on some bugs discovered during testing. Updated the AttachableBehaviourTests to validate the different types of auto-detach flag combinations. Fixed some spelling issues with "detatch" (not sure why I got into that habit)...corrected to "detach". --- .../Components/Helpers/AttachableBehaviour.cs | 52 +- .../Components/Helpers/AttachableNode.cs | 20 +- .../Tests/Runtime/AttachableBehaviourTests.cs | 474 ++++++++++++++++-- 3 files changed, 477 insertions(+), 69 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs index b8abd7980f..8922b4ad91 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs @@ -90,7 +90,10 @@ protected virtual void OnValidate() // Wait for the next editor update to create a nested child and add the AttachableBehaviour EditorApplication.update += CreatedNestedChild; } - + if (ComponentControllers == null) + { + return; + } foreach (var componentController in ComponentControllers) { if (componentController == null) @@ -112,33 +115,33 @@ private void CreatedNestedChild() } #endif /// - /// Flags to determine if the will automatically detatch. + /// Flags to determine if the will automatically detach. /// [Flags] - public enum AutoDetatchTypes + public enum AutoDetachTypes { /// /// Disables auto detach. /// None, /// - /// Detatch on ownership change. + /// Detach on ownership change. /// OnOwnershipChange, /// - /// Detatch on despawn. + /// Detach on despawn. /// OnDespawn, /// - /// Detatch on destroy. + /// Detach on destroy. /// OnAttachNodeDestroy, } /// - /// Determines if this will automatically detatch on all instances if it has one of the flags. + /// Determines if this will automatically detach on all instances if it has one of the flags. /// - public AutoDetatchTypes AutoDetach = AutoDetatchTypes.OnDespawn | AutoDetatchTypes.OnOwnershipChange | AutoDetatchTypes.OnAttachNodeDestroy; + public AutoDetachTypes AutoDetach = AutoDetachTypes.OnDespawn | AutoDetachTypes.OnOwnershipChange | AutoDetachTypes.OnAttachNodeDestroy; [SerializeField] internal List ComponentControllers; @@ -252,7 +255,7 @@ protected override void OnNetworkSessionSynchronized() base.OnNetworkSessionSynchronized(); } - internal void ForceDetatch() + internal void ForceDetach() { if (m_AttachState == AttachState.Detached || m_AttachState == AttachState.Detaching) { @@ -260,21 +263,28 @@ internal void ForceDetatch() } ForceComponentChange(false, true); + InternalDetach(); - if (NetworkManager && !NetworkManager.ShutdownInProgress) + // Notify of the changed attached state + UpdateAttachState(m_AttachState, m_AttachableNode); + + m_AttachedNodeReference = new NetworkBehaviourReference(null); + + // When detaching, we want to make our final action + // the invocation of the AttachableNode's Detach method. + if (m_AttachableNode) { - // Notify of the changed attached state - UpdateAttachState(m_AttachState, m_AttachableNode); + m_AttachableNode.Detach(this); + m_AttachableNode = null; } - m_AttachedNodeReference = new NetworkBehaviourReference(null); } /// public override void OnNetworkPreDespawn() { - if (AutoDetach.HasFlag(AutoDetatchTypes.OnDespawn)) + if (AutoDetach.HasFlag(AutoDetachTypes.OnDespawn)) { - ForceDetatch(); + ForceDetach(); } base.OnNetworkDespawn(); } @@ -375,9 +385,9 @@ private void UpdateAttachState(AttachState attachState, AttachableNode attachabl /// protected override void OnOwnershipChanged(ulong previous, ulong current) { - if (AutoDetach.HasFlag(AutoDetatchTypes.OnOwnershipChange)) + if (AutoDetach.HasFlag(AutoDetachTypes.OnOwnershipChange)) { - ForceDetatch(); + ForceDetach(); } base.OnOwnershipChanged(previous, current); } @@ -551,11 +561,11 @@ private void UpdateAttachStateRpc(NetworkBehaviourReference attachedNodeReferenc /// internal void OnAttachNodeDestroy() { - // If this instance should force a detatch on destroy - if (AutoDetach.HasFlag(AutoDetatchTypes.OnAttachNodeDestroy)) + // If this instance should force a detach on destroy + if (AutoDetach.HasFlag(AutoDetachTypes.OnAttachNodeDestroy)) { - // Force a detatch - ForceDetatch(); + // Force a detach + ForceDetach(); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs index bdd2fc47b6..d768b17fcf 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs @@ -43,9 +43,11 @@ protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) /// protected override void OnOwnershipChanged(ulong previous, ulong current) { + // Clear any known behaviours on all instances (really only the previous owner should know about AttachedBehaviours + m_AttachedBehaviours.Clear(); if (current == NetworkManager.LocalClientId) { - m_AttachedBehaviours.Clear(); + // Rebuild the list of AttachableBehaviours for the new owner var attachables = NetworkObject.transform.GetComponentsInChildren(); foreach (var attachable in attachables) { @@ -69,7 +71,21 @@ public override void OnNetworkPreDespawn() { for (int i = m_AttachedBehaviours.Count - 1; i >= 0; i--) { - m_AttachedBehaviours[i]?.Detach(); + if (!m_AttachedBehaviours[i]) + { + continue; + } + // If we don't have authority but should detach on despawn, + // then proceed to detach. + if (!m_AttachedBehaviours[i].HasAuthority) + { + m_AttachedBehaviours[i].ForceDetach(); + } + else + { + // Detach the normal way with authority + m_AttachedBehaviours[i].Detach(); + } } } base.OnNetworkPreDespawn(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/AttachableBehaviourTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/AttachableBehaviourTests.cs index af0568ab7f..1d3d3778cf 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/AttachableBehaviourTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/AttachableBehaviourTests.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Collections.Generic; +using System.Linq; using System.Text; using NUnit.Framework; using Unity.Netcode.Components; @@ -18,9 +19,9 @@ internal class AttachableBehaviourTests : NetcodeIntegrationTest public AttachableBehaviourTests(HostOrServer hostOrServer) : base(hostOrServer) { } - private GameObject m_SourcePrefab; - private GameObject m_TargetPrefabA; - private GameObject m_TargetPrefabB; + private GameObject m_AttachablePrefab; + private GameObject m_TargetNodePrefabA; + private GameObject m_TargetNodePrefabB; /// /// All of the below instances belong to the authority @@ -29,6 +30,7 @@ public AttachableBehaviourTests(HostOrServer hostOrServer) : base(hostOrServer) private NetworkObject m_SourceInstance; private NetworkObject m_TargetInstance; private NetworkObject m_TargetInstanceB; + private TestAttachable m_PrefabTestAttachable; private TestAttachable m_AttachableBehaviourInstance; private TestNode m_AttachableNodeInstance; private TestNode m_AttachableNodeInstanceB; @@ -47,21 +49,25 @@ protected override void OnServerAndClientsCreated() { // The source prefab contains the nested NetworkBehaviour that // will be parented under the target prefab. - m_SourcePrefab = CreateNetworkObjectPrefab("Source"); - m_SourcePrefab.GetComponent().DontDestroyWithOwner = true; + m_AttachablePrefab = CreateNetworkObjectPrefab("Source"); + var attachableNetworkObject = m_AttachablePrefab.GetComponent(); + attachableNetworkObject.DontDestroyWithOwner = true; + attachableNetworkObject.SetOwnershipStatus(NetworkObject.OwnershipStatus.Transferable); + // The target prefab that the source prefab will attach // will be parented under the target prefab. - m_TargetPrefabA = CreateNetworkObjectPrefab("TargetA"); - m_TargetPrefabB = CreateNetworkObjectPrefab("TargetB"); + m_TargetNodePrefabA = CreateNetworkObjectPrefab("TargetA"); + m_TargetNodePrefabB = CreateNetworkObjectPrefab("TargetB"); var sourceChild = new GameObject("SourceChild"); var targetChildA = new GameObject("TargetChildA"); var targetChildB = new GameObject("TargetChildB"); - sourceChild.transform.parent = m_SourcePrefab.transform; - targetChildA.transform.parent = m_TargetPrefabA.transform; - targetChildB.transform.parent = m_TargetPrefabB.transform; + sourceChild.transform.parent = m_AttachablePrefab.transform; + targetChildA.transform.parent = m_TargetNodePrefabA.transform; + targetChildB.transform.parent = m_TargetNodePrefabB.transform; - sourceChild.AddComponent(); - targetChildA.AddComponent(); + m_PrefabTestAttachable = sourceChild.AddComponent(); + var targetChildATestNode = targetChildA.AddComponent(); + targetChildATestNode.DetachOnDespawn = true; targetChildB.AddComponent(); base.OnServerAndClientsCreated(); } @@ -97,7 +103,8 @@ private bool ResetAllStates() m_ErrorLog.Clear(); var target = GetTargetInstance(); - + TestAttachable.LastKnownEventStates.Clear(); + TestAttachable.LastKnownOverrideStates.Clear(); // The attachable can move between the two spawned instances. var currentAttachableRoot = m_AttachableBehaviourInstance.State == AttachableBehaviour.AttachState.Attached ? target : m_SourceInstance; @@ -196,11 +203,11 @@ private bool AllInstancesAttachedStateChanged(bool checkAttached, bool ignoreIfD continue; } - if (!attachable.CheckStateChangedOverride(checkAttached, false, node)) + if (!attachable.CheckForState(checkAttached, false)) { m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}][{attachable.name}] Did not have its override invoked!"); } - if (!attachable.CheckStateChangedOverride(checkAttached, true, node)) + if (!attachable.CheckForState(checkAttached, true)) { m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}][{attachable.name}] Did not have its event invoked!"); } @@ -232,13 +239,15 @@ private bool AllInstancesDespawned() return true; } + [UnityTest] public IEnumerator AttachAndDetachTests() { var authority = GetAuthorityNetworkManager(); - m_SourceInstance = SpawnObject(m_SourcePrefab, authority).GetComponent(); - m_TargetInstance = SpawnObject(m_TargetPrefabA, authority).GetComponent(); - m_TargetInstanceB = SpawnObject(m_TargetPrefabB, authority).GetComponent(); + + m_SourceInstance = SpawnObject(m_AttachablePrefab, authority).GetComponent(); + m_TargetInstance = SpawnObject(m_TargetNodePrefabA, authority).GetComponent(); + m_TargetInstanceB = SpawnObject(m_TargetNodePrefabB, authority).GetComponent(); m_TargetInstanceId = m_TargetInstance.NetworkObjectId; yield return WaitForConditionOrTimeOut(AllClientsSpawnedInstances); AssertOnTimeout($"Timed out waiting for all clients to spawn {m_SourceInstance.name} and {m_TargetInstance.name}!\n {m_ErrorLog}"); @@ -271,6 +280,7 @@ public IEnumerator AttachAndDetachTests() // Reset all states and prepare for 2nd attach test Assert.True(ResetAllStates(), $"Failed to reset all states!\n {m_ErrorLog}"); + Debug.Log("Attaching Node-B"); // Now, while attached, attach to another attachable node which should detach from the current and attach to the new. m_AttachableBehaviourInstance.Attach(m_AttachableNodeInstanceB); @@ -315,14 +325,352 @@ public IEnumerator AttachAndDetachTests() AssertOnTimeout($"[Despawn Detach Phase] Timed out waiting for all clients to despawn {targetInstanceName}!"); } + + private bool OwnershipChangedOnAllInstances() + { + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager.SpawnManager != null && !networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_AttachableBehaviourInstance.NetworkObjectId)) + { + return false; + } + if (m_NonAuthority.LocalClientId != networkManager.SpawnManager.SpawnedObjects[m_AttachableBehaviourInstance.NetworkObjectId].OwnerClientId) + { + return false; + } + } + return true; + } + + + private bool ObjectDespawnedOnAllInstances(ulong networkObjectId) + { + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager.SpawnManager != null && networkManager.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId)) + { + return false; + } + } + return true; + } + + private bool AllInstancesDetached() + { + m_ErrorLog.Clear(); + // The attachable can move between the two spawned instances so we have to use the appropriate one depending upon the authority's current state. + var attachable = (TestAttachable)null; + foreach (var networkManager in m_NetworkManagers) + { + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_AttachableBehaviourInstance.NetworkObjectId)) + { + m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}] Has no spawned instance of {m_AttachableBehaviourInstance.name}!"); + continue; + } + else + { + attachable = networkManager.SpawnManager.SpawnedObjects[m_AttachableBehaviourInstance.NetworkObjectId].GetComponentInChildren(); + } + + if (!attachable) + { + m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}][Attachable] Attachable was not found!"); + continue; + } + + if (!attachable.CheckForState(false, false)) + { + m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}][{attachable.name}] Did not have its override invoked!"); + } + if (!attachable.CheckForState(false, true)) + { + m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}][{attachable.name}] Did not have its event invoked!"); + } + + if (attachable.AttachableNode != null) + { + var nodeHasAttachments = attachable.AttachableNode.HasAttachments ? $" {attachable.AttachableNode.name} still has attachments!" : ""; + m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}][{attachable.name}] Still refers to {attachable.AttachableNode.name}!{nodeHasAttachments}"); + } + } + return m_ErrorLog.Length == 0; + } + + private bool AllInstancesDetachedWhenAttachableDespawned() + { + m_ErrorLog.Clear(); + foreach (var networkManager in m_NetworkManagers) + { + var localClientId = networkManager.LocalClientId; + if (!TestAttachable.LastKnownOverrideStates.ContainsKey(localClientId)) + { + m_ErrorLog.AppendLine($"[Client-{localClientId}] Has no override states!"); + continue; + } + if (!TestAttachable.LastKnownEventStates.ContainsKey(localClientId)) + { + m_ErrorLog.AppendLine($"[Client-{localClientId}] Has no event states!"); + continue; + } + if (!TestAttachable.LastKnownOverrideStates[localClientId].ContainsKey(AttachableBehaviour.AttachState.Detached)) + { + m_ErrorLog.AppendLine($"[Client-{localClientId}] Does not contain the {AttachableBehaviour.AttachState.Detached} override state!"); + continue; + } + if (!TestAttachable.LastKnownEventStates[localClientId].ContainsKey(AttachableBehaviour.AttachState.Detached)) + { + m_ErrorLog.AppendLine($"[Client-{localClientId}] Does not contain the {AttachableBehaviour.AttachState.Detached} event state!"); + continue; + } + + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_TargetInstanceId)) + { + m_ErrorLog.AppendLine($"[Client-{localClientId}] Does not have a spawned target node ({m_TargetInstanceId})!"); + continue; + } + var targetNode = networkManager.SpawnManager.SpawnedObjects[m_TargetInstanceId].GetComponentInChildren(); + if (targetNode == null) + { + m_ErrorLog.AppendLine($"[Client-{localClientId}] Does not have a target node component (null)!"); + continue; + } + + if (targetNode.HasAttachments) + { + m_ErrorLog.AppendLine($"[Client-{localClientId}] {targetNode.name} still has attachments!"); + } + } + return m_ErrorLog.Length == 0; + } + + private NetworkManager m_NonAuthority; + private NetworkManager m_Authority; + + public enum DetachCombinations + { + AllFlags, + OwnerDespawn, + OwnerDestroy, + DespawnDestroy, + Owner, + Despawn, + Destroy + } + + private AttachableBehaviour.AutoDetachTypes GetDetachType(DetachCombinations detachCombination) + { + var autoDetachTypeFlags = AttachableBehaviour.AutoDetachTypes.OnDespawn | AttachableBehaviour.AutoDetachTypes.OnOwnershipChange | AttachableBehaviour.AutoDetachTypes.OnAttachNodeDestroy; + switch (detachCombination) + { + case DetachCombinations.AllFlags: + { + break; + } + case DetachCombinations.OwnerDespawn: + { + autoDetachTypeFlags = AttachableBehaviour.AutoDetachTypes.OnDespawn | AttachableBehaviour.AutoDetachTypes.OnOwnershipChange; + break; + } + case DetachCombinations.OwnerDestroy: + { + autoDetachTypeFlags = AttachableBehaviour.AutoDetachTypes.OnOwnershipChange | AttachableBehaviour.AutoDetachTypes.OnAttachNodeDestroy; + break; + } + case DetachCombinations.DespawnDestroy: + { + autoDetachTypeFlags = AttachableBehaviour.AutoDetachTypes.OnDespawn | AttachableBehaviour.AutoDetachTypes.OnAttachNodeDestroy; + break; + } + case DetachCombinations.Owner: + { + autoDetachTypeFlags = AttachableBehaviour.AutoDetachTypes.OnOwnershipChange; + break; + } + case DetachCombinations.Despawn: + { + autoDetachTypeFlags = AttachableBehaviour.AutoDetachTypes.OnDespawn; + break; + } + case DetachCombinations.Destroy: + { + autoDetachTypeFlags = AttachableBehaviour.AutoDetachTypes.OnAttachNodeDestroy; + break; + } + } + + return autoDetachTypeFlags; + } + + [UnityTest] + public IEnumerator AutoDetachTests([Values] DetachCombinations detachCombination) + { + var autoDetachTypeFlags = GetDetachType(detachCombination); + m_UseTargetB = false; + m_Authority = GetAuthorityNetworkManager(); + m_NonAuthority = GetNonAuthorityNetworkManager(); + + m_PrefabTestAttachable.AutoDetach = autoDetachTypeFlags; + + m_SourceInstance = SpawnObject(m_AttachablePrefab, m_Authority).GetComponent(); + m_TargetInstance = SpawnObject(m_TargetNodePrefabA, m_Authority).GetComponent(); + m_TargetInstanceB = SpawnObject(m_TargetNodePrefabB, m_Authority).GetComponent(); + m_TargetInstanceId = m_TargetInstance.NetworkObjectId; + yield return WaitForConditionOrTimeOut(AllClientsSpawnedInstances); + AssertOnTimeout($"Timed out waiting for all clients to spawn {m_SourceInstance.name} and {m_TargetInstance.name}!\n {m_ErrorLog}"); + + m_AttachableBehaviourInstance = m_SourceInstance.GetComponentInChildren(); + Assert.NotNull(m_AttachableBehaviourInstance, $"{m_SourceInstance.name} does not have a nested child {nameof(AttachableBehaviour)}!"); + + m_AttachableNodeInstance = m_TargetInstance.GetComponentInChildren(); + Assert.NotNull(m_AttachableNodeInstance, $"{m_TargetInstance.name} does not have a nested child {nameof(AttachableNode)}!"); + + m_AttachableNodeInstanceB = m_TargetInstanceB.GetComponentInChildren(); + Assert.NotNull(m_AttachableNodeInstanceB, $"{m_TargetInstanceB.name} does not have a nested child {nameof(AttachableNode)}!"); + + Assert.True(ResetAllStates(), $"Failed to reset all states!\n {m_ErrorLog}"); + m_AttachableBehaviourInstance.Attach(m_AttachableNodeInstance); + + yield return WaitForConditionOrTimeOut(() => AllInstancesAttachedStateChanged(true)); + AssertOnTimeout($"Timed out waiting for all clients to attach {m_AttachableBehaviourInstance.name} to {m_AttachableNodeInstance.name}!\n {m_ErrorLog}"); + + if (autoDetachTypeFlags.HasFlag(AttachableBehaviour.AutoDetachTypes.OnOwnershipChange)) + { + Assert.True(ResetAllStates(), $"Failed to reset all states!\n {m_ErrorLog}"); + if (m_DistributedAuthority) + { + m_Authority.SpawnManager.SpawnedObjects[m_SourceInstance.NetworkObjectId].ChangeOwnership(m_NonAuthority.LocalClientId); + } + else + { + m_SourceInstance.ChangeOwnership(m_NonAuthority.LocalClientId); + } + + yield return WaitForConditionOrTimeOut(OwnershipChangedOnAllInstances); + AssertOnTimeout($"[OnOwnershipChange] Timed out waiting for all clients to change the ownership from {m_Authority.name} to {m_NonAuthority.name}!"); + + yield return WaitForConditionOrTimeOut(AllInstancesDetached); + AssertOnTimeout($"[OnOwnershipChange] Timed out waiting for all clients to detach {m_AttachableBehaviourInstance.name} from {m_AttachableNodeInstance.name}!\n {m_ErrorLog}"); + + if (m_DistributedAuthority) + { + var nonAuthority = m_Authority; + m_Authority = m_NonAuthority; + m_NonAuthority = nonAuthority; + + m_SourceInstance = m_Authority.SpawnManager.SpawnedObjects[m_SourceInstance.NetworkObjectId]; + m_AttachableBehaviourInstance = m_SourceInstance.GetComponentInChildren(); + m_AttachableNodeInstance = m_Authority.SpawnManager.SpawnedObjects[m_AttachableNodeInstance.NetworkObjectId].GetComponentInChildren(); + Assert.NotNull(m_AttachableBehaviourInstance, $"{m_SourceInstance.name} does not have a nested child {nameof(AttachableBehaviour)}!"); + } + Assert.False(m_AttachableNodeInstance.IsAttached(m_AttachableBehaviourInstance), $"{m_AttachableNodeInstance.name} still thinks it is attached to {m_AttachableBehaviourInstance.name}!"); + Assert.True(ResetAllStates(), $"Failed to reset all states!\n {m_ErrorLog}"); + + m_AttachableBehaviourInstance.Attach(m_AttachableNodeInstance); + + yield return WaitForConditionOrTimeOut(() => AllInstancesAttachedStateChanged(true)); + AssertOnTimeout($"[OnOwnershipChange][End] Timed out waiting for all clients to attach {m_AttachableBehaviourInstance.name} to {m_AttachableNodeInstance.name}!\n {m_ErrorLog}"); + + // Reset context of the AttachableNode instance to the owner of the m_TargetInstance for other below tests + if (m_DistributedAuthority) + { + var attachableNodeInstanceName = m_AttachableNodeInstance.name; + var ownerNetworkManager = m_NetworkManagers.Where((c) => c.LocalClientId == m_AttachableNodeInstance.OwnerClientId).First(); + Assert.True(ownerNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(m_AttachableNodeInstance.NetworkObjectId), $"{ownerNetworkManager.name} does not have a spawned instance of {m_AttachableNodeInstance.name}!"); + m_AttachableNodeInstance = ownerNetworkManager.SpawnManager.SpawnedObjects[m_AttachableNodeInstance.NetworkObjectId].GetComponentInChildren(); + Assert.NotNull(m_AttachableNodeInstance, $"{attachableNodeInstanceName} does not exist on {ownerNetworkManager.name}!"); + } + } + + // Detach on despawn validation + if (autoDetachTypeFlags.HasFlag(AttachableBehaviour.AutoDetachTypes.OnDespawn)) + { + // Validates AttachableNode detaches AttachableBehaviours when despawned + Assert.True(ResetAllStates(), $"Failed to reset all states!\n {m_ErrorLog}"); + m_TargetInstance.Despawn(false); + + yield return WaitForConditionOrTimeOut(() => ObjectDespawnedOnAllInstances(m_TargetInstanceId)); + AssertOnTimeout($"[OnDespawn] Timed out waiting for all clients to despawn {m_TargetInstance.name}!"); + + yield return WaitForConditionOrTimeOut(AllInstancesDetached); + AssertOnTimeout($"[OnDespawn] Timed out waiting for all clients to detach {m_AttachableBehaviourInstance.name} from {m_AttachableNodeInstance.name}!\n {m_ErrorLog}"); + + m_TargetInstance.Spawn(); + + yield return WaitForConditionOrTimeOut(AllClientsSpawnedInstances); + AssertOnTimeout($"Timed out waiting for all clients to spawn {m_TargetInstance.name}!\n {m_ErrorLog}"); + + m_TargetInstanceId = m_TargetInstance.NetworkObjectId; + + Assert.True(ResetAllStates(), $"Failed to reset all states!\n {m_ErrorLog}"); + m_AttachableBehaviourInstance.Attach(m_AttachableNodeInstance); + + yield return WaitForConditionOrTimeOut(() => AllInstancesAttachedStateChanged(true)); + AssertOnTimeout($"[OnDespawn][End] Timed out waiting for all clients to attach {m_AttachableBehaviourInstance.name} to {m_AttachableNodeInstance.name}!\n {m_ErrorLog}"); + + // Validates when the AttachableBehaviour is despawned it will detach from the AttachableNode + Assert.True(ResetAllStates(), $"Failed to reset all states!\n {m_ErrorLog}"); + var sourceInstanceId = m_SourceInstance.NetworkObjectId; + var sourceName = m_SourceInstance.name; + var attachableName = m_AttachableBehaviourInstance.name; + m_SourceInstance.Despawn(false); + + yield return WaitForConditionOrTimeOut(() => ObjectDespawnedOnAllInstances(sourceInstanceId)); + AssertOnTimeout($"[OnDespawn] Timed out waiting for all clients to despawn {sourceName}!"); + + yield return WaitForConditionOrTimeOut(AllInstancesDetachedWhenAttachableDespawned); + AssertOnTimeout($"[OnDespawn] Timed out waiting for all clients to detach {attachableName} from {m_AttachableNodeInstance.name}!\n {m_ErrorLog}"); + + m_SourceInstance.Spawn(); + + yield return WaitForConditionOrTimeOut(AllClientsSpawnedInstances); + AssertOnTimeout($"Timed out waiting for all clients to spawn {m_TargetInstance.name}!\n {m_ErrorLog}"); + + Assert.True(ResetAllStates(), $"Failed to reset all states!\n {m_ErrorLog}"); + if (m_DistributedAuthority) + { + m_AttachableBehaviourInstance = m_SourceInstance.GetComponentInChildren(); + Assert.NotNull(m_AttachableBehaviourInstance, $"{m_SourceInstance.name} does not have a nested child {nameof(AttachableBehaviour)}!"); + } + + m_AttachableBehaviourInstance.Attach(m_AttachableNodeInstance); + + yield return WaitForConditionOrTimeOut(() => AllInstancesAttachedStateChanged(true)); + AssertOnTimeout($"[OnDespawn][End] Timed out waiting for all clients to attach {m_AttachableBehaviourInstance.name} to {m_AttachableNodeInstance.name}!\n {m_ErrorLog}"); + } + + // Detach on destroy validation + if (autoDetachTypeFlags.HasFlag(AttachableBehaviour.AutoDetachTypes.OnAttachNodeDestroy)) + { + Assert.True(ResetAllStates(), $"Failed to reset all states!\n {m_ErrorLog}"); + // Mock the edge case scenario where the AttachableNode could be destroyed when an AttachableBehaviour is attached. + // Remove all other flags but the OnAttachNodeDestroy to assure this is what is invoked when the spawned AttachableNode (TargetInstance) + // is destroyed. + foreach (var networkManager in m_NetworkManagers) + { + var targetInstance = networkManager.SpawnManager.SpawnedObjects[m_TargetInstance.NetworkObjectId]; + var attachable = targetInstance.GetComponentInChildren(); + // Directly assign the value to assure this is the only thing that will trigger a detach + attachable.AutoDetach = AttachableBehaviour.AutoDetachTypes.OnAttachNodeDestroy; + } + var attachableNodeName = m_AttachableNodeInstance.name; + Object.Destroy(m_TargetInstance.gameObject); + yield return WaitForConditionOrTimeOut(AllInstancesDetached); + AssertOnTimeout($"[OnAttachNodeDestroy] Timed out waiting for all clients to detach {m_AttachableBehaviourInstance.name} from {attachableNodeName}!\n {m_ErrorLog}"); + } + } + /// /// Helps to validate that the overrides and events are invoked when an attachable attaches or detaches from the instance. /// This also helps to validate that the appropriate instance is passed in as a parameter. /// - public class TestAttachable : AttachableBehaviour + internal class TestAttachable : AttachableBehaviour { - private Dictionary m_StateUpdates = new Dictionary(); + public static bool VerboseMode; + + public static readonly Dictionary> LastKnownOverrideStates = new Dictionary>(); + public static readonly Dictionary> LastKnownEventStates = new Dictionary>(); + private Dictionary m_StateUpdates = new Dictionary(); private Dictionary m_StateUpdateEvents = new Dictionary(); public GameObject DefaultParent => m_DefaultParent; @@ -337,17 +685,47 @@ public override void OnNetworkSpawn() public override void OnNetworkDespawn() { + if (!LastKnownOverrideStates.ContainsKey(NetworkManager.LocalClientId)) + { + var localClientId = NetworkManager.LocalClientId; + LastKnownOverrideStates.Add(localClientId, new Dictionary()); + LastKnownEventStates.Add(localClientId, new Dictionary()); + + foreach (var overrideEntry in m_StateUpdates) + { + LastKnownOverrideStates[localClientId].Add(overrideEntry.Key, overrideEntry.Value); + } + + foreach (var eventEntry in m_StateUpdateEvents) + { + LastKnownEventStates[localClientId].Add(eventEntry.Key, eventEntry.Value); + } + } AttachStateChange -= OnAttachStateChangeEvent; base.OnNetworkDespawn(); } private void OnAttachStateChangeEvent(AttachState attachState, AttachableNode attachableNode) { + Log($"[Event][{name}][AttachState Changed] State: {attachState}"); + // When attaching to a new target node while attached to an existing one, just overwrite + // to get the most current attach state. + if (m_StateUpdateEvents.ContainsKey(attachState)) + { + m_StateUpdateEvents.Remove(attachState); + } m_StateUpdateEvents.Add(attachState, attachableNode); } protected override void OnAttachStateChanged(AttachState attachState, AttachableNode attachableNode) { + Log($"[Override][{name}][AttachState Changed] State: {attachState}"); + // When attaching to a new target node while attached to an existing one, just overwrite + // to get the most current attach state. + if (m_StateUpdates.ContainsKey(attachState)) + { + m_StateUpdates.Remove(attachState); + } m_StateUpdates.Add(attachState, attachableNode); base.OnAttachStateChanged(attachState, attachableNode); } @@ -360,45 +738,39 @@ public void ResetStates() private void Log(string message) { + if (!VerboseMode) + { + return; + } Debug.Log($"[{name}] {message}"); } - public bool CheckStateChangedOverride(bool checkAttached, bool checkEvent, AttachableNode attachableNode) + public bool CheckForState(bool checkAttached, bool checkEvent) { var tableToCheck = checkEvent ? m_StateUpdateEvents : m_StateUpdates; - var checkStatus = checkAttached ? (tableToCheck.ContainsKey(AttachState.Attaching) && tableToCheck.ContainsKey(AttachState.Attached)) : - (tableToCheck.ContainsKey(AttachState.Detaching) && tableToCheck.ContainsKey(AttachState.Detached)); + var expectedState = checkAttached ? AttachState.Attached : AttachState.Detached; + var checkStatus = tableToCheck.ContainsKey(expectedState); + if (checkStatus) { - foreach (var entry in tableToCheck) + if ((checkAttached && transform.parent == DefaultParent.transform) || (!checkAttached && transform.parent != DefaultParent.transform)) { - // Ignore any states that don't match what is being checked - if ((checkStatus && (entry.Key == AttachState.Detaching || entry.Key == AttachState.Detached)) || - (!checkStatus && (entry.Key == AttachState.Attaching || entry.Key == AttachState.Attached))) + if (!checkAttached) { - continue; + Log($"[CheckState][Fail][Wrong Parent] checkAttached = {checkAttached} | parent = {transform.parent?.name} | Expected {DefaultParent.name}"); } - - // Special case for completely detached - if (entry.Key == AttachState.Detached) - { - if (entry.Value != null) - { - Log($"[Value] The value {entry.Value.name} is not null!"); - checkStatus = false; - break; - } - } - else if (entry.Value != attachableNode) + else { - var attachableName = attachableNode == null ? "null" : attachableNode.name; - var entryName = entry.Value == null ? "null" : entry.Value.name; - Log($"[{entry.Key}][Value] The value {entryName} is not the same as {attachableName}!"); - checkStatus = false; - break; + Log($"[CheckState][Fail][Wrong Parent] checkAttached = {checkAttached} | parent = {transform.parent?.name}"); } + return false; } } + else + { + var checkType = checkEvent ? "m_StateUpdateEvents" : "m_StateUpdates"; + Log($"[CheckState][Fail][No Event Logged] checkAttached = {checkAttached} | table {checkType} does not contain the expected state {expectedState} log."); + } return checkStatus; } } @@ -406,11 +778,16 @@ public bool CheckStateChangedOverride(bool checkAttached, bool checkEvent, Attac /// /// Helps to validate that the overrides are invoked when an attachable attaches or detaches from the instance. /// - public class TestNode : AttachableNode + internal class TestNode : AttachableNode { public bool OnAttachedInvoked { get; private set; } public bool OnDetachedInvoked { get; private set; } + public bool IsAttached(AttachableBehaviour attachableBehaviour) + { + return m_AttachedBehaviours.Contains(attachableBehaviour); + } + public void ResetStates() { OnAttachedInvoked = false; @@ -429,5 +806,10 @@ protected override void OnDetached(AttachableBehaviour attachableBehaviour) base.OnDetached(attachableBehaviour); } } + + internal class TestController : ComponentController + { + + } } } From 741b42bb5800404adad5dd151b10a5100dd3fc04 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 30 Jul 2025 00:42:08 -0500 Subject: [PATCH 29/43] docs (update,, refactor. and fix) Adjusting the component section from a file and folder context. Adjusting the component section for the table of contents. Adding foundational components and helpers sub-sections to network components section. Moved foundational components into the foundational (components) folder and updated links and image paths. Adding AttachableBehaviour document along with images. Added some place holders for AttachableNode and ComponentController. Fixing some invalid image paths. --- .../Documentation~/TableOfContents.md | 26 +-- .../Documentation~/advanced-topics/physics.md | 2 +- .../advanced-topics/session-management.md | 2 +- .../Documentation~/basics/object-spawning.md | 2 +- .../basics/object-visibility.md | 2 +- .../Documentation~/basics/ownership.md | 2 +- .../scenemanagement/custom-management.md | 2 +- .../inscene-placed-networkobjects.md | 2 +- .../basics/scenemanagement/scene-events.md | 6 +- .../components/Helpers/attachablebehaviour.md | 166 ++++++++++++++++++ .../components/Helpers/helpercomponents.md | 12 ++ .../{ => Helpers}/networkanimator.md | 8 +- .../{ => Helpers}/networktransform.md | 30 ++-- .../foundational/foundationalcomponents.md | 34 ++++ .../networkbehaviour-synchronize.md | 2 +- .../foundational}/networkbehaviour.md | 0 .../{ => foundational}/networkmanager.md | 22 +-- .../foundational}/networkobject.md | 20 +-- .../foundational}/playerobjects.md | 22 +-- .../AttachableBehaviour_InspectorView-1.png | Bin 0 -> 23713 bytes .../images/attachable/AttachableDiagram-1.png | Bin 0 -> 70586 bytes .../images/attachable/AttachableDiagram-2.png | Bin 0 -> 69939 bytes .../images/attachable/AttachableDiagram-3.png | Bin 0 -> 85502 bytes .../AttachableNode_InspectorView-1.png | Bin 0 -> 8167 bytes .../ComponentController_InspectorView-1.png | Bin 0 -> 43859 bytes .../attachable/PlayerAndWorldItem-1.png | Bin 0 -> 7087 bytes .../attachable/PlayerAndWorldItem-2.png | Bin 0 -> 8959 bytes .../images/attachable/SpawnObjectA-B-2.png | Bin 0 -> 4244 bytes .../images/attachable/SpawnObjectA-B.png | Bin 0 -> 2763 bytes .../attachable/WorldItem-Inspector-View-1.png | Bin 0 -> 72855 bytes .../attachable/WorldItem-Inspector-View-2.png | Bin 0 -> 41631 bytes .../learn/clientside-interpolation.md | 2 +- .../learn/dealing-with-latency.md | 8 +- .../Documentation~/network-components.md | 9 +- .../networkbehaviour-landing.md | 7 +- .../Documentation~/samples.md | 4 +- .../samples/bitesize/bitesize-clientdriven.md | 2 +- .../samples/bitesize/bitesize-invaders.md | 2 +- .../bossroom/networkobject-parenting.md | 2 +- .../samples/bossroom/optimizing-bossroom.md | 6 +- .../tutorials/get-started-with-ngo.md | 2 +- .../Documentation~/tutorials/helloworld.md | 2 +- .../testing_with_artificial_conditions.md | 2 +- 43 files changed, 311 insertions(+), 99 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablebehaviour.md create mode 100644 com.unity.netcode.gameobjects/Documentation~/components/Helpers/helpercomponents.md rename com.unity.netcode.gameobjects/Documentation~/components/{ => Helpers}/networkanimator.md (97%) rename com.unity.netcode.gameobjects/Documentation~/components/{ => Helpers}/networktransform.md (94%) create mode 100644 com.unity.netcode.gameobjects/Documentation~/components/foundational/foundationalcomponents.md rename com.unity.netcode.gameobjects/Documentation~/{basics => components/foundational}/networkbehaviour-synchronize.md (98%) rename com.unity.netcode.gameobjects/Documentation~/{basics => components/foundational}/networkbehaviour.md (100%) rename com.unity.netcode.gameobjects/Documentation~/components/{ => foundational}/networkmanager.md (90%) rename com.unity.netcode.gameobjects/Documentation~/{basics => components/foundational}/networkobject.md (86%) rename com.unity.netcode.gameobjects/Documentation~/{basics => components/foundational}/playerobjects.md (77%) create mode 100644 com.unity.netcode.gameobjects/Documentation~/images/attachable/AttachableBehaviour_InspectorView-1.png create mode 100644 com.unity.netcode.gameobjects/Documentation~/images/attachable/AttachableDiagram-1.png create mode 100644 com.unity.netcode.gameobjects/Documentation~/images/attachable/AttachableDiagram-2.png create mode 100644 com.unity.netcode.gameobjects/Documentation~/images/attachable/AttachableDiagram-3.png create mode 100644 com.unity.netcode.gameobjects/Documentation~/images/attachable/AttachableNode_InspectorView-1.png create mode 100644 com.unity.netcode.gameobjects/Documentation~/images/attachable/ComponentController_InspectorView-1.png create mode 100644 com.unity.netcode.gameobjects/Documentation~/images/attachable/PlayerAndWorldItem-1.png create mode 100644 com.unity.netcode.gameobjects/Documentation~/images/attachable/PlayerAndWorldItem-2.png create mode 100644 com.unity.netcode.gameobjects/Documentation~/images/attachable/SpawnObjectA-B-2.png create mode 100644 com.unity.netcode.gameobjects/Documentation~/images/attachable/SpawnObjectA-B.png create mode 100644 com.unity.netcode.gameobjects/Documentation~/images/attachable/WorldItem-Inspector-View-1.png create mode 100644 com.unity.netcode.gameobjects/Documentation~/images/attachable/WorldItem-Inspector-View-2.png diff --git a/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md b/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md index f784cc5148..9759fa90a7 100644 --- a/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md +++ b/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md @@ -18,17 +18,21 @@ * [Max players](basics/maxnumberplayers.md) * [Transports](advanced-topics/transports.md) * [Relay](relay/relay.md) -* [Network components](network-components.md) - * [NetworkObject](basics/networkobject.md) - * [PlayerObjects and player prefabs](basics/playerobjects.md) - * [NetworkObject parenting](advanced-topics/networkobject-parenting.md) - * [NetworkBehaviour](networkbehaviour-landing.md) - * [NetworkBehaviour](basics/networkbehaviour.md) - * [Synchronize](basics/networkbehaviour-synchronize.md) - * [Physics](advanced-topics/physics.md) - * [NetworkManager](components/networkmanager.md) - * [NetworkTransform](components/networktransform.md) - * [NetworkAnimator](components/networkanimator.md) +* [Network components](network-components.md) + * [Foundational Components](components/foundational/foundationalcomponents.md) + * [NetworkObject](components/foundational/networkobject.md) + * [NetworkObject parenting](advanced-topics/networkobject-parenting.md) + * [NetworkBehaviour](components/foundational/networkbehaviour.md) + * [Synchronizing & Order of Operations](components/foundational/networkbehaviour-synchronize.md) + * [NetworkManager](components/foundational/networkmanager.md) + * [PlayerObjects and player prefabs](components/foundational/playerobjects.md) + * [Helper Components](components/Helpers/helpercomponents.md) + * [AttachableBehaviour](components/Helpers/attachablebehaviour.md) + * AttachableNode + * ComponentController + * [NetworkAnimator](components/helpers/networkanimator.md) + * [NetworkTransform](components/helpers/networktransform.md) + * [Physics](advanced-topics/physics.md) * [Ownership and authority](ownership-authority.md) * [Understanding ownership and authority](basics/ownership.md) * [Ownership race conditions](basics/race-conditions.md) diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/physics.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/physics.md index 72dd8c5d9f..6b009f3d51 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/physics.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/physics.md @@ -21,7 +21,7 @@ Some collision events aren't fired when using `NetworkRigidbody`. ## NetworkRigidbody and ClientNetworkTransform -You can use NetworkRigidbody with the [`ClientNetworkTransform`](../components/networktransform.md) package sample to allow the owner client of a NetworkObject to move it authoritatively. In this mode, collisions only result in realistic dynamic collisions if the object is colliding with other NetworkObjects (owned by the same client). +You can use NetworkRigidbody with the [`ClientNetworkTransform`](../components/helpers/networktransform.md) package sample to allow the owner client of a NetworkObject to move it authoritatively. In this mode, collisions only result in realistic dynamic collisions if the object is colliding with other NetworkObjects (owned by the same client). > [!NOTE] > Add the ClientNetworkTransform component to your GameObject first. Otherwise the NetworkRigidbody automatically adds a regular NetworkTransform. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/session-management.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/session-management.md index 6f3e0f0d19..ba3d699ed2 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/session-management.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/session-management.md @@ -19,7 +19,7 @@ You can also decide to clear all data when a session completes or add a timeout # Reconnection -The best way to reconnect players depends on your game. For example, if you use a [Player Object](../basics/networkobject.md#player-objects), a new `Default Player Prefab` automatically spawns when a player connects to the game (including when they reconnect). You can use the player's earlier saved session data to update that object so that it returns to the same state before disconnecting. In those cases, you would need to keep all the important data that you want to restore and map it to the player using your identification system. You can save this data when a player disconnects or update it periodically. You can then use the `OnNetworkSpawn` event on the Player Object's `NetworkBehavior`(s) to get this data and apply it where needed. +The best way to reconnect players depends on your game. For example, if you use a [Player Object](../components/foundational/networkobject.md#player-objects), a new `Default Player Prefab` automatically spawns when a player connects to the game (including when they reconnect). You can use the player's earlier saved session data to update that object so that it returns to the same state before disconnecting. In those cases, you would need to keep all the important data that you want to restore and map it to the player using your identification system. You can save this data when a player disconnects or update it periodically. You can then use the `OnNetworkSpawn` event on the Player Object's `NetworkBehavior`(s) to get this data and apply it where needed. In cases where we don't use the Player Object approach and instead manually attribute client ownership to `NetworkObject`(s), we can keep the objects that a player owns when they disconnect, and set the reconnected player as their new owner. To accomplish this, the only data we would need to keep would be the mapping between those objects and their owning player's identifier, then when a player reconnects we can use this mapping to set them as the new owner. This mapping can be as simple as a dictionary mapping the player identifier with the `NetworkObjectId`(s) of the `NetworkObject`(s) they own. Then, in the `OnClientConnectedCallback` from the `NetworkManager`, the server can set the ownership of these objects. diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/object-spawning.md b/com.unity.netcode.gameobjects/Documentation~/basics/object-spawning.md index ffab9107fa..0fa234bba8 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/object-spawning.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/object-spawning.md @@ -31,7 +31,7 @@ _In most cases, you will want to keep the `NetworkObject` component attached to By default a newly spawned network Prefab instance is owned by the server unless otherwise specified. -See [Ownership](networkobject.md#ownership) for more information. +See [Ownership](../components/foundational/networkobject.md#ownership) for more information. The following is a basic example of how to spawn a network Prefab instance (with the default server ownership): diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/object-visibility.md b/com.unity.netcode.gameobjects/Documentation~/basics/object-visibility.md index 1c730811ed..6d98cb5990 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/object-visibility.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/object-visibility.md @@ -111,4 +111,4 @@ NetworkObject.SpawnWithObservers = false; NetworkObject.Spawn(); ``` -See [Spawning With (or Without) Observers](networkobject.md#spawning-with-or-without-observers) for more information. +See [Spawning With (or Without) Observers](../components/foundational/networkobject.md#spawning-with-or-without-observers) for more information. diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/ownership.md b/com.unity.netcode.gameobjects/Documentation~/basics/ownership.md index 93035f2653..cd63291f66 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/ownership.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/ownership.md @@ -1,6 +1,6 @@ # Understanding ownership and authority -By default, Netcode for GameObjects assumes a [client-server topology](../terms-concepts/client-server.md), in which the server owns all NetworkObjects (with [some exceptions](networkobject.md#ownership)) and has ultimate authority over [spawning and despawning](object-spawning.md). +By default, Netcode for GameObjects assumes a [client-server topology](../terms-concepts/client-server.md), in which the server owns all NetworkObjects (with [some exceptions](../components/foundational/networkobject.md#ownership)) and has ultimate authority over [spawning and despawning](object-spawning.md). Netcode for GameObjects also supports building games with a [distributed authority topology](../terms-concepts/distributed-authority.md), which provides more options for ownership and authority over NetworkObjects. diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/custom-management.md b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/custom-management.md index fc64c2d204..883ad9914a 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/custom-management.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/custom-management.md @@ -39,7 +39,7 @@ Once you've registered your in-scene placed Network Prefabs with your `NetworkPr > When a client first connects, it deletes any in-scene placed `NetworkObjects` in any of the scenes it has currently loaded. When using a custom scene management solution, in-scene placed `NetworkObject`s are actually dynamically spawned. This means any changes you make to your in-scene placed Network Prefabs will *not* be synchronized with clients automatically. ### Synchronizing In-Scene Placed Network Prefab Instances -If you want to change an in-scene placed network prefab instance, you need to handle the serialization of these settings yourself. You can do this by overriding `NetworkBehaviour.OnSynchronize` and serializing any property updates you want to have synchronized with clients when they join. [Read More About OnSynchronize Here](../../basics/networkbehaviour.md#pre-spawn-synchronization). +If you want to change an in-scene placed network prefab instance, you need to handle the serialization of these settings yourself. You can do this by overriding `NetworkBehaviour.OnSynchronize` and serializing any property updates you want to have synchronized with clients when they join. [Read More About OnSynchronize Here](../../components/foundational/networkbehaviour.md#pre-spawn-synchronization). ## Starting a Netcode Enabled Game Session The recommended way of starting session using your own scene management solution is to assure that when a client attempts to join a netcode game session it should already have (as best as possible) any scenes that the server might have loaded. While this does not assure that your newly connecting client will load any additional scenes that might have been loaded, using this approach initially will get you started so you can then come up with a strategy to handling: diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/inscene-placed-networkobjects.md b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/inscene-placed-networkobjects.md index 28fcbe3714..4bc8228a36 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/inscene-placed-networkobjects.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/inscene-placed-networkobjects.md @@ -13,7 +13,7 @@ In-scene placed `NetworkObject`s are GameObjects with a `NetworkObject` componen - For example, a heads up display (HUD) that includes information about other items or players. - Or some form of platform or teleporter that moves a player from one location to the next when a player enters a trigger or uses an object. -Another benefit of in-scene placed `NetworkObject`s is that they don't require you to register them with the [`NetworkManager`](../../components/networkmanager.md). In-scene placed `NetworkObjects` are registered internally, when scene management is enabled, for tracking and identification purposes. +Another benefit of in-scene placed `NetworkObject`s is that they don't require you to register them with the [`NetworkManager`](../../components/foundational/networkmanager.md). In-scene placed `NetworkObjects` are registered internally, when scene management is enabled, for tracking and identification purposes. > [!NOTE] > Items that can be picked up are typically better implemented using a [hybrid approach](#hybrid-approach) with both an in-scene placed and a dynamically spawned `NetworkObject`. The in-scene placed `NetworkObject` can be used to configure additional information about the item (what kind, does another one respawn after one is picked up, and if so how much time should it wait before spawning a new item), while the dynamically spawned object is the item itself. diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/scene-events.md b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/scene-events.md index e178aa2058..89933f4dcc 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/scene-events.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/scene-events.md @@ -57,7 +57,7 @@ While client synchronization does fall partially outside of the scene management
Below is a diagram of the client connection approval and synchronization process: -![image](../images/scenemanagement_synchronization_overview.png) +![image](../../images/scenemanagement_synchronization_overview.png) Starting with the "Player" in the top right part of the above diagram, the client (Player) runs through the connection and approval process first which occurs within the `NetworkManager`. Once approved, the server-side `NetworkSceneManager` begins the client synchronization process by sending the `SceneEventType.Synchronize` Scene Event message to the approved client. The client then processes through the synchronization message. Once the client is finished processing the synchronize message, it responds to the server with a `SceneEventType.SynchronizeComplete` message. At this point the client is considered "synchronized". If the server determines any `NetworkObject` was despawned during the client-side synchronization message processing period, it will send a list of `NetworkObject` identifiers to the client via the `SceneEventType.ReSynchronize` message and the client will locally despawn the `NetworkObject`s. @@ -228,10 +228,10 @@ You can stop the coroutine checking the progress upon receiving any of the follo The SceneEvent class has values that may or may not be set depending upon the `SceneEventType`. Below are two quick lookup tables to determine which property is set for each `SceneEventType`. **Part-1**
-![image](../images/SceneEventProperties-1.png)
+![image](../../images/SceneEventProperties-1.png)
**Part-2**
-![image](../images/SceneEventProperties-2.png)
+![image](../../images/SceneEventProperties-2.png)
So, the big "take-away" from the above table is that you need to understand the `SceneEventType` context of the `SceneEvent` you are processing to know which properties are valid and you can use. As an example, it wouldn't make sense to provide the AsyncOperation for the following `SceneEventType`s: - LoadComplete or LoadEventCompleted diff --git a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablebehaviour.md b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablebehaviour.md new file mode 100644 index 0000000000..2bbc29c4a5 --- /dev/null +++ b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablebehaviour.md @@ -0,0 +1,166 @@ +# AttachableBehaviour +The `AttachableBehaviour` Provides "out of the box" support for attaching (i.e. parenting) a nested child `GameObject` that includes an `AttachableBehaviour` component to another nested child `GameObject` with an `AttachableNode` component that is associated with a different `NetworkObject`. + +## Attaching vs NetworkObject parenting + +Fundamentally, attaching is another way to synchronize parenting while not requiring one to use the traditional `NetworkObject` parenting. Attaching a child `GameObject` nested under a `NeworkObject` (_really the `GameObject` the `NetworkObject` component belongs to_) will only take the child `GameObject` and parent it under the `GameObject` of an `AttachableNode`. The target to parent under must be of a different spawned `NetworkObject` and the `AttachableNode` needs to be on the same or child `GameObject` of the target `NetworkObject`. + +### NetworkObject parenting + +The traditional approach has been to spawn two network prefab instances:
+![alt text](../../images/attachable/SpawnObjectA-B.png) + +Then parent one instance under the other:
+![alt text](../../images/attachable/SpawnObjectA-B-2.png) + +This is simple enough for many scenarios, but can become cumbersome under more specific scenarios where a user might want to have a "world" version of the item and a "picked up" version of the item. + +### Attaching + +With attaching, a user would create nested `GameObject` children that represent the item when it is picked up and when it is dropped/placed somewhere in the scene (i.e. world).
+![alt text](../../images/attachable/PlayerAndWorldItem-1.png) + + - The WorldItemRoot is where the `NetworkObject` component is placed. + - The NestedChild-World contains the components needed for the item when it is placed in the world. + - The NestedChild-PickedUp contains the components needed for the item when it is picked up by a player. + +By placing an `AttachableBehaviour` component on the NestedChild-PickedUp `GameObject` and an `AttachableNode` component on the TargetNode, a user can then invoke the `AttachableBehaviour.Attach` method while passing in the `AttachableNode` component and the NestedChild-PickedUp `GameObject` will get parented under the TargetNode while also synchronizing this action with all other clients.
+![alt text](../../images/attachable/PlayerAndWorldItem-2.png) + +### AttachableBehaviour + +![alt text](../../images/attachable/AttachableBehaviour_InspectorView-1.png) + +The basic functionality of the `AttachableBehaviour` component provides: +- The ability to assign (make aware) `ComponetController` components from any part of the parent-child hierarchy. + - Each `ComponentControllerEntry` provides the ability to select when the `ComponentController` should be triggered (via the **Auto Trigger** property) and whether its enabled state should be enabled or disabled upon attaching (via the **Enable On Attach** property). The default setting is to be disabled upon the `AttachableBehaviour` attaching to an `AttachableNode` and enabled upon detaching. When the **Enable On Attach** property is enabled, the `ComponentController` will be set to enabled upon the `AttachableBehaviour` attaching to an `AttachableNode` and disabled upon detaching. +- The ability to control when an `AttachableBehaviour` component will automatically detach from an `AttachableNode` via the **Auto Detach** property. + - The **Auto Detach** property can have any combination of the below flags or none (no flags): + - **On Ownership Changed:** Upon ownership changing, the `AttachableBehaviour` will detach from any `AttachableNode` it is attached to. + - **On Despawn:** Upon the `AttachableBehaviour` being despawned, it will detach from any `AttachableNode` it is attached to. + - **On Attach Node Destroy**: Just prior to the `AttachableNode` being destroyed, any attached `AttachableBehaviour` with this flag will automatically detach from the `AttachableNode`. + +_Any of the `AttachableBehaviour.AutoDetach` settings will be invoked on all instances without the need for the owner to synchronize the end result(i.e. detaching) which provides a level of redundancy for edge case scenarios like a player being disconnected abruptly by the host or by timing out or any scenario where a spawned object is being destroyed with the owner or perhaps being redistributed to another client authority in a distributed authority session. Having the ability to select or deselect any of the auto-detach flags coupled with the ability to derive from `AttachableBehaviour` provides additional levels of modularity/customization._ + +### AttachableNode + +![alt text](../../images/attachable/AttachableNode_InspectorView-1.png) + +The simplest component in the bunch, this provides a valid connection point (_i.e. what an `AttachableBehaviour` can attach to_) with the ability to have it automatically detach from any attached `AttachableBehaviour` instances when it is despawned. + +### ComponentController + +![alt text](../../images/attachable/ComponentController_InspectorView-1.png) + +Taking the above example into consideration, it would make sense that a user would want to be able to easily control whether a specific component is enabled or disabled when something is attached or detached. + +As an example: + +- When the WorldItemRoot is in the "placed in the world" state, it would make sense to disable any `MeshRenderer`, `Collider`, and other components on the NestedChild-PickedUp `GameObject` while enabling similar types of components on the NestedChild-World. +- When the WorldItemRoot is in the "picked up" state, it would make sense to enable any `MeshRenderer`, `Collider`, and other components on the NestedChild-PickedUp `GameObject` while disabling similar types of components on the NestedChild-World. +- It would also make sense to synchronize the enabling or disabling of components with all instances. + +The `ComponentController` provides this type of functionality: +- Can be used with `AttachableBehaviour` or independently for another purpose. +- Each assigned component entry can be configured to directly or inversely follow the `ComponentController`'s current state. +- Each assigned component entry can have an enable and/or disable delay. + - _When invoked internally by `AttachableBehaviour`, delays are ignored when an `AttachableNode` is being destroyed and the changes are immediate._ + +The `ComponentController` could be daisy chained with minimal user script: +```csharp +/// +/// Use as a component in the ComponentController that will +/// trigger the Controller (ComponentController). +/// This pattern can repeat. +/// +public class DaisyChainedController : MonoBehaviour +{ + public ComponentController Controller; + + private void OnEnable() + { + if (!Controller || !Controller.HasAuthority) + { + return; + } + Controller.SetEnabled(true); + } + + private void OnDisable() + { + if (!Controller || !Controller.HasAuthority) + { + return; + } + Controller.SetEnabled(false); + } +} +``` + +### Example of synchronized RPC driven properties + +Both the `AttachableBehaviour` and the `ComponentController` provide an example of using synchronized RPC driven properties in place of `NetworkVariable`. Under certain conditions it is better to use RPCs when a specific order of operations is needed as opposed to `NetworkVariable`s which can update out of order (regarding the order in which certain states were updated) depending upon several edge case scenarios. + +Under this condition using reliable RPCs will assure the messages are received in the order they were generated while also reducing the latency time between the change and the non-authority instances being notified of the change. Synchronized RPC driven properties only require overriding the `NetworkBehaviour.OnSynchronize` method and serializing any properties that need to be synchronized with late joining players or handling network object visibility related scenarios. + +## Usage Walk Through + +### Introduction + +For example purposes, we will walk through a common scenario where you might want to have a world item that had unique visual and scripted components active while while placed in the world but then can switch to a different set of visual and scripted components when picked up by a player's avatar. Additionally, you might want to be able to easily "attach" only the portion of the item, that is active when picked up, to one of the player's avatar's child nodes. Below is a high-level diagram overview of what both the player and world item network prefabs could look like:
+ +![alt text](../../images/attachable/AttachableDiagram-1.png) + +#### Player + +The player prefab in the above diagram is not complete, includes the components of interest, and some additional children and components for example purposes. A complete diagram would most definitely have additional components and children. The `AttachableNode` components provide a "target attach point" that any other spawned network prefab with an `AttachableBehaviour` could attach itself to. + +#### World Item + +This diagram has a bit more detail to it and introduces one possible usage of a `ComponentController` and `AttachableBehaviour`. The `ComponentController` will be used to control the enabling and disabling of components and synchronizing this with non-authority instances. The `AttachableBehaviour` resides on the child `AttachedView`'s `GameObject` and will be the catalyst for attaching to a player. + +### World vs Attached View Modes + +![alt text](../../images/attachable/AttachableDiagram-2.png) + + +In the diagram above, we see arrows pointing from the `ComponentController` to the non-netcode standard Unity components such as a `MeshRenderer`, `Collider`, or any other component that should only be enabled when either in "World View" or "Attached View" modes. We can also see that the `AttachableBehaviour` points to the `ComponentController` with a diagram to the right that shows the `AttachableBehaviour` notifies the `ComponentController` that, in turn, enables or disables certain components. + +#### World Item Component Controller +Below is a screenshot of what the `ComponentController` would look like in the inspector view:
+ +![alt text](../../images/attachable/WorldItem-Inspector-View-1.png) + +Looking at the `ComponentController`'s **Components** property, we can see two of the component entries have references to the `WorldItemView`'s `BoxCollider` and `MeshRenderer` that are both configured to be enabled when the `ComponentController`'s state is `true`. We can also see that the `CarryView`'s `MeshRenderer` is added and configured to be the inverse of the current `ComponentController`'s state. Since the `ComponentController`'s **Start Enabled** property is enabled we can logically deduce the **WorldItem** network prefab will start with the `WorldItemView` being active when spawned. Taking a look at the **CarryObject** child's properties: + +![alt text](../../images/attachable/WorldItem-Inspector-View-2.png) + +We can see the `AttachableBehaviour`'s **Component Controllers** list contains `ComponentControllerEntry` (WorldItem Component Controller) that references to the `WorldItem`'s `ComponentController`. We can also see that the `ComponentControllerEntry` is configured to trigger on everything (_OnAttach and OnDetach_) and will set the `ComponentController`'s state to disabled _(false)_. This means when the `AttachableBehaviour` is attached the `ComponentController` will be in the disabled state along with the `WorldItemView` components while the `CarryView`'s `MeshRenderer` will be enabled. + +**Summarized Overview:** +- `AttachableBehaviour` sets the `ComponentController` state (true/enabled or false/disabled). +- `ComponentController` states: + - Enabled (true) + - World Item View (enabled/true) + - Carry View (disabled/false) + - Disabled (false) + - World Item View (disabled/false) + - Carry View (enabled/true) + +### Attaching + +![alt text](../../images/attachable/AttachableDiagram-3.png) + +The above diagram represents what the **Player** and **World Item** spawned objects (_including cloned/non-authority instances_) would look like once the **Attached View** object has been parented under the avatar's **Right Attach** object. The green area and arrow represent the still existing relationship that the **Attached View** has with the **World Item**'s `NetworkObject`. + +:::info +**AttachableBehaviour & NetworkObject Relationship** + +Upon a `NetworkObject` component being spawned, all associated `NetworkBehaviour` based component instances, that are directly attached to the `NetworkObject`'s `GameObject` or are on any child `GameObject`, will be registered with the `NetworkObject` instance. This remains true even when a child `GameObject` containing one or more `NetworkBehaviour` based component instances of a spawned `NetworkObject` is parented, during runtime, under another `GameObject` that is associated with a different spawned `NetworkObject`. Of course, there are additional considerations like: + - What happens when one or both of the NetworkObjects is de-spawned? + - How do you assure the child attachable will return back to its default parent? + - and several other edge case scenarios... + +`AttachableBehaviour` leverages from this "spawn lifetime" relationship to provide another type of "parenting" (attaching) while also taking into consideration these types of edge case scenarios. +::: + diff --git a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/helpercomponents.md b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/helpercomponents.md new file mode 100644 index 0000000000..d146568479 --- /dev/null +++ b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/helpercomponents.md @@ -0,0 +1,12 @@ +# Helper Components + +Understand the helper components available to use in your Netcode for GameObjects project. + + **Topic** | **Description** | +| :------------------------------ | :------------------------------- | +| **[AttachableBehaviour](attachablebehaviour.md)**| Provides an alternative to `NetworkObject` parenting. (wip) | +| **AttachableNode**| Target parent for an `AttachableBehaviour`. (wip) | +| **ComponentController**| Provides the synchronization of and control over enabling or disabling objects. (wip) | +| **[NetworkAnimator](networkanimator.md)**| The `NetworkAnimator` component provides you with a fundamental example of how to synchronize animations during a network session. Animation states are synchronized with players joining an existing network session and any client already connected before the animation state changing. | +| **[NetworkTransform](networktransform.md)**| [NetworkTransform](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.Components.NetworkTransform.html) is a concrete class that inherits from [NetworkBehaviour](../foundational/networkbehaviour.md) and synchronizes [Transform](https://docs.unity3d.com/Manual/class-Transform.html) properties across the network, ensuring that the position, rotation, and scale of a [GameObject](https://docs.unity3d.com/Manual/working-with-gameobjects.html) are replicated to other clients. | +| **[Physics](../../advanced-topics/physics.md)**| Netcode for GameObjects has a built in approach which allows for server-authoritative physics where the physics simulation only runs on the server. | diff --git a/com.unity.netcode.gameobjects/Documentation~/components/networkanimator.md b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/networkanimator.md similarity index 97% rename from com.unity.netcode.gameobjects/Documentation~/components/networkanimator.md rename to com.unity.netcode.gameobjects/Documentation~/components/Helpers/networkanimator.md index 21e0a28c57..51b8d52eed 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/networkanimator.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/networkanimator.md @@ -34,7 +34,7 @@ The `Animator` trigger property type ("trigger") is basically nothing more than The default setting for `NetworkAnimator` is server authoritative mode. When operating in server authoritative mode, any animation state changes that are set (triggers) or detected (change in layer, state, or any `Animator` properties excluding triggers) on the server side will be synchronized with all clients. Because the server initiates any synchronization of changes to an `Animator` 's state, a client that's the owner of the `NetworkObject` associated with the `NetworkAnimator` can lag by roughly the full round trip time (RTT). Below is a timing diagram to show this: -![ServerAuthMode](../images/NetworkAnimatorServerAuthTiming.png) +![ServerAuthMode](../../images/NetworkAnimatorServerAuthTiming.png) In the above diagram, a client might be sending the server an RPC to tell the server that the player is performing some kind of action that can change the player's animations (including setting a trigger). Under this scenario, the client sends an RPC to the server (half RTT), the server processes the RPC, the associated `Animator` state changes are detected by the `NetworkAnimator` (server-side), and then all clients (including the owner client) are synchronized with the changed. @@ -63,7 +63,7 @@ Usually, your project's design (or personal preference) might require that owner Looking at the timing for an owner authoritative `NetworkAnimator`, in the diagram below, you can see that while the owner client gets "immediate visual animation response" the non-owner clients end up being roughly one full RTT behind the owner client and a host would be half RTT behind the owner client. -![ServerAuthMode](../images/NetworkAnimatorOwnerAuthTiming.png) +![ServerAuthMode](../../images/NetworkAnimatorOwnerAuthTiming.png) In the above diagram, it shows that the owner client has an `Animator` state change that's detected by the `NetworkAnimator` ( `OwnerNetworkAnimator`) which automatically synchronizes the server with the changed state. The server applies the change(s) locally and then broadcasts this state change to all non-owner clients. @@ -90,13 +90,13 @@ Using `NetworkAnimator` is a pretty straight forward approach with the only subt If you decide you want to use the server authoritative model, then you can add a `NetworkAnimator` component to either the same `GameObject` that has the `NetworkObject` component attached to it or any child `GameObject`. In the below screenshot, you can see a network Prefab that houses two authoritative models. The `NetworkAnimatorCube-Server` `GameObject` has an `Animator` component, an `AnimatedCubeController` component (used for manual testing), and the `NetworkAnimator` component that has a reference to the `Animator` component. -![Usage-1](../images/NetworkAnimatorUsage-1.png) +![Usage-1](../../images/NetworkAnimatorUsage-1.png) ### Owner Authoritative If you decide you want to use the owner authoritative model, then (for example purposes) you would use your derived `OwnerNetworkAnimator` component as opposed to the default `NetworkAnimator` component like in the screenshot below: -![Usage-1](../images/NetworkAnimatorUsage-2.png) +![Usage-1](../../images/NetworkAnimatorUsage-2.png) > [!NOTE] > While it isn't advised to have different `NetworkAnimator` authoritative models "under the same root network Prefab `GameObject`, " you can have multiple children that each have their own `Animator` and `NetworkAnimator` all housed under a single `NetworkObject` and all use the same authoritative model. However, you should always consider the balance between performance (CPU or bandwidth consumption) and convenience/modularity. diff --git a/com.unity.netcode.gameobjects/Documentation~/components/networktransform.md b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/networktransform.md similarity index 94% rename from com.unity.netcode.gameobjects/Documentation~/components/networktransform.md rename to com.unity.netcode.gameobjects/Documentation~/components/Helpers/networktransform.md index 2bbc224f0e..c3b3456148 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/networktransform.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/networktransform.md @@ -1,6 +1,6 @@ # NetworkTransform -[NetworkTransform](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.Components.NetworkTransform.html) is a concrete class that inherits from [NetworkBehaviour](../basics/networkbehaviour.md) and synchronizes [Transform](https://docs.unity3d.com/Manual/class-Transform.html) properties across the network, ensuring that the position, rotation, and scale of a [GameObject](https://docs.unity3d.com/Manual/working-with-gameobjects.html) are replicated to other clients. +[NetworkTransform](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.Components.NetworkTransform.html) is a concrete class that inherits from [NetworkBehaviour](../foundational/networkbehaviour.md) and synchronizes [Transform](https://docs.unity3d.com/Manual/class-Transform.html) properties across the network, ensuring that the position, rotation, and scale of a [GameObject](https://docs.unity3d.com/Manual/working-with-gameobjects.html) are replicated to other clients. The synchronization of a GameObject's Transform is a key netcode task, and usually proceeds in the following order: @@ -20,20 +20,20 @@ There are other considerations when synchronizing Transform values, however, suc ## Add a NetworkTransform component to a GameObject -Because a NetworkTransform component is derived from the NetworkBehaviour class, it has many of the [same requirements](../basics/networkbehaviour.md). For example, when adding a NetworkTransform component to a GameObject, it should be added to the same or any hierarchy generation relative to the `NetworkObject` component. +Because a NetworkTransform component is derived from the NetworkBehaviour class, it has many of the [same requirements](../foundational/networkbehaviour.md). For example, when adding a NetworkTransform component to a GameObject, it should be added to the same or any hierarchy generation relative to the `NetworkObject` component. In the following image both NetworkTransform and NetworkObject components are on the same GameObject: -![image](../images/networktransform/SingleGeneration.png) +![image](../../images/networktransform/SingleGeneration.png) Alternatively, the parent GameObject can have multiple children where any child can have a NetworkTransform: -![image](../images/networktransform/MultiGeneration.png) +![image](../../images/networktransform/MultiGeneration.png) Theoretically, you can have a NetworkTransform on every child object of a 100 leaf deep hierarchy. However, it's recommended to exercise caution with the amount of nested NetworkTransforms in a network prefab, particularly if there will be many instances of the network prefab. > [!NOTE] -> Generally, as long as there's at least one [NetworkObject](../basics/networkobject.md) at the same GameObject hierarchy level or above, you can attach a NetworkTransform component to a GameObject. You could have a single root-parent GameObject that has a NetworkObject component and under the root-parent several levels of nested child GameObjects that all have NetworkTransform components attached to them. Each child GameObject doesn't require a NetworkObject component in order for the respective NetworkTransform component to synchronize properly. +> Generally, as long as there's at least one [NetworkObject](../foundational/networkobject.md) at the same GameObject hierarchy level or above, you can attach a NetworkTransform component to a GameObject. You could have a single root-parent GameObject that has a NetworkObject component and under the root-parent several levels of nested child GameObjects that all have NetworkTransform components attached to them. Each child GameObject doesn't require a NetworkObject component in order for the respective NetworkTransform component to synchronize properly. ### Nesting NetworkTransforms @@ -48,7 +48,7 @@ For example, if you use a [NetworkAnimator](networkanimator.md) component to syn When you select a NetworkTransform component, there are the following properties in the inspector view: -![image](../images/networktransform/NetworkTransformProperties.png) +![image](../../images/networktransform/NetworkTransformProperties.png) ### Property synchronization @@ -77,7 +77,7 @@ The following NetworkTransform properties can cause a full state update when cha ### Axis to Synchronize -![image](../images/networktransform/AxisToSynchronize.png) +![image](../../images/networktransform/AxisToSynchronize.png) You often don't need to synchronize all Transform values of a GameObject over the network. For example, if the scale of the GameObject never changes, you can deactivate it in the __Scale__ row of the __Axis to Synchronize__ area within the Inspector. Deactivating synchronization saves some processing costs and reduces network bandwidth consumption. @@ -99,9 +99,9 @@ The __Axis to Synchronize__ properties that determine which axes are synchronize ### Authority -![image](../images/networktransform/AuthorityMode.png) +![image](../../images/networktransform/AuthorityMode.png) -The authority mode of a NetworkTransform determines who is the authority over changes to the Transform state. This setting only applies when using a [client-server network topology](../terms-concepts/client-server.md) because in a [distributed authority network topology](../terms-concepts/distributed-authority.md) Netcode for GameObjects automatically sets the owner authority for every NetworkTransform. If you plan on developing for both network topologies then you can use this setting to preserve authority (whether server or owner) for the client-server network topology. +The authority mode of a NetworkTransform determines who is the authority over changes to the Transform state. This setting only applies when using a [client-server network topology](../../terms-concepts/client-server.md) because in a [distributed authority network topology](../../terms-concepts/distributed-authority.md) Netcode for GameObjects automatically sets the owner authority for every NetworkTransform. If you plan on developing for both network topologies then you can use this setting to preserve authority (whether server or owner) for the client-server network topology. By default, NetworkTransform operates in server-authoritative mode. This means that changes to the Transform axis (marked to be synchronized) are detected on the server-side and state updates are pushed to connected clients. This also means any changes to the Transform axis values are overridden by the authoritative state (in this case the server-side Transform state). @@ -120,7 +120,7 @@ When mixing authority motion models and using physics, latency will impact how ( ### Thresholds -![image](../images/networktransform/Thresholds.png) +![image](../../images/networktransform/Thresholds.png) You can use the threshold values to set a minimum threshold value for synchronizing changes. This can be help reduce the frequency of synchronization updates by only synchronizing changes above or equal to the threshold values (changes below won't be synchronized). @@ -131,7 +131,7 @@ For example, if your NetworkTransform has [__Interpolate__](#interpolation) enab ### Delivery -![image](../images/networktransform/Delivery.png) +![image](../../images/networktransform/Delivery.png) #### Tick Synchronize Children @@ -160,7 +160,7 @@ When unreliable state updates are enabled, NetworkTransform instances are assign ### Configurations -![image](../images/networktransform/Configurations.png) +![image](../../images/networktransform/Configurations.png) #### In Local Space @@ -182,7 +182,7 @@ To resolve this issue, you can enable the __Switch Transform Space When Parented ### Interpolation -![image](../images/networktransform/Interpolation.png) +![image](../../images/networktransform/Interpolation.png) Interpolation is enabled by default and is recommended if you desire smooth transitions between Transform updates on non-authoritative instances. Interpolation buffers incoming state updates that can introduce a slight delay between the authority and non-authority instances. When the __Interpolate__ property is disabled, changes to the transform are immediately applied on non-authoritative instances, which can result in visual jitter and/or objects jumping to newly applied state updates when latency is high. Changes to the __Interpolation__ property during runtime on the authoritative instance will be synchronized with all non-authoritative instances. Of course, you can increase the network tick to a higher value in order to get more samples per second, but that still will not yield an over-all smooth motion on the non-authoritative instances and will only consume more bandwidth. @@ -198,7 +198,7 @@ All interpolation types provide you with the ability to enable or disable lerp s ##### Slerp position -![image](../images/networktransform/PositionSlerp.png) +![image](../../images/networktransform/PositionSlerp.png) When this property and __Interpolation__ are both set, non-authoritative instances will [slerp](https://docs.unity3d.com/ScriptReference/Vector3.Slerp.html) towards their destination position rather than [lerping](https://docs.unity3d.com/ScriptReference/Vector3.Lerp.html). Slerping is typically used when your object is following a circular and/or spline-based motion path and you want to preserve the curvature of that path. Since lerping between two points yields a linear progression over a line between two points, there can be scenarios where the frequency of delta position state updates could yield a loss in the curvature of an object's motion. @@ -277,7 +277,7 @@ With quaternion synchronization enabled, the authoritative instance still compar Quaternion synchronization comes with a price, however. It increases the bandwidth cost, 16 bytes per instance, in exchange for handling the more complex rotation issues that often occur when using nested NetworkTransform (one or more parent transforms with one or more child transforms). However, when you enable the __Use Quaternion Synchronization__ property you will notice a change in both the __Syncing__ axis selection check boxes and a new __Use Quaternion Compression__ property will appear: -![image](../images/networktransform/NetworkTransformQuaternionSynch.png) +![image](../../images/networktransform/NetworkTransformQuaternionSynch.png) :::note The rotation synchronization axis checkboxes are no longer available when __Use Quaternion Synchronization__ is enabled (since synchronizing the quaternion of a transform always updates all rotation axes) and __Use Quaternion Compression__ becomes a visible option. diff --git a/com.unity.netcode.gameobjects/Documentation~/components/foundational/foundationalcomponents.md b/com.unity.netcode.gameobjects/Documentation~/components/foundational/foundationalcomponents.md new file mode 100644 index 0000000000..09b8f52274 --- /dev/null +++ b/com.unity.netcode.gameobjects/Documentation~/components/foundational/foundationalcomponents.md @@ -0,0 +1,34 @@ +# Foundational Components + +While there are many classes within the Netcode for GameObjects SDK, there are really only three foundational components: + +* **NetworkObject:** This component declares a prefab or in-scene placed object as "networked object" that can have states synchronized between clients and/or a server. A `NetworkObject` component provides the means to identify each uniquely spawned instance (dynamically or in-scene placed). _You cannot derive from the `NetworkObject` component class_. + +* **NetworkBehaviour:** This is the fundamental "netcode" scripting component that provides the ability to synchronize state and write netcode script(s). _You derive from this class to create your own netcode component scripts_. + +* **NetworkManager:** This component is the over-all network session configuration and session management component. The `NetworkManager` component is required in order to start or join a network session. + + +## NetworkObject + +| **Topic** | **Description** | +| :------------------------------ | :------------------------------- | +| **[NetworkObject](networkobject.md)** | A NetworkObject is a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) with a NetworkObject component and at least one [NetworkBehaviour](networkbehaviour.md) component, which enables the GameObject to respond to and interact with netcode. | +| **[NetworkObject parenting](../../advanced-topics/networkobject-parenting.md)** | Understand how NetworkObjects are parented in Netcode for GameObjects. | + + +## NetworkBehaviour + + +| **Topic** | **Description** | +| :------------------------------ | :------------------------------- | +| **[NetworkBehaviour](networkbehaviour.md)** | [`NetworkBehaviour`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html) is an abstract class that derives from [`MonoBehaviour`](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and is primarily used to create unique netcode or game logic. To replicate any netcode-aware properties or send and receive RPCs, a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) must have a [NetworkObject](networkobject.md) component and at least one `NetworkBehaviour` component. | +| **[Synchronizing](networkbehaviour-synchronize.md)** | Understanding a `NetworkBehaviour` component's order of operations when it comes to spawning, de-spawning, and adding custom synchronization data. | + + +## NetworkManager + +| **Topic** | **Description** | +| :------------------------------ | :------------------------------- | +| **[NetworkManager](networkmanager.md)**| The `NetworkManager` is a required Netcode for GameObjects component that has all of your project's netcode-related settings. Think of it as the central netcode hub for your netcode-enabled project. | +| **[Player prefabs and spawning players](playerobjects.md)**| Learn about spawning player objects and creating player prefabs.| \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/networkbehaviour-synchronize.md b/com.unity.netcode.gameobjects/Documentation~/components/foundational/networkbehaviour-synchronize.md similarity index 98% rename from com.unity.netcode.gameobjects/Documentation~/basics/networkbehaviour-synchronize.md rename to com.unity.netcode.gameobjects/Documentation~/components/foundational/networkbehaviour-synchronize.md index 8a34cc0286..92fc4f68e2 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/networkbehaviour-synchronize.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/foundational/networkbehaviour-synchronize.md @@ -45,7 +45,7 @@ There are two cases where NetworkObject synchronization occurs: - that is, Full synchronization of the NetworkObjects and scenes. > [!NOTE] -> If you aren't familiar with the [`INetworkSerializable` interface](../advanced-topics/serialization/inetworkserializable.md), then you might read up on that before proceeding, because `NetworkBehaviour.OnSynchronize` follows a similar usage pattern. +> If you aren't familiar with the [`INetworkSerializable` interface](../../advanced-topics/serialization/inetworkserializable.md), then you might read up on that before proceeding, because `NetworkBehaviour.OnSynchronize` follows a similar usage pattern. #### Order of operations when dynamically spawning diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/networkbehaviour.md b/com.unity.netcode.gameobjects/Documentation~/components/foundational/networkbehaviour.md similarity index 100% rename from com.unity.netcode.gameobjects/Documentation~/basics/networkbehaviour.md rename to com.unity.netcode.gameobjects/Documentation~/components/foundational/networkbehaviour.md diff --git a/com.unity.netcode.gameobjects/Documentation~/components/networkmanager.md b/com.unity.netcode.gameobjects/Documentation~/components/foundational/networkmanager.md similarity index 90% rename from com.unity.netcode.gameobjects/Documentation~/components/networkmanager.md rename to com.unity.netcode.gameobjects/Documentation~/components/foundational/networkmanager.md index 74e3720b7a..a67560fc21 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/networkmanager.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/foundational/networkmanager.md @@ -5,13 +5,13 @@ The `NetworkManager` is a required Netcode for GameObjects component that has al ## `NetworkManager` Inspector properties - **LogLevel**: Sets the network logging level -- **PlayerPrefab**: When a Prefab is assigned, the Prefab will be instantiated as the player object and assigned to the newly connected and authorized client. For more information about player prefabs, refer to [Player NetworkObjects](../basics/networkobject.md#player-networkobjects). +- **PlayerPrefab**: When a Prefab is assigned, the Prefab will be instantiated as the player object and assigned to the newly connected and authorized client. For more information about player prefabs, refer to [Player NetworkObjects](networkobject.md#player-networkobjects). - **NetworkPrefabs**: Where you register your network prefabs. You can also create a single network Prefab override per registered network Prefab here. - **Protocol Version**: Set this value to help distinguish between builds when the most current build has new assets that can cause issues with older builds connecting. - **Network Transport**: Where your network specific settings and transport type is set. This field accepts any INetworkTransport implementation. However, unless you have unique transport specific needs UnityTransport is the recommended transport to use with Netcode for GameObjects. - **Tick Rate**: This value controls the network tick update rate. - **Ensure Network Variable Length Safety**: (Increases cpu processing and bandwidth) When this property is checked, Netcode for GameObjects will prevent user code from writing past the boundaries of a NetworkVariable. -- **Connection Approval**: This enables [connection approval](../basics/connection-approval.md) when this is checked and the `NetworkManager.ConnectionApprovalCallback` is assigned. +- **Connection Approval**: This enables [connection approval](../../basics/connection-approval.md) when this is checked and the `NetworkManager.ConnectionApprovalCallback` is assigned. - **Client Connection Buffer Timeout**: This value sets the amount of time that has to pass for a connecting client to complete the connection approval process. If the time specified is exceeded the connecting client will be disconnected. - **Force Same Prefabs**: When checked it will always verify that connecting clients have the same registered network prefabs as the server. When not checked, Netcode for GameObjects will ignore any differences. - **Recycle Network Ids**: When checked this will re-use previously assigned `NetworkObject.NetworkObjectIds` after the specified period of time. @@ -25,12 +25,12 @@ The `NetworkManager` is a required Netcode for GameObjects component that has al > [!NOTE] > All `NetworkManager` sub-systems are instantiated once the `NetworkManager` is started (that is, `NetworkManager.IsListening == true`). A good general "rule of thumb" is to not attempt to access the below sub-systems before starting the `NetworkManager`, otherwise they won't yet be initialized. -- [NetworkManager.PrefabHandler](../advanced-topics/object-pooling.md): This provides access to the NetworkPrefabHandler that is used for NetworkObject pools and to have more control overriding network prefabs. -- [NetworkManager.SceneManager](../basics/scenemanagement/using-networkscenemanager.md): When scene management is enabled, this is used to load and unload scenes, register for scene events, and other scene management related actions. -- [NetworkManager.SpawnManager](../basics/object-spawning.md): This handles NetworkObject spawn related functionality. -- [NetworkManager.NetworkTimeSystem](../advanced-topics/networktime-ticks.md): a synchronized time that can be used to handle issues with latency between a client and the server. -- [NetworkManager.NetworkTickSystem](../advanced-topics/networktime-ticks.md#network-ticks): Use this to adjust the frequency of when NetworkVariables are updated. -- [NetworkManager.CustomMessagingManager](../advanced-topics/message-system/custom-messages.md): Use this system to create and send custom messages. +- [NetworkManager.PrefabHandler](../../advanced-topics/object-pooling.md): This provides access to the NetworkPrefabHandler that is used for NetworkObject pools and to have more control overriding network prefabs. +- [NetworkManager.SceneManager](../../basics/scenemanagement/using-networkscenemanager.md): When scene management is enabled, this is used to load and unload scenes, register for scene events, and other scene management related actions. +- [NetworkManager.SpawnManager](../../basics/object-spawning.md): This handles NetworkObject spawn related functionality. +- [NetworkManager.NetworkTimeSystem](../../advanced-topics/networktime-ticks.md): a synchronized time that can be used to handle issues with latency between a client and the server. +- [NetworkManager.NetworkTickSystem](../../advanced-topics/networktime-ticks.md#network-ticks): Use this to adjust the frequency of when NetworkVariables are updated. +- [NetworkManager.CustomMessagingManager](../../advanced-topics/message-system/custom-messages.md): Use this system to create and send custom messages. ## Starting a server, host, or client @@ -47,8 +47,8 @@ NetworkManager.Singleton.StartClient(); // Starts the NetworkManager as jus When starting a Server or joining an already started session as client, the `NetworkManager` can spawn a "Player Object" belonging to the client. For more information about player prefabs, refer to: - - [NetworkObject Player Prefab Documentation](../basics/networkobject.md) - - [Connection Approval](../basics/connection-approval) + - [NetworkObject Player Prefab Documentation](networkobject.md) + - [Connection Approval](../../basics/connection-approval) ## Connecting @@ -81,7 +81,7 @@ It is possible to access the current connection data at runtime, via `NetworkMan If you are using Unity Relay to handle connections, however, **don't use `SetConnectionData`**. The host should call [`SetHostRelayData`](https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/blob/11922a0bc100a1615c541aa7298c47d253b74937/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs#L575), and clients should call [`SetClientRelayData`](https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/blob/11922a0bc100a1615c541aa7298c47d253b74937/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs#L588). Attempting to join a **Relay**-hosted game via entering IP/port number (via `SetConnectionData`) **won't work**. -[More information about Netcode for GameObjects Transports](../advanced-topics/transports.md) +[More information about Netcode for GameObjects Transports](../../advanced-topics/transports.md) ## Disconnecting and shutting down diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/networkobject.md b/com.unity.netcode.gameobjects/Documentation~/components/foundational/networkobject.md similarity index 86% rename from com.unity.netcode.gameobjects/Documentation~/basics/networkobject.md rename to com.unity.netcode.gameobjects/Documentation~/components/foundational/networkobject.md index 4cbfb18423..5ea762d5af 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/networkobject.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/foundational/networkobject.md @@ -1,8 +1,8 @@ # NetworkObject -A NetworkObject is a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) with a NetworkObject component and at least one [NetworkBehaviour](networkbehaviour.md) component, which enables the GameObject to respond to and interact with netcode. NetworkObjects are session-mode agnostic and used in both [client-server](../terms-concepts/client-server.md) and [distributed authority](../terms-concepts/distributed-authority.md) contexts. +A NetworkObject is a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) with a NetworkObject component and at least one [NetworkBehaviour](networkbehaviour.md) component, which enables the GameObject to respond to and interact with netcode. NetworkObjects are session-mode agnostic and used in both [client-server](../terms-concepts/client-server.md) and [distributed authority](../../terms-concepts/distributed-authority.md) contexts. -Netcode for GameObjects' high level components, [the RPC system](../advanced-topics/messaging-system.md), [object spawning](object-spawning), and [`NetworkVariable`](networkvariable.md)s all rely on there being at least two Netcode components added to a GameObject: +Netcode for GameObjects' high level components, [the RPC system](../../advanced-topics/messaging-system.md), [object spawning](../../basics/object-spawning.md), and [`NetworkVariable`](../../basics/networkvariable.md)s all rely on there being at least two Netcode components added to a GameObject: 1. NetworkObject 2. [`NetworkBehaviour`](networkbehaviour.md) @@ -31,7 +31,7 @@ You can avoid execution order issues in any NetworkBehaviour component scripts t ## Ownership -Either the server (default) or any connected and approved client owns each NetworkObject. By default, Netcode for GameObjects is [server-authoritative](../terms-concepts/client-server.md), which means only the server can spawn and despawn NetworkObjects, but you can also build [distributed authority](../terms-concepts/distributed-authority.md) games where clients have the authority to spawn and despawn NetworkObjects as well. +Either the server (default) or any connected and approved client owns each NetworkObject. By default, Netcode for GameObjects is [server-authoritative](../../terms-concepts/client-server.md), which means only the server can spawn and despawn NetworkObjects, but you can also build [distributed authority](../../terms-concepts/distributed-authority.md) games where clients have the authority to spawn and despawn NetworkObjects as well. If you're creating a client-server game and you want a client to control more than one NetworkObject, use the following ownership methods. @@ -77,30 +77,30 @@ However, when you want to limit which prefabs are available (for example, to red ## Spawning with (or without) observers -![image](../images/SpawnWithObservers.png) +![image](../../images/SpawnWithObservers.png) The `NetworkObject.SpawnWithObservers` property (default is true) enables you to spawn a NetworkObject with no initial observers. This is the recommended alternative to using `NetworkObject.CheckObjectVisibility` when you just want it to be applied globally to all clients (only when spawning an instance of the NetworkObject in question). If you want more precise per-client control then `NetworkObject.CheckObjectVisibility` is recommended. `NetworkObject.SpawnWithObservers` is only applied upon the initial server-side spawning and once spawned it has no impact on object visibility. ## Transform synchronization -![image](../images/NetworkObject-TransformSynchronization.png) +![image](../../images/NetworkObject-TransformSynchronization.png) -There are times when you want to use a NetworkObject for something that doesn't require the synchronization of its transform. You might have an [in-scene placed NetworkObject](./scenemanagement/inscene-placed-networkobjects.md) that's only used to manage game state and it doesn't make sense to incur the initial client synchronization cost of synchronizing its transform. To prevent a NetworkObject from initially synchronizing its transform when spawned, deselect the **Synchronize Transform** property. This property is enabled by default. +There are times when you want to use a NetworkObject for something that doesn't require the synchronization of its transform. You might have an [in-scene placed NetworkObject](../../basics/scenemanagement/inscene-placed-networkobjects.md) that's only used to manage game state and it doesn't make sense to incur the initial client synchronization cost of synchronizing its transform. To prevent a NetworkObject from initially synchronizing its transform when spawned, deselect the **Synchronize Transform** property. This property is enabled by default. > [!NOTE] > If you're planning to use a NetworkTransform, then you always want to make sure the **Synchronize Transform** property is enabled. ## Active scene synchronization -![image](../images/ActiveSceneMigration.png) +![image](../../images/ActiveSceneMigration.png) When a GameObject is instantiated, it gets instantiated in the current active scene. However, sometimes you might find that you want to change the currently active scene and would like specific NetworkObject instances to automatically migrate to the newly assigned active scene. While you could keep a list or table of the NetworkObject instances and write the code/logic to migrate them into a newly assigned active scene, this can be time consuming and become complicated depending on project size and complexity. The alternate and recommended way to handle this is by enabling the **Active Scene Synchronization** property of each NetworkObject you want to automatically migrate into any newly assigned scene. This property defaults to disabled. -Refer to the [NetworkSceneManager active scene synchronization](scenemanagement/using-networkscenemanager.md#active-scene-synchronization) page for more details. +Refer to the [NetworkSceneManager active scene synchronization](../../basics/scenemanagement/using-networkscenemanager.md#active-scene-synchronization) page for more details. ## Scene migration synchronization -![image](../images/SceneMigrationSynchronization.png) +![image](../../images/SceneMigrationSynchronization.png) Similar to [`NetworkObject.ActiveSceneSynchronization`](#active-scene-synchronization), [`NetworkObject.SceneMigrationSynchronization`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.html#Unity_Netcode_NetworkObject_SceneMigrationSynchronization) automatically synchronizes client-side NetworkObject instances that are migrated to a scene via [`SceneManager.MoveGameObjectToScene`](https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.MoveGameObjectToScene.html) on the host or server side. This can be useful if you have a specific scene you wish to migrate NetworkObject instances to that is not the currently active scene. @@ -113,4 +113,4 @@ Scene migration synchronization is enabled by default. For NetworkObjects that d ## Additional resources - [NetworkBehaviour](networkbehaviour.md) -- [NetworkVariable](networkvariable.md) +- [NetworkVariable](../../basics/networkvariable.md) diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/playerobjects.md b/com.unity.netcode.gameobjects/Documentation~/components/foundational/playerobjects.md similarity index 77% rename from com.unity.netcode.gameobjects/Documentation~/basics/playerobjects.md rename to com.unity.netcode.gameobjects/Documentation~/components/foundational/playerobjects.md index 9ad3260a70..6650aee374 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/playerobjects.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/foundational/playerobjects.md @@ -35,7 +35,7 @@ public override void OnNetworkDespawn() ## Session-mode agnostic methods -Netcode for GameObjects can spawn a default PlayerObject for you. If you enable **Create Player Prefab** in the [NetworkManager](../components/networkmanager.md) and assign a valid prefab, then Netcode for GameObjects spawns a unique instance of the designated player prefab for each connected and approved client, referred to as the PlayerObject. +Netcode for GameObjects can spawn a default PlayerObject for you. If you enable **Create Player Prefab** in the [NetworkManager](networkmanager.md) and assign a valid prefab, then Netcode for GameObjects spawns a unique instance of the designated player prefab for each connected and approved client, referred to as the PlayerObject. To manually spawn an object as PlayerObject, use the following method: @@ -45,19 +45,19 @@ GetComponent().SpawnAsPlayerObject(clientId); If the player already had a prefab instance assigned, then the client owns the NetworkObject of that prefab instance unless there's additional server-side specific user code that removes or changes the ownership. -Alternatively, you can choose not to spawn anything immediately after a client connects and instead use a [NetworkBehaviour component](networkbehaviour.md) to handle avatar/initial player prefab selection. This NetworkBehaviour component could be configured by the server or initiating session owner, or be associated with an [in-scene](scenemanagement/inscene-placed-networkobjects.md) or [dynamically spawned](object-spawning.md#dynamically-spawned-network-prefabs) NetworkObject, as suits the needs of your project. +Alternatively, you can choose not to spawn anything immediately after a client connects and instead use a [NetworkBehaviour component](networkbehaviour.md) to handle avatar/initial player prefab selection. This NetworkBehaviour component could be configured by the server or initiating session owner, or be associated with an [in-scene](../../basics/scenemanagement/inscene-placed-networkobjects.md) or [dynamically spawned](../../basics/object-spawning.md#dynamically-spawned-network-prefabs) NetworkObject, as suits the needs of your project. ### Client-server contexts only -In addition to the [session-mode agnostic spawning methods](#session-mode-agnostic-methods) above, you can assign a unique player prefab on a per-client connection basis using a client [connection approval process](connection-approval.md) when in [client-server contexts](../terms-concepts/client-server.md). +In addition to the [session-mode agnostic spawning methods](#session-mode-agnostic-methods) above, you can assign a unique player prefab on a per-client connection basis using a client [connection approval process](../../basics/connection-approval.md) when in [client-server contexts](../../terms-concepts/client-server.md). ### Distributed authority contexts only -In addition to the [session-mode agnostic spawning methods](#session-mode-agnostic-methods) above, you can use the [`OnFetchLocalPlayerPrefabToSpawn`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkManager.html#Unity_Netcode_NetworkManager_OnFetchLocalPlayerPrefabToSpawn) method to assign a unique player prefab on a per-client basis when in [distributed authority contexts](../terms-concepts/distributed-authority.md). +In addition to the [session-mode agnostic spawning methods](#session-mode-agnostic-methods) above, you can use the [`OnFetchLocalPlayerPrefabToSpawn`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkManager.html#Unity_Netcode_NetworkManager_OnFetchLocalPlayerPrefabToSpawn) method to assign a unique player prefab on a per-client basis when in [distributed authority contexts](../../terms-concepts/distributed-authority.md). -To use `OnFetchLocalPlayerPrefabToSpawn` in your project, assign a callback handler to `OnFetchLocalPlayerPrefabToSpawn` and whatever the client script returns is what will be spawned for that client. Ensure that the prefab being spawned is in a NetworkPrefabList [registered with the NetworkManager](object-spawning.md#registering-a-network-prefab). +To use `OnFetchLocalPlayerPrefabToSpawn` in your project, assign a callback handler to `OnFetchLocalPlayerPrefabToSpawn` and whatever the client script returns is what will be spawned for that client. Ensure that the prefab being spawned is in a NetworkPrefabList [registered with the NetworkManager](../../basics/object-spawning.md#registering-a-network-prefab). -If you don't assign a callback handler to `OnFetchLocalPlayerPrefabToSpawn`, then the default behaviour is to return the `NetworkConfig.PlayerPrefab` (or null if neither are set). +If you don't assign a callback handler to `OnFetchLocalPlayerPrefabToSpawn`, then the default behavior is to return the `NetworkConfig.PlayerPrefab` (or null if neither are set). :::note `AutoSpawnPlayerPrefabClientSide` required For `OnFetchLocalPlayerPrefabToSpawn` to work, [`AutoSpawnPlayerPrefabClientSide`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkManager.html#Unity_Netcode_NetworkManager_AutoSpawnPlayerPrefabClientSide) must be enabled. @@ -65,7 +65,7 @@ For `OnFetchLocalPlayerPrefabToSpawn` to work, [`AutoSpawnPlayerPrefabClientSide ## PlayerObject spawning timeline -When using automatic methods of PlayerObject spawning (such as enabling **Create Player Prefab** in the [NetworkManager](../components/networkmanager.md)), PlayerObjects are spawned at different times depending on whether you have [scene management](scenemanagement/scene-management-overview.md) enabled. +When using automatic methods of PlayerObject spawning (such as enabling **Create Player Prefab** in the [NetworkManager](networkmanager.md)), PlayerObjects are spawned at different times depending on whether you have [scene management](../../basics/scenemanagement/scene-management-overview.md) enabled. - When scene management is disabled, PlayerObjects are spawned when a joining client's connection is approved. - When scene management is enabled, PlayerObjects are spawned when a joining client finishes initial synchronization. @@ -93,7 +93,7 @@ To find your own player object just pass `NetworkManager.Singleton.LocalClientId ## Additional resources - [NetworkObject](networkobject.md) -- [NetworkManager](../components/networkmanager.md) -- [Distributed authority topologies](../terms-concepts/distributed-authority.md) -- [Client-server topologies](../terms-concepts/client-server.md) -- [Object spawning](object-spawning.md) +- [NetworkManager](networkmanager.md) +- [Distributed authority topologies](../../terms-concepts/distributed-authority.md) +- [Client-server topologies](../../terms-concepts/client-server.md) +- [Object spawning](../../basics/object-spawning.md) diff --git a/com.unity.netcode.gameobjects/Documentation~/images/attachable/AttachableBehaviour_InspectorView-1.png b/com.unity.netcode.gameobjects/Documentation~/images/attachable/AttachableBehaviour_InspectorView-1.png new file mode 100644 index 0000000000000000000000000000000000000000..9868301c1257b2713fbd7303c2c4b2beec5472b6 GIT binary patch literal 23713 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 literal 0 HcmV?d00001 diff --git a/com.unity.netcode.gameobjects/Documentation~/images/attachable/AttachableDiagram-1.png b/com.unity.netcode.gameobjects/Documentation~/images/attachable/AttachableDiagram-1.png new file mode 100644 index 0000000000000000000000000000000000000000..569fecc721f568644783375d06781630104cf953 GIT binary patch literal 70586 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 literal 0 HcmV?d00001 diff --git a/com.unity.netcode.gameobjects/Documentation~/images/attachable/AttachableDiagram-2.png b/com.unity.netcode.gameobjects/Documentation~/images/attachable/AttachableDiagram-2.png new file mode 100644 index 0000000000000000000000000000000000000000..05a428e82d47dca1de7a1f9310a9599743732cfa GIT binary patch literal 69939 zcmX`T1yq#Z_dTqrDBWGs(j_1*-Q6&N64K3(1E_QlHGnh{0z*o7r*wCBcfUW61D-~u&l)#^t z&T7(PFNz0AcYr6a%|#VOU%V)bLb?C?26&F-AfxO2;stv9^Y6=U`#+{HUOeT z8}6sRNx|J9>82=-`Yn)MU$6RE^fQ#50}G{IplqeYIOAs#VMg%J@BZ(p7-T|R+<2$T zo^FKeo%at87J#4D&bxq{{l$O#{{*<|&mK4PtD)^;sEB^8U)TweU%V{V{_hcLAS4P) z`2Su-mAWSU?}`uXgexZO&yO=(zW7HyzXdfAL427VW$KRE@%G$OxAe=8CapgzqMHNB z^f>fNal^wZ`5O5#H*@#4k37ES*H^2ZVP^d>mQlDa*HmgJ@BvPN%({>&y9Gu8*Ztuu zYN>E&vVD{8x1qEmUuS9(KBsJEqb9-5B_=Jn%+*nf;nLDv|K8r6m)GX?;KA+V;^K{A zx@A{5kr){5kj-|kDt^ky*Ixf=eteqlQc!c#kdsg#aexbtyAd zY>?yga9gKTX*H289)x}?Vg9txdUT&$u(Rx8=txS~@x@Y;0_r4iVt!V3uUqIC!!2tGLHu?Uw28c>d`|f{|PpJ`2d22o+I$^0;@Rb4pfTxbHD_a`zhn68#a3O z{^GV?Z^sacd-)2nW-~XCWNde$a03{o=WjU$1s4{MYZ6cdtvoEwX`?USE5mM~u9y~4 z{Ly5l+-x*gk)*L(A(375u<6cj3L6N49#UZ>inN+s<#bkHMjNe_3prDK0im zKZ$6R7#iGO?B$Umu*&*Tij3yS6AV1wZvT$T_I-LRt_1>TSzre_eXqD)NkHAOQ51Q1 zOj(2_nb+QR0PCVv8?K}Ja7IVSv9(-&a_)7!EP8X){PYnKC6s`TRax-Ff8ME|<+i#N znocMTsZmJcloY<(=dv8m_+efAJd;Yl+RX2-PfmgFv(npPYrbFo<%Mte`L|TW=v8p4 zz+sK}+IQ5gAjM>!{E2}kEkUBSUmaxM*w^X*5NTCds7@IHhE$R8D1}dy@7ltX}*ww5AYGiHYBp9lV;EG5IrluBs& zOOG<#sM)Y6MZ=p12$_G&6RTs@_C|cJ`-$OX0$Ns6#RtEbIISm{YcD4CxtrD(+?L`| z*cyf_JD_v#3+;w8M4eVUkcc=7+1tk*vtJ1-N%JkewEd13--q`P`fR9FL-6=J1F<%lr506&&t8xR6#rNV7CdzVhG1ZHn@_N;FMZ7v{H)TIx z9t@&zhyLQVXKnPln!LhIQJ_!h|I*_O{Vw{(0~t;X+)GKeZf&hp1aYyOQm$e$%;o?I z3CVF_fE)`8ORdCEh^!Pd^X0n~%|fl--dOsHD0@F$5_w#)bLRi4xxYCZt6WdvbC&9gAfac{E*>9hxYnf0xv}V@2Z2H7AY2uv=-kaRHeayC+DByQg0v67G3gkz5B;G*T zz9o2n++NK09Kjs<{uqbv-@W@(|1JUNkSuf!OJG&`c!C;kv+^@OPbQj*|5QNPrOGUT zUI3v;K7q9eEJ*ILj-@!!MJygj#49S$nP&3&PvdDH(6E88ms|ZbKd7Sn z9?sVqTI0_+pc6)wnCGt2tM9Kf>p}sc+ zpo}qfWuY8fI<8{fzu#szgUCH)3P*2Ay#7%K;&2e2qTCN&+mZk$-8UIp2)XaQ<4nC< ziEA5AjoWvUh-T5AQiBdW1mm62oY8!Bddu-Y(V@s47>8{9wy(t;97;op;cU+dqMx;1 zVSx`t*3`S8Gu&)7k1l3JGHbf*&%h(?m@h|FB5 zmLnG#j8kCYXRFCYc=~Dyauy*hp^`rj9J~3QoR#o=Di#y4lV$KWAE-? zsr3{q*hE$2Z8h;&gU6W*Foy3{b^851iS4sy6tDaujm^YMZ%7bEUzS=%U;0$9y zE=Spdn{-E7i5gQ$Ax&x7eJHXw;w`2|0Ja~-7ruqR=244(pFdt3Qhq%RLl#J#TY7IH z^P7HRegV1duBPTws`7rg5XBF_iEeZVBC}9U2ZSt%^yT-QZmwL!LYZ%w2RhJFjTO$n z^dMJefu%1TuY9}Bf$RnZ>=dx-#|dj$jze)@;Ph&>SOl*0yUP0+J0ef_>ijO-P@DMo zk3rsRJXBR(Y073h5hT1;4xI1Z=@fru+c62G(Na5BBI7`neO8a^>A4X<d~ZtmDoj80;r-?9`Ocaiqh1U2 z+<6yu!RPM4>cp6tnVC->c~OL2%g_+RkmT#5_nR`+rN+lTBQR z;>(w$@(#tVa0s~1-$b&eJkhP|B~*bkKXTQ#F<0Wq60o(1K~_z5y)Ee1QgOBIRXTxq z{rbocwSn+52KC%OGLX|=dIM*){}w&*+VtclBb~`WOQ$&A+WZUH2RO@2#P5}}BrKut z%O|)(EmaOEn}{RgnUWG1c0#^rT*l9kP28|6kC1}I>i#{?hdSwvui}RRqc1>gEsEym zo6iS7&KiI-%6|^wQ?%@gX9~G*CYg_Ced+O5>4@$dY2c&@_TrHHA4udB?!K;RA4a{@ zG1OqnJuRhlYxy2=wdC7eN0ZO=(RE)34E}Gqzw~5xb_cFd_P8T3i&QVTCKJ6l6Kwxn z0_5v*koUy|qbDLd=?5G-1@^4&XC#Tp3nVYj_S2323I6r8Ri!=`yLbC6GAjPjvtlh7 z5vC4k#2;mzcnD9KI+A_rdV!1M2<6U!%oN^u8FI%mTDv~o5gP0>rA?!y?wQfAWg@X=;k<<$XO{XXaOw}m`| zbl|c?m&-RO_}dm60CpU0#tD5Z1G@Pv%ZUFW1S_BeOG9RhWRUG82Rc%)LQDou2sY${ zD#Gs+qoGJLfn7pJAcsyg{})_6$Tx=9G_Wh@* zwwY$f<0Lu(7P=q6@i|Azt_;kM+~Vhrlt)-IO{Ee%4u;KMhqcmLC5A;u{~JH5M{Xp> z0TmR>&~t~jay`5I@!@WxlfX#hhfA(mVuv=mD+|ZEW|qFcJcu^05_3?vtKlc{%&_#{(W1W4AFQLq--zE&t?hoyDrea&i?wxzVFLo*fakaOo3C(*_#_zK_ML27Cp#(b|)brA&!mx zg3q;1m-BP2B{{@WJ)d7f|Cb4 zQz`J170vebNc5??$3CG3fn&bfe6a70_n4UF%L!1V%!JnI}vJa-lGFO zE|avo1F~AT)v(hoShIn1c~`umR{`OkTEE*%Nw}6;X%CQ9&8`j?{;NfR;8ck0zJ`Np zM=w~N-+S6MKe*!YnskNB;tTQI4?yf!&4DxiR!&Og+b76o;=YKtj-9n{L`Bi;KW<)b z9VNrV2Pm`0Hoflcu>_GVcwUD&QMhrFJ72%5&ZT5d_0eOUfXHo3z)G?W&1^3EaL2`u z8M6G=qhe3@$3=60`uGL0HoS078ZyxAn_{R~TT&et zz*9S3%85s;K8MXuKB>)i3;f=SA2#YM-(tsLZzP+=W}CWmcpPUX8#t?D9u>S!fHC)XWUMq_x1L_X+J*3Qc(uvm|VbMn8CwCSAV8- z^|URsR^MU13&x(*ulN0{_sxO2ldhvb&~Gl=Bm%~Hxap?R@Pf6dpTvNf?ZQ(90r^c& z$e&pLB{Gn9yQk25dXvGPV?L8?gNx0{OKQ`_$s8wsp3zDePRGzBb$#~No7rBmTz@b+ z`Jq!`O0aNFcQCibXwmN+r6jAm6rOQ5ZS~MVNsBv?+Dq=*teu=X)~$^nDE5NaHh`;S54I0t_{+;uAt6g& zGWiz7)wpX!gY%Tikl^c?2YQ%z;PlzhkZ1oG!$`~6Tis7DuK@~&3)D#3kv+vVJZx>1 zJ00;eXjfFwg7^zJ5Pyi$8~t_Bd>|2RKh^Y_>gQ%*jqN9PiMQO_l)DUqL4$%e8oUopMIVXLyTq%w2)t1$$?FoMgnUQxH`aS(GV0&4h9l`2x#H$Vr_rK6UO}xGhMdFyg%W^_VwKQ2rN}pfkR4 z?8K-iezB&j(c+4CGkEl!_!4`BhUq@H`PO!arVk@DH)bXcDP>BE8V&8YKDx0Ni-T@9 zI{cpvw?XWj7!OEo-Qg2ppXqA*bW%Zgm8{>XSD(cne><$SsTG6xo*6xBrAJEXAb5+F zcoCi z{=K@i)?r;d;Y<>fob6M>qIHFxpJ$z`je~k(WQ=(STB&6$qK8RIuFxk*O@xj9rWNv9 ze{)?o8dw<`i^Y3pxSly5&41^+6Y`xAyop1dUxX&5&p!hM-39ly`=s@|M!qX9 z-E!ynx>Pyo6ve&BiVk!~?zzar$;kP%QRgum;d1p!ih0ILIqi5Uyxl+3x^>o-gVvfk zWrJ%%du0#M3`|SC{ItC>r4<*^5M)LRPUEo^veCO(eY2sESEm|RnT(suM`VTT3ZQJ4 z{0XhlI^zhNQb9^%hoKqPzc1wZ@G#i2j(%LpM$$ywoV?~Bz{R9Nsb*7lR60!kq!1Om z(Q%`}q&ssFvXA33&+y5eiZ6kAKK6tC0~x*c6E-ckbb2(2PXvrjtGgEFnfHTME6sv} zip>a@(cTD~j^YYA_UJaC|LP%iY>Z5N3|FXh%`}EI*rkU;0B6H*!;vv-)(4M4s7taJ z#kaH1s9up$T6|FS&3$Alf3Ne9Py(|j@*7DRm5~B$Py;OKbO%=_I}{h$^%Bqb@g}b> zPQYb9k!?3`<4++|AwEThuBk2NHVCf6p{Qy%82oFT5w-?iD9sq_mwxP z-lHszNl$9QG@0e4(}eaFt$sk-kw>%O@C_YWhL6YO2cpJfFOSt#f%P)GYU_mzRg|Xa z1oKF>Xf7bHffpA<8vnGdL^MU^Gwnazn1EBynT4y&kU7L1%DT|Jy_>7n{)(3LR}S3u zoL`)+ckUeJlZ0OcUO+yF2I0jA(KplKXenw}PqWw-Lq)0|S*oLa^{#(Hzz_Bo9hxKH9TOqo4)X$@C3^*;9WtOO|u!<;TXv zEj~lNNtf9B!S~of7b4W4XN^r*Vt*tV z?>SI*$~XHwxCr;_4jF(90GxhHTqhuk-1%)`*H3@*4o_VJKK&j)E$ucpB_s?+@ir=E zns=&q()?+-V&53{qGgxE?F_rN^qchzuT3-@a;}ZX!Q@&Jz3orPhvzH>J9N=2g&p2% zzd<_r)L*pmePWANe%}9eP|v(qb9Agde0+I1AJ#&=gMSu_^)6AF;OYHAA-DWsv1bo9 zSxp+IOCfoBmhIE#ZMDXBM1BLD#2d*pc~GB@#wZGs4**RtO3CZI!^ zcHi_mr;2TK|5zzMjHEi%Tym)W>ro2QnnUe2K4yEmKZ~m4#T3$Um8Vy6*n>#KyWM%YEi6p_cawBebpv;{bwbQ1Lc;vV#2Dv4$?fy}R*nc67=-Goql zmV$$<2-7o=*FmKf7BZ>=nNh3xNC6JBs;yHOeTe4ovI&o;YI}7j%rdlotArvt z=W1qIER;AR8I}^g@+^GPf4e-R2q)rlH(3WvrboSg#kKjeg=mNP6jwE5lO?*ja=+%x zn&VK8=gH4n6MhEM=BC0jVr5Uq)s<-z*kz*fp2FiPCcEX2eubjMOdw84o0XhFEKO{aH-xr=II`A^VG(^GnN%o1MILU^R7`eHzcVN5o?~DCyZk zp#Dyqkw?n!s^|`bzuUkywnC4&Ik3y-S}MVWtZRbtMG3G$QFertF1?m!B>`L)pFqC5k8vy!RTA15i*K1!HgM!v3vh{XwV z@a~!H5S6B(w}0-#$XPT)v3kcuseY)_P3NGelUy;zpDWXoePMN4e>_ zXBMQto#ZIaWIE_TL36)hAM*|RxVxWT2PVLRi4%720JJZ@x;6yk>^%TzTFA)*pH_kh z(8Gu(Fmx9+@}_+M{qJH&rOxFWy3L&k45?{9b#OEaLKc&b&?-gd+0@hb}D9e{~ zsfeKiCp@gNRTCepO}chZ*-Qu1J~@gZpd=(5huRuAr#4*a(CglJekPi~vPnWC;di8S zYZ&Nf+>j5FFY4>ec9)p+$q4@bD?YUE&BhRH>zL;6-L>U)PWkCvVN9Nxkr9LX(hdvn zW$~IGwKv34-F17iAZFfI5r=5HGSFISP^CtWfB{!e?DA-d&3S7OJTUOE?j)qxN# zi`~6!F~lhsa=b=7u-KSm?=57sm0a6@)$9B_p|5(3w{~-#^s8CwD~qk1Hdt*{WK{DC zdp^J1!Amu6G>=zsSB*_Nul|G;`3A?cI1AlUdJ|rHgnYGVF||C6?+s3>iA}ugh5(89 zBvz&~8Y^D`(Gd&Pb{oL+xW3IDt#; zE#Ldcm{7Iroa4=P0@!I=r=r4AN6WCWun4{g(NL1^ETr8HzqsvBpEH|nGpKt+sjiNZ zFXCxFnFw9?@aEUl3`4pTa1qpNs=jP?kKj_r3&U8LjaMEVZKmX!E1j#bU9y~U`=mC9 zOOQ^}%(g(ibnmJK&jt^Y*Hnd!|IyG8PCPa`<@=+T87%0d?kse~g?|(yfcP;^auU2M z_+qXuOJ9*en-ip@Kh^ng?RY{t`%N!BncZCfRPf4n86B=)RC902LgD1$jf932r%YcI z%jf$qHnvb?v=qBd;)<@$O8cdT1CHxM9TwHf{f!Ut*0i#1*PVV z8a*qu)&~o=UmAg{!A8+Sd1Rb!=2`b;V;)>QgviL!k)$J28B}-|cn-b5ja6(8KlYhXuXiNr%z%%>2hYq%zy-Jon_&4uC<+Q%oEJ0Y&SGxGAkoj_ zuMshm9-q95_&+kR4eZ8En$_$(EUdJ>n&sS&!E(*vk&<-~7UoU!@V0S^qJq8&<@=&Juj!@3gQurd^Zk#Zo)9OG^J(rnva@H3ITv z<%Wk--FTa)XJymh+2)JU)1S_V@{jWf&zgJ^$t#WiU4Xq|odkM%Fsy#*TAt-y^<^QO z$j|~Eb+a;`1Qsl=pAB9N!<>CFglfqkG^ZWCVpIa%33J^ObQAWe3!4ZD>eEynP_xFg z?U;2wD;23&;ASF-cOyTfiJrV8NlRxL(x>WK&}(-@e1jj;y{OMz&pa|!Z-VDBip^02 zHM~8isJzxF)X^|zp+t0XeAfs>VZstfEAGf}hqYBSVBOK0L_nzOWUeiBHuul;YmeiD zfI1AgW&4pen*hs+>rn8x9rfT$HJee^oyS8z-s$67>c(~mVeLV@2>3=I$|a7jQm#KN zojDd8#_lnT?eq6>k;UG-UQgJoxKKhT-FH1Tpd5T;xiA@|4Ei&fPA|m*+np>E^fYl! zV)rTxPpi?Y4yx>1M4XD=8*Y|IODeNS5ESstFMc(C!N>Vz$)H>Ae}9$k-1B>0=?Bof zKxS>~JG7fX*r>Kf?*BtBbxs=T)YHwhw!UEI=+u2~QAWOwc9Q}Kd-kl=?q{+CU>E%| zV#YC~LPWpGRyGajvz=<+ftyY7I}q_Bv=6PQ?dRu|4D@*j;-ZEhrtL_SF$r}TZAy8T zZ@(&te$jtz`1hciV)uO|h?UJPQm6Y%{X~^+>eJ(IRkhjMOzQA?(Hf3sg%fsNfupSgs@uC0*12Hx|SKm|q zij^M(R@ieRO_-6tvVcS$Q1OxB(GNxG=8pY7VucOS6=|dN(b}>)6h&Y1Kq!AZSACff zMnXo0q9kj_@u2m|O1PIYFD<)hd%nutJY3}S;n(W?<|-TNj1Dqrf_Qijpd{8eP%g#7 z4k-)wf?lqi4s5K9R={r<+}r+~jukU77cn9BT*jjBT&c&w`E0DdZfFy8TVYy(%tU!C083a?~7p5D(=O>sv`P(%hWkIuPC-EoxAN&IHV4* z+88&&WMU?SulAPFv-%P~#9yl`O&PWaF0SP`QRg3C*46@TKPaA{Z$oej+g)J)kTx!i z2k*>|EZJGKw^WR?*r?{b5GVT$`CG+MSWfYZ$RIBOcYQP0-*%8Yc8yfPwWSO|@`B8` zDB5HynLpS^2N!-xe_?BT(=gzd@m`N=)?QLnub>S%Z?d9deFTvp!1lD6DElYLlw6Z*#r0wKpO`0@@Zv>_ z*N$e(V=CZATCWg6pVmUj7~V9cOEKI8lXHM?VDqsJgPRZ`(Z~x zD4=!U%p$s-MUMtYnHS0)mX-oW^!cqFJ9+fyNF&&_XM$E1AWbyiAhf$ zW(pDKu<^hA$05|7R;n#q_V%>kf^2;DG)!j5u}++zi-G#WCDi8v{IoxW$Z>hJ`P=DA zGs1;T?Y#O(fF)hgHP_SY^zy2($V4O?p_f(ddYpwty#qb^jTxKQ!J(^Kxy2tu-ltD< zd|xhPtV!g#rlmYhT?Su>`dPG&Rydi=>S|IbT`NCU`cG5yejKxHyij?|=R_s7;QX2R zp#kqMJAl^pskC@zJi3wl^RJpe=Q6?k%qQKc_;5h5r2@>?8z3A6=BjMRL?;LvQ=@}1 z$OS)eaO7q=`_M9HCC0%Q)CvN+w^axLL1G*rPOO6wbG94R<7{MssE8E-l;}KXTfJj3 zm${)Qb0Sq;v-u4bmQ=72W+fd(OI74C9!Lp$D;kgg8qq|R3cu?yh3%N{XAl8gjtH`c zj{kv~HM`#gsrQ|i_p0@%wX>(G8%a|Nmr-lX7~IuzrtZqrDdVpI5zE#3bwQNIwTh|; zQ>WPw!5g{j_wI&}6Um#kC=rk|>YI2afL^mMX`}=1UTYaILhsnyx}Tua&tjU8u&~&y zixQxqH~`6`2F6Jorm#fM<;qA%_%jERyK3)ygA* z5^17!5&;3l{n1IdE1HlVX+)Zq;WoH~e2KdAi+9=kH~xygBMLf;CPK#xoykrX1V+91b(&@{0hU)8cN zKQqjo&@yOUF_M|k)X30Md!F(IAc({FK&oQ>JY~gq@oKqi>>+==b8wN7V^x1k$pSjP zGmQyVKT3L{PYxLk=cDqsX_@b`Fp7t-5MN0g>8CJzE&>V=L3+>kL$!52PY-TngDnJv zbiOU$UzS@h1XBi;=%5o&7> zwk;202w!J_F_cGt$WvQgMrX7!ggp=;qa#JXUH>=9fbDiJ0vyw@k>4UQfaK|4b5*U)!6&$hm^GRoh>umJl$LCS05PPMHipax429w`WlM(P-wF#j z_>lmO`#rYktu!wsLyrx-DWm+aPdZ+o>abbJp-^mYgXb{mNS~+d)`mteLn(Z&Mg0WO zn*Lsei+e^y#Q}{#)5(l?3|4`ceQy6!_|$!Fcj$pYti{HOEniNjd{VD*@CSKXVi0|_ zP)U>1R}Ld35O$%N0y#%C4KYRu-~I_FaHhD`2-`cG#RgqK@W=`j3$S0@C|%+%009ODssiF_>xAa=JE!>x@Xba- zKOhq=IQomA%lj;osOzUwNX(d^+!{#EvqS2@KsXrSU2-WpFumBF*!5z@)zFc1a~DPU z$w@9)q`xPp=Mh_`_2sISi_>Qk|Hjt?tW;+kp?jq@(L{7g=Kf>@YP+@I(A6+t@p-qD-A3Q&>NGEnn2>}8k)9o=nM+K zflj#V3AGbeKwpD&pM|8ii_q#`#~{I%zhBdsztKPU_xGm>BVUf6Cfkh7Rbq}3xWo}) z*lP+VmumLDD6Uegb5HySYS8b`wgjRM0S4Xk^r8uQkx zCF@<@*T>9|7Nm^)=x&}g=&#(;>e)wO8Bq@1;ltUw+F}5- zB*!Q}+L=K|cxfpPif>-OmH}i0AZM=bymX&}TooxLVeoF89z-slk+w*$(y1t#<5`v( zPQ)eEIZ)%cw!Rw1=E~yute%b_qdNQ2`tuDo5m8c#))kF&=|tBaTXHgYLO9*&9(#8}MwmnoyIYlKJH%3B4`9D>Jn zU9OXAf2b2br{OFQBYo83u!1Mp#q~TqsO`H?IF5RgX^o&ok-#uEK^(PtlA_dIzDixG zpriU3?%W&r$19^Ti9^z975OIn@=a+e`s_QtOYRn{S8FXFi#6p!^-2=J1$1Zy2~aGf zlF`wA8CNmVd{X#%#`@YqCPj-Q^b7Pe!e&dIAvc-H+7(Bor=XT7As8ETVSqx*Lo zK^V;wxl&A`#NJEVpd#UcU)-_M7HFz->{%H*enLZ_Dm=b=})HKWI(0pZW#7!!*FeRh2aw z^F!zV^hsrtb!iWLpLtho2VW~}$y^)ZTtB_FBKwKREob%CCt?Ci9ih>)GwXEHk8C}+ ze|NWzHZ*Yenj`g>CKO1RJ$SH;+NoJ`GS3uRi|IoH?N8Db7iJ>W7#11*Km8EK_5281 zu9(97kx zi{Ry;qP{?0VGmlwwjIc>q}8c963C%O{3|T>HX&)Qln;R2O#N#xLX$?8cQs<@wpRfI zOnt0-p9tNLBE05ghb}ci>Bs1IKv!Cel)~)Ls6H?C{7B6p0em&x-9)V<8iy~66%XJW zpS%oC(=>B|z~Bq+v6BWKQf}&6Kzlwf&A4*bfun3Oa|%b~smCEC{PV*hJWFAh@8?!= z{h``R|2M%NX*e)C35;~fantjU(##m~)HkGd9JBUrpqG^9(&dUM#-6KdIA%*L z5WP-)0%;ki(dXu@z+D8D@Hk_UtJ~6aWglI7^DG<}XhYgTp`2r3yYEvF_vwA2*=c79 z$#~z_s2BS;wPVFY-a_Etp>ODftM62@k%NSS)S?bibh>6gGUwrE1TE=iqX3w@(&_at!m$!MVoo;Av1reYCuCb#X1l8`w@Q=^Js+Jdx*>r(eG zmTr)9BS2S|Hl03LI<6^?-UlL$ishwe=UliICez8y>HEMG>j(9AS`s*Hk&)xW0INW5 zt6G^1B_Gk=uOxBIwRS(eW{>AnEFMsQtaLpqB6X{5lAh^HJ=$u^v0OE{P8ap~EwNj~ zHiIhF#fK(z>6kr(S86z_=4uKc5NZ4nrr5rsLLTCgv;uS2Eq>ofjUb zoA|`(8U=~!HaQo3%7kV~;IYsx(f6yfVmi~y$jqd~G@6XKBh69zLaCv|1foA)8eWSa`vP6M=+=hNm9yM-`TW?P=ayW@EO*LgVz zl*OTgw?kmr@}{V8f5<Xo%v6)S4v{;TSvCmo-|CKd;HF_$)_h2I|@#0!mMKIFwNF(#vqoa0wp( zDOoQdQ{R{_Gr8rPHL8ODt+P-Abt$jb7op=j{?zZmfzzgqhJkl79IrkDRD?wL?PM9E zo>&V$&1X^!Q*#lx^EfsNMg-_X>QN#Mnf;yQEXIwllrpHW-`kdA^~D~>A%BM-z02)n zr{lv$$7r(E3RT)WW9F|3MDuTux>sbs)ADCcyY5ON+lnbfKG?>R3#ccz;%pT2J_6~; ziGnB^cPsoUUnR4F`4N%<;esdMA3a{4UMnh^^=w>ZFG)^pjJguYq!ySThpMvMh z@8bUHluM(NM`*dNto_FC?{rg+KD<(l=WW(aCB=wea!idR4Z3Sj6Y^4!N&r-MxPa=e z7dbrLKNujjp@7eZRq>?#p2WUEb&9$ub?65R1bfhww2;GV#&E{#gW9cYG2JUr8Rh2| zb}gu4y^_q>Yz{XJk)J0&L(KT$^4}Cx^xu2t7rcr>g|8OMo+`dYdmQHZS&Qs(PGi_5 zK%t*^#`kS7-BKAlC%iXNzP6gD_dPuxWyG;yv0|WGWp!iN1YNzxHu+&`SY0t_VzrQw zqGtgs5lMmZ$Rh6H!Twa}G_8~tj}@1VDN1_09GpPhBhIe0-Mvl;503>Hd75ehlxV$> z^3QEMjkkJ$!~_Z8BD*Fpw!b2!?s-j4d_PDyv|)Mj%4m#Z^_k4-@ElvLRoL|9|19iX z6$Toq4BQpZX0{VXm3lV@@S?E2v{V*tLE*6r*=J#D3WgdIg>Toj>01d@V>(t=rA~}W zuX)jP_9c9w!%B!=l?x(LH`zeVB`W?8EEDa%rr)Zk`$a{j(HGf6V~+_#ZeytVWn(<~ zW-Ijx^?RH@Gewdc3?B#H8+MS?v|8zBy{<-m*;{rJa(Xs@Q1{PSgvH>l_XBG{O%^~Y z_G%JLDcIfh1PNHpaTy2D^6cvEo$&pPYfdRw?HZVc|NPcB*I1eA8@ZFGtDBN%pKBij zuo6A{7;IDd5+j(E%#y~MveGy38Bc{bqtsKpu||vg`CFKx%Y)~V^&3|?UhA`mm8L7^ zuhuK;=X!6+y3NKhtU=b4=J9=!J?qt;J6k^PjX~$TP@m5(dL3l?B_)?v^jx?| z((hpL1V$Zx9Z@3I)#Y|Gkv}9(qhex$IuX6 z^@ zIE+0Tj;{b$QBY8*TLXVZqY7h&p9ZMG4>gNxw=mpRxNKKgr$FL%E9Wp->_)F*J+BXS zNjNG5NYylzF*a!wjT*Ui-V|~KD6z@65YutthZ+;92BYL>FUNor0zt1 z!`5`2#dvgX0l4Mny^b0u1%guUk5FX82O$cPB9#5+%WGU?%;*-ML})rl6L1Pes+#Cz z^-KyJ*Sd;nw-CcKQ9@6}ox&|4AuVXK5Ld~KXY5*Ehbs->$)*IWpK6RsYnxZJq3H+%re&{iIwL&Hv zDwIDdTHveBat|48up_FiQKThnj~GpU?K#JA(#$fS6JLHt6}~dD^Yb&M)Z>=oWB(T} z@c$Hs>iFq!m2y*AvYxmf8>Ar|*tOttQr+{cl-cvx4pKvw6Z1=1HRIO8VtpvkeAk`w zXzn04G%>geaWXw&=li>14&0>m3t>raz%;j}cXuy(u^Wv6j>AabX2z2Xu-+tvVHX&w zzsh&RA3Jw`HO1>R>2UkiyP*m2y|v#^P38T)LqT`m{FlV(d2V5wocX1_=Mm6@S4UpJ zNSe~qIyz|kyw;|Au66Z4Zr7&JhJI^W(k>F=p-|E(d^OCuFKhRfNvn{JI)GD(tAvFa zEN9j{K$p7UP|U7ta1#cxKPk&}-kT^)c=obB-k&LZ?B?fve9Y6Uw9=#Qf%>l8Y8B~e z0(P8?AEmzcCqaOoKoi((u}qQ}{sFK1VtIL|tcG=b300RG^N;khf)Nt5MY!d8xaz!F zgKBbwD^s;7eCz4~?*!%F^$1&<23w=Eet`R-@ymrrcCgkb>ts(r%KrJEeMVY(VDpJQDphn>DXoGD)n zF%InLGPBE=c9xx!`{Crg_Qj(_IuT0~aQi-iU<81d}E(6HjBiHEJ zqovcm%OTn%h3nTp75Urg`+NW&56Hvcm;g?pQ}%j{G+9^sl}ceOBKqovkJ@iVxYvzV z*VZ-ww4v9zxa{vNTEyEm+mE(hb$R{jkOLt1Vz(`px>f&006qTN#%G_%r~XH3)muQ-!B797Ao&Pl?q?RDn0xeXYX{Fg z8rka3SRPXDCj3gH-@QaCsuG@!qc7N$^^^67J?BLB@mGH+qea-$*r1%wQb<2z;YacG>F7UPwF(a0|uTl^5 z0M>#5mgmNL&3v%=msDhfeY(WpBz0pvbWISKK~18#c9YoS{-hgB7N~rRs1@;C5CaCn zS5NZP8Hq$j8~~OQts?%K^`6b9 z`_ul)M5eoIz_YFXkNPmd7M(&E^hWh;b6_GOXrmagjGx|YrSEa~Y&SThVkviq5hN@& zdga|Tt`d`r_<*WAn`ZKwKd9hSyvEpcd`%<*1Fq-?w0{xXd~Y4ZXUa^1o_)k`Qx3-S zV;3_%Jq5yXNO9jE36IM1^RTc*bA&jZDm082>49S4-AN>OE6{*lv%$@A-!WKV~22tXYPwGU>mBF{BYa-3jHiUL)lz@YrBXAs&zLLL~lI_ zR~?b$P;(=6j#7dR*)KI2bmEY)IDqDTEhz3D0GCsRNhO8Pm;gaVGhki!6m+>o{Rym@ zy><7XzkAJL-QGCD1n|!aJl3P7N5P^_@Kgae7~rDce*BaW+ydyF^%I;UF>6Sh8_~sL z&iZdoo}K%8h0KhMa#3i}oNk98vbvDVk;!K#?3~gA|7)^t|6@_;d)67AyA`fWL2`|x zZm-#ocJrU`w7!#IOzk~fEtO2gm~4?Y0b<}fQmIJNB#5ET+m&{68-n+}NQ+I{Ej=g) zbJf}_9URwa)+P+4uYddoT&n1WWq|LxTP?9-_$v~sw3=Fs_2EtDt=g_5hpq^2P|ka@ zNrHI}-$zIgZ7?ZJjYl0_DaB+jUvlWi4?}pn)CbZ#wFa2Y!3MOz{tHufnLlqMl|Cb( z`*bUKo@}zx{1zlX=)}nR|Cl<BxO_)gH>;AB5=TQ_XuO67_M z5vzq9SMYYfxq%dm*+EDmy4;aw+nCPRzsyzS8vWfGxW55Q&!dX0xhsK+X0UNZI~-W% z=;J&3?QwGw4R-GhJNeyMglNT+_8GiRgTP@0%yCX1%qO}lL16k3Py#yMEkLWmAJjf3 z(8y1;Ew5Lr?SemO^tgD?UW=NM4T4;mW{8x1)9QMqi&w$Zetc=1RtFWli;iPG>s=vN zSsZp7;cE7$D9(ioMyOa% zxr{>KQsaG*M0C)u?@~Ff(kwJ4n&$Q zeo?w*d9}1#W6u(C*9|5y@BzX_kq^vbZFjM^+0nIoR!Wu0(1Dpoh|}b~ZU@Mur!Oh^ z#GNt;rVc6%!&(@Xd0vi7ajc;vg@sE58QybjF@l>FQn*XZ&b?#&?{{jWor-?%<+ZBW z87iX?8NXfbi**K^k%?grb^A(qob{dVD?%KzD313J*RbYi0|W%EbCC%t1oa`9L}GHN z)BSETJLYMtXvuG~^3+Q;sX^*BX31?g^ImIn-|g}Ks*EYW`yh3?v_k+3Ip~q<6!F0% z6z?QG*%hmp@@gNESauKuhKvD&fma&?H=stk{E@hvHc_WzVoSqq6^rF-rSICBe(h3) zP8~X}VwS4xgrd+R)r?pQBu{b|oPHIf@7Hw;bmZ!DLjx*#T{7orlSzq|KB*O+-ZM}` z;wm@|mh+;7r4ha(+FI}bw*1nDI@^JTdVuveaO`hp`%4;6ub^VVfTCMQTSA|n_7?621zK!}yxL+|1@GggX2I5q z1))W7%EtGTaVVzB=E!sqc}3PyZ33lrK1i-Cta}8E zBF`d`EcwGBfA|5*6|y`~7*c-RSCF7#dpuR5p(25v$!=9e=E=^BSFdA>K5x}!C6(Rj zE=)Vx!pN>#q&jdliYPj&#h<`}!7g@0549?H=?2@nDC+OLF;l%5b4viC^FJ0dG}w4q zz=;B!?=DOu1oqt@vaa?rRo~-qDjdY;} zdIG%(s5u(B+P`e?b_ya2ObhbLcogm(i)e0im!&s061Gk-FKVC0?(&@nUcJMlx2GGV zSX4B%i6V7|h`*Hbi)(yL zR8$f;qgQIFs3GQiobr2$B&Md3k$NJ+5i#9$4vL(eV=2%2*8Y+07gboR3tW16@*adx;RtT!y zcVYKkXcx43Niakj{=G93nAu&@_Y=#oi?j?m-c-;6FEmJ)rd^d-Tj0I5sT3dSmfxzU zF;Rp2CX~3{J!r0)$iIa3>5{$D&Y0eX>v(nxz=WWQf`v88Zs6aFzM-5feVI zlJ)PfEj#eYrU7qiW_?pt#9~NB3&@tsl~4r!3BFau+Bc~$g;A;HweiE zHyFOI@9{DSPxlX}Lvy_trhf+ILbqMKKUyeuiBN`xwpI7@+(rKzBhpIEZ$THG0f@Un zhr@0iSSDI$+%A{XS*r}9s=lYNUgWDbTJ`q<#J@JX@S@f}=b*j%WuXBz-i3|w1X1AM z2oWyd9!e?hfiD?$ zhjKdTHiQxW>x+Lz8|XS^oD$xfj2opb@hG#Va=BDoMU7Ya=fVB)^}jdN)8RPr4P5yp z1e?hGrFb>Gg6&?I#V{o5nw8i~<*sL2t$S|In-~9PZcB75f>w!>F?i(`W!6x$hUb}Y zL~Ks*>xMi%2o9@t81b)Lm(FK!!841~F%5wsnUZ%2F8I-{JKBtIp4R$ZbjM<)sUtHt zjo=uoo7RaN8r$3N*T%TlW1Qw~T;o0G=YJyc|2ES&zp8)Wx#47C_`}@9lD{%R;hGWL(6|b9*o#a z;>~%_4ZNpc1@R|_j%Aw&mLxI)s9;V;Gl2v2Ta|(z@K@xn?p>D$Xt<=}n2K8bzm&d1 zf6gXa-!Vh3TJ?F>(L;UuE?MvvOW!L7Qy@qzb9KmvE4xj2_jLi%$DdBC%Wi`Q`FZjg zG39?W3`(VCFJIj-@N(K@;$+_(UG7=h(i%^lS7lsH988%fJh08|wr5V`vC95DOc+H7`i2hUv6F!5R zq1=QY4=9qn#Ax3|+X%Tk+3360SqO^TTbpdW)qpBSRMd2~qgLg87nbT#DLUV3np~Y_ zH7e$XZ9LM@105J{l7*T~NHaX1XxrCg;-j%7f@8Go4qg#BzF=gRc`2~6s7xnVHHkIV zXWe$0uL8}0!++&1k{6Z2`0i}<>%Zcf9ie85gy-IqAv;olxPE^XkIDCJ@ zEp`GMfjUE5#JaiC72%hZj8b+fJf|F#mKX6v0|Zfkoyxwyt+=&K5N%$Y%y))P!pCIH z!ag8zflo$MWguxO+^aMY+<>?1HakbA^sXm>hBA8!B*P1zE(`N>f=hX$hNOGkh#m?9 zSV4h^8@}#u5U{&3?068!15wsosE1xv3nVi~FJjM7w}!O~)pg(@R{Gz?dgUYzq>+Ca z#?`M0gV2p4pRkk|X1MUZLTlssVF-(dV}KZ+-_3hisZ+vS13$;y8Jdn#p;%&F5;Cx? z7yAk7n&ZQ5h)drigP!~{oe%RW?dzB=huNekDk^T_9nH={lSek?2Y*BR4aW@CQYG=ehlP5EQ=FKs2Q9Flz(;XI z3`A~Ixi>w4ck`$an%#1ZM29MZ@b6JoZs-@}@X~x*fXMLEE*be2_v2y__>IM)k=dDO zeQS62y2BoJ2<2JQK!7bW&XWt!mO#u)r6$gF-sp+R1P!ZgXgVn3$t+~hJJ8Q_?Ur-C_ja0>KI2? zOYbVmN%(Zg)jz~zXrH?#R0SCv z(e-9k-D{eJ8*2yC&oOCwUp9L|#J_Ki;SSI-0+DawmW#O?yF3k5D_kpDb>LqgBHn7Q z(y;%bpe(__gXkZj5gPbfM5GeJh6|95`%S}xPtdPG1njqmUT~kfoR^T@FeAid3Q5*b zru_6tkA;03V&XQ?W>^_NHDkeS2~xPuk>w$b2T&t(QT6!?8!iqHg_UqDBAIGEp*&IY ze~>PX~;FkY5?7U zoPh+RtYyM+H3?0LX;zXlrV0WU^k79*feDXDH^wYGn1V;Rt2@F?X06)41~Eh$8aw;) z^hqvZU9g9@4I(-J@+?t!{mh;t>-0-mP*g#pcW4Uk9YYnV00Dy-u+yKAo_N*!F9rXX ztwf=&>1p}09mEj|g%d|Jgs1b7$9R9Cal-80UdAgU%$$tl6F?m(M>E#0J8>B*pUYp5 zY8aGbuwf-#-j^wEiVM}#y>*MkOl^GzD?@pdTJeMdInJsY^HA=rd`)3FQQg1xdb1hJ zJSNnLkj+@SDz$SvOcu=|`B-pEU<0W*xlKhHaqtH-mKq&j>GGN$G&It{vN~rn?$w}l z0h<}J4s<}q36G;B=LtVW+fz~%X(GjGyBJQ_Wh}9B?3yc&b@ZxUMvTM94Frw$6ZHIj zJHc`nh2wOvez9e?_g76yKTlD4ys7LX9jcn{D_e@>@Vl80y0j_6hk>edw&VM{lK*)n zo!o3e82&(G`odFWjoF&+E4m2|GoIfPB<&y0D@z+}Rx_=lQTUhreKKdEE%i5>V280` z>Za{q=ugQY@nN#izk)^TY>i}V&7DT26+`S0+#mf2ueMp}7poB(L#Je3 z@_BwFECh~_7NP!WTr#2(K4+;f5u{HFHgedKcQnCpva*`2qPv8xU*-Wn%9m1pHCsdR znscmhuY)6GPvj5gD;sX1OG`b8uj=n;`V@Nm6Ug$3d3X!wJQgTY9`UIap9v_RoaBh? zu^}Y_3{{gF?xXBmjF3yW`WobOTyf|SjrtXadJFu;Fu#E+r1{UVxa^#XZ5zU0&=uiO zq*(2@q)XirNa?%{EnK06ITmW2Q5w*z?xYLhd--J<$QNy`L!;#r+n)LIf1Nxsm(WO1 z{!`DHAnFlC9@xXN!^)(ycSz7jME^U&D;tU4qwucX9DU*&SuPu|n(OdWHy*T|^FP!2 z!cU|yp)p;93fdGm`r&?m36Of@BRd-Fh6qEkx1PS>CH9m?J*2@?XrsQNGg*^PRtbk? zfZF~z;l7s#i-`UWWhX4P^0ySGYF&_Q;9q?H$SQO;$#=$5cPKmS$-B1`3(;1!h0AZW zOgtc013##<;_l5Yv*$jR{JVCIW9;%mgZy@D@2oaJZ`^Ola1bHu;-Qp`hsZ2Qr25m= z1g(dk$;3uQHf43ARM}LTdV#&qg7-rDj8l#!0tHGU_Ik6!XoGhompPuJQ(p=v*6WVD zO}}_6W%X!d;++egYbkt$Rf793SPpbHop!b-%R)w7P85{sR`xBoD60Z9X3Hhsv#(f# zS-YOHDg{4&pyF!y-J!SjBG?HEDHN46b0eM1!+0tmjjei1i(MP6X)G1B{~UBQ4!(Ht z!ff5NxyXvwPi|lK^AL8lXxiLXgAmwFdKRC5}rgD`>PeJ$@47$D5ge=N>G-WAyUCa$ETFYxK=x%wX&Pn zNy}m5-w~M3Bi z8vr~mD3ar2m`DDVH?!zEXKm+rsFD)h<*(48>3$PrddG;a&#$l22woqSff=*WI_A2{ z%TVDLMW36f=lJWJL*ZjCqz>r35YDySHDXFby3LJmHHMqAM2K2zjgMeyzCaU5T^=eb z4krm!23J`zyemMhNmmu2zv-!xk9U(~g_DOyR$2N6xtF`Ma-I@h*tyjL;k~FbMp;eB zy%!2X7B)_LLM366KPIIlH~r_LtqRp#3x%CZSok)DkgLn}G>4~(#P3~LGVXG6FNjs6 z^NpV9$A_EreSQT%gtgap7~vS0?ZFa4JCxjO{2F}GGnD^A%c^5PKphR{o;{uo;>H_UVwL4aCz9kp4oCCVeV?jL<@l2kx7fw?U|s`abH;qEPRO{u8(B}1iyH4^?`wWnfO{r|7&2sJhRQvUFAGmZ*S#1L^Vz4hF_GA``YmgQkMl#-UP}|eO#%GPn2(8w)m6m{2H)4}n=)^os)C|UWX*nJQ#0$( z{i-bBnDnw4Oe?%o`XIIh6vD`N$~zS0&V7Md=$+fUHx;@@DRGg)UEG6O=v zf(Tnobeg^qeeL9@`3fs-^7^0sk<^?)(Kxna7!H2vBUxr*QW3~ubEw*Uj)vV4Yw?CY z&O4ar-<^NNXJU%)L1za=)`>OQrMnI-{Y{?UZWXNU!-%Nw#315y+^N^x3l)eNv;O@% z@?E0>X_1}NdRAUDVx_vC&tq+G-bU|5^7)n<34e^453h#KHAhZXI#V?NV*mAB>sgC! zNUHkM=an)_>%tdKaTIiYItEo&2)gBI3oTBes72*YiefQ$UZG{mx~>94fa9-It6^rRQ-WG`xy8qXX)$sZ?r#5A>PzBy=QJrj@?~B-$69zYujL z8W*%8YZN0umq?uSEw%ah;AmzTZK%w(qN*V?lCHELNS?7lwS_X*1%0VtB5Q4LL}0$z zTyNB0z6fTq5@&qtP0%{<$c-tmvQ|wD|F}+@?V0EGb`?e|v{F$#yxhplJ$~FJ>XeaH z=7baf1{2917A#e^e&dtrft}mWx)k&2BH+^j9BW!Zit+J-AKAoA)){NuByKZ4Lcly9 zb=q+MOCy>{FLg7uQNNIqR;}gRw(!l!UuoJAWZ_wcZ6@f`#B&T@NOnl|Zk* zSx!qvTE3eVyOn5m^x!mfAQo^}P0QmiymIdTw9?~NflMLspGk;8aKxDu``NTc80p^2}`+DiP=2YKCmx?htF$DW}x})o+5OHj8eJ zBnEYbat>Q7MyPLZd(Ezo=A#X|f(z>F=5vKLUd%Ew{?IV_=9v>SFtPmyBW8UGO$YI5 z%D*fP=9?e#QBG78fhIuDL*W0p7D7R=%piASdD3>TdWTw^@VS`7j;ro2`vy|z2LGR9uJyGyTH62ck zI|{xCtkb9N_-v$yr)`?3aE`MeO2}u;bRFUmX-6_yH9BWS6_mALv9ciTiLwi?j(ILj zOsmFjX#VOq)(oypx4sVLL&WCVQmp@|ot^bEyCu_j9xj!jg^zlF>jm5>huX(h3r}?Z zHVapnpVLy3_{qRAVmvJE;zE@yKI1}m!N&LICw_L#T7+yYky2Z`fbFn;|EVXpL8&LF zTg#HWn^(j0LN(WMo7zbV$%CTob3~Es`CA^XAzB7o2NpV3K_0{_Q(LA#A|_lx_$jD{ zo$OD&ul)=JYAp&S;ZtXtBkN$gQ`RPOt80XI8IG_ky9HYs;OB*)hf~5QEF;?W>yoA~ zq`2ffFa%p08NAHDAB&1nxpm&bJP@n!g>aJpnD`2FsKf@yj2sqdYh_+G=l~{Msf()EoG;XFv}Xu2!t#mBJrDK`c4Qr zYoZjLob3q#VwYQ8DPRWxO=r)?hsg)s$S6G-?Z2PpD${HxVYJv7JqRQE4u$J2#y)(M zkx?GXD(o+Kjn%VL|HRWVBT$$xKCbjmhkuHLmYVM-I}R#lEEdd<+avIN{im|dLn;AU z^~qUek!P$K43TF8dLZ8;VT~<#&GBl2q{->ZjEJqb2U(6+`1qO+U zQTP@z@Xtp}%3g&_a4a59H!XfmYii>rJdV9-XSMUCqLWBjU)v|~wJ-dl9 zoqbdPC0Ro}|Dkjg*8ln`HvplD6bPsg`Imnc`1Ime;jvauCJ>@#>5^^(;GAj95C@`~ zu)2mA;|#oxV~OBZ5YXp1<6**}gSeErdItY%y*&CddQ6z)&@x19K;idy$%=_1>2bGV zk)xKRcWf!Ie7@jI=w97eHy>awqXcKPci%$Z$EFLp9+v+8pa4I9@lsu}+Un($`s@b- z|J9vMWN^jM8$7AX%S%$Q%@4HdEXQ-q*{<-Bd$8F;Ldht+vW{|(+UIzHvYtXIH4*(t zEiE>FHDigU+=&dhfkzV-21%j?Q`ab=S2wm@j>ibcKu+aBz!HZ1_o z9J=qq7Dtsknt%EBn^4)u} zRDBkwa6f{eJUxMYSbPuqJkQBSNTG981O8>{OUppRQwXava7dL=iDP0iS0ebTDs zNxfVum#zo11x=JGh98`r8x2#+g{Z29nzeOF7bKsr;U&Z}I-lh-!Y;t+!UN=E6$~R! zTh{sH_f{#7rAIGtJD(FWrW%iw3(lxosLkARv-FoS-voCH3( z1SaMEVr6b8~vuJnx6EZ_CnDw*zuupQ((^HHF(!qC2B=~ zEzfuKI3c)To~^QE4vbGt*1I5(H^@3m*!Jn*zJhE++ZUy{I4grXTS1crF>EEMOFkvD z_F7J-LPauC=hx|jE@Wi)>+oZf+V8SThs*04BvR9)XgpXM;NaYloyzku^SI`U$DbYu%wb zUZ|bSbA?AIbsWJQ;f@Fml}?ffraq{h)t!uUtQ23bO6Pa0ay~&M4L6bJzF0T4H|p#eWVGF3KDFmK2D;Qe6B$r znr0|(&O3&S#)OhsoEJPEduMymp5+y?RiOC%sd$9>HKBZfAtr@Csk_Tkn`Y469qW)P z`4w2by>`4>UVzkkV!l8x*p4%Yv>pESg<2c1wffX+D|J6J>w%nuuWDx@dGGC?Fls99 zO`sXley#_nswOMXcSKnjrD=AiaigwRiNKwf0GxlnZr3lIG2V9+@t~4WCzHV<{`tnD z)2O%FDq(&B!qM%|7o+FtO3PJ!!wf=KM*GufWO}tC&B$Wz>Ew5rptpbT@rJe!&C?wb z!U0fYJ1jkY=Y-b?0Con z7?@n5`4FPqE_U`kc;y!-Hct!(-eEA*5Xj&S~2-Pk#Sp5p1*4mN@-u zEA!?%LtKA>E3)^H(Jgq&K_4t@tJEvBojM0wd$Q!Iq2u`pGWl_xUmEL<^L0aIRDbIeSsR`FE`IoA`TE34peyHOf4(j&$VftWCDMQnaX;zGnF0By_FO$VC_Wv3SDsPRk zc{{);^Xs-l!vCQ!{gwhh8A_}2h}^yP{(zxY6eAlNAmnjZxE2%Ze<0 zo{lJAhM_D-it5+)34f)WNs-w1fzW;v-{5Y@l39@W%OLBFCA&jU?EcrsbCr}qsrJ8% zDl;C>4qDM^T##dEyIC;TBBzHQ*+W0nxs>IgLcV=L@3_N}2^o@7)TN^&JNS7zxl#OU zo=orXLT`4cudO8E7Iro0rN9U@2}H(_+2RSo_P_N;sH+vx$0!#4Q_{2trLE|w+r4c3 zJ2;x9VikC8E##2wzp*1WqOgSLn2=*h)xMa~ELwYDk*l+UeFRMdpOU?7WWqc4tr zd2aQ^*8mP`dwy(O4#Pl8?%nRI;Z#;io@%s;;DTM}?v`NLs7NM0*~p7fbZ(^xTBRH`t zR)F``DR-XSvwwrKH`MEWTMRq%|7zgWHBlKlBwGMEA9mfam= zJ4+BK5vZsX4V|$rbjD8g?~bM&4MDw6mfGD&(KLJt+Wv68kZblLg+)kS0I()~TXJ5s zDr?YyDTeJ*!FVtK4~2xPj3u!11>ZkeyG$V)TH`8?vB`pokfGI{=68kE;Y)50kBln{ zdy~+VdO^E?yoz_r&-Gp~TaD-r(H5Ihb`VYN25w0{R4ZDD54U{SUpDrX{w+#nX53o7 z?%3jLSu(a+#&)bME^3^Rg|+Rozo+=93i}{7+AY zzqC0;f3!|6);jOZc@0u{j8T0|GK5l|&#xgGWj(n;@Sa5=WD|Q=D6(?`hM1x*j24hC zApCBw6%wYgwPMpx1o9Wz6v4smX5#1@@Z(|_wwihHKaT^hpd#Q2B%eq>aPX0HRy4a} zlRzT&JRM{%goKCEEXPMI@El?0#i4F}7*QORrW_ z1dKE4>r1O-5HI!Se&6nT0SiHAecEB*V>c%#|WW`Z6#UvS#Xk+_+VEk|O z)z|kK7NP1j>jt4zWUr%kvZ8}BEESf{Ac0Fd8@(F|zoncn>>oVyrcqrU0Y7TT`3YZ*KL@-tEYR1C7)iLss z2;@6NxH^G+)-z*XVQS&yhUqF~St%(0)-z!&Ll{)3q7U9KpMpztZMtssH40D=x25^l zTOYw1sp*Mr3of#UHe{_&?4FlKSstn@JcG6hfj@A1){rwJ9c?^z@eT_%&sS<5f z`s8;<_Y4>M$6;Zzs_Wv6n3pFIiT6+c9#=*7Cnk7KmX8WNl#QqE!bxI|+2{#ScHfkA z@dui|F1h?xiwpWZK>HKdtiM_xZDGG<1q*>#q)3pt6Yww-o*EUp5+Tvl3k|eyetdH- zCb0*YxsAzL9?*`Q$OFC-7)gyTk;kOmMtIp?2RbgI&eoH#A0`U$Nx*) z?GmRv=HhENor-^!v}Vb+Y+sC=rh1Jhs7o_)n4El_j6isC!RvMzY-(?G%3H?aX*4{@ zeYYCVO|bA*Jj;@7BhBJ{h89RLBmw>O0{PO2+lGz1T}D2y72l2Ns`ap77)m~ z++G)>-)JdHYp-WzBoLGaXLQv!GXC{HW5u`Uv-nMt(rF-~{5{^LE8NxSAQ|?~*{b=n zn1pJUYp}VeU;m62<=6oLb(@}ehk*UJ=4&H`jbT+I$W!TkQLQ$v6Gy+Sg&n(nT9>@U zw}*q}QTet^5h8eNm=Id>w z(gl9NL;a<8exzR>{1091^rZuj&0$CGn~+bdMOki6`)z`1gc*i9>?hqm1hRQ&OQ9*!Jvk%3G3|-9Ra}_Pa;Hy zMOA;pwD?Qj$bQO3BPRn$M#aper+uCS z=Y;@Z5iJc`%+#z$ztq@?t@O0mSM$y4xMcEQ470mGj={UMoTvP2xsF}(u`+77`g>p2 zVD4}Rsd;&3g-gH=y|CP7PzMJK$DHk$uFyfklz0loNIDjnpFaZXxpGMdD%P7%9ko+R zN?}5^S_-Al3RvH-i%sr9T0RLhXw@h0kbwLD_BSHg1EDY&>^KVI3w@Tzg8gB-KEGtV zo^ChMQQtD@HrKzD_~{;oZ&4AsWW|b%jn3S#OI^j;Q?q+7w4Dd>XwL2)hwu(=o-$3i z`FJnsg_|mkxb=MuAqMS22R$(PqJfwOzB`%egF92~}2^p~* zoeKT};L=tud`nw9RQ&hCt3qe(Fof{Q1m1d$QjM#85?Oa&eu1!_QdGk=T+h}h6l%Wl z_+{UyeW7kGmU5kV>Wf5#Gss;%nfx!MvWJ#){O;>MV;2t%Jv}{-T9I_=6BfQEK=k~j z*_H_!0^6xW21jdM5udRq|B}}K4{yAA<)urWbhaL$2kH|+e#AxF;jfVKFb1QXoP@Ky zv!^$RYuo7Vxf#%>%oE845*M4WPZ?1roBbPYE? z7u#VsDgEOKm25`m%!eKanyFApLlXUg>nVe4tykI(o`n_+yD&Y`UWNI_l}bFeA2b2G zx8P#pEltOKg%TN~V7)PrDU6`gA7RmTX%#YJ+dH?Zn+>%8ERGr+TGDZcnfbjS;kJq}($A+I@-7+A`Fojyve)9d6b zeLspmAW^%PmhM7+mP|y`x6fc_XXoyn%%WH3^AbwyjV57;iyKYD%!r|sir(|EsUSVP zXgKm!qRygV`JB^p+;d|A3<89=8Mt=mAK6SdcnNO1QA!JqCd)qYWKWJ)cS$fYAU0`K z9QdL%eXn(X&B0J;BN1vBbR9?(F~(yCAXWE-YGpyKrriSS9P#R>Ul|G_LI#F=noPEP zDV6hWD*}UI8)y2n`8n!?Ja4;$?w5npk|G9w_VLO-mvN}hJ=c9nAr>MjHWcF1&m?%( zblIQ$Rb-)XLRDhP?lmGC#3aw#1V_SH&=7Xjp*LS|XEYl>YzH!wU#tQ^39ysH{5;nS z?aVYPC@2U}b0?KewlTV8^e+Hsj+x`@lHdk;9dSpdg-Odc2;{e_pw{=1wW64 zmC<6=;(m2W82jVf;<`uo6!7=of%TuZ!&sH$F$JK61JDt{o&&ny3@a_zZKti ze4wgje!Q~td3O|$)^M;(r(yU0qjzIoMGa&2lf$=WPAK3uHpp;}f-}zlby_3njHDb8#OjT^otGn$nK$X~Ol(mqJ0n89N z?Yv>#MyVSZ$h+@(n+2ZxofEBgZu70Di7#)@CG+-jDMkgkAH=!{sZExzH~;n|q5_7q zqy2+32{+{0$Xj zL;EofZE5iF1B*VRAg-gM*TePn=-G?zdV;_nz~qh~UNGs5$7O5|I;eE$t@|e^eCLA; zopiex5OUlZ$^1l3h;Z;6nsWIt^FSFlhF7n|J6uu2SUNH$ncXj(h)bLOw z2?DbjYIr^XDplM6N^}FPJw6`Zn|xOKP^zLiTj*bJRUjgK#BZy~`+=$4r+V0W^%#cm zi-$i`We+DNo{3Cw@t$3w_Z3!^$=I(C5@WLaHI-u6r&g9<5o6nZMbl9C6>jR1w$Ibk z4&Ak!vlH2wtFo-$J8 zF0Z>?f496MNbpAA#n{bHr^$KXrEIMJCDYr*h(Q@NJ?u;b-6ykc4*h!ocR=opTQLg- zB~%s#w5^Ry6%TX*Z6;2B7)T%P5ugX>gg zL$0xQFe7bsq-O`BQ!=a(QD?W51Z{(Q%wmK0~cwshvaZdq2R3+&DdgY zrc+EE-X1Svvg-5X{_IFm{@u6uyeM&~U5Un}eAPlLl>$>iU_m{P5i2^tw z5(pw(hCs+=!hHPzhOm3F-t?o>E~y|U6N_h+TozfioGIgil^1$If$lWcM77uFfSTJfLPufD$K@Tpf)_H)5MF0=QKDP^6Sx#yd0U^OwDxe?R!+iALbU;Z z2$#v17Yae*wHpi5%vNGb{75{J?cJIMKrZu#D zRX6u8I*fn|FpL%Rm4NK;!Hx%jP#BNDHDhgxoNI6>)P;vd$DYXt*+1Q7f5%&Zip=fZ znX@I;18a7#gPeFeD`G)USNma%#jYYTdhTQa@iM+b#q61{we?MZk@D@keA2H6rWHrx zT}sn+Ycg@S;jI6l9n##5D_U&NkIQZc@9s*kh7@XXU0+oJde+^*f$u1njSd#g=0iyO zM$j&lw|eZG?8@6df&W3dnBw>TL`cF8#bL9S@x0Z#$(XvbP-QOi+5Ee3S|yzo7XF*m z7K=&5&bduQ1WNgrTwrUT$pLePv&LIWu$Jn}gC6J*sQ%3la;+54X^+-=sB4 zfP;aBdo3M$HD5*D<>h3H5htgz1CeFICpUIeQy2i2UrBo2Z@Qj66eD27q3@#e@_ zh0z5rV>O3TgHZ_LJE_ACOMz+d>*uZwNqMRg(HCjAbp4G`jREj!xgXPEA@cuf2CBl> zbCK8`oTJm>tsr=lht|n^L4hLG&{*|0ZHgB`>4n!~hq{!=(pHh?d9AHZqR7BvaBzEz zw|M^zKjqxT{P*-HN(EbFH~-r1$=ZQN2#xK3&|lOOUdZndY65+kq+fl4`kd)+S>&|adc3n<+Rc*=?t#((PZ$Z zExZL0`e{Afq(7%YDbd3sjezqH&H&njiVuxT3p9pP>hT*H=sEc4f04|G2BBqQNCl?o zMUGJCH6l}jK8Hldqb+%L(i;~q0c0!Zyn6yE0z=a+vDm-Ger;sit>AxQ_-$Rhqnx9eLF%5N_>9$LCt3 zXY+3WtKFEHHp^5>BB*lsdV6Z3#`AIAadVLKnAcq37M2EYHjwvo_#r@zGu~S65Ka#a zB_qs2IGaq-7S{!=_+urBo+}qpF(CsL;stPh8qLx_wY|dFzIj9@rhLn!a)nCziP_pAbmjD_c zAw$npqmxDLlX2pC5Ch71*j1G8Zgb%A!Qo6}_`iGSQ)4nOP5stZBVia+erlxzxi@-7 zHvbS6OHO_~mjF7}KV-2@;iEW?`!rJRt`~k7K-K?;A2z-VV^YcCb?3>&qWJ*qk!^(k zU|FD6*U@HgIHn{!`DrzVV3-0RVoWuHVzGZyR5%CgH2|bZ~`6m-?EsgJyFn)KQAbpBCyr1SoUz~M$H)pKjq zJ$IWn+Uou2KGJHKUB^SZE-`~Q?+7rWlVf%#?_TYrdU!x~mcX$o7@8d#8p==YiE!`z zA_0y-W{>K|9?i1(+UqTpCknKQ19`VXFrLqdS*i=vloq=Cesv{HkZN0?&XjGEdD z|GV$Wd&GVB&qGa9kPuhG;D$;pi1Z+}$}br~yenRP(n%SnB@#!sqt42MuVBQqr~=q6 zeiiYU98+yEF!xQ78+OC|@#gb}pC#YzWJU^;H3y@tCS}-B`LM7syCEC{(Su?^Vm!-5Ke!pM>-3A4A z;4Ng;etmI5s#tQD8DR$Ts5brm!sHmrcOs*f1FoqgM7)}8h%HWg!$U=T!1`syDpc*? z8LS{wolV`dsB-ygab4RLn@q!sGKgXlTR_<3NwYynB@h0KqM8JH#)|kdX~;{hx0~dp zZ`k=c_6bl8j3UUsE|I%i-XsHUYvLy+CjN@_>?kq@d;n3bus+-GpE4u(%BpYbire0* zK|*p24D%tu|Hso=M@1R!UthW#38j%9LJ&!5kdhMV?(Q18yFn0XB$aNE?vgG6>5}dg zc+cE>fA1eG*MhZVo?)KzoU^}se>Uoxj~OeAMp$w92N@qPZNxAFag(4EL!sGM3tbXK zX$`gR?hYTSb@kV4oCw0DEB_E2#h;3`z=?mTz2J!JXLw~9P~(i1*~7~I>+|RI45kV~ z6cl7|HoZ}VareZdA`^E11<{pU%y@g-&A%kLhoFJ zKsbAFrosm^k~-g(7{n*A=>ah4iF8wx9u9yne0J6FV=I{DTcODW?ya$lu#aE4 zM)fw}@fx*#ogI_tA0B-$-`oc}MDE-4Zxh&^B%i)XSjl}}Tk3304@k`QsPTGLtGdi| zYQW0_P-L`4kN5H`NfRSMzIt7#*<{a7r+~g`(tr_^l4!a4;bI;Wl&1eU!RH-mYMASl zt@gaMV%>cY!GkNS|H8Sch}$OL15r*Ico!Qv?PZkdz=H4L2mC$FDfu{mO9**r8I!vl zrjJg>i}}#uzGLc(cCe(G=1(OF`PFEP_M*MS-yCdu%~@#|KYRZAZ0BDso(VdBLUhx2`)&F{Bug{=IVuj;v}3|3w|mQ5#US=U zM%o{tj$cW>cWh66Iz8)K*C3sIqmgx-?09LwrKUbACiZLtlb)XbZ$|+h_r0{zS|HC^ zbEk~hCLCXlYh(}kgCfWdm|pD0T?i!;zwTl{p>BIc%M&bsh%nV@__~YL1cM?pBnSBe zkmg*ypo7oN1_Z;+;AhAC+9&aCY1!z{ z`Cp6^ab2a?*G)KK4k%xW32b&=^HS$d#Y#8@xS2sC}Yk;qqVw1@TQngVxYVAahgJmWK@aEh@m zZ_LXRVEx?ZWESR1*b@S1+cM-s?9BY1^Hc)ijnwrCQ4HD)D}fxL9+oRelMw;%Z1 zJ`OneaN?I`yoX<6f*Y&b*5RaoF*g~Cx6K3<$(4dvIL9z%$4Q-Oc%E<;5(98=9Ghe% z!EvNN_Ls!yj6$I~)vv!r{tT+T1)~~QLJxFFJf7Yj|BUGyQQY6;OtS$E%bqOI(o$zM zTdL*FpT})e0cwAvEg6DhIrpGa-@X$gR?0`e;FdKqGSYEw5x=xRjxtUq{7uDv= zKH?Zb<5-ds4I!cAQiHvRkbrIy+RibDn!-oalnCHUY21F%BDxbAPVJQC`R+(bz!(sN zzAle8suGHtbkX&FF+h!mp~7DRl&F@#5CgT zYs~y1r^pRM<;vcnv#nuM;7n@9LI0Yd^@~^Ms9S)8o|I-MH;iI(Vzz|;gTSD_!Bp`zIJ^khj}EOLZCBpLWYVTo8-d={83N!W8dzf+y9)(Zpe00AL3(29ba$u z05$VM&^V1YDbQyzjn+G>S;1tCBc}{&B|!M`M%?Xasid4;KXhlnK7OswJ#Zw6;+-dW zV=y1XR$k0HQ*5@x#F-Ch!-jIv?@Ez&mI(O&0!rN9aipyA+m8d|6f>EzaifV7V}DT( zV&g{%-3f+qA%OhQ+B|EMN|MQpG=wYfjhzb%dDlt6k+X7YIgEw+O_tjJ& zFKso=ZZV^ONJx~Z#NV3$TXxNAiFQ9!oQ1Bc(>G&1PW8gBd#ZI1>K6xNC%Oe#-l1AN z8kqx$2;lvAiyK?oT?MXVquMmenL}|gFXS@DI)KBl5f$R-{}MeQl5X@tFe#w-$`U~= z0xfq)C^0dy7M+VG!tI>2C%s4#)tx9{uM9*UU&$bqtB zS?^pcRrl;UD0jz6(boX$e6mXwv|h9$kI*#R;0j0AD*RIbi?j;XThJnjU5Ees{-O`0 z4@=-oXsS71yA0 z=~!kL>vL{DvJ+(l&>c@T@Dar@nI$+P638J&LtqYgn+h z0FXJjYbu4oaQ8h%o!kz@DX7d+!-ZBnv};~Q+9Ad4iNt(RdIi{srbXB{#yS>e2@<65 z+YvO5Lgnwu@E;|*2J8Y<9wH(Xep?Bw@)xCICTIyrp;S3F4Y?Zy=cCvfiVky4I1S2W zTHJLG2CmhelIC&NWHNw4Sa^PA z#xw-ch(awj>oN-7qUPGDp{=9n-1 zX^J6-lY#{wRTEUSy52%%S6Nulpsz29vDmrQ=h&Q0tD~0De$Zi$b-e^gm^r6<^E^`fzXNlpl2!3%`3<^$hr|Jq4(s#PH1m~1j`-(>bBDP$ z&1T)Nbp~&k`KX-YsY3CPArBA7?-EV^1Sq)g$*d;XkiDSpWV=?EqVgW0DhPEB058bT z(8${f4%S;!=*ZRfToS%Dz`x@xUReh{kB+6QciGnsNbHM-XN2zz*vHDjS?t^4oprl6 zMZnijEYIi0TCuLK1Z(Q&g$^&5aKs`Y934){>XJBbzH7?rv#8eVmF<75cc4Z)Q-&SH zfX7_3vj^-#!k-Ly5pr@eb#=xJPJ#_(!_8@q!oNyG)B5T%x{55A%u51fCB2COL$@>j zlYF!yTL7WuNbw0U><)ooW5F?&Wl&wPSYVn=>(~$6o6#FxOs{~SFz=Wwo52@#OQqIe zL0@C8ztgjtX)R*(>s}x)@Sl|(BGeR4EgoudX+U)pHi~tf+9}W&fQme7=h(Z1U4TL2 zpHfxg-xX>yx#fx;hBSEDx+jg7+FJznQbsla`%GN~Ak$5lf!t_1bo3#R3iB+eRDXCE zQr|f4Q8xs*j*?$d>1K9*yd~!>B<@b-KxIbwss~cW<=p2euCho2{>`G3d1Bk^g}}=K zwkYT&)y<6&E!WFlFNEVixu*{94GBdDDwI)-=9>FZig)J8r*Xa*7@0Rk6a)nu&Qaj! z`ry&5gMMjVd@d#`f=;5+5cax=gkUJX-4GQ$6wff4O$rKY42|Wrmr#`b-){lUU*MM| zfn-Ria|qZ2f_n;lgZCJ+;vmsCwm{Rbz!!#i`hN?>;2tLl=1yiD{XYiUzVbv6>~7j7 z0ol4ynAHrq8;WE-7W+`-s*w=XpDA8`1M*1!`Ky977Wg3@C;#%+I`gZrSV=x}0FV~P zR?eCFP0X%WN^3_c=)nPXP!t~(`D|GIIh+!RubD8X&iUdjBA*MV+A`Fy9`RMrF!vwp zK_C=5uyO--`C@~8Dmb9+DZK`#9uPv`{Dhuyl6!Xu<}eTt-F{=VBiJdu396b)mcTe} zvRIiG;L%0%7u^%8&y&SvOZ6CIxrz#mlhDsPlQKUbnS;-hG2s`NE0A)vW?ly%$W36_ z0SHd=qh~Ay+FzUtVhyw zEAQ<5BnxvkiyGjpVQd$8YVYl_Y<3vEl1#u&9^=`v&-I_lRxfuZ;9{}H>TU`??FW&+ zTw{gTi0_-6woE`N_hg%o*W8QHz>bxB{H^A}iOCjk|2MoR+5f@nxIBG22t~%BDBVDAR_tj!SZQCWle(h1@V^()>OM1I zqTsR10QB5pZU%Q_N=48^Zmn=f<-#izdp?rJrB#+0)^U)iEO2l>^oT*jmpJnrA3v|6 zP0w4Oy0H{AGV^N`vBMy`8_672rwG2lZ|0-27V3;}B>6p;Mt&{fJ;lTtDJD|T77G4l zooR?jiFRFM-z6bB$Hcp)7})wOml2QJ)#LM=rL40Z_ddL66PWp3e!bo#Vz;z7cLU><|c!9JrGYN=Y=0JFlkw)L{n?xOv3~|bZvc37C?R~HhcN#(5X=9HbK!^r&CvzGNM8qN zB} z9zaj0zzWTc&1@Q$0PdTc?eyk!qZzA|8#T2yCq}T=Iab3D$hv1s&SNs5h@P!q{0tb< zO489``Z=YWdVPUx1#gQMO#qS{Uhyj-Wi ze;^ud9x%)?KNI%;Ixi?q1fq$o)30AIQu5q1h_fi>fzp5zHuIXlls({47kHAw=zMjo zvwH8~`SzwuzV*Kb2c3uLiYahc?^L$g!~$~_?+mBhg8&4tu;TBp;k}+JJFV!pbGOb!OGUnOmM(%RW?!Al$ z9!h!Rcfb7P>by*)=5adz#Ul_H6hsatW-?igJn+{o{|6I(cv*`5djhPT{owSd<)*RPvr#rxYLFZj-x5WQkn=`e6HY*Yq+-njTXb>|N5rEg#% zX(qqYZTI!*2j6qmG+XsGMnn074Fi|Q0wZ02o@CU8y}?hc7jMSBp6}z3a`wEo7^e!O z3wD9(=wuiJ);ZI{4)&rQcra}AL=fEZEPAX*FkIa5DhR=Z#Lc%FG?H;K#v|diqdzX6 z{qPHFI3IEh^wBE|Gk!?-x!RjyS=`}nNTemATz3GOqjdC7>7O{xb3K?iEFvhd`Xa-_ zl?v>9M5p0{?=e2@jly;kR)htM)uwX5s3Q{d&0(%gyQomfy^_N)%|U%;d3#GOyo5R( zD=P74#B-QCir%KwHN$z-@prYUuG|=wAQMQAsLSD;yfPbDsb!?3(*1$l41CvWl~x7; zRYjnwa<;Aqjp*6Sqp>BPr44~5KSfZwSd)snblIYAROg`hwyGyYtLWYFzQ}~;HUUof zVu%OH9oo^{QoCLIgIk?QmEgAyT?o(8F_WX%PeIm0jR1>utp4XOUNEN^Ogtj}Kz)d} zt*SyaYECPu1yqyz!T!jt&^Aui$_@kQj_=6rIp(vmA!uFC=ect$G{MZL-EVR9+3JEw zntT<|0J4bFHH;I0pZ`I529z~r>IBUj&hvuR^+{?P&x0IGJo#Z4~0%1Taaf{MtYncBH*INmUU*#S`sm6S(OIIr>@J z%kj{+v@c$pR*y^-^w%GZ8EF;R9vOI=3JuFxy9hrmJ(KPe6tdgz5 zxn_9PGY6ap<+IGe(`60IUm87Kec5Uy%4S#OPZ{YFLsOiS{v{#?vJ5T)GNv?< zQI=7(Z`fm~7nK?r#hzepA6cD~N5{HvMCA{yy6@JOeN@Q2*otvdo{q_=1ncgNiVOJ- zLIwpz>;;NF=;AGe4=C3zM$`{JdVuLXZo$H$C*H38xO(}$nL z31S(7Pyf#EG_#~}5$Z23XG=BjO#8lep8>m{3KH5m+&;S6%O!hAJFExxy&(q6Lch0s zdc9;A)w?XW`3i;%IZnzf=r(B+;5J_(ix!w#9VxwxaBes1MSoB}<m&}eXOl2g)p}c|q&G{-rBbEKtEMh4-DHdI)n^^46IYH)Si2hiiK=c zhG3-N#u6BY+(5yo=R(d>8_**EvD`-uCZVm5kU#bXN($VZ#tTL8p3!q0rjwU^J&22- zMA->9mKdkvzYUn@c{YTy+H6b?5fe1LVHP`Vq*0v9p?<2d`D1u{lj?dl7MfhH-F+K! zPkb$Qv?Cvm-=iqCDzu&(`}+1qm%v#}?ITE`>RR(s7Y3s+@vG8PWI5D$XmR+5X)m3a zuuvm6wx5oNJ`a2hIiM|l;kNhgzUI%)(TUQ~L#(UCOGETD*EDQWPKlbRs^F6oxd0uC zlPP$1_}|vvp_fgH1V8tF!6RI}EA8)j$lyS@`!Gsd^)^~0egaB2H6m1@5#u2(z27p) zZv5$NtHlX(c@(y`gqBKUtjq4A{w@A1-+K6Ul#mB+JtV3#E|wqPvQv_J7aapx_>FwJ zAliEMR}djqX9?xG5!9C*dpl9(ce56D*DE?eRb4qfZ+n)}zjK>z+(8!g6KhK$8Y3^3 z=970*W!7q+bC@x7H#cm0a_bR+CI(-Q@Z2NrtgmYmy9+)w@4oqMEnX6%B0~nP1mXT# zVfS6o%kC|F17`he>Gawp5V<8~+19pux#5640^vb+P1 zk<|*oW^K#z<9F(F?|DRTA=f5 z%RN4W#2%IKW0#H-HNuGEQjY94L0+WSTcur(U%YL3c7jk8Mp3TEE^>rBIeVbzOTKnO z6-}9BbvGVeIXR7@N_mz|%^*Bq;H$*%1mOVskbErnY49!NeeH=vAX>uB^F^x;;y&UV z72I^Oomk1Ctdl3BLOO#v^?8PE1tU*f6%*ytV3Qv<({E;9E%GO7-UJwBN!r%fdWl^; zyB9TR#^7CqH|yp{rGVezXr&o-IEDt*JNY_XHXX-3oOg@hp5+J3gfnn2`HXa1g?t`} zSsgYa~38#h`N8`NG zD>At%%Ns5a9T4>S&4sebClC(Uxeg0;!^5H_a4NBh{OlLUz`njA$@sOo_eQq3??%k|4tp0mb~@hP-ZE=2B1@v!qFgT+%ofnTC!DyYB^gkwe*W4c9i{$9 zsN4FB>W3%{QfTQF=#mBlE#g3MEc*whKgE?2{BgX zw}`!K-)ZJ!MC{;$BSOxQ>O-nie21h7?evY;fxT^E8hbs;-o44>=5>3eMK&{-^tvO1k8Ue z8%CU{{nf+I2otk5%U>6#)9b&}F5I0reGDv0_HaKix`ISF;1{Klih?faJ^iuMcu9rC zhbS}aggYN3bBJeOz8r&UI9A@_5+|iF;SZw;eKayyYS?0Ns}J@%h{3w=?+w8{?aklA zsJH(r@-u_OJyBPxgMx_03Tch#%_ffL6E5A$IJ&`?y5d_4$jj{l8)mi%!0uCWWq1?G zbM&EIFZ#c-4E5YKB>Y97PSt=xXD5E}?wY+06L5PtEaHmDhbjId^|KZT4lPnT%?RTd zVBC#1)zGP?@$~ocBzV{Hcsg|l2;GY#B^_^d0yPHxqme*kInGK&jQYgK3RQodk1Idr zX8Jtwr{*5se^evl0V0uK0*kS?pyWbjSGb9y=1EL*oAT+Q$Udrl|4HgBCIHBvO<; z4KASoRedQX_Yc$c1!fb06&@ZLmJWmQ;h=C!EtP^oZ-mYqhsM_wIpO>KDmrh9rreUt z^yhpYkcHzEEf|wPhjJrHm!#tm5LBnq8Q}(0`A5H753cH0F1Lcyy4EHhqrKv41E`@= zIv0>L-ulrNQs7Z+ZH8>K1Qi=fWrNT~=93ln{V(b**5+4B@x~&ahzfDlFU;QS{eb*pr`%*UXCo%S`hp*L3J@BS)-h@(n zDUF3e1bkhN=KQMJJW4Kg zV?v3H)GH>rD`Dpk--0RLqa;(GQ?=s*^qIjqQWnaOX3Ts$@6UXq6+KVHjS=0XD9q)V z(QjWXBzHb3{Ro#`(wJi`w%{-!CfG>g#uZZaGoPm#r6ym4DeoaNw!_bGLZa11mEMRX zs75v|Z1IttBu=#b{fEcmO@YCm-JdCjA|Id**#m_%Ug)CTttpNeRgat(Sc?)O`E=gJ z?xDR|9v`e~n;JsuH|UEzAmZCfc-gT}^^10LpW_@qkduY_b;a+XOJ z8PsY4Owv<$QEX-_K2w%ALEx{pA?_sL+i+84Co> zCGP%J3?NLpYh)&N@AZ(a_}-UofGz)IJG3s1-}US?jb02@R`CT0gI_9NQHH`+8 zzpFCo`gH}P#R6EQ*c^f!Dxc@Y?%M8LMYi!$qa*eYXEy+|SOKwHY~4{7zs2FQ(Rt;c zZnp2iE}0xgTyb4m^R+?aeh>Oc$Y5c%?!6mw(0A*I4L*$SNJm=+&GrEBkNa_W5-X zsi!}hN#{leFmm!j>M*{27E3wl+%W#TSQs71OJnr58MM@r0Xoa{*p3=7TinaN1(Ex? zjDG+tg)Dx>N4nuq%9p%*$722OZVtPDH+z=D(&?u}{LAN`(2sMKYaDSzI6LB{H<(7zKT%R6 zu-2Xt#)7l^x4s>X%tGr3E^mKZ!MojWP9TkEh2*njv_D%mwbin&Qga<+jhPyMXmoA@ zK7xC`xjt_j^GWUm%;SGz8fc3Ze7Jd;}C`$L) zcTnLQvrC1hKxtQF^U@fL9p%F1nDEo?O3#<|qieEY(gxh_%hj_Q#KQXCW#aOBm|du5 z#esdoY)K9FcXu;S1sBhzY(F<#22j3d2U}<{x;yaNqx1y z?L8N5q3OyYGxVJ871prHgXZ3@jJc<)w^*XiX^%YV#evO(0Bg9Sp}zrOd_8sE1oVW! z%!e>l{qSPRSQQt}CHw+UUUl*b1k5rpSaBs@vCk7!(YwsQb@`VD2e+>0^(&Tm1e>&O zLVsLg-38?De$c6ghZs6o2|W9q$x_}Z#-+!nhn!<(Ba7TxZ@6mqvlSJxZdd#vbt>Pz z6;~{L36K$ofZSMD$x-#b>7C;MY#bw=0=*m4U{%qeQQiFMy9Qz<_eow-CoWzW`OX$m zPITQS{f|>lyI)8vSc-Ctof^V%bB3sfquIkH5u*?S>cipqtpY9obaMKe!dqh#B>^t* zJE+p;{JcilVDHYspn`=#hHxqIQ3?U%gJq|0-||A-n2s-M&nh_nj221i!s;*L>aBJ zU`z(d6E{2h&nMIZ@FA(`eXyxi69(j+;z&#a@cO+Hl-^7q?k;eeC>4I6$-B{#rqWpH z;y zI;WY_N<)w%+LE8N`o>PR>cCa7rQloy(gj~nVQb0r_$xj)6rL9wvI~piC$FjA7#udk z3jDf)80)ChhlygsX#}~C`d2|gN$vn-ZtB6)2Lzq(C~jc4k9Y+ZRr0|!w2ihq6l?f1 zO>xJ}>1i-~=)=yCNIzEe@z0)k%&>=wh>uGTT_%_EVRHI&lHsPplhYCqyuYXc4^)q1_p}c+idK3 z8b+3&6{^h4(a3vLrv3cj4J}a}2lo8#7XWbDcWnT-Zkff4-6t(UMI8tO?H_L9NwDRHuMgytO;w>DKWdB{5{y@U(QENUL%vk+lQj|F(x#xvMeE*& ze<6qP9As~-XDFqP)R4c;_jATPNUilyY(%aNySzD^VuPrJlOLg=Vk~dW)*&%DYSpIg zcXJy*g=>zkfmj~5!l2J`zD$zp$?tw-(oBJ|eaRK+pSTr(t2MD7@H6JR=icF$%xAxi z7@mt}JRFGwoFJtgdUO|ZN*HnRC4s(MTV?H((uu{)_s>Ff8)^frJ@mXL*3P|W%k7hiPzi$( zX@Y&}S$Wwr)FlcUUeg_(#~NG^F-UmbWZav#{u+kVVmS6*ia}s-EKkTkEL5HprJTXo?ZRHZdVogZly=hD z1BK}anxybrD~tiGC^8~V{C5qx1__NmRj=4cSYtE&_E!+ox8K%O32U@$=fFU2{5(Gx zTLsh-08f_|2C}H?W=o;3%dEmi7S{`G%vIO-`SfufO`*@}_}Ayu^jwo?<$MdjJR&dT z8hM!P{aJ)60La1#cQJa81raN;t2nPnj25lUD-`kCb2&T018n9J!F<`TXqoU$I5tJYBLJRe1(K!!{c!QD+^u@=LJl(?39`}su}IE@@6&6^qB(u3duOzH`~O5j=133et-H0y2d9|>vBhP z($PehyZ9bI9*NBXB2qz*%I3B+mi(Ic!ANqy6D|AcfFXQMN3{P5SRwYl=Cp2aUVv9XlcXq0G%h3JF&1(^Qhmr{UcR=IK;+kr)(McT~===|;^dY4Sx z@G${TIGI*a?w`^{gsKV}c2YdcO3ikL-^5gzR*rm2UZvnTe-AgqdCM%FYM&kj+lOJ# zxj7(A>}Rs@Qp84W45O9X;zEek2GeZCMFyGir*}T>>rz7x>*g}J9tJ|c$vDlhE1&IV zkZ3|VWctd^$GSxn4EFxdykVyVO}4l!6>HuCM21p0!(>PEFTRn^c(jkl=N%lBF4p3eyaMb zLK0o^)ao?+>$zxp?%#d^bBptku*Kkk&|FEA#XqB#Ke)@<%EG6m`ba>@beVaWpfV zjq)_=Nj(rSaKFz^SoDiZ0r1r6E+_pT8P8v~ReIRx2@0@FE$iU_=ziFF6>w-jrH=mH zG)!C>yJ_=Yy2(W0^rlf1f=Lm8;GMjzhQGlco~F-wUd&sh^LjCRIN+!c`a?Ps5U-`(?A z|6#uiE^_FtY|UjIW$V9u)3HD_-eVvKZJnc)PmS59EWfW>t7+iIn5A)BPC=02`!$TR zcU7e`k#Yf10X)ybx_9o|NAk(Xvwg!87GcTQ!i(bOwOFw~tWKYS{!}vk^D}cl^h4lb zT}%xgoc@8ae<3xe^e)ckx<6nIIOmg(+6B^4h7}PowAWeY@iC~K%IL^!Zk9fhi(StD zX+UbcEA~7%Qn+w=a>Tm5pL`*DHrqS8-*E?5$F5o>Xxu0Nf&CY=X?NLig=>s-Uh@S0 zsNXVf?!DOoE9?^y2l}Uw`1-=EzVz&JWTAKVB}>1b&POSKErIhlGMvKd?dtX!;;AtX z?+%-9I8vZLI@aY(p?aAClbx$jO#{O~Gp!7BAg*|{KRJb}^pBmaaMyvmZ!*=F9*N0y z+wXh1GW-X&jb*CWEb_?W9Home;0x>z4fw2DS1f+n8$=YSv-2-b#O)H%unI$=YvDo7_pO((a3^ zP<@uPiZ#-r$Gad~M6G_$Ryu{eGHkyF*AXyF)y(wylBF+l!`%=YGt_fWOFmhlP}yRX zS@RhM-&H>x*Zp+%n^a|T<(vKGsaTUaa&4o`Cru+Hxg|WgjKlg-(Xp?c%tN55xZ>zY zLh+iCtx3uhe)Df^?w+gw+1B{EF5|okq`+F*#wHqj{tMBfBCje*A=2;A@D@oP;)GOo z^t!A4!=X*GBASh@rU^OI?L=S_ZUg8du(ehL#{_iF{2E$m646xEHE>9?T4U;yFx2@{ zuKTm_i}a_Ww+MMfpm){kui;C}0Ff6wb~@EJhBhgiaGNx;0{XgCytwF%PANxnW*kM5UVV^bUOwlHqhT4s>~7 zQ#ra})uPDym6Ld?@(M9Z#lLxsUk^8b{rifdveR`m2`TSu*+M%HHe0{KDd_(o_IxLX1!poDfQv5lIS(9Q_*(9 zMQyywdgPM2zD~aMT;LyF<_<~>Q3#QiUWy^SPQ8Q7IN#r9wY=8duI!`Ifhw$@)AEv$tEg zDuyt45T~kQs7Cc}Z^$e-X4T%gAU>OoF7@x|>^)Ps=BQIcY_>w5slMtYHu$wx>zvY{ z-PMn5Egn4sN2_3CF(pSF`h8~gPKp};#b*6D4d*iTJtV-a`>GNE4LGMox#glI5s4>r zv~>6@+|iQOb3^hohPe?NAJoXClUG>bdTY;35-+MHer=>YgSTH~+^)h}WHxh#EEw9( zC6J8;vY=l-?+<=C(OMRDMK7H(6Fyts;Z(|@ar9W(lgjWZlCRAC2V~78=n|W{uW1Z3 zoV^W)2{mFo_0I%#3a^5E7OH~KF211#>IR=>Q*!$p#q^w8%y3a9{G*`z&!xw`Y)&8L zg%qm)npnlQkJ^wBA$-8MHM7RzV47g35K43|$iP|WuWsbcx!$ysHI)!fY7o2jNGeK9 zme7Ckyjc|^andj(zlcs{*Bg(On2_ z)m`bt+Fd#3jb^FTp(H<a=(It)ZXzvwaLFfzn4}Ln7xoP%t$nPyM)$L3NUlg5KuDse3?=D9uGQAC@ z1^AM?ax>U^4Jip$6pcMSMRpVhbLH#3J)=(5CQA3hh^Hwg8&Od{k zeU>CJ)!Jn8?pZjw&MTWZLW?C?XGlPTf3p+9zJg%`Jppa^QEP8zkJbX5^)$*;4?V_9 zdL5``znHRZZ-bv2fIW%%<3iJg~G8Fp+!*4m0TDcW688pUv~LENF1ofOA;D8njv z*6-Zs^zUT@ta_b@#g7bJof?V@kksh=bFuE)KGoqj>v~yPf0)rdm2D#J`IRM}fKT*- z@q-oLduY9ki%%TPjn(aoeJwjFE(XPhDasD#4sOva}sGkPyf@XhwLVpCdKz@nL%}Em)elSpQ~Gavhs!_8Fh}CP*HA;XaI4T4*H@m$C(;OA zVmd%)=oAYep)o^DIZ{j}P(xYj-a8mHf#*y;xNh^0@2n2?D$b@ubl_?dw^7Mn7;!&p zm*a+aNiyj?nl>^VY?HvcXl-{bF2_N|vp*H-sA_joE`GyEkh2r|9F2y7 zLz01|4*k~cm#WY5)vx}rbwxGO*^exwaCl3z}74wD=b zFlvf{Qapoljg=<+v78BVZXc<%k3r5+CDP4?`I)M1Oq-&)_~5J__@-8(e^NWL*gB+Z8gMXc{*sVd!c}`q zo^`VJX6iPsQ5+q=53Lm{T`>Mr{iPXY;k-(@Gyrto6>DRowNB!UH z@_$j}@{+W^5PZ~o_NMQ>aEIpWc6b3i=l%itpB93{Q6Dr@Ej!jIN86ZJu z5qD{Dy}9Bb({Z`=zwqO7&}yTF=TX*mai8t`1lhNCs7RKPSRDrk*)riBojppA@lQ6l z=c3jNLEfP*mr+bu2-g^72vvSkh&|qgCYtCJ!dQaajVP1z70pjv=i1sgL$0IKT$tH6 zHQxUY=}8^c37WR6F)~ZczkQJXJQ8?DZ*mv=f_q3%ed!lpR~QB=!m04%X=w2|lPI);%9LnRV2-uKut%gvMEH8N#r-Nj~ny^e-H z0{^OPiHiA)1v8$GdR0DqoNQH2azyiXn-c&TVZ5 zpGlzKuB^cN)RFeBhpQ-#GO?$Lmdk(OEJn#7$`LqoB{J@|MjH|fl?iF`NSkNSgA_!4!9@TY5|9(vLHb~EK zcSz@=M*1Fc?p1r4O(;1i_@NZ;W=Pll|74y*R!}X9Z`& zBYy5FOHFS+=sMysdsh>Gohe|H4XrjlR)$`F89^(N%BsCBue_cB zMokJ7pi6Q3xFlWU>fX*$gUc3)NBbh3sXsP55z4MhFfE(^_dwPxw~N6Gd?AO7fkTAJ zKs%h?YhDxbH&7@qz|q1rJg{O4uig;r~eAA{hp(!?iNZD zc6j_Cfxb0Hhe5BmBYzo(PD!@_v$Iu$<~k^_2NeJs(&%(YmpNeb!bVjvPS%oRKl!LC z-K?^@dNf)#U}(v}7B}Q};GiDnp3|X?dBN?p&3xJiB=Su#L3;bV1v=`!1{ya~K>Afc z&V)VX(Y|4c&#G;@LQ_SLa-lEnJ7ZeVbsgnvXTaPW0OMYV*b~_7-V_We`IV7M4}0vLR$q8(E40Fa49- z@$w-gp>_V|T69gJbm%tZBR!)my;JXVb>6X_B?p(|W!>+pB}R}6oh0=#EmO#`25H%D z0Knz)j}h|VUYD1H37TvIjpWF=g_bwT7vNH&gSp1RrG@G+EEcc>Z~6Ot5PXI$QRcCE z`3_))lVscEbMHnQZvN}*)(Bdh$ryX4{Ey~Ok#bo&6F+zO9B%RnnZ7JSKl?r|yGl5{ zdL{iE|2Bi^QCU3|-?kAOyo?#>a|$rnN&*_mA5@xN@}Ab-B8RoEL>6_n^MBJNAGiHH z4Gr?r-wKBU(Q@XCT*Mz#qvhCNC z#XotiTmKbsYSVcd*D<%c%VAt|J}ysI2on0t?N|7#nKb%b?l~%xCm|heM>ChrJjc!_ zk7$zDVv=#g*drvA1y` zkis7v{X9-V9Q#wQE!xt0yPXbDAyVeD2OUCH_6OgzsZ%=8H-J4o>|oyS-zF3ED)aN? z#ss_RWnZ#s9JH_?wZPhK5KCMrl_|T7yJF5O_>$&8ePl<|FWY z5Y$*pT86`$n97sHF#c<~)L7+x_1paHZ&CJFflKiCk;xYF`3Hb-KrS440*fp2%puW- z;-@L#vp+}Z)m_J+!P38iK^(#LI2TGG!!_Qt1@-2@*ueA(y+ZR>pwUqbd;M>SkI~Bf zd!8v!Zm^NEb$UT@AX8Sp=DW<%HGq>;H0ocHq=8@^V?{9jHE7+_HUoD3T3`nnLP z(Rzlr4`d6sqlMq!Y{bRfbS^mDA2wvu`GSTQ2x!;jd@Z68$%(#1n?G2g-7uQwgxvE? z@|-hCr9hUn^BN3?U+&Y8;<2~gC8!fl0INqgT!GxD527AUpr*BZkRh*0<4V zLZ>bHcnb0#d=-;@Pgv;;4^qEUkJW8bzY`OtJpF4YqCfxcJy^M+0LAK#(x9t^29PrUX!gI~CMBXaN^CJq16z@=By^KSsSr&kHB7=Z^Y9dq z(UK{YPcurXJI8aJ&e8>2S{1r*)xa4p)4kv=ASI!Pi~C9MV#>AT~w zIBLn?GlXAET58(?)kvuwHi3?f89>Jqg_;~Za#S44&A*cz+--6A6-@Q0 z5FF+RoNv(+`(w#+j{X3k_>IDo41t(i!&uTP$$Rx{P!RyMp zg66oRr)L*j`jrsLtcE-fxYbhaBkm?f(WsZr_Tc{+r_Wv`?K%ykML_k$WVV_uGmAq} z!+xLU=sZ#d>4cz^2t0owTZO!HMir>j|AyGfQH0Qd}p|) zaUOTy7ziO@p9Nih&&+(mXb8kAATxaFTw>lCH1K+$^~ne+y(e&evy&oalHzG}lI=S7 zt#i$w06Y{}J}o2Xfk43zm!>7VaUv_!*m1yrb-0WrF9Sx)^N_=XD+lmmlvAS*YD9;| z?KRkTI*rUdOLN5=OW*25`}#M`e{bz1cI$h0U76QoVwvpM-EFZGarAPE6=zk9MqR0(Ix#aB71fC~TY|=3-nUMBjhCKr0JzjLpH4ANh zj0)8oO3N9E6@~U!;Fm!-f>lV{Gfqx0-4U3jG;epZtT7#r_tvLE{2-`>jI2)0Yaip*P#ktKTcJBBfu?{7IGe%JpMz zWq?EUMP&Jo+C3!`Qzf?8QDB4F;rmN}UqGwLfQUN8C8khZqw-q+Z4mK|d9EFsh?X

Y8VcU{ve1lws*o|RZmWPWi1GU|^m9Nw=1p8m5Qv7dR zjAiiUPJ-;YWrL+Jb(9b%eX{{R(Ni9_e}FN=$BmZIKhP46)U%sqpU=m~PwcDU=GQ6x z>$0~@A$l_J8*FCY#f>Ero)TkGgJ+{Zf916|Gu+No!udR;=0vOM9Vd^77rNtjNA*)xlA|_;?>rmw* zaU};4OPdot!!;!auYJ%Ojhg*;Vvi<%_J=)b29maJlXe2ckUqQ&bYw(s0tLJ*J?w1Z zoEVv<(I?4aLa0S@!QAfB9o{?;!e8cxS!)mJ)edvPIdJ3zN%JJJ~hd9mI9`X?T zE+fku9Nu@o$mMP+v8p@gK$x%fjm+6!r?w7@5l`sifyG}8?|WBqsoQK=2$iUH#y7uuy$t-16FMY5dO?vl{wGnc&xe6?2nF*7t=vjkS*oap51 zel;EQPdvLQw>6&_yM0~LVXz*ti*~1W53yyA5=SNnLLaQx2JGxV^7mLldyyG>MwfdOWp_ur>pl09W@|6o$#GD4ZB(M04kkJ9U<8$|4zNl3OPjLKie~ zuiVDM61GN8@IDGBycfh8m%e{|sNiO>G9U7w*UEBqj`d2FwO!IiXR``yUZB;YoHH-{ z%Mb*i*@-W&CWXF>0)A^}K_6(KV+!6L5e?*}sol8Odklb>R+iOesbNzhq;sy@z{m3k=BpFT|yV0v1c z(pPHZD6&c<79gm-{OqUfYu`TO=40cZW~NzaBIBU1j3BF}3xfBDhamW=zyU+W=z)hCmN%^OA|9Vhh;a*W2 znu+}!RkYj$(daLskZ#0^ih7CD2Buoag)H(~y9?Ek@JQq5Uu!|^XVdq`+X?y#z3QaV zYj67r`^`~wBoZl#3D5$K$wb-ufM#WYa1!*_){c7XNY!V3Jp4f#Il^P;wBmDZ6!9m~ z{17Pz>S)^4(6~R{tumw^rKgZs9WuHD)Jb+Ah_G(O@q$0AgTXl*3=u{(eNDJJ)(it9J&{N(4pqGVGVy_e5SZ_pp~3nLuUvGi#q4pagW; z)!c7ByRtmH^YC;6`LW8N2>#*aq07HL(HVnNIPqxLx=MPD5o)nHm`gLf% zjwD491K!f898vVN;m2q*z77^dO*QVpiX)In=ls+>q&9)~u^_T8yD+SA0;L?KFq z?!C0xZ+)AzN)E{+ekseI@R_y%+Wjb62SoSu0}R;_8yDZEZJFcAtGFObO2`)*K@+Ts zwPLnCRaVweHMK-AH_ukRbCm^+PhasdOl#%CjBJeh2~$bLhm?D|!g`P+Rz92Z_iuS_ z{J^yW*}oa>PZ39EO0$JJwRmyLGm<59^nW84LosRaV4MK~^WXfNpvFyyPZ>9e8xF+GUmpr2XHn$^Q0U@$UK={>yQIr~CK zr&i!qG>M8vxoKPsH51n)9zjXB^y7^tXb?RUjxBd~|3S-N0^qJcJN|aFA#skJW8&9s z_KW<*-LLQBy)tkgTw7NM_yvtFUzTg7xzXMx3X{5iN7Rdj132Hu0|z%B+*j029h0B8 z{ml5CzC?tDuQ>_HRO*jOAsrG71VSeD0@bEMI6gGYoA6XEtxbt1DepE>trz&)kMDXtcPNSN9`wkDr{{M%8#`Vsb^ZPXz^Heh)uMX%9VXleo&%cD_nv1t0zWgy?G6MQj z^};Vkfob(_r<*T+q)}-SETJw2!|yP204t`D%@DrzRtP{wUjR_16#QE5A@EgbOUi%U zv?WXDSb~sSqOG>qLbsZ2Y(@p$#%f{1wDb=;|5-Y}C9DxX3VcCg0DL_@nwSRe?^it}6ALsP8 z{2mySg?Z`kUn}5oO1*tFNUKjuO0xLZ*3$nQ>GjEpMO&V7CBmQY=^SOi+`oL6hAl`i zTf*=Bv%s8m-y7N_*4w2@10MS?#knEBssUj<`v8Me8gPr-1y}?tXOGgU6NBhVRv^sZ zA}7w!o$Zyx>4J7H308*o;_X|9ISx1H?F(M5m6T>Jws_`Dk~bj>hrz$E0s`7s65pUk z4XVEzU7T9Kh`d=|B=-IXfR#;P?voC3EXCCF#sKhWiOSEZ2};@~?Mc_rjddTuCErrV zz{z?7_d6NHp%QUT15$i}pW+|1u(!u8#WHpRcM-6tG%gVXg<+`OfAz_AdE;pRSR6

kUv2|#Icx?Ie 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%0A@^m;+`oV!y5^yNLIGXGw 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 z zsCO7Op$yNk-$4umnumV^x@uQ$ISUlx zaMIgTpS}7DLNUDk@O&eGJT(*~uMz8Aas$`^jZ7WVN0G;^zAHe!}a>-y~~3@U&-e>Mjpu+PO}v zcCnNBlJYR_l&wJI^i(ONG-R!>1;1N>tuK^ctk7SAKJvI#nwCOmwT?LGtv!j30pC7F zMmRt#(H*^E{;Scy@L>u^a(UF{?Ai1&5;Zt>J@|ltPMm#!Ro^BHBy{U-gcip0G>-Uiz&!z z7)kh+Mlot0G zyoJ}Iv6ROX^I{tKXs&guj4u z6IN={VmZ1*-9{R_M^L_z5>vmFhTjYnj|Qz-etpzJ=PRs7k&5UEZ&V-7ulYJGK>dq%)*OV#&Du z3CNjA^|164rmXuhb@*X@#eKx{!@dDKk)XU|M70AuMTYm)3Gfa`0yXAGQ9iX!EMOMC&lwd*sH4>AC zZ|;;K&pDEvNL*P0pUqqSN4@U2Fwv0GP_gkxjTUR0U-Rwm*~jEctvGQsTiGNgqjgjwAs zvD{hNjSXR^j7-(vlPOMYJ1jp9{s=nSc{-83s~vfHQ?bTZ12}NRRW}ZUMivqdyq<&l zq)Fty2JHuzF>SwFK_`UEVR3PW&rHy#?aFN+^wPE=7RqVpe7=B}Ko(dbQN@{av8BYo z%{mYYln3vMw4I5r(>bm5*CM1Yyn%^=k?2|v+}x)d9ZbTaqPF^(zHXclXW%$O9a()I zJkta`IK}wDG*%9ccApJD1ggfUkQP*|>7s)ayRRw6;4skNCt{Y4jlr=K-T>Q1cTm&(e0*3y-|F5fViEa4lAK{l6@vP_lxwwfn}YD84%jiYHSZb^_i3k1jN;<3mC_@P$3Wn8J>0 zevI>?dF_ba%(Gge5jc;^e+39+^@ z{yLsE&hrz}p2pvo%c0)8Og9Vi^qVYMQtJlw3`&GU+El!S+o*?k7x` z8O)W=u4=olC9C*YI-AbnR|FY3bS={*5I%XwSyEMG_q)A~v!{r>z5S9&GK0ElkH{PMy? zgbx$22Cf8BxnGez5ieQ&1s|>q0KqDBLXB4U$-qe~WB@Lwz)6`JaSSUUN(czz?#a`j zge6z562IeD?Y{$*Xok~XrNzsyIfCKkjMm#phY#tf=@|5{i82)`%(ITYJt?0 zxje%mXy7nf2q(jer;I$*x=859FH*5-=fDS5dE|$ZRTR zkfqWr#PBmUx-#6;(Nyv6BM?mhd?_G!4<|$%QA(TD0V#rJz4eHdsA%5PJnK_z`3M+N zxPAJAf<+z^9gQ_NM_5HpJ+*#WsE{oRcwcOUmi{qH;CG*6$7WT6C?JW@rYG_Aftmlc zzJ-Mhz-r_Il52cxYXX@Hcp&tBz|;V6}krl42aB5>mmwR==BJ{L6yyj8%i1JaifQmJzH5kUMd1ZOkj z@snIV)XY_n*lj5VB>DIN?s%bkrM`A8A|A#Ub`+dFkl?GFCFo^(xYRh9w2*G{y6uVX zSo3#KCtnNzpWe|tIbEexz5d0=-He*SNxeD#o}U2Z0FzH<6h-Z6(g(ov<#A_8;gVD* z20LzYadmZd?o-}|4@x!PfA!)AaEqAIxYYvvXb@__^;Bfo>-n*=gV17=><3_LJ5f0r zSliz2gBN=LJ`Qic2|Uf>-&3L&1)$>u@Z=OH72ANiYzn*J8 zM=kH&3oinEm%QFqsPcmR<$x(n33mAWAoZOO$9o&V!I}%us0v_s!J|)$dgJ@q4uFJU zhlg?|qu->IseaJgo(wo;!V3QP^fKW?gqK-w0{?YkDP+c}ii?wz(=&@nDK7*>Bb$I1 zd=KO-(Q8*`&E8a4eJ$+yGj#bJXCi$#8R9WIu|9jVOu%x?D&TdK;5E%*KD}Dyq6heJ z+B6W>tHpJJm$PXA>H8GQZ1FL=u#hejhAgzq?PAcC63tXQ42aS`SZ!D9=yfNC63hav zBHa&}P=d4MY+a8Lus&V@(ae)<`zcxs1mEO`gfyh8Ae}usi~z)C+X^Ots+TT_`3y3u z=qa1SJPp=B?CB3sdY)Q@oY&l}0v5>Fr}wCL066v(71-E&oF^5(E-?MXo^J(v)t#mh zaQ!SrQ8WQg2>s8m2#-23Ep7+DIS!#9sa{6|PXa_1Q6TIp8#JH8;0m*Xe6j#68~7P> ztR1dTjq-yU44zn?O7}Pa*cjhu#2K&=D7c}lXib-i8w&A>pT&m*0n1&2K`9l5)5*zV zz^xX463@WoY!G8I)PYeT@?Pi}D8^U&<-}t$pn@HjNSmDC0+7WdL-mICe_436eq6#*g zm?C1cyciNvR*v8z1s`F@*AS#3WrfbK^S8=4mg>*wCp}`L&gFJq-)(!aabcwYF^u0g zUc5flsA8+;vvM)JA(V&yE(LdK%-_vm)I;6mB8fc-%j2o~u7E8nm>J?~=)U!G(lC&Mvbgy%K7l+r@4QqJ0@QrVvk zr9y2y2O*;b8{`0$b%ev9wmGw(@#RnqOV0BYTL&7=um+&jU%3-(Q2g?$qqsX}1@%st z^L@^Ah?gFsa-d)xdVhFa1!x~}c>72@X*R6sZ=5jM1gYoyqm%RAIl>q)NV&)Fel14P z-hmT|YlvH|W%TXowIbvW)H@6V0|Q(9#ykmZk9v>Bhpp3$Jpnq|ZAzMzjnm*&1wg9Y#2|G-KLr zdFVX`Ta_C8#VrZ6HS_aT&eHR2H|vaVMjvn68*Dmg;Son`vI)%EQZ}fr zYhTBZp20;@nq(Awvk%iCG`$_JuOKBsSZs&I$>f1-koN#1Go`NAnvi1Ht}d?6cDunO zleWOBxs$P0{fkz56tR5!#$ZmSCv@92+=}Jdc7f?c&W}#pzxm11z>Ge8r(n>|Heb;~ zH0BByyRuz#Xm2+)diZT)f)^xlsYHri1g#AF!XZR#zI&N1Z;*A`rSv@|!Q80;vM;LJ zrXKoPj{9|10)J&NwW=kPgpyLkJB&KGA}FmUt!^l{BNluY#ze^RuOEJ^Cc+Gw*&)ys z6fREaYivvt=wvCSeq=u~F_!p?{23)Rvm1zH-KRODyy3+U@XXENO=W5>XEnxUh_RDGZoGH&p%5?h0| z{MkvTuf94-3w-*%w~fJ@6svb#6)8yGVuaf^vs^F+jC=Xr2*LS?~sg7AJnBaq)GSWiei$7eRkd>h6>L#zwA z4p(KJQ$c=I)=w68Izo;K4pMKY#W0iWD^-t5a*MnIs26vY!bmFsI9j7T6ByMzi3wEd1A3e z=G#h{;H1QSvp+Roe;3Izq(~HYl<1!JSd2KF7PYhBPE4k`+b!EvqnlO19GODGv z@e+~Tz(8&H(fw|%M1YdHHtJ*Xu&;nJ{#{piFLR>L)xjxIw)`f! zH&&ZymIaj`65Xe5&oi#KyPw{^M!5aPNFn4D<@x?f+s#9mwlT*+pYz3o(Ux!LRFtc(k{CD(z_Egb3RNrlWogIR{|CfB13m7yTEASKeHZty^t z-x#?k%&5XB#&rQ)-KM&y^JS7Z7>X&6i{{z9?_4)j0Rly|$tv(ti6oQ7M^4P%S)>QI zv!JuHzvV$Mt71IH(A*~l8s%oo&n{hhk6igJy{Q*UOaCS8-S-o?R4bvEJC(vA4iWn- zdmZIRR=%5tB<#c7J``T(iZ^@QSj%UzL@{>}KQYo(zmL6JR+RS!5FfE}-=F7+Wb>Tx zS-9P(o@H)zOFs$j&Mi+!&IoQ0Ilk*H;L+%I8VLOGGxbq2a@%}}vUd+3e1Ca#aM}&4 zwLi8kPGCxfwNVNF_4)#Fdko!hP#||fyuY8TOHkR)eS8?>sIuLB;Xtkw1v_j$zVNMv z%?_nEDNjf#{i>-dUt)8({i8+|;C`Ce;QOVis9rvSv{T|e-Jf;n51cFQywZ-o;tix` z6l{$#^y5Bdw)C*~DoZCQoH@;hO+&8MMMlvA!HAlSf+5Dmv-7jpm|RPG#XGPcWvoef zxEl0d2Cva;ywZhY37|_0EP@EQ_BgZ&Q|CIS@A-O#Z97FR?OwcU3BS~%AQfEyrYj*TvNys^=r`Iak_Ipcvh8*=hzkH;cK#yMbOUJm%SV zh7MXZbFi1UKeopk9rLkm9@r2K{rvC{gQLszekgs8<*od~whAPKqNz>QT_ye6tK8a} z>Y@AAi9H(q_gq4?T+uE&F>lJUuSF9n2?}DYhh3lMoA;wlhdaw3!w3bFWV$^xP>!1G zv;xkJGBni^7t1Xfh;jH&Xeb4nH#_7CTpMswI&Bqbh5C zd80|Wi?#d>i85{Y#NGkRB+l&n0SfvNt-sUw`!zM0!C(MAQlLtBi0-dwhv7vPkH zJp62a(GS1fTH4*LLyUPY`cb!L!LnNCpVH}RpF@U)crpL#{tBGSBlgly64iG{8nxc_ z+RKfZD)ee9KbYsKeaaV^I2Z`y*)&z1M!VTl-wSQDh8{TbHWm-7Cww~3cG`IU{$h;d zz2P*Y+kG}$p4h#8)ih6LZF+pZSb%+8n!8G-U6PYvZPQud8=1MoIH&YDS;%^2x?HT% zuXw|BhmWLZx}$9?wiBUBEs`Rs)x$i=1YuGf+b4Io4zOT)i;>3kYRhyp#|oyau_>Q^ zvQf9(%Fe30Yx$TY%%P#+jiiA+U1s*96YGG3(c9bkdFqGxn{9(QciUk4gqXP`NgDRm zfo<}AOpHQWA-|A<1QBb0>REfdyyxg#bD$(jrE3-P!-59g{@XLj+z`O{AuX@tfBcWu z8)szSqXEhKzr`~0arJ{-U8mjluM>~XtT5pZt5YwbAAh|~zpzZ?D)3}jVZ8d4ksIg_ z_#LUe*$0YuAZXa=c(v31pmy$t3GX8gKtXtd;NR5eo|LbWEhq&%5DO%GW!ryj4mIJb zY}$)a7YPe4R5)evZM%+|DgOzXp`b%xs$VNh4Z z?aU|qqk-P~w@7369yN5M0?7Q^1fDHK^|g?{i=>KUr#7@@N#arJv^Np1nYD&8H&u`K zZG)&rIS0O(0-d_5Q$;Zhq zUP@({)4u1xS?0hh* zl=gH+5)63JIy#CNCKDe-tPWxV|7->gTj8&0tKc4m!S}d7Rq=KEk@Qsb+SEMd*Bgsp zP(298ojQ{3sUJ=x1lM-b!v(EL%Tqa zkwLS>niph(RD1P#|Ar5QQr258LNp5U^&ULmG+8g~gjf!R`8W^ZNVLq^^|*)TAQ|nA z8id}U9(!KPz4Ir%;GtSr#I_@p%J#pG`12#L`sfeeTzn{mC6Pk6QNdh=fjM6$Z(;X^ z$HkZu^?=WJ-%NPocd~nij8PrVanCn}4(CG#iJWFty9J2&oXI}c{}YDWo?qw_E?sWu zBPANg&S5nggWFrEv1$+9Lh(1}qX{Em7tJ!*wu_s3r=R(&Ir2MqT;2T=%!@uYz#{nR zphd`j9AatDi+)amu_Rnn7Z9B|nbAf=lh}hagxH9nPJw+9As@5j7o~u@tlmLx!X1@t zu7`1cvE(q8x|(W}3NBOy@?27TDr;T#A9}KiRs1N+zxr)I(>f5iT#6UebllA@4IrZa z{pLu35z{=$ew!4lYYKhBz#C89+;?-}7XB!U<&!7ESY-;S-Rzo29Xfu$$!zOe9xFb2 z2_;!bSF*~sgJ^&I<-O8h+o`!NJM$x!h{RuvW4#Ynj>I@(;rKCcoY-Cv_d0)L3F1pc z?#>{!CzkZMEI62hGZCr^Q1iUaH0j^|w(WJ;bT4v*eAM;cFuL`iy****_CDnTK6oG1 zv+b-X*g19kf-kR!@aYz!W$ABGacpn}oSTu+iOkGj!)d9$a(s}|mVCL@?5i3}y$CL3 zb5LxoF$u6zJTDwSHbNU# z9gjG3tE#s?QivICw&5pEx@$=`EwG5+N! z%=iyDi!RT%5+e%>nq-X`)hjjYQAlhn%(cyWW0*Ur&R4hR8yt@Ybd_78tKWG4v~jiAeXE6hn) zy-aLUIb>R&SaDoxC}IuxmtFrp9u;q$uE=iDZ@OruPt;L-%ZIs6hz+f zxa3N`q`{e9_zZ!f^RD06N*A9jw4cm~Lar!wW|YiT7_D=rpew;%f-A0PQY`{23xsZP zK0J{xR{m8*6muF$Q>S`Kj8q(v)F1F!Kl*yjucJCp-6wz3(7Mt2BN~Q3XybY1(*ABh zUH3+3O1qfv){g*fEz)VLIM_#eaWrvc(gSjQ8UHdoXdoQ}eg7+l?$t( z=XF(>1p36AQzi`Z0No^Jdm6hncqub^{;ANOrGMd@LRpwGKU9eaLkoVY0& zF=ggn1OFgT1kK5Bi}L&x>go@LP(jLbsUMqLk>dp>p~TnJvtQnx5nK`0$)6ey7<=W} z2CXti`1#ePXb-;;4-;brJkL9nTO=8P{jo+^Xqorav?Utzt^F%jdIJq24 zWs~5voY%8X=%j9^uS6jdw$w>vP<=1J<@y6JU!{UYBh-_T5onJ?Jz}GM+y2dJZFZrv zu)Fca5?%{KqXzk4m?y7R7cYrAFT48ZL3geO_Et$}FpH2(abaIPDY3=iXs{AI4Q{q8 zJ)OwcxiB(K+LeH?yJOq~DqTg~*ivX4E`K)R^(w(lOlk*8oD-kZE;Ue=$8{wxN)F(8 zG|5n*1n?_DvYJk%&uvY&pD*z2wU1Wk_97D=5!9@cfsPzqSPrQ~KJMn`*E+R9G1;+C zeiT9DdXzS(x2bX25ES4p=7Zlm3 z8lT_Ii|@hShUL(`5{6y!{A|`oC_J$9O(khnJKV=Q|T|2`cIr zWmn=CclVLaP{m2AD?Z~kY6<0wRHdJwo7dh94OIngu^hUvI4fKlt08ie8gdpM>IFZN z2Q^(UGi1lb_6wo~!Fwd08jGN26)EUTp)X^TV@BKiYLOkFZN507&MLi+S18x!ekuTp zzLr8-UBv{~H>8<&OI-p7Kz+#h@A9?oS`{eQ4F+^;a$qdl;->@{Wo0e69^g&~joJrz z{TG(;-F(S1gNLfd-r%9avV+Or8@#4J2@sNRn;p2sE~k0~eF}{VyHtGU7;0IDITvs( zsiZnBs;UO=)t_Z}eXb>Y@5udg0{u<#uam&oCzWpHawR&EjIXZQArb0-nv!4uAbrhz z5}`!kgTn49>DO&FXw|;g(Jj*WQS3RnXXIkN48Ct!W8V~QixQs_HeJf%Q1wi9CiBCK zqL&-1eRSQaa-B{CXw$^nh{RpC{9dZpGw)5aFFwuq(+Lqb$v=Y3)ilBrvhfwji0Ea2q-t@-|2_PBpcGdyY56W*$g-T`RV_iK{p zJ9b^hphlr^I~(Ep)9Clb^y=x9-bX-l-p=$Wds?Z5swGM12a9vi^P<=HfF0W;gBuUU zX}RE5U40vdYXSaQN(Fgu*Xz?E7TM1_n%|ZU`@V@fp%!O@e5k1m$eWw6RV-+`_dN`N z^|(S4q5JqW*z*J{fFP-C>;PyAU-m-7*d69rk{&%dh0{Kn(LtK9;_#;DlMVC-;^lSl zv2e!|k7E-OsKMkcftbU*!2zF!AG|7Kh+3??%iV@h8&#r=N{rvk8&=_`8ggpKj+((B>W4%?3u0iY5RS*r`0H0}#t~r7sw^jX zZRlb>O4%NK;IM2U_OAW73w_J|xE~VTZ&m+iYc!H&%SxT9-t)I^nyMMKjiZl}m^X8X zbL=Cacn*PYK1=)EX?W#Z1~>HeaF102HSO4v+pL_c9I(PEb;P`DRtc`3Hc9@g)qpKG z=j;dyP7*}H0C()xZS?yA+N^%2`X#EOqN4ZuJt07j4KRFMXA8mMEN$}{B=^`>2JOK2 zb~q|kav+yu`YyEV{QLM`XJPWv{NIs=>#g(=%wUIsrpo*)68tcWXEZd8FYfkqAMLk| zv6#78+OBdMQQ^EVJ&szr<|4ng`1Vf3y#9~}a?ajVQAAJNl~P@U@0V+iD(ng4v31jT zKcZBs&KmDHS_?&^K;*O)o8P1OUUb-YYu&gJ)sOTi;YxH5hT;eI@>9@YP8A9M4rbL= zh6A`opPTDaUl2l(3CXB_qk1A1*Cz>xQos$({Z`w_vg>_S1vrmv-&{%^GpLT~`F;a} z<`V$qRes_QFM=~p=(czUL29U{__V1d2T$oE zDn=U%JKe$&U zxpxRGqPgOcADK14jB%tUCos)7wBGgfL>}*#VuFHjAX!5rrOE(e!LL`Ae0c2(3np1s zh=Q9em77;DJ~_>(stuHsx0zdyqiO^S+WLvDP7Ov9_>H-VVBlFnx`)p_@+#ALUyMDiQtG5p?|#j#;`XJPW}T(kgVHa0oa}Xsl>k@5E8nY4?#410bX9@` z5q0MTy6S+L%@t|TT~`1vbkwQ8ko^G#kNx{x$TnirRYZd^gS52>xC(}!w(cZO=@Ls|BC+5R{H#^7&Ni&a?liNB1De53RFWcjCQ2AXudRn3g}IG+7s zq$#AuAye(A=4RW?`~{tcv^+dQI>?XXcdODEy=m~nxtB1EvE4%YGRCTCMxUY+V%kAS zemu7tio%dc$HG<9s>E#W?);UmElxnvJwVUfW5I8a+)bjBvnFKKfh^CY32X(%P#gF6 zi95a)gW!G#sPLYj5bXf$f0bD^VIQ0>iG_Y4$e$MgKDD)|rW&y9AjBtTE_<^h2|edt zOK#g4;vsR|;m0tq)K1Jpkm0~JhI5lE5zRSvAj?fC{0>6+{uXo6^%b9U=@PgO^%@JS zgh=9q4lJ~q*~Wg?rsn!4!`#^O&4NF7+2KhpfakPc&3n{x_Ay`&1VeBRc-C?GF2e~* z;HnPzROn|KWW)I@d?E28W}f(ZKl+Zi>~j4T3+3tUx7p9k*1s1r9QdEUh@}TzdtPHq zcV(kOkDl?2L$%|ag=_(y1;8^k6iSPMIrq0j=u}G#2kTqR$LC*#^%HMwzE4K;X3IjUL zumcPA{q>zgY+1D8z_ zJE@+Obkfbf_j`5Xs}2Tv!M;6j20N(EUniN1LITY)AjNC}FxB#BVy5bk-p<*bF^Q31 z?bFrEj*HKP9kC<3{1abY$rowkS}2O$W@Kl-!qPd|_cv9Q_dcEvQ^U9I$7E-|Zneei zS?+bh*dw1M^LDthxfg*DOHyss6Ni6HV;_JaE+E&j+`X|Qi3!KYU|-q^!l-GJ?7ok- z;1qf!2ndwZzL8g!ZJ|^%2i4J3kz?vQ`Pi z{V-#nm@=VA=pG*Q{;-UcD4`!YQ=*pM{@_lF|BEQ@@imvCqql)}U&YQb-Zo_kv3qIyzNEdy84*=BSiA}y4w?}IAQVCh20 z`>u2f_O7hBDCk~@Cu`OQq%tub32kJ-&+uI-59;&fu_o?z9UoQ!mUCPMp5VD>x1F-= zY^LgKZc(%8mXwx_O2yoYm=K(&X^!y6Q@$@=ch=SD?X>{DWC!mMQ=d$1}#wW9S<>qoUH8-rMJgkHt(-){oJ=J;kjIP zVH@Q~GbPDVj=#h&=MAD$4Su-5TabNrgktlsEDcL?jvmSq!r66Wa;4A_D|V0zxThco zT;}-AeC>b9Yp8A zcf}cf-()NQra`(m8Z@mvjp%NT;OIjUwIM z-MNSF{`T3|KK}qPa5(pMf8u=&o%~@5?~OP>!a$skX^#vIun(Nosivc@w0YB8gdG#Yoz-a+89Wi?f$hw1L9 zb})L`Elwb*>4Awk6xr%veZPlRloJzGS^_mQTHHXK#Kg9PcZUDLXbgs#ash7|47NB82FFom{-_|IPjHI(e&ir z4iZQ5L1)^M)+$!#dkzNs2j4(5BR4mG%e#SbqPYKd6$pr@R;zQgUnVmpN($+NS0CkO zuYqE!N|EY%L*qtUKr2?P9zxYGw&)&&Yhi1?P5KH-)Jo8jNefwGK1=y}Bqrb@)> z>n&K-9BEQ*6{%ZQQ3)dQ>DdTh^~GeSVB}51)%}gT>;K8d0I=Pc=8+1Ht(vavdY} zeYm}~#Yuo7@$q$|bkvX8wO@C^)`!eI^(Q18B}6(glDbE#^e|6a zKhN)W5N?A)3j|@LQ_ID{JUm41SqgkQwE%T;%-3-b*2F|=8l+UN7Sc@jYt%1!S0Kra zgd4>afzvGCMuIM><_V-xNF7+V68w}{&KJ;vNwW;PYE^uQj6z_;2hCvn|Gd{@gbb23w zbB%15EO(xE7(5z>#MemPhconlv?4)>_$fZUG+t~MvOhW`u9T#uo zI$#$=@>-BVbbZUxC3BQ0dOdvC0&T~$uGo(#enU1mY*=)L44}bF>_p_=lVBUsi>N-_ zurCbwpwUNw)|E9`eKzOgqjI+FdNdk8(Ek!Y(5M3m0OuPaER?T4`5IT|_T>+h5r%g6 zwqf5=C#!RCwEOVZV!x0f@1)M)K3Y%&=ljT&uTu00Z7$0g$ztDs~K}fTCRV7F>4uV49KvH@S3z0<(Y~0$$#}7XwRD)h65Re9f>yVS+r_ODkb0 z56Fu7;{?2ECHo};cF^{G-?>JJ*Oz|0!r}W*!5kOF1!#rQf%GSVMrh!5udPb=NEC6n zEF&HSFHSI}F6=>pvqq^BeqD|cVnr6&cl|MLrUboeVM@4(T?&d~~HHButSRu!( z_pG>+aRF_)Cq>%4%|Iy57)v89Qb0-J7qIal`?KgB?C(@ydh};BEJ&>XwhYPA8Jpk8 zL~)||#S-1c@KO2_dAuADF2a96zY&~e%pyw%?hBu(L7RR=Ka|6up#&#Dv~iS}@KyX1 z5ly4J=;z4S_O5DiD_{U*$G#!{fQ`43oIZ*G%S($V+G}h$UNW@_;yjSv81wYOy zzUdQ}cSLH(C&R^JRa`z34VW5}^e zG&pH0{($$f9J8mx?RZfiu_O5tASJ=Q{R;6W_P^WUu2$YH{rT>=BqRKv(AHfvx&E@ZIU z>xBT$x=hU~3mGh|M}&?}iUSQbuj`lh^ED<)#frgL7`C_g1SP0On@nVwOTPi_7BW!Y zQeeZZ`_60ii0&A5KWZF+p^i3A*fww6v4WC`5Wb~U0ngdl$C)UCqV69uy$EfoUT0CO z)=lYc$wV3-kg+&t;K1ImXX~QeOzp;C{yIw1R)RS5fm|%-FiGeAH|?jJ&e01!1`^S` z4kw9UB*7>U(G-?n{&5sy!mE*ujhcZd4Bi4H!9qiGS^c6aJ>5HTzu0fqR>iLq;3)Sh*1F-^+r=U^x5-fg(C&f&j@cddfxKUO-m8E z=Fry5DTlz)TFT}eIY)&ZY zD*K2Hm+uPUNG{sM13q0ZXeyE#@U6Vj@(=nlw%BCngs)>g=p}1M>Z`73jKd{Bob+{ew}6I*~!* z#XL3T6h1hpgfd{ObRD0B-w@@ld(XzTFrpksCe-z}+2@P4&~f}4wG0l;_o3Ngnqm>&lp*|QEPTU2=@#ixV5(+m`98CtraU+>S;XuFQ zGVHd8KH*(WYTF6Vl|8X;{@>1T*M8lIy7~)wrnVG|QVbI*QDSklbJ?dn74?TaZRgC@ zU<)FRk?xRx4|y^aY|@@G`RsG(MN+CKf$%9B!_r!_-Pv-@kbkrt^e= z&%_ZJ;p-A36$FGaOxvEOoWIaA6eZ)t-Zr1<{|WEYjkzsD%cTT^leyg@-+WY};Rn@2 zma)emxZYS*bs?QF&pxfoH5s$>6CJ0nawFXLbV<~W@}bG$_-SU?izqfdocQd5Mo=aj z-)c(xjQKyj1~vmuBibpeVr}6Y<_olNwd5pr9uwpUyfk6pMC==&OIO!2>*OGW`4jU8 z11&Ci$oBHAWtNfPpx`0P781t>8n_AX5ewKM)>MbYK5f=Xmpj+c3?|5t@H2mOHg{Yn zx3Wf;5bayUF!RAP7qm@V?7emYG`%BRKsaJI zh_Gri%+g6H9MUP~)e^KtJR>p|%kDED=C(~3Pbd(IP{MgsBF#-Tw^^7vH;X4QO#Hv@1>kxjVhD4Xu)ErAU_EVis3U(%NIk+ ze{zRD*yD#Z7<8*S;7cJ7@FTCDAc4{rY$KsubP4?xphsnNU>YSf!+fztcQ%^Ex=t}f zS(q*A4_w;=o$=z;3XkW|Ff(!qmmb9xOUV>RUu@%_5FqUq2FZ@2W8kcW+Q+BJQ4G%W zP67TVrHA;1#QXY08Y#WpbCH#`ZOQ;vOA*g>cr_Yl#gz`NQCoyGPZBRBw^~3J1HQAT ziX%}F1|7Ev;g&G*J}4@!Cwzq$8vH|UZs^969MyL{PH_H}iI`acfEh#)T+jJb z#}WCdhsWQ|PWkMZWp){(=!LAT^Tz82IUJMxV{6_S{Cgwd_`#*yAvzMamqDI2h6o#9J1Z_LrFm{`i{UXnt=VRY|jg8&K9kJVA+XwS5YX%pJ!a5N$&{BGyWKf#wB&iWs z^8WY%FZ%y;SZQU&+1}ezqG+%vv31Y8V}EuF+tS{o@lcFClb#~Na^eK9%)6JDY&c4E zvt(5g?JzvA$8cn^L$B!!!7I9k@H-f16cq#VkUA{>6&UQDOC_oiSMYgIq=Xr{4>o@x zu#kttk5+j(;C`>HEFiCA0`0f>GVu2!?hrW(uQ^!ssj`1M7V(S^@I4a#`9&=a-0)Gc za45~P-8Dr2Fy}i|U6i^Z$Gy87gOrwO2+@KU(9Q*Tf7NWe!~xa>mI{rYX6mgJV2Gjy zHJI4X*AGU1;()}RRcz?@wyq&MM~;R&a0X~Dn@CM*RkZ@BHaU%XWH<@ES#b}VfCpRZ zyL9tQoeXNqz!hhQ;krqhRWZ*BLh4W|27|Pm9Q(F=0wd)=AFCHE$=f0#+i%F6$6Q>G zgWU7or}e+U21XX2No}AjH1;Lqp=7hF1Ut5+Z00-=sL7O<{zXwADyf~H2N>f2JXuR~ z#!-T>-sSe44{r3Ap*`NI6}tf?p=7ecXzgyCICsGO1Frs(1VKC;z)T#Zu(?1$;nemq z(t}ymi&{&CP|T7m>Jn@f)H%z$yR%t81!<2J)7mY=-cF+ z6M3sRBw0;7x}5@8V0aEr8R%f`xkDs0G|8i>_meVAUW|%tz&F2*(V+9j6G2JL4_++` z7yq!}fv{9jkc=@{1ni~bO{beyN0^g~3h6y&0J$gy)lxtgPit`%`;}xk$SS>=M>L`$ zYO|XbU6g;&OwL-^+`1YGdV~s2Q-|_l<<`K`x(Z4XxnOt;RF6YWi`6hzTQIB9+fH%%ZB^` zccnjhp#?RBmH++SxkDbxI)(4{2X6}UfV;h-x_DyOT&Xf4?r5o-J{2kIJEPN2<}zpW zHc;x1@wXd%b4T+{nwE~5iw?+bdAUA|=-eUZ$c{#EaX2J29S@^BQa{2vT@`+DV{m<==(S@zXgMGp4iG1$QvJ@G3R*(8 zB)TF%L!&eQiOzndKsJT zBCrJh#u-5szX5>-Zwl7IiydX>otrX9o*wocZ!PLXqFv|}d!U7;85zkxJmoG2rE!5U zaOzSq?M5amU>~K^!aaRle zfW+QJQoetXpve4xb4`OPvK^M=75cSD5tJoXD;>!k5i1c#8~wwNM(rhg*^WvrKRztA zC>h~Yg7fnvhhVrdBSxb%anLHh>hXU)O=k=$T?}L}KG?LDYKwlfp(cTdkK{|NX#eR- z7=6z1Ic_<|p~D8Md3^Xjk<}*NQPzSwP|g}O&EvnGhGAYb-xb8uG9%F^ix1wnPz%_T zis)<`I*0KBI@njYt+eCCfglD6&Hc^yMVgyD1_B#qvq~RkcZs4%&!ipDJ(TI5S4Eo5 z`!O#H1jE?^rPzt#dAPY-afpDy0^z^hOr&o?L^LG=Ar7=MlE|iBvb#tojPCqRBo#IP zdajAs9gt-S?CeYw33e#~glP*|@fkCuzYjnIZj?7C-)FS&Y*di> z>|-`MA&|sVR2UQ7K;IZ;5QdOH0$AWI6dVjux*9680nb29=d)FsQ|~+!I0>S1!H@~8 zhAw25(+siO0{iEEmJ89>h{8Zn7sG8xm$eEgdC{?Tj~@RPRRw4;sZbpUAyaVn;_TXr z0KRVBI}hQ>Q=ncgc1OXROlefal#VvpwUMz-Ko8Vr6_GauHR1^yI^8qC>$Itt@A-)?7o)B#cpIY3Ir zq3ukLSim8N_;cbD^_`X`>*gOWZlS0da$z^l_R66%g!4^1AiGf5-RS|099j3bQypc~ zA|Vues`Ss@E6Of}lhi*veUXj4*1|&VR&|**jqY=%#R|I01Tr219iO%|qjpFNJ| zGcA;nDmJ_;f=3-kK67aS8-RuGHdB^bCSlEkI8mrDK{(2kA!EFWlER`5tS4lpBa>qaQs zFoV=pFP%_);XGm={Qw0w^P_$TurP2|`%~df5R#&UiS{v&SNI${DLMO8I027!7{nWU zyBvngJecGex=IByPAWmPfzA3&{Y5S~|6NLHMV*OqB6i6(9K|8|qg6-%RwAPjfO z$+Z7KG(H*ZrFp=Qh{`ZaDLq-xeI6+mag`)G+>8_H!P<%9mwrZK9O(<6-s04KJ=kP0 zbTfn;>Im$@T>9D;l+-0e2>^OqY&FbeO;s>SM3kmi5n$d}GWK_+1Z<`0W?5KPNy!6d zQP=-9i#1`I7(cjAsBd2?*rq4~Cx0aD8K3&(+F7c?8bQ-R7W;G0_W^ zVj8_ZyEbXcHPxvkX8{tSe7|NBymuWxy;|Dy4_-|I3v#p=4itJ47N9^Tu!828nqMx6 zsHhiSX$P=!_V|{9|c*d3VQ93n_Cg^cl21c&IP^90A$IrOvm)dB$^i@Cuy&d{wEb{w)W@tlWn4B6K_y&c zhkmo&iMcQ=n>r1KS*L;#Xqa;cp1+2ZT)V+ag%G5oXdne+ zt8t~EsIu*G!Y=ZBDv^d>E(b!BgS#i+E zP~jQs__)XypdzYb#utIpA_!2OF7nf7myU^2VXYn*w|Kw%< zb+r^FyDL=)NV(gc<%NBYo>;JjXo>TJ}hMS7fp=B$*;9%|I zr+=K*S4;$)vr?MYsPhPTmUBSZmjOpW(WT=VycdB7|oMC^vZp7#>YNgcAf;EpigfxoJq zSrdn|Iy=h&c>qFSsuR&yvnHsT9%Ev~UNkUs3_n_U#aSU2ujOhWg@e(I;es4F|CbXE zN{)i`S8{dHy9uPW>s~AnBDcQexpFk7YJVI1^luQ0@m#TOE2fWd+P~mTtu4kkZj%y= zZX@y(hHjiFu@t!aSQ!lZ?6UWfe(WTr1!=C28&#y9a{j-@5S38(-43F#Boy7O#IjM*jWqw^_PkvM;ziWQj(wi8x^UNBKc>G z>_yhPSk7zOu%r6SLes)v;EVUc1%9&P*HRQl*!!X|rpoYJuSN29_#BB6?f-6<6GWF3 zG+#hFdd?LCG%qY zS+U3xxM`eZ2l#I89!fY$(}b!exvrP!ML02YQcKTp!Q^-f2E*m>$$UM{hI3R!Lw$&R=Rj&Q+b8iV%|~kB&+kC|&>l6k zB&Qz1Wp`F)kARklSsQOPlc`TQR&xyXIywtjNF>|{){3*165bOV>y_0qA@R%Thu^N} zm1t)T=%2c&3=TQOh;oouMa}$%lxL0Ys_2L+$z%>T85qOR@RD`@Wh?w0>i2xv;%hWq z05&F!tY}DnPqX0=z-i5{!wG>H+7k=l6tv|*=%7?X5ky`231RZc;;5856Zi9OV8uI! zr4sFh#{Izr*f#0>RjL^aw4#;#E@W^5BnVUm`F#NwDi+nZ32TyYh78X3d6Q;!SnzJv z4tp^2J(SvB%mpfbT4Sy|$fWna+$h9Lj@P>J$wV^JT&8Gk0*K4ju|t*HG@;BliMz_l z90Y)z2FIfKGiddq=nYOBNTh%eBNZ4a=KN%r1G}tCEaLm=DOhYVqypT}|D~Kz11IYw z9HByovfGjO!J8etqEo}q*i44A%_v|XZ!@v9hvkn@@8M1PVMNJIrb!qa1k4vxk^Xe` zK$7f;y=$M4s zv+$|`8?h-G*U?|Q#$FQZc{fDeWK<&zs&K9A^!_`61=?A7zhB1LKL@xY*?)FZXH z!2&sa8_yzu9n`fkNJuOpmM2#`B%eU>DYUKg*cith`xX+VSR3adh_vEh;6KnP=R{>A z2})W6kGmqNNKqk!KfB9Czh)SxmcPKt!{sL5AVk84<^F!(lm)%>4ig(wAa|??iDW24 z8t|-z%V%^e*cZ`Gr><2q!8lOh?QTcx-c&a+|4x01J+^Q6zkhD9=yyNJqP6^Lh}d%* z!7V~Q=yvhlF{DALlA#Q(oCFk+atW!TI_>UIKJ!q9NN*~>*Tx& z#;-ihrm2-vwLoBBYNEAaGi;vVO+2+r*272Hr~3<21VxB)xaG=Vd%w*-s=#>=OWVjL zKPOCg5*5loX_e!dr()~p%XaQm1O(Y-KDaZ_>QO*=$T#1+chJkk z{V~T6QEb@P(`6_4k3m#f>fVY!{#{OQxr57ao;dXuPS<{`N=8c)Z;~dF(65hlF>Y{GKG78fh=bRnFnT_|Fa5g9Ix8YU1y=k*2HG-m0rQHmAF z5@iyMxpJG!9T|}U_2#$>=gd+ZN&oc}e7b_FclFoBv5)6xI;L80jK1FV;2vw=uC;bM z&qFlnb`Z{2k>T2dTmvURoL!l(XyWO$xResV5A-q2VE>@Po|g==*N^pC^~u#y??~R^ zG(iGUK%*3{i>BG$VePW_ckWfuU%V>PM)(os9#(AL%-d9_px(#Qwl7CW@!PGf^*vGZ zuGGUL5rdA&-|%BU_G6K(DdW_{1x{8*MKbKKp~2-}$eU5)6WCl*UAMHz@m#yc=qPK7 z;wpCkw4P>F#E?q%=bYWp29`5n-_M2)NF?v(~xJ;tl;qjsfSt$)OosH*Z@@!Sblb4?J%_yV@thntk7t*HwbC*Ws zmo2)x?vQ-OGq*}~l&xh55n}=#W?R-wQO3GYET>!w;2X|kZzW)+Rci~h{1|9+>C7B8;hVx$4tgL~NVU^9g*l-d2sL1t@PIo!d zOVPGH6tOX^{%<`J5xFN@ZFW^@-^OkT3adi%fJy~HFxG2Y=w4D&X z1h>HZV*3l13P&eK=*QPNk@;NrM2|zab4K?gfG5wtj2>h3;9TXAFF#M~O`#m9}Fto2JYph8LBae zNeQJW2lyayBu~wAOn(tTvK5cAN;*N*h5Z$F9@Y3c7{^#6K84wP)0j)}bFvuRf5RCr z2>)&pOFEx}yOdh7=SsDsG$!NFr>cvAI;={F!mZ6Oc7iZ2gxUGxSG4rrI6z$b?2hDFJ;*GgS}hCou?~xh&L5=aX`wHqg$pe`uXT@lE!q=(-_~ zOHdGat!Gn%M%G|J%3>yucf3lBxr%scJ+qQ@Ty^l4LT3%G{;wj_ML{-4Rxi&T{AJGs zZeeKN|81%~u8&1gV1Akd%jd4_Ps&6r3ITnnXpaAaxho;_?q+DA;{3Qh;>24KWYkVT zF+A%$IVgOgea=stFocid4DtCp+A|u` z!~g$c9eQMG-s7$8k#IB)<^RY+R%>XZ8N}VuUDlWHMIV9?#Ibvkkb(eE{x@%m%F_k& z)gMleNG&i_pNsWZQv!e(9oFaZj6a#or!+Ewf*Oma2|Swdy8qk1Kn{{SH^&VX3z0DW zjY@ytXH(nuiul=n_;k?AG6~(;x0Cxj&ZJ{F!;pbUB>PWw`9AR6kAb zT<(UUSZVXb#le7QOiXuQ;wp8@EwmiR0O(|xzbnZ|?-RRIR9cr3Qax-yx5=DCIaMM7 z{z*^(myhL{I`ayEvhj~AI<=qmk^Y~1NqLYeP6K&=V2mi=)7RsoP^_!XGJsFBi+zV4 zcm+fZhk$KYqxJD#*+f*bPjOx`mJI$!N>0w<80c?x zX}IW8cq2j?4ivZ%K8=m$9DA{Ar31_GhH=$JO_Sr(PLtCHW`ys~lzF3kA>=wref+TJ za9=sYyL54|hYCyg|BhYZ;zBD z>V*9r4sp>v07T@0*Do60-J>?Be4Qfn3&6(IwW8KzWctAQ=lgsoN&Q<721a#V5hA6% z^X0psfqYP$dt~Y#MD+yJX|ndO@<=Wo9UnN2n9~^<2nCJ~$KoiS%XL(h>TRei`{Lbq z7z^RfdJ2N|2vK*#;N*_2Olp8_bXih2k8z7fXgZ3~44gPZ7??j$qs>gK`!bCE=N9ut z+;&aubBE4=5fu2vvJm(Vcy-qt?Us)#fpKkL<(>yc?}_Z3$UNVoA`}&M$-IgATK%{G$*GQhmvqbtJWkCWOWdVNjc$qw?vu1k8$ z8KL-A_Y?1Jj4G4x+L%1Ezi%9Q9L1P*1=j7ELc~H2*K)sbPrv37((gjLpN*hI0Ih7t^?2eOh3<1JKjq zQYJxS(t^3e5ZIUaMWYM2rn6-WIC>a^h{oXIa(lG8rg=?&g}Rw0e2NOkf?K2neo)j| zp#i_v?JT=kr{lRgYXuUFH6&})6@3@M^0qHzSb4rH2X2x{2_LM1eN?cWHcE$1Kwx0R zut}1@VPq-H5cm*PbHW}K_V#oTN$>{}FBm(SQ&+DyVn z^?z0L%|gXNa_Q8U-j#&lCnq4*ligX<^`}&MtEQiVr(eDs3Ce!!^H)_MGbeaa+MU5( zd94CN{r3}a@^B>?lUM*$vn0)8Ida`KRs(7&(&{WGKtD}*hdCVIWKCZ%@NyFHVrQ^r zP%L~s*KGZL@wwOBN|IGpGxgBq;%yr~>3K3ejhwy@r^|nw9_dQQiSSSygT->HL{_*O;Z?a+(E)sq&;u z!Xs?=*CetE7m~fgY;s_yY8G!&NeL~FYOyUqB;1RC^{JmSK&1)B zxx;EK$96gdyR2!DkX1a#hvZ`_mF%g0HS7~$gKYEpBI$+9s-I&D-CP>{tI+=|+V78d zq@k5|F?v@jFRK{RMo2OPu=ara2ypnoM%Ay5o-9tp2*EKg&ThnBo^A8F=k(zw8M0zW zBJ`D^&=(DRuloU?N7Rf$^F$e-hQ}g?&72x6!#5+sQ2JNI%1ei2B**K;!0ERNa-i?Z zZ$$mVh_nsm@bZ&Y@bJFTBTTw3l!?f4H?;$QP4=r^-3!>xv{u;&+-C;lqGZ60 z`SL!Z8g>%KYyWH6NLw^MOV%AhqYOG` zt^J#OrBUv8TPa2yKwEaCdU9B=;)l#;I3qHl4_Fx1e_bi|cUYV6-T9YA6QF@r3RLlq zlW)ytHV10~pjl}lP1YL`uso_5itiyQN84$zi2NpiG~=DLEjpZ9zDE3x0s6>tdWUJ% z_bUu74j*w+aZpW(dKoAv(HGc+1=P!o|BeLFsH71>Y9z4tLXa@HDg4GL?PsgIwXxTm znFwQ7`;4Ef0-lBqive&9gTRV!UDU7S-+{Z^7gYCS#-bOge8|W0v)ti0Jvbih zMR?ahmh@5+{a6smmoz>acx?DsU09x4*h2wGe<{Q4KSyR&Yh~(Z293`)Q)-7mY@^FQ z>dZ#|Er1t+3~0dIgP#Gr&wa-NLmEwr6n!@E0SGVWz0J=bC~oNJfdgbMBbiMKYIQe! z8whp%`$l^vKq*Bt*u=G=i+%wHxia~x!IaC6{QI+5qOn%CI_q)%xF5i~%&K+#vYHBA z5gcDS+1oN+bHa-g@^suQ;+p_?=ma*j#x*_311|vQdsJ3pDI&A88jn?9`J67zIWIlB z6{M0j(N!vPwW=oM;uJx)+@M6uH~L$7oFyHt2f}H=x6ZZ_&?8^6znS9XV=_v@w08!L zfM>;s(qWLm2F7%Rny?Dzv37}URB=ZH@J*;(Nq zW!5essR{{T$l2SH_gfPP{?IQs9kjws^Txbs@W+64S1l|&k^;b;9c_J#@*QK+Vc~#m z?ePt$Y+e}I&n*u)vP!=D0^~`Ta*)4~G6eyRO}x9P$Y|eQ95gT`2R(f7#5+vIG_|B6 zK4Haf9O2I@U>BPirMaeSU7>o*_{=X^sEt7t-`s#4H;CjF1MqoZoFx(yP{u0zw1RC^ zEs`i%A%TESbH^GcKKPBlkk~Wl}kBJwM855N|G z69t^JFO4F!3=G-PqP$dhY=|u9#4tlmnx->hf;&&NH?^2W^R(ml$(aK?Ue%rmJj~9# z-DeZ;yCpw%Bk|zizt*2MVF-^+zH`99S35b&tmbP}ho%bMQ84}66n?cn3$@%!-0Db6 zrWBh7QSZ>QDe{y#+IeRz6KxN{mo#*z34)z*4e>U@i7~CK-HDOz=dQ!9<6_o5bnE1x z7l1X}svN(j4v38{lIl_(Km3wHd$j%E@}ks`nEr-^3YXuEJ7bG6sE5m;|0NSBBCqn* z@kZ;IsHUGmS`j<3{t)>-I4K>lJ%$03Bu+keOHy0l`=;#=v`Zt^T;YGYxTH?8fUP1h zKDeV15BTo5slO24D1P5pEwQzHS&C7T0~&cosd&HD6HKspO=2Y!_}8T~suaALe9o8p z@`@P~wHuRcFSl5OI`CckGCT>okq-vypUYjGNd|_9r?uy5pzU~r|6DkEy5QKEnHQlK z{6IlN_xE$H7@gAUf9VVCDi3lx>(llfX@&Z}X$efeiicn{_C#R6eYP$8=9KjH%+bd} zLlRVbuan_Cmnu9)sIMZgY|!tYGehyjUk@`6n;lPi*^IqxTA0VcXl@qJQcgh+B$%Tr=A(zPAC~6i)}6maSTPmwUxD%gq@y z%k{&!gfh(b_DWnHmcCo=OL$O5qk>*m7`5d1FGF1LYqxvvP zYrAAbnGW;Nwbl@7MV`qWkg|9^g5c>uQYGdUuFd*ZkNxDP7_jlN7pqNrF)su-1RE3) zz0V~+ZH~8)Zbb=LH@hY{A8e^^tq}4h2P6I=kue;+rcg)%pgIG}l%r|*FNa(gJRz=A zrJpdo@0T*Ut^SaBI_$2B&Quym-*<|uJ{vUm=4Ln9zZ~c*Dn9qquHo#R(Xk{^VpMoK z9Ma{;c}7iFE(s~s2N#rW(rAq5_DllDr*8p67bf`TCD5m$N=(S!gX_ael!^2aXa5zJr3uWXjdS=kJavc*e^z%O?y{r|u zTAY0&_wV&+Mv>dRIh>aiCu&kLdN2!ev(eXQ7cXwiad+Xnv)vc=_VSuWwOk{(CRYdQH&XR^q$C2Bl;o4^?;SHgB17){!o-Vbwcg){ z!pW9O_jfI=5Fi{9SAHAnfM*(u)7)G$eQSBVVhiFjPBN}{f^At_?|a2xCXcu9W7jkh zWG+40fQgAkS4u1#J|&s ztyFQT_WXIaRI3yEbps+k8{Yj5!)H@M{l#><=99e;%CYv&NBQElBS(Vli(f**O{(s7 z@o&{Cr~npNGVm0pW94pq-pONGUU|xa{`sOPtH_*c%zrY=K`C4nk)3;`W?Ziy?w8)^ zw+k>l%L+B$iQnxx3bi;Re$}aIZoA(Tbsg!>K_)@G>ENAHfxkEuUQG|+NsG?j z*vi3x`h3yfPALA$RJ)bnKbC^$`r(FrTAT%ZG#C*ku)>l$D!D0>FEA8FEsL6TiFsiii+}W z*N5{Hx!Fm`K26DaJA8?`KA1z{l@BGuwtSms+3INqX))O`y=$r3G!7X*=9mIRz|Hnw zX>I=rR%#(&gkCeeOQLrl8eodYM(Wua96Q&Re^-~|{$USg>l1)tlz4xwwDb1+Aj^$= zV(Y&@a{3j=DAQ^Tx~?Q>qnilwPsh$AxE<9Agl^?@0c^AO;H&5I52c}a9-+72u55M4 ze;&Cm=m_-po=tL^d`Jh}+QBXB89t1dtFaDu!LPM673%3(V>Q2RRau6-$L?m=+B^Yt z-=A_TTkjswGSgU#<@-yrYI0?UGShe8yr}heQEoquIh)RydROlRnVD)X~FU=wfI-z3&p^UP$SF#8sG zW%Ieme|@i#LA_4uXu_EwPw6+so1;$%Z;yy`OHeft005;`q)FXBQyXj9UI^%Ivo0{F z>^LNQurUv&-b4HCi_iK>Ms8;)!x&rm-lKK6FrIJ)--wit^Aa~gl$~g{hY7o zkH4!-iR#eY6%G3MwkgqP04K)nJSKN{!eFcEh`{%7JBbyLy|I1^yvgx{(6SlrE zJGG58vxJq4dpQXf@?2SS;U?oUoOmoiKBD6+LnzOBMPzw;73(3lW~J~Ddr$bz$?HHz z=(DV1*CGH_p+PPsJ88c$diyR`D<$$#kF-D$6^r+>+SPP?4`A`2CNn()#w_Ykcd86f zY=_ftK6CVpPbOFBDz(TU*TbYEhpN#@kLgCZ^<6V*`Fxj(l|S8BsMmoxwMU- z!$2cTsOYK$X)Eq?Zj0CVn&Ns#P5u3MEKJB)q_ zIP4F{#N?EezCE^kWYlb&Qr*7*wB=fx(wBh~IQcdjJyJo8Py&o0;_TrQo*9vgc%`~~ zOV!`7KCzXIA&JYGz7wJmbn(qgO%lyPeOWUc*6apX-!fF18uYL?T+p9iJypL)aOxs;#w4@1f7dNs8M3ICf5z1&isrT(*M88pW z%;ue}5~0L2e#ePPbPYqrQcezgy0<95OY7q{if6zhmtBbce8&pP>3Qj!Y#ZT=#$!v#UG_gc-$0s6}Qeq2Ht1IEnk9MUfe5VPcadKpUeMF zFebhrTnIO+PWf#ov-OjcMtxh3gi=+5sTT)fyX1O&c7*z~_CY~)8TLHwB!u-Ybvn|M3155Y>Xkz0V;futl^# zsBSWZuX?fJu$1k|HkOC8=r*dT?|ILZPZmtC5a}?D|p(-8-EXoZfJd5yxgm zb9Dslt$^E=%2LgvKeZ0SMt@^C-Mi_9ylx{EE9m|>7<>L}nfsm>wuP+MPdp~X{P|_J z^ul`Qw(&&Vgn1u3?d4OS#BuV*nI}c-T~5T4S_m3x(r9k;7iB-&Lo#&-!7M05>$>Ef zXzlB(5+m<%3LcquuHk1G8gRi7(}rEU?`|pUXd>Lc$UOjnOQ_MWI%=QrAm5}w6N2Uu z{&Xy#w2;|~OYBi_<9D!G~wgf4>a&4qo18C ze7S{4we#Sit!W!ToHbah#Ke>-Pg@(ageBp%0(D3=(f1|gq0ex8I+ z9e16w^uc1=y%(dx!g<>I9ofyo?`gV|Rg~2c2J3a3!Oa@ozAhJ*3|W59>zNlSxxPlf zP4rF=F0an-3DHX2&S;6RcIUR;^Ra}~TN}O;ry@V!WRhBZaed4sgO#{D8n)<>u^!sT z@KF)c9L|axq@PSjSloI!d^0PvdLpVZ*zZGOGm%olrmt2pm!*~-J8B{jIm&kSa;+ThDD5JCZ zAgCb?;3e7N=F0Dy2GN>15c7T1?vax$ouX-PxIBFYlUPaI4fEwCjo^}l&wb<3uRr4> z;8A#Qf^d_b0T5A%dcOE}-Bm|M>zyz1t-pqqfg(-fYtY^&a!2@1Om5|q!}?3RePE73 zT^NAPq}B6$rOy?GqZfC6{P*p0Z3Xl&6?YvrlN9>C+|ZS2sVG52zzutgp38l1K9^W7 z)>3~|iB3h9*q%!$D4Jh;DXM2x!|U?9E-QOox`S$!G9@dSPy5HxST?4gCs*>o+aPtv z_TjRSK3Tm;Pyp5#Dc(=f-#}RL-n~PX{gX$$$PLk?2Ez;Gj%O`&S3}7A(M$2oUz9_@ zm2xahgh_h~gip=SQ`>e(e_{~GpMM5?4d+E}2+d3vz;+{kKoZh2BxGucB99xgs7QfB zhWy*(%asV|cwIOIM8?R>Tk5HB0IP!VZN`#kOB_|55fVGAPK93hH;GFkAkNyOZAb%0 zzAF-vkW;|aKLVYNAN4E&D8qI(Y{#(Za8@^nN0v8LR-;*a7lBX3g^&jRYb>JXq;H=* zB=kA}jLzTh-LBC>CcSdBAs>(*q`6Re^H1y&BP&9XGEap6S6OErRP`5jdkFzSq(wlw zyFofUbV-+h64D?IQUa3FDJ?CX(jkI$cQ+im>qy;w{JnSPoqHX|;V({nYwz`}^;y?x zJ?uF(wB1{t83Jt~7XbgjCekBbj%m{QofEcSfm1!~W!EZ!Rx<;T7N&qM*zIi>PTEnc z;|ApDg@(|Gsa}^bxZ-DJa}BN?j0@0@ z!u*BLgncQdsGBOWf^>Gg4b@md8r_#g^Ln?!7|BXcm2OhdX~BRjYU)BJ>5_3-o@s+D z+9H0BEV|s%TRhJ6Jlzlw$q@_@JN-`aW9?LonqS!KfEE3_W% zANW7U7-+2+rb#+|CmPyoe;=E!_tOg$1_4FWq=%{A~qf4*H))^xx<$`%t zpjV|?UIv)F&vhGma&!`L?zG_5#e-(?!sESIyH7Cw#Rk2w0kI2@ude~$pIgoB0D$(q zcqO4yGig0~5=hed&!}-^c%G5FKTI-Q2*8|6)ylC+dAjX3xOMCpd~9(W0XMmZuoShd z>q^<=B#Yw>=;PhqVSVh=m2xuCj2kc-Ao7D^L8uYqEVzDW6sdyo3#XqPiW*{~Nme`G zc%@1A`5xWx=XAkHR}@XtbYK*(3~kk8C=M41^rrmQanCI%7$Ctp#(X0lpD7P{ewle!ySFJ2@notA zR7}CqXS0XE{b14M>am>_JRoSHsH1~QU0nULP3V%rutT((tcYc-;}6$m02Fd}AX{@k z#GQ!&jU2}E#6*lIdz5{#VHl0ARFiqP)qZ&ZMd6XOK>IlO-cwFa?l6{TANsECwLCHW z>8+=2&!oh}AFXif+|3;G*!me^kG7$Q)Zfd4y&=q}gpkIDsQj1F22O@a@kl9eN?)q< zEk#s$E};peq!?3O(;)s`??|>J$e5e7qNO)2R`Xq|a$A*zwW!hRu1AWOc8WrrDbt8M zted15)?(^3x4;zI1{k(PD}9>*+crNYn77M6qLW$F2~pLG>cG%f^f?8K@KWbn7O`3f zxeelZjOU~n5l@y(a*o9zBnY;v7ilU}{Q=*x_kNDN$d>p$gZzBi1-laZ5~t9d6igyG zF$sonu=WK)eCmGe$$cQ+`$oK3n;EH*{@HLB|9JxG{BQ z#4O&*XTH_iR2T-|-4+~z0U~ta8}?tBOZylD?%=-FMaxNb{LPU_vnr>puZ|llY}AKF z&R;6qdd}k~t2d|H3S`y%Bps#t%MKU9A+OWDiuOP^C7*9cAP|Q1ZRFnBo6h;akfJf3 zJwY|J@65f3fW`wGKx)RL8w!6rRFS7B%;HD~1%K*f{9^`esp-=wYFjkFzB0eJkx5o< zc6Y!ltb>O12(=6+0G&>`eF;ML_(nz)g^$TT2VoDZfpe)Vn9%y5S59ulT?zz9<)>KO@8d_amfGiK?Ie1^0U<|tg`|)nsQ14?>+@0KDk8-2I8k!n_b58RBM3- zQwFErhftJu{cIO&0na1D!n={zhiM}wQRvc)`PgP=NAGZ7V~g-wd>?7 zQ0+0ajb1Dr0PLVQ(AGhd?U6;^b_|=|6_me~#KrGb^RlNj^7|cIuJeEOA9 z$1PH?bfbj*cK{2KCp0tOd4Oft?h3CMdC3hiN5^VA>zxRin!GGL+MW-69s31&{R+lEH?sFEVAl{lB9!*a-06Lr0p9x zuC`Vtf%GLYWN0Vw*e%>9*s~8_4@ZoSjQ)z927!7X=kEON$eZs7F_Ru!*NjZmoUZIBQSF%ZXfb=7n?88#*AuXC*ik?2Zc|+(~x5G6HJZ`a-m%$gTR0<(12#B zryWY=4va39Pjh4tg$T zGpt}R<6ubDm~^*aeKs&xQU1**i4+QP-N=e+3X9NlT#33j2q;-0zbMBPPc5|8;x5#G z$E>09%|-k$%amQi)Y?FuQ(;1){1qBhO*&uP$JX7w^)u{QZa@9)5{eW3NN_r>JaOgzu^5cbNdkkop&^x044R#XC* zeDklsSPWsYTk$7veW z=1d9xx(oRNy|2AeG3i2{Dr!RstU;|>QIk=h$5ZzjgxXLiS~J@m3h{fAjFbyw5?6jO zjH1J+fjfZf%D{#qE5x~5D3tDWG}}vN`+zd6Jfb}u3}P^FH8(oG1-A9}FlP4Ti*%!C zXV;JgY&5%Q&dHYC&;0a_5JJgVtCuCy`V6UG}??XHCo$>Jfewh1iDRc5N({YKM zfsPR4hWrSsBGxuy>swBJ$bT?P*Yqqmf!5}2Wm&WLAU5>&kB^3+$C7aJXhZb`DTlZf z@(UcjP$e`5S65N}eHIPJEUx&l`hl!gVINoNh(TM%Q45q*EDt=Ie|00xt zv*bJ4lS&%1ZnTAbc3)Eapp?mv7?!3p^WZM#`xh2ye#0|1KVd^T{C0x<*DOY)#mUBc z_d~I_XW>~7wHkCac(EbtwFoRGP*tb${Tq+R00F&mec`nBpk+*6bR7LgiSr3DH_fJ+ z5}HyG+;t`Kol1lcM*ya7xeUrLp#AI=B1>h?;h_TJg; zO_l25zEaK#4vPJ?VBgEV4WtJmEb%ALE;djlc)s3(p=Q6+P=qaTejdK0#XZ)4cP@^y zSAi7CATNx^n&G8$)kRWup5d9hGjFeuS6t=@4MKt2TyTrQ$%h64$>V|@f>Kws6f8ue zv8$DUs%xs(Du4a%*E4C(T-3lIb}aFIvGbAFA2y_py_EcjAXCbrwqYHF$uV0n3sjc!&|>5TaKI&&o@ISLg1<(M$4FL^dLH&G4T>1{@e zwXZGIZ*naz_h;$21~6SfQ`6zX_Gg@J(2`E5uDb;g!5m0h5N6$!o?D8^PP1lJ ze*+0$D9K*38ZdoL_q>|ZHsb9cJWqG~oB8bW!nMKM`}@W0PFm}dGU_Mq`pDs{3%|tq zosa2J&4R5ZhU8`I)#IU#UI>P5$6Kjpm$XSeOSB3D&7{7!l%87yoWo8S?b6G8HK6BU zqTFct7VZ%nTweG!m<>{T(kpe=)5@(kheG3|Zzn8U09a?>>G6unHXbKZ;yUaZEk^ot zT2bHotuOGTQ2a^ms^jIXxx$|?f{2i3G`Vuhn=TuK*K0@K$)(IqBX3e)Ib>UY`$7_% z6S|_lGtR{NU)RCEV$Fh(eR3?bB zmO5eCW`x;S@$p_@tvY@M(}mJ~Ee%<}kTgV?pR0Hu07@%5oT)udu3E+S`krX~qD*;! z`|hE7U+`MlhlS6=VT!!j<(+t=8uZJK?+)}}pguFNi_}dvpdgcve_{a_U^TD=p}t?M z?I%ecutIK64H{+t^t1Dihh=xNk%-;vxC%4^iyJ$rgCb;W*<&l*+75}G9q>cf_(vPS z4hU}yq74k$KTzHa+u8qI(Vf~d)$k<7=Iccly4Ok??rMSF&_tp7AZC0j`YkvKPT>yF zt4wm^HpY>V_ap{sB*-~^FiB5)P-4}!^;@=E{7VY#T(-gC3YCL5gY+5C4J?iM@uV8>B^dLXhdjL7{=O4IVLP%*U*dE1W#3Fnmsa70>mWdCC;> zCoeRwz^$7PN660krO@vSz*YYs>(B~$`b!Fh-xucr(B}CiQ#@RSA8%1NhxWlS`&)P6 zB9G3su3OowT_}$&-Ld6NSJ;USLp;7Cd;}Q4UQ}`~iorB;bZ810vVOG>@Zx<)L+yI8 z!c7Wlb8v1~KTG3n@>kK&2GH7YdLuqU^#0;mdR`gG^mG^Zf}wE|_*YBu5^4czSZj!Hn=jf(F6yLltp#CD^1)L7Q~ z*9txiER=PIlb7YCAPXzt;Bs7Mt#$;bPBBBH`(zU*CN!LcS=PV37>p&CsBayAi8nK> zsv3V>wc7wS)oXICg7vd63Y;L^zs0>&b3Kgb8fwWNRbVH2XaNrC zyjd(1U_ZgE2#sd=D)RV5x7Evon|w5CvU3?pRUw|Z;s>atVGVglb`0w0ld+zJ97B0^ z^xa1cL@Y)q&VH#uYJ{7zU53&G=!%rrIAUzGk2k0`ad?^|ZDgpWUmel|yW4)^_&e0>^$Ml}HMx$ZBS8MZ!Nz^#OS{hgy< zXLkxVGx_@F{(if!c<`~e&QY8X<}|@cEV{0Fg?|3-`>4aB%XIR_?8b&E9%J|1`eSyl zns+3x?@vkbX|>?K-|#U~A^AOWAFwotA^E2`X1wyM*J!!_ROmIxd-zgH6*rWr#ZzI@ zuw|oj+6as3D8x$&u^N!~BR`!wA;wsGE#H?2UP?%zFipS1%j&0z9+6p5ci5_1_Tfe( z_ry*lu#b{3e0_wVYG=bWW40f#-lxo{Qo?hsCvXV>8@XaVLgKgP1rWL?Bd#_GEc`QH zLb~)r($tx_iS7DyKJVNAX^INN-NRmt1lhSj$j?CjC5y&-CwU(CQ>+q0RshuC4gE9u z_uK)>6c!tz;pB`*>qxZfIbCCUxbO=s`LHOgKxI@IDEa^sFSn#zzv!-^02xwMG2mi9 zhs-~>OR834!9)ep=gNrCd-C|R&N=^!O_zen9>5s20S+4P^QzCDlq1e1)b81S4~mW8br#v^Z-(Wpr=Nrj@+ zMv>dgkJ>tw6;}?3*|)J`*w;(c-rZJ%MO-`t#jnkHgktu1$uoJ*71-^9-2+9RWNK|w zCno2I=Mh|oNMbxE65c@5w2@qW2P9iZU}7OAiSYIUP_leFF}W_9HmV*uvJde~KpN%! z-b4R)I4P`$&_DHRoLK@UuuS|AE;dqSrV!Wm`SHMw=)BY&ba-4AqAvrf~z@2HtGlI}=K|X%MjPPVW8BlUG7+vN4H!EF~iCaas3MglDhqzw+Ad4uQ2ij-12!N*SX% zjgeYx{LVygdMb`E4EaOLsMp0ta56jvKog0W@UEg58TVTJ{MIta(oLKrR;LKUJ`8RS zu+kCtJpcg_0ud_NHx-hP;d{RiI@dbunQlulfuNwK$$ZEe3Tn4PUM@9GUY}04-MNYz z0b~_*kAd5w8H&yC><-x~U58%Uq;C@6Wd;9`hmk@;MS(W|!A6JXDR;CEhY2-1Bo(?o>nwito~UwOF6Z9(SZjOwj48Apo}@XJ&?(sLNXsMLO_(Q=c?GCH~- z&Y(@kk(*E)m)EbM@g^U;7X&?Ri@y$`3a-r77@^rh4QMjnvFv< z`w^{_K>Y`gEAfUd$Q((jt^wPn3@2Z#3NP7r!bhtrn)R<({Nw@gsJRpOJ?_I_E+~heI<0wpN`K-($b!RGJL0Ps){2T%SA)=Uoy3WvZ9j0)WZD z>U98|eqpgLG?{AaG>d@MqPFB}n3~t4olT-=Oa6+VIr|noiAc6kC5f zD`FCq#%Hb7cpSy^Z)b}waPiM^1&&CT#qI*f9u{#t+cDv?M}ZBg(3Ef#{*22E7D_8P z`Q@P7s@#!5fam}=wUY8BASD_^b+M-~2v$$SR}Xh}4IV>{5BKMSuqJ8_J-d_Vtcv&# zUl1Sv%Q`*-YWL*J6T<`spGPF`|9M<|n4!i_;C@z&r3+r%FZ8O071_7=s5LHtf7vFV z`R*)A(SF%b3tTwKL(H=EwzqxEDbC<3MZvyK)^(AszXMoD#f*IZOa>5++VckCD01yu zRrnia39*aIBSJ@zga7>TUbi3e#M5>@NZC=NmTxg0QK$qZYYDfiuOO8rxKjoymShD7&g; zY>`%iB#64~*or-{_==r|l#ucbEPQrTPN8Yj{6PWd%=KfH>C-!v*Gtw$5}D+`U)(G2 zKinB#WaR8WC1fpvUu9g`{{gUGb~e#38%J>$ud{A0$j%R@(wn{T8_ROOxl}ukg-_M?p6TRQ;w&Q)(dy0TG;P?$V7PffdXT%@!e`mz) zi~m~tAY$$ZEf+_jr%A5F7_@qIeB+SQ!c+A*%U7TyO-j|P*E0R~GgX^OcVw0EA9NUq zT<3y(KFD^agb>8Exg8n80FTHNTt!uYRX+>XqPe-b^$H2@{%DfPTxPjydGcG0qow-% z+BVd`~D6JhZG%uXt=d+a9;v?BV3}AZ0yk z;R6~P)>!50O7-is`jz8B^`Y;@?j)D{@jDn;4lM}CQ~l=sSI&1SMtk`yPQ^5YY|*`} z;_RSqnON*ziqL?jyybGv!C(ncuIpqj4|(%=Cab_bF$r{bHMt$}$<@uc{@*Jx)8aV{ zo@{>VhBr)OM9HXARg2GJpnXc85aSit|H|ms=N$_9ryZLc+p8jAcBBk$F4E5qZs(Jg z`T84O518OACU~QqB6B4|?I54+TxZL_@bd;(lqX=YH@`6ihd`JRe%fV8i@-P)c>qq= z=fPDkzA3g0=T&d?)Wz)Mqa_d|y8?iUlnzvIWZN z;nU17K)}k33I_YC88cRTBPeNPOC(Rsb1q+3FBi*{Zk&H}zC#VFC%J|bw&_OqHlPH5 z^lJcu5n#$Tkinmgo+*+6yAE#O*MDNDWpd4jGfsi1)DJr5B1V=$VzOyM3=m#;Tg2$` z8FoV&^l5yv)(&x{1f+YrL~_M@_3~HU0d=^+DZzpc*PkC3m`*27Vm}tcX_Y6zV4^Q> z^Ce<9c$f*F;y82#j&-Gz0Q`epE)ZE&r!>30K2hsav4o#P@=dM>rl8J5tw@(#u4rVz zAl4mjKDTSE3B46C&-pKmu;kcprnGS$$JF0bPT1EuO9DuBulxK+tGsvBsS1NP1F%Fn zB4nlHoc|pG3b45QOmP?&8yh~82~-jxe(0MfG8IC^OugcX+Jaj%AeEVc%0lUI*x9!4 zX)nF>3L%RoI}@@`-MDqnk^NgL@@InduZOipe?Q)+`9Lse^tH`HAWx%GQmtH1u27>a zM2Mcc&TvU1zvYG$Pjk+wzkx(3m58sX#Ut? zUr|Qg>6teDD?LsJztkcB%vW^$)goBk%_-6{0ejQZKbYTbEC#$iGs*$bIjDYGPxHl` z1JQX-qpp_??G_$pgB;*ofOtQ5_7cYL`{uKuI9i3oZ)Wi3xd=b-#817&6rgO=c(tx8 zKxt%Z{|3@!)X)}CvRtLItHnwQnv)ZPgH?F@*({9I%i1a`HOe&ic<~LI%0O)BY{eo6Qb~e$&%tr4I&>J6)ek#>s)xgC?xLIy|i> zVr{7DC>`bF4>A6aPl+ho%#SExyLAchSjL$l6>M?1ls>igAg*sStZ)2iD3(0GgttFr zqYl_s|0k?L+u^EpTdgm8`#yQ#=0@lB-U8p`CX=~0u-S9;No8$2brep73s3wI#MT^dxp^K;&xQFYy)d7tsx&p3ga z?RYg!y;wm`rstk-om{u-Ljg~zwzO44;um_0+ zfl^B;fukDYDwAOe^LsW+jp`a<$Jf@DhHWLf(>eUA}Q@v`j&Cdw=^o;eb&l?`|b7bCn3-Ba{Kx& zn({PrPEQveesb>xGYd{9-1BGH)V9|a#j`V!>T3$H;r3dSZBNR}KW8^JRIjs=tA21L zjmds@Qey~0oz!tn-;q&AZNK;kV|dkHuYLV5IE{mI-DExxJqYA@S5mMbpuSjvxHYREg?>4!il6%vE%=thaQCW!mF@NSRi!rqBTv zRf=`G@)+r#^8J{E{H{>4fP=JEv`euMcG02%K5?i<=}g1T{Qc+H6bpVPLi;?Az?(3u zb(!bJOY8cTu@32L8!BQ>tv!mnG0LzpJli!tQ!M`_U^_OXklT}_F?Ks?9iAO>d$yGd z58Rr85(v1afFBdEH%+2)KSk~Xrbbl$-*cd5J7anITHYX58X@+zDk+X$B@rH}JOayz zd)ce$_02xT6@4bnA{zes*+q@TWFfaQ4>C+7L&^|qZN~R3%xG3NT)j8XNLDlB>R(ck z+zhPD&-LiiYjdxWT4^&{X0X5g*`au2@{{_MH3SwA9QjQc!N~BzF>7}DtI*UCwywEQ zb9Ym^Q9*eU2w!~bLWy?!e77~+V5#YRNh-@3@f_nx^QJtV984{6bN0d7Jl5O3`5M9q za4^~TF<)?EBuE2BBR-v;1yz}+(@xqq%Ei{Cq-Qgbp~tha@}}*n%?_pGcmW&>+$t{) zih@nS&;(x6DF8}kOAK4)X!!2`?v*2ugH=chKC7OF&)*&TaYRKWC706|_(+!VRMgQL9sa3oaZK&1nc*=+1J3re1jlN-tSX4IYawahS zJvZoSwv&>+RoP?7b1Q6X&?y2d3?3(zJjZ4nRmkS~4P>HScV-uWf%tTlD4D?205kwq z2znwTb%N(*d=X$WIdS3s>s4Nl2mUHKAbaM4NrO)*(1|8?fik2}x3>Uh(g^mBXtw}1 z-0kJJ1gJRU*6$wA#k7#5Uzsh5qtVkW;Qq|0vq`OKygWNcl`9QI-{8jl{7#_SgW>SRR{TgFSOu|YVGeYV~>aaccRKFZ%UouK);6cR^rgefV zG?$c~mg{O?#3tYJGe}5myi#eq5{N(}MwdyS#&nJhFsa1aSBRl8QC@k(Eys1X zogFokZ!gWb*0Q*js8}`KK6ftgw;9spg%QT$U}Z8^FSSV;*55d*!{`c1X1}pyK2*KE zJW%;Mf^-(-TPq%mzLivmnntU>1WPLHJi8gV;IdLo5x>9gHskXB<7#s8-UxMhX=(%M zO++`>d56F$D7zo4wnnNh7iKuqE&rsqW=->|-mG@KQP5Oc<5GK2E|jd|&Vl^9Hb+L* zrx)GHy7nLH3wIa37Xv+BqyF01Ei#)SRB^MoEOrh{ghmss zG;hAz-UTJfPNv}LI|hoQ&jMHTQCkt0CCjmX<`pIwl`ZDPiWSB;(R|t&sjAwa@g9LR zvbLV>YB5t@6eYw8_}a$}&fi^GaDLXh2>W9;HCBpXgoi%!jQ8SXbo}EZua|^|dh+co~{EvlJ(d-_0OS`+l zJ=GJZB1**BA8B?qkgQhaV(lgmR7n$PuAG`+QhUN@s)qz^!ZLKSG{HL~mbu)I znPhLZpt_1M^qD=MVr@ARE%cZ+Py?lwSDL!qrajnTg46I8V2%#cOiYf`m*JI`N_rKD;oc695+9%CVqv{#i z8IfBwq(5`@b^8CN+%PZ)cN8Ms>n2>eP&jHcx@3FHP}+K+uy4}>8M01 zfo;}2cYv3A``(eEKq>Wubn8WI;TRLJTNf6=*{CFbZQTvg4DuZDjOtzCkxe5Fx;|iC z+ch0!#4?HF%h;sdw`$OTffF2s4|Ono_)w_RS`x|>jSQI*!|@`xu7o3s2F!y-;M9)A zhEOC|F1v3peR(9f?9zcmT$L`A`a=|Za=FR5@THO*b9&Qs!`5LkuR?)~UO~>sPY=t! zB%>#-=Z0evQhhykKWSNJGGRs72Ucb)ATV=Pj6 z5*_(a_7~9sG)h)Q%$1n54o^-;zNT8hJ)_bqSR{NbMO3i+S5=q=T_rM2s z;ENgHB}+x`tquETj5vb@uR4d{=H*S_kF@TL7o;NGRy;=m9OsFr7>Lq5wAp^;&qJIi ziZuI$;ex0!d81clVKk5LKVK9cAxaWu=9exNAduu1O_c%*kw5mwU-HYja(J`AH1L#6 zcqX0=3o;Vp#jYZJ@@1zl_w?B?wa9H95FKN=?{Cjkber8P8!{fiu{+&}Q|YrVZ=nM> zNf)D-8H!^lkoovQ3k4Cw9+0y5pz^GiVc8o5I_dVH9$@{)`=>eYFjkRCd#`@~$qYAa zd=j#S^@2kj2uv=PAb?1OA3B!0z_NYF`sVxRffU~Ur@7hVf~D}EnDh()gTuL0O6B7< z`3`CWXTg5HahD0+Jpa~p)+|{%2sZ*?$_u84>DePtuti8gpqEqA-Qp2P;v+fxg)0jV z&cTt>|Nhu)I-Oe7kPr9>X22w|^*7+h1CVS7el`HHupN~sWCQxepvkpiIFihEsTnp3 zFuG*ve6}gchrs=nY*=X=nC$A>qsVU5dRuR~yeZ&)M`WHBlLM|7yMlwizwiF}L6oD| z^@e1=Z@LOj{qNtjhGc5UGg}OWBqtTI$b_%Ag^rp{GMJOAKd%1H0b|x*qkvhJx+4KX zBTfPUw#4lX6r|)Gu1Sf3t$gVgY<^Nm!c;3J68<<{2K+ z@O2uulFB8Zw`UlVCAOtm3w?ZH7SJ;8Dj>47(j=@=j#aauJbw{*^Z(j~&VZrlzb~qI zI(YBe>%Y%4dBXXKH5vdO?RF6+7Dt!AfhMiR- zK1C{x!xAO68JY7WvGe7@>u~6NRsmONRS`WKTLCBw>4-rC8HArWNnAMb=z(xD4om9~ zME{=8&517j-QC>2Xwkn$?_QWcM$m&0^j5}A-{_5Z1iY)ujHh0f^oeplF&)Ju^S`T<0wk9TUX~#^K>FeUbGxc2;UZQuW zc+uNq`D9P-xA_?{1SJu4UGjF_8O>GN*PpvRj`US_op)5&U(j;jEv+B5Yu--?cq;&g zSWlKH;?Q|Ha6i9)EP^IopGfj-#Bj>Mm1F7efotj$L8jN=-eh{8Yp002+HsqiLN5RK z)%4JMjxXA#=zpGJ_WpOZS*Q?6TN_nJAz-0gw{f;J!O5*#S3=f=eiykKA4#%kJT89NqJd{ix8I3?8YY-Vh_aO4sOH!$H06?VLdRuNSEo%QRUTpE z_m*pw>crm(_b&(0WvGg-$JH$P+~f)`c?s+{Zf3wQkCqz^aA-cg<2ekX^MQpeNbETF zF(>9cB9h?ahE*WRW~pZ_I{b0rcYiO`!pM2c0%k5pAj^thna z2vVZGM6a=m%(TB@C6sft;C=RJ7;hU4Z(HOWh16A_+UVBrY-ce;uVeXrWz)JD+LVAp z9_9SIsTug-?91)AyR@hT?@J;K$E)>3VP>CM(&9Zw3S6`*QSI{zm-i7xuZmthqU8o|5^8@%__r7kR$- z`Aj}p72NB+s>E^Ur%7!~^WX2Hy3G^y6V7L?%XQwIf&5aTY9Wrda(8CQCiufPi~Q~ESdUY3982{I@M**ZdrD<6MSD;qy*g_;`)rvUqZC3mu(rCmLRvV7 zr6*%Wj#|m5{n=WwYt7)gST#uFLLDc((DUM8!TS5I=$|i-oU~m6Ul%O(Ml(56)oU6| z6`i)YpN_I-dc~YFFdnpCES{w<-Le4L;fDkps&|>1i_3`{nYwN%b)(&My;CeKR^)bfnt9i9US`^I_wQyqkjn1Ol%cP; zMnblbzN+ut&6399Vk?!ZjDPQLdHdqQ>xC7klv+1v)0QuFQdA(d#B-Wa%huYJx;eZ4yWM3AE3c#@SGckkuQPMNZXxOo~8Y!i!l*ls}%ZV4X3wi)g{Sz;pvXetyCjReowHn0m(7$;j zUdy1bjMzP{DtTPxvV`D-9gIB+&O{z*w*he~>g}$Q36X3CB3o1+PRu>Z_%4ChFk|=C zy9%93G4^JOnqPRlEm7@W^DoOZ$<0r^SI&>GKK%BX-40>R=!J7eUd+zyNGF|FhaRO^ ztK4Kbc2kJjn#bl14_oFA=)p=LvvL$6BzA+&L!zwpqVKbn<-NdTS&t`^GUUE*! zdht{w8h8{!R{)5c>`cU9LORPYA#Gks<^Xdf>R3I7`TCp-4*?$}7ocG~~bfg{OS=jxE+YEZLRCQf+wS_G5c1@Q>lNmG-K4Hl;J78ilLY7 zVMJ|Ft*qx0+s<(7!{);_E@W_`2dw%NmurnQjpa~UG8Q)Zh;Z9+_#Po+13it&pyl@n zh!kT@LthO^KZ@?(QBa_#d+3%^>9FnsZY>V28-3V|sYR!LZta>%{B31agQ#5t@PhY4 zb_fLY)X3_6ulfy7Ov<0}iLK^Qt`=aeCm7A&b@BQ{6O{|gH+x#y@RingxGn8^b*h7UorN}&y7%uq!BM87S)4ioW7hM32`46 zK;*HM(eEq1CcOL;tw7rsI*B`ox@?fQ?NS~x4B#z48q3SvaH$KV4 z#mJnVtZ0%w3+2M1Kc!yc|ACaG+@k_J`Eo*&7JXa<794&4xadt-E}}rg0?quOHr;t% za}jSUP~PFo(f8~A4bv+Uu5i?{0DV*#mZE{&bK*{!jwfb@{WF%C>BA&+C+6}e?tF<( zYNS}@=`dGVdBKeb%p1hw0u}bOiL3uOT4EPBnSg>p^6!gnfMP7mdRKv?hf*c+G)9n7gZ%mos-X%1WqXZ-ub>wt8(9ki9zzQrd?&EJHACL7YKj6BU zDbAFg)f6zQ9U5w`(;;LmH=vi*@8n(y@9*FIp6w{V^AwZ%xOX-;JXd2>U20$VuYr(L zJlS}@-Y%tMd^vZT^I+cRX&h;=%^|E;v(mD=MI#rYT79H0B8C?>Z(Y{NLN~!pV*9%c z`Bk%au4viU5l^sRllzA{JiGtIWQh4#>IO*# zN?7L^plGfK5uOK)mVWVhp|}Hbx_rkof~dRR6MmPSaT{+(TkTd792PAoW-M?0V5HAk z_MQ-&Xyxb25*ilANgQKM7;!LKqvN*2fm6vQ#ot15q8|(}rv1Ztn z@`MwM>A|@K2+M?mTHz3EwL%T17H*7hukySe3HBFZc42UH0U%ka^V?VQ*Xv%c$Wu3?E&<&61c^td985nX7= ztUqtti#SQ%61ttNp%Svu`1WS!hMZuqD?B-=b#u|Aj}N06t*VE9_j$eQxB=Osr!L2F zfgji2U%7`l^G)f2o8IP~DQF;GGY%ZkR1SUch1qYTatnLJFa(enU(T*(hsWT*+B`Bn6%L$= z4(zSuJOQBPe?KFoQLB@5xwjBCwbmC~eOcj}1)?M=h0nkMUqT6vxwkZN1eChUYcYspU#i=?sBw;u1XUT_oB@CBFRn%(2u9m-wp6 zLPYJXz?n3YV0pufJ?#3jWM3`1-f4zhV>IS7G3h(nCETl~Zr^A4!ShBPRBn<2ch#i5&)nt;#7#AE-4iX(-zKHDBRGeKcvHi*+1Vn`X9 z62)%l;tF=cB>2fR2tX6G$IaHJ{&L;+k4pszeacEhEPzfj9K=npy%Z3JnWaQ#0I4RC zSkoi*_csofYfVzr4g)pH=bHteI~)Qe0~y4=?d@a2`<6^E0*R$OD(?R-m4b+X{bN)KO^M&_KT0>1rDNE_xFs9ei8+u zSZ3s26ah2;e)j*UoCMbYN-wql(Oc89B$(WRbl7K)Vp6YjSMTAOB9i0e`PUPNEwG^U zhzwXD<9#lP>)LNdV6!=@?#jWgF)WF>KDRCHZ*nRPX$WM1Q8F%Y$e%+I{38OiArz~O z_fN+AIr!9s1%xn?q-E5bKh*77A8&R&yoEV!AT^7jxkQjl?%U^^MoJ2W zQ@;F|ZK}UC~n7%1n{ltkrF6uHIg!`SX6DX5RJ~XX?}}=q9dqkE<#lHRT=X*UG zVbW3<6ezM&kUa`;+vttK)Wk(V2G3sv5z?fr0+4%Cn{@XBLW$R^>LOZ++5=V^!Ka(U zYUM2_Z+Y;omA(%5)4TlninDQvWsGe(B_14Gzv#hPp5ZTU|IUTxyT;a7zS{moce$UD z(T#gAN|vzCaW+eXTd?FgCeGsRentv8i!!UtzGuG`HmB%(uN^&%u4M~{A+2M3UDX;(2Hw?1#<_*{Mq8fO z;Nmc!{o*BzR)>ySDrIunng6{GW{T+DJTsgmc5isC z(~U7Dwux4t^NuS&U34j-y>2E=Y}y%H7n2D?*Xyy``ej1OedH1aLF8eK$KSYDC|J3o zzR~QUK9-cAc;`hv__p#F#FgvtZqD1Xl)$WVOl@L`v>LtoUw6@iC^g!jt=bwVv#*9Y zzV$WvGm;sOcb()|fPz+zJZr!w6#I9y{Oj~jXNtO5owO3xJzKUI&Pd1c+X?M!1e^u4 zT~$eFbccTb!<;N^q?V!gOCp|gvVnB*dNOg)XC7#YL3AFk7r%GY#+ab#?|~Yd2~YS{ z(RyvQVOPgbnP0}0ub%VoRu$}&UspobTjWD9x<$dyJBPC5!%k4}5u`K~ROPzuYZr1& z$D|nc1;jF?mD2lnes>~_jnjO@0?T$=rvv5w@ck`4NG$MQp6^fUVeW$?$|230Pm<{z zRz^{&FRh_N(>tL{{Y4;bX1^Q^Jiw(G>c}(F9Gk>$eNiAY3pK;;p$Dn&)G5@Dw_0D+ z7+x6MI}S5q)virHaE=FUu!ovZImZCTNTA@pB32qK8PMvM+5vCd{wBW zSaUp8SO{t2&up;*7wiEwsD?B+hs&f*?_#RjbGSw+QD6N+vN>PURrhW;8J%<+f}QYq zk~DQs`NyEiLrVe*Nsmu;h^N*3(#Dl0>bw=*VDb^mVk%E1Iv%^VdkH%@sEpDKPAlsF zHXWlydKfM_q4HZUp{5v2gNakGKEb@^;r`(?x}1NIq09?!VybL{h1t+f{5Xsr4}lcr zVnmq)1pcXXbR*bAWfc*;WyiM{+cN@Su?c$F>f?yCIDi z!pPDBpjhzVRb6m2D_yQD#7nsJ*2?_aX5T}}iWCnI5vZ1O>9D2Hky6RlknzkYI_ZV^ z+Y||Rr-LQFzSP@d6Tf#Khvo3eNLD)rk_DVSN|-c53)tNBeYIj*CQN5*;P9RC!Yd=R zab;l;lAp{mggn%(@G&wbQs_m*MB;aP!hV%LEedK)PE{~w>g%P=HtxSsQS2tKlbq43 zvhk{0A|?Bv-WJvg>Uo}1~eCn)87VX%X&s6Deq?liv8=5eFaux~M{$i3yV62Q5& z-Zq7tM;_Esf#-BFO$Tk3lrGb~HJeR$=%-WPq!Dwy#t7E8B|f1d#Q7X@_$))ZNpT`9 z?qM-iZx~|El&D++W(0b4-rF-5_Dug!XR-}TSm7;K7n-X&uL`WF`LJB)B&ih1|9VDS zX}q5HxFIr)Gr`mpxA<{!JXY)r5nj3uxz;%8)`m+*cwUAkSRbRR&{3{ilF2X)oUusHm}EN4O52?nv<2UcbsOBNjIucSgvEO66{g*1A)i%??-3oZpouo`AH3*6H1Ha=B&$zEp>DxnoI>B(0Q{$;fUM zU&johuj3)>mPX%j!;vq~dJ^13m|ZNs6xq=eBGQS;RA}mr5K@4BNkxcH4>Mvr0c92Tsb=ctMEnEj8WHHY;g!3{nIONC!ld*udwW^}BO6_=yxi zVm!YKV1{D_%3~IP{Et^X`HQimHsed;BWu_cM(?_Ab9{+>nHKF0tnSnw#Is5ed^D{tjn&& z>wFDBKkixG??BeUYdtriRv;*Q6^`Bhg9<3aOk;(JZu(sc7A(KQOl(&hC5g(ZzuUa6 zXX@NJ-V%vjY8^Mmv{H>NMn4gQ*E1)}zk=)=yi0C9zFT%Ts6jwtk0z5XXc@J;m@r^) z;835<#L(KR(1^_pPrHB7R8q1wV13r46Sdm<3;zLx=9)aYeq5%2MuudyC^g`$H*yWG zi2^?Iryt53~X?r_a z6-MH0!y`?Yomz4G{7&_X4QLaz=xKAcpvyz<-H{jT6p##TWwZE;*)EqwYu?QClm1`m z<80eG)Jv|}URWlxQ~S3+1V zrU>~zV1`#EA+>FQbkdCb`o&`{T0sazJ4O5iwW8R{;&qy%4A(7Q@Twd5jv}stV+pAM&FCvQP1~9jAh2*_tk^P#nldEVZ zg*=G&_1g8DuxBeAV}$+*+2ipd>UL`QP`Z7uox=Q$7Fc_CBwZ#}yqJO8a=?}I@pI#j z;DRS=3x4}yD!7y;-Hxl_!g=f(d21DbngH+N+r-Y*g-{{BJfq+@k*njI_qo{zWazbq z$3f%f$#WZ|&+TzfHOLKMdSf;4v8o1QyWGB=V?e@N2uJj#^fCndFFE$m0IX9{w8Y4% z9X+W#HYmK5h6UbzD_Lg${8KOp@m;sxt~M&}o@IZUv#rm)Ic%2-13fII7}vi(QD<4% z0Cd2Rc^km{g@be2yl*V{kAFNOT65S}_bz%toI7aOxSn9M|Jx#p=Y5vVY;3dY6{tB^ zDbeC5=3TtJ&knQX6zRB@C)_%L3XVA^jHdD~Ht8k?GEtyX1(N_n*8fN6;75MD`39Rp zK6f$Y0d!KFwm3|ENaA$Brqu{*#oz^hH6|(lNe3RU*|9qP2blZXp#1n`(d3m!L##iE z5f&rPD03BRW66TeolQXLX(jT2>&@u;V-s5j5ml+bXEurqd<_SW<&v*|WlY%E)MVqFEhwZ}c~Y|h(jW^H0)0(1^lAKTq5pF#B*lY&^i5g~&Uaa5hvN^% zDvVXAj5KcPYeGip^*A5Vlt$(2G{`8p#N+q9K;kG7OEdg73Ni2 zEA7&W7l5VS&RPSd-VZt@gee+Zu(q0d1^mrEVX}5b zTR$x0$?13PAPiSPYq&lfvFx_(TEN@}D@PdJ$lMh+JqpoYqI2D@mA83J2(UdQ0K=|N zFy76xpQEPdR@k0Jsf_ZCG}SiFhfDicSX4A6+2B4s)aK6-2AV_??A5ak*?rIPNiG0# zcZy*m4Po)B04YXxWzoipSO-+4vgHg?e$W$^qZB5VmiZ9Osw|4)9>H3XrZ$h4`YrpW z;w@<~S4SWp<21|!gmXZ*uYRJD2#B3EYY?U5_)m9)s_vI{1@Vur=1>;*W_{X|_dQOV z@pb67VDSUasVCMun2gGjp+9iz=ytFOD80*z?`xxxB|WY8$U{A^<1X!%N))EJFeW7W z?X=J%N9B?6C1O$_L$fPYgs^a=M?|OjRFO|PxAF)b_3z!4Y!b5n_Ma>MpJ{UPKLz`L zYcoI$?roRN)fC{e6G`|MOgzUOG5C+llvmzv@2W8fpp;@Soz}xpU`%lA`SEJ9qA}-??))2ZRIs zq}(~^CGgK(=hq6-cPa*{Hh~w|)>5icckWcj5L}tv2VUcSP}Fn2bLU|P=I`AeXz|-S zcS<&tWTiAcjCZf`sKsyY<>yM?JFN$#`1|EtShSjxG*S-ABQLH%T;bRKBf`Px64sAD{x@_vxh z@1p;BRE4u(;r8Y%>X}&0h&*M1k^3CK&k})&4cp(nnsN<8N1RT^b&4JcOl5OVib}QF4eM~fne#d3L6PmU zB-#F)P?|LH^Toj9b7YKQlK1EOc~4gRuf0rB4fE3lXlw}&av&Gc7Ms)dgWvhaf1{m7xQ#c^IGjow2fe|`)R4M7ZW3K z^WEeu^mq_*^lRtBYp-Gbsfqzo#RUEODX-PbKfoc?cn}59=^7N9sEKUm^V$EY<#XGa zGHm@?--;fud}>feVRDO}99s&dg(qUcYpaNM+T%Mo=!+fz6>?^|BYR8Z-AQp%2(0iW{RE~jkuT#qohG8g#AF^&x6A28HQY$gH>i7qp-8y2 z4~*TG#$(nVbi51o6BWGYcXO1rj`L*1eER$lNoi0{ak<~>2cN)>Y7sk~VpK2xoinx3 zNuqu=lj-*7#W-_F9*XT_K=sTFzXt%zPTv&P`MuB^d5~ww!{g{ns#th zqmZ<>Lbv64!IjHjxU@-FE2XP1`J=Qzhs1LDi%6p&?_M za$B;4FGX0^k6yNP+feBQ?7LIi1H0NY@5BN z@;IS?r8jo?Miit>rM5sycqGcGoZnkxRTvB^71g zkLqyzp3_Os8DaQwGN%a~D%pQF18p@FD4cU&5Nk&Du}%+*TK9a|wZ~oklJ&T2_vyv; zFW#a2tm_?XSo7s5clbj~!@f^FitvPQkV!G#K9y89!|z-K_X7qIH`;>p9u}VH&7vdU zQTFnX0l@{tc4Hz+PB_V1RB$K^*?fcEhOun#TKKJM?6LPO!hZj8%=#`+$5^&o>I5u% zU6QHVQLfR-gzL*LPT#mBHoec3wp$t}?@DQs_kH%gY!lTKfi-0~>2kSSzutO~+_Tzp zxz~T><4>I^c;ZkxzZRvP*;&*s!=jn!rJiXKVFNoRiz!UKZRH)M?%#h|d5b=A8OVJ< z7TS8rb=`ACTZrW@%H?xN;I*=Q$*K<`9qS@9$)NFA^hcC2kF-DQrV|5-#%A4&Lw|Q6 zIc~fUMXFLf(<(vRL4vjrQc+h(FyJG@IKGu31s^+#m{I;M)=sX$X%y6(F_aSYtUET$ zEPvhu8D*Cb;~{_6vlRZpn=JHj{m}GyRi{yZM;&q~%i#_ug5%66CAYabCJGbX%?R9` z4T@HJJFO4_uYMM$U8WmXA8Z*dSIjlhCohioNV?tCZz-5;w7Q2TeeP4F*wW%uQokq> zsUuxkjvcw}=Feo;3IgJMsyP`5^I&L7oJ9Z1AyD&%nYN6(*0=x!~WUAXAQ%1NBxj;Vk*eUWpMxUFLlFU0N?&Oav-9744jbPQb}KiGPYu4|&H@t-!*P;_HW^PK&7|C{_=J?VAu?M zSJ3Np#|}dA;~pcMUth4!FJbP;Nh5b7wWAP9hoA6Z7F4(Cgcz}dv8D6v_1U^hc#@Od ze2L6M;eQCi0w9mXB(9c-YO1wq`YxFzE|f@>9k%Whkn+_T?`~>DkLq;Gb|PR$PUsl1 zQ(e+fStIwKHBRwR%YqY0@Rm!h=~4|1d+j}$*t z^mz?t2yaTTAKFw;Xs8%EPGj2`&d)R4C#P95w_T~tJvVAgX+ss*Vq%`|`oC1o& zaskBgEf^eMr%;9JLbE#Z8l(|D1S zK4y7l7}s)h4K0k(xqkno8~IsZaT9Dq7v@ zZ__!>w{Ft~bWbWdAIIg^nEPQ9)W&|>prDhMV>S9IV!nTQdmY^`eY5bz; zDyhQxg)}z+hFfP{Cyk;^N{N$BjlQv**n3JV7;?62a+_q{^!r_KM)CWETF?Aup)9%j z_Rmt~MT=zq6}s7^h|{iF27R?Io;8o&ov}Ti?-+G6aG0FRg@Rf9t|$HGINR=pgo;KI zAE(vmAQ1EYN9_aqRKZNgj;TDB%IG!0)>Kgr?hR@3YyUWB!pZ?2U$m$sNQzZoL6w7F z+H53OuDQf;iQr;vB)mF-BWL7O@u);VoVx?s|Mx2agn-Chzioqsz6*xNgs6I*An)Uz z;a(kPi)-=xN#!E;9&OU);(~Z>>do&seTLIBYNk-9Ukn9%9R}n+i$uVwj~rB4e86e;weV8rSv6#2uAjmx~wiZDnFO<>ab{5f|pJWIY^AmWetCL z_8c4jY_T9NL0x|Ha+-{&Q)V9YwHSYXz`xB3nA=qw*85^^bXOO@#zk^K-T z@o&N&xl(g-v@P2+oObs=dOCI;Y@MNIQ$Rd}Zx<;NYh+40KHgkCD1kH?Hixcty}ICS z6zjSwExyXM`DU?!mYxc)m;Ma#Ag64a|<;;DEdxMsoUW zs@SI6*YzIpbsh~#WMNPx#AH3weO_tCp~?Pc&U(b$!%tKcG#M55j(TS`*~GzaLd#Ed-rEW(_WhIQTl>+E^z_jZ+-)Z21_^YRshp)T1#B& z(Qx~!PFW@!IW><@dJ>tOx08(m;5>b=_4cN=hsJB=B9z9nxLN=t5^9wj9P(kIQnxx+ zC4cxHhN_`2hWy}gfCM7Sl(lM)UqCvWzGCQN5!{nS?D}|YvzIyRB`4xQ`ZVn^hytF1 zp>a@?s$WvO(B&L@X~=n!L8W>10&uS9rrw`m85iF?3~2kf{w3Aaf87kV(%TT~^Msu| z*Aigg4X?5p4t{YbWa2}+$Xd_4)Df2193zYL*olE6Dw!@n?&9**ZAfgDy%QVPX>2N# z8tw%%WA_K#CjbPC=7f2#{btC8eX8}8fLQei&^2%BqZXQTr8S6cdJ&g%SXBPJs3Ya=x$qH9?EY>x&6hiswM zU8D`J6xf-caKHZ%aoi_kyxhCoFWjbWJ`inmXy%JT8a4F-!6dQLS<83xeX4g8&mcBKBL`Ammw14o0N(E!oe7sP_RvJvL&Dx(o#!o5fiBadU}Zj;mrn#z0Vt4u`)-oT_JAAi`?$ay`NM?W6jhSBE{62DojD%Tez0ibP`de9gB_ zXJd>KCP~JZC!Ay3t-j6Gr$in!;W!V%S)Sq&K@KDZCJ33#q`iwJ$!+ATf5bjwgzypa z`X8*NL#VWY%}3T zbo9mLmz6%>=t}c9)4egf336ic97e0A4G=37ULdv zhJg!Ffwug}0^NGInv{M{r0t%YUso#K8G?-3IldzO#2 zshDmWn{*Md0N->XY*h_Ll^Li*@Wc{(8$Y{WCJ27?ETK&Z^gaT_m1G%ViOsaiB$=GF z$-QC+#wgEe@aKV1j-N3&rozy9u6tD(GOPfV@{hz(vVA-ZX5cJj=qzU)jM&?Vw;6dY zw2>Y7HMC&SAH@Yb5KA#Exz9n%U8X%8@M6;`Fs)L%Oo}Vz;$pj1QNnMRY%}xBH{KX_ zIeuKaaK++taH;O~CP)GjufL&nd`r%4F6&!I?t=66N9?=E1TLsaaS`JKl1kZe$(d+f zo3)E#b20UfT)7Bw?!&U<`wv(x+tOdlHRn<#C^S=!DiNz_$^*8#PY z>-O36z-*GmwkKBXa1HYU5NUr|jjH;wQf=7eJ26FFZ*3LH9^NO)j{Tk*!I5Q?Ir8^^ zD#oE?lx=H!{>5_HD*N#O?!AdYGym8<+lw+OB14{ zZTM_tLH!%7^gDeim+w=R6m@#kavtQvNO8eH0#mYXscXn)96|B#}Qg}iryj%V(xj}gGskjXu;s9B4c|=PCm;h zr{&I6LFD-Rxg1|Dt3DO+9p|6)p?niKQkTe7#$PeQYbl0+(wur#_foO8-m_R8N(9po zb76T_p|_{T@}<+`n+7Ch(TA9TWBctd&t+_V>e1@vPj80&mV2Dc<+==Jb67-rSZ8ph zigI6Rw39uD60L%}abEa1jIocdrQ0RB&AY8@)s%KHij?X!e}=!ZJW z--48@^|-@Pn!D;G*jUsD12P2QLu|P!D-1^0oZF|pW{E{Azv=>lfOKDi!hmlHW z7L&EhWua@!Sic8JS7Eo&>d(oQff>tRZe!6iE=6Ogdz475=~8dmr>wgeH)D|qWCrFq ziIoQAWmD;?B-0V~a`68+8m5v#p%}nP2l8Z;Rlxw!|1ZYe7{h!OT4NTI6o>l@dk6l! zS%kp08!pnd*ll;($<$AhnC10zygPoZ9|KaDNTzQ%sl^=%5?Ra5_bDMNw`@Yk!!k`c z`X%P3)i*qLQ&m@IfF9T9S4v*lwcd4^)-n`DncTZRZZYMb&3W+k+{MS_Ak}>ygN@aS zhJ5s+C2r37e6P+S#<8DJzu&0u@?07IUWa=*w6bbsZGO82K>DmMN-45ciYrlSxUZH; zv8GA8MBR__s=Csn$oqI>B+H{70-9hNANdV@KO6#|N9>W8m{#lXeJR8R86{YVQH=nR z*k`)x@>r<4omUD$4*Fu+sb~BMi}49z(px&Az*uSkm^Zj8l}%-NeAzQu1vXcexad_E z=WffkmroM-=VRQf>v9Hl6l_=u*D~jI=(#YUM2deidBWk<6YFcF+k5MeOOrCDLn>8O zalWO~nzrwSd~_c`>P%?9zM3lB4i2ifzlh0~jyH?41|82Gk=>S#KR#Zj6%EI=&6C6W zBej+*tCho&7^8(4BIpt;mr4|(sV>5=qXP8hzK5K(+r+x&Tm06#r}&gnKJsTCVvA97 z_+^&37y(0GbzkT!(_jY>OO{l9){!V+`Z!D6g)xtYr6|zA{)zFTdyp&VYrjsiHhUpE z5lO*|+9;FzFnu$kKs&&|3exYIk{OWBz8}!#a@a*ygiGT=$4OG?x{o6Tb`b&2C5z^K zh((N;fEQEP9V8a{sd1MrA^q|apw$nx2Pky742hlP%SDi`UWa3K&pOQ~msYlj%mH9? z`a6jv_CxhS5_QOk&=xJ7evqAqaLaK4=W6&_2O#QIh z^*WulcBXOVt7wmq$-p=|s%d;LvP5GdTJFm}R)59KH6(HCy+=iXeck1=D$MwLIMWoc z2FPCmy$A$55I_Lm<09&*aTMTMhxO?7mGh|qlyr#ZcfWB!q!LNQxCu*Mm5+af`ZQN3#IYt=J!r-Em z0cIgH{2=w0h-v|!xl!r}7#e=LjI8w8M2h2z&N>}#=mgN&Mip!A82)=N7XvsrC+p&9 z-MTVj_|+5R#{q;0X#^KSK+8kV!{#shF>MRj(WBQ4O9Jc>#?ZKg`$1o}dO!g!uVp`R zz5Dw?;-|K-?eco31>$Qpa!+XA#$?-et{;eTFYzsunD}|S%tXY%wrKlNE@!weE%ryj z&?}B969pZHjvI)G*0F7MmJ3zUOKGjnkcWtZ_=;NlQX+!Gl>vG`*MFG!j+KbJ<2cv5 z4Dy|;ZeE~sAt%4zWNn1?zsYZnMotgHlzQCIZO;iVKF5qUOjhTqs|=HEQ9Fv!N|((~ zY)512TAOK(dam4R@er;ByLawF7#I&pNJ9o)1wEPW?iv3@LPsdd8QQ{#t}cFS|Hen= z=M99{^460)lzdsBGQ{R3_M6SxVn6(#YN|Krc{heIa;)v}$1{ED-UkT>`QWD_6Mf9b ztcxd0S}%J1o#+xgj3kGJqIs&DdE%Hx)*u1dT4~KyT+e)Xg1tpC-n%Ci(!V%$f54W&h%S{}B^Reg0d^>Hb^5kN-8Xr9S$9 z^l{4{kG|Qa~sanYGx)a!aNC9}d7&H9HL+W1{12^PW z=->y|kq2y7I;)$8g`Dfd*6%Ny-;vQO81{^k48Y+%R&u2F5uuRY#h5q_rRa~*e^n0f zmzeifv5R^&R;yBYceem7$@#koMoY#CjUoG)n7JSYI@>nSSr%gk# ziTzi2SL>2#1YfzXtgzQjF|5n&n@hJZ23P&^U>ilvm_~>&2wwz1L|cx_$2NOXI8kOl z>Z2YCQYO5NoGhMYk5880YcBX5NY*2af zQCm;xJhBh*Mx0jS6a-Fc$p=!`^B)rPI4*YrospQjt01 zgY+NkET2)poc?-;TluTK)nG>IgKO>{s`zNYv}eqk7w8lLgY z>VunL?#_f>-?1X~p-u1unGnKWm;eihnG42AL4qkpPqu&tDwbHE!?xRWdvj^M--}Ui zI7yeB*HW$5oT(*zy$7>u!#Nn zF#Qj01lKZNs;IleAd%V1)af$~&=ccqOtEa;D%gL({nqaye*S^rY;0E_Ae$Wtpai|o z4-gXSqo~z>=ktiro{OLBQn=I!70suAc9a=&!^&YtYXv}kTTgFqAm&BL{pZLcNE!QT zf_JFXf6+Svg|`rhRFqbC{m5a_7bx?4+wHCH!{4|4I!M<2K>DB87wtb=k-aQ(xow#OJ1TTaF@f0D*Wc=7K7|OPf_v!OFLrG9RrjM{WczEjv z3oY+b0QB#44+0>RO~5%)0&WF$9p*($IU={I(gRk6+vNJ;w69x@x2qdn4ZVe`&5s2u zcsNVsiO<onn3`cp9eZ-NIwVu3ZTfqH`&fxc?oW>cK8Q!a`CcW1R&qc0f&{*`Y zK>A|^Nz9CWFNxhCsrjFx3@7Emz!ok~ndGC*hwblsKR*-p!(dDiK%#SQeX{0}a-ZWbl{X8I=~<xr zJyF)i&--9$$w^54eJW7xXWV#SNMA0eaXRMo=_YH6E`RN(!egKl=P1guhB123z1|HI z&BlHC*ckn1vL(r(VKr%|>u*9Sd;bGZ?RyiIYt?=N0W9AgdgBbtK0xT0VkzjH1P%kr zkb#bT_&@UX1l1gcO9z;tzlJy_1234DC(&BGpAbs$gVq3>#vk4cZIS6l++qCXVoRmh`FaKW_y|} z-v>xDtY1%PO#|oxd+z9T26}d#xPH==WZ;krylCw-PXn*a*k^PIZ`Y{`LcCwqD#fD)Km>=6-Ci}0;?cy@cl zs|whb=`~%A&|beH4Tup(+rZt+F!euq2vC_by8*tZCcK(}aImMJOXan;s+L87l}&C? z$xVF-=E!rszN~Dg>uMl;wvKui!lMcISzVX7g^_-fwF53!mG8wbIfA^-J1&CbQAwm6 zd9nz}{@YY*c(0$}ADcWxWNh^uVB5s6!{o?iW*&Q3fdT zOB72OAI#I-D`erLim(o*RX8i3gMdJ0#2Y0I9j^6i#<93xM6rc}Rp!e*h%81e5X&lWMGo)O3ttmT5qrBISD4 zs zA8kYfrowYEpaMoUBve-Wq~l40)jB_WlNn;oTyBfMSY3vcnphG7H@&#mgl;XDRr$-{ zL{=R)N~@|_G|O7B!lx1Q1i!-wla;FISbTB~oY*)daf*IXb6sOfCf{D?ep$2n*#M6k_D8*#`n&X)(+aC>{UJGO82Fz{-O~9%Ny-FuS$# zSB2OkfG4s5uyxsfHd>DN<@)Oqzx?3Zq8%aZ-wt^bk#%Sl3G!#qAb->v%tKCc1mRy> z|CJi=dxfMeGJrPoN70Chgg*EI9?X)EFmmdBrbqZV3>>lx*hsyXMvh%ivJOl&tKt4n zl$ZQXe~}|FNMyf9ls-$2O>&;2^$O(z*dNw`9@i(6)P%X zfvK0Ia8keD2D3*JI=)75TT(##)1fWX&&Lvt@ro9y`-I}``2!<6T3Cw|A!oper|Ff(()(i~>_Nz`8Z zE3K`N+C(2yiJsUZ9_|&y{kajgW7!R`DuSJfp>Qw!qtA9v4SNpI6rh$CD#wpU%uxok zQG2ox$I7?aESRw3QD-SKBj|PCg?2NuZ!+r5nr!}iCK-Zeny&n<^@YJqQ_FdGr=Y8q87LBdaTam{3W$R zAX0t9@8_eDG&Uh$iIZN3V2%Q9(Hs<-{lut6Wf`gte}EHz_Y_BAI@HYk%ER=N%}88) z%5XR;h(t{h!)AQg(qL>QiV=sCN)y|^MXUt2FJGA=j%S7!0>eHK_S96WeL+o6PFB)W z+czA_4@l_Fg<((89EQlB)51_4CYoBzjzC`4)buWO|$Pf;C87h2Y=#IzI|YPM?Q9B zEsoBCocRdV8K#tV_>Z)*huqV;c)#2qPBRAG9qn{vl_9=h!e%8Cf=#eqPzpGlPq{H^ zdfxM1&X0Ht(H_@mbbP|Y{?2j06`gsepI`Zs-JPa_lAc+0KFUf_WnJHrj{05~>^Wz= zlcSm(m587%v94^o>ss@`TQAE!L#p&_$GvB*_gCcHTR4E&Z!Fam2K%jxA{cSsU8}m{UTTTn6xL-f|A1V8P(d2(slK&U@EsL^C z4KpRg(AA0HyrwYENt9y32*WJn`?Z*~gg_hXX3K#}WbHjiW&8l)Y%P8P9(zpZZ7OLDBhXhOQHp2R~S6( zzzYas^iw|#R*ihh*8D0@-qP$VASKp29j(uVuTUsjw)^SA>AgR^Nfbj8Vce$hx)PX53dD5K$xO zb0g2I4QaxktoK^v*?G6j!xD&V15aXJmCRZDNM$7f$@@BPG62l`YAV2JTq+@=7g<*; z%$tA;z5j?S=&m|}|GoAO9{FWJa2aUDbon9xq&*`@zE{l!WL{nR+3Wu%_}$5QqK7a- z@)tr3b1S2vq{+J=!0a-4iH>dvi&%e zcCj6tm}s;Fw_ZIH7#oQ7@26>37*6YsQAt|>fq!A>e(SBTc+0T*6WA`^c6c7T;c=Yi z!Ywk!z>`EALjuKlrEB@qc8Qr z$JQ@Eg3X8Blu-@ikB1)YD+`m^CRt*?M7`KCfWj|B#M6^PLMtw<2a~`toi~%ThzBw`tK|1 z*2X<#Y9-PM8_pJfGpd;){O&D@4~dU~Hkxk{EzIQpJCoZ&J{gw@a}YTA)Yqr6AHsZT zq0J}Sa1Z7%{MMyia-1fQXLj3XnBKT!s2I^uRsA7v^UFD%7$Ma91l#y&za8fT zIODqQ^yg_3Yo;Yaic_wtu@>_nbGVPOXUhkN6&{m-bI|AaU*F?KuCG&ScK0wErx8;-0yQxpTjqd% z7`@oE8vwZkl{ni3|65ImyAQOF1#I>t<>xbHSY2E^!U9BBVr8B>`~4`|?=L#P#OL<{ zuFU$-e6sqg4#o*XXWU?FNZq{WHjoS(--*8z-(0|t-aNXJk9jDP1rQxx%!rXug=WPP zPzF4s6ljS8vcvXZTR+B@)GNF&q@?a=<;6^H6;JOOrpL zfm9kM%!9ncA6Ug}rC~BMbXL8xq4{eE?=M5V(LTcAJsBgEsxcuXu6fJZ!o?J+Ylfz>LNMiNsTq z_iKj48`_y4h7eMqUQ|mJJjI)K8{jyR@TIWQb`mS(+m02w z;>7~B)hbb$E|N-^M->hc=Hvtu6=H%+s(>jNktO1g0Nm!>1eEZ{oAo@)dzd*fjCt1a z5*w87h|vo8RP+br=aSUpW5+tfNydP=DW@0|w?+5cLoJNB?>rxPDy3K~s6P#?s6n~8 zN+~o(%q9{)rwfe+rD%(tVP!_|jpg3<0)tlZaM)UE_vdkpLv!I>c613084Wcj2fy6| z&91h3|qp!=Mw4ygz_2~2J1 zgn&5EEaiWBQ&gy9;=@l{iGSA-kk<=YE50)jB(*2Jc;f_l4adx%dB5Dmzptc{l@?4u z_IGk?y-OHi@oZW95u7b4$Uw~4O(USv967arVh-(5@H|bl8m9Sf)!g9WY6aYqmrtDm zKjtag{r8ts+mSmu`z9Vs!FshHC*e^`Fp6T?tR(zS71xr7yOku2m|3XV!$i=bBh3=U z(qlM2{DE{%;{uQ~Ouoh627`iBpWO2YFF&9Fg!a=d1R!zP0v+_d-H0fyA3|T<)6YmB z!8bbUw*;7cXia@oB~}%J%4?@uxc1r5pppXHmVv%B$S`boSeO-qN~m4Mo8GdV+rva# z%{0j$!%5*$=otuysa{XbJvgy>aET~};Kv?nKjfxE@K_IK>|89`He9Twk73k@GZN#` zEYs7SX(Ko=JF}GWOeJZ>zU1J8rtO;TNN!RVkUz6FdnN%9a4i8gpfh7+=o1fm5a&E? znQW;Zd-ap|uB`tN;r-pfi8nP|Z=alP@76EOrw{~~a%r5@f-Y;wFmB_`BGuI0If|Cs z>;3f$e?<@di97O{?&ddF7kZDuEGPBOZgZ}a-Ie3iZV^3&iv)hBj8yq%pP3Ga@zHizGXPp7Me}fGd z376L0>_O;hc(mKwJb?UMLn&Lmf=28n4xQ;b(%T87**3CunLb4cc8cUK0u=b?3_;Ps z^UeORz_?lwMk$IQnNf^WrAp5b);u0f*<`BatJ zCp(3xNrCoPB8!ih9~W9Yq8qPuDPgw-qHsZ3#kLppYIa4OQEIvnR!mckcK=f`U&%y% z4zAo=9bGQY^Ly>rDgjW@%Ur2adV&W!Mz~Y#KC@H&CpVa@|E2pxQW{j+M5Z;x7Ukhm zYh$)yq-Yku)%o#FCZUM`Qb0$#%wW9Dp`+mT2^0q0`d?EV+bDcV?jpZgz)31K$exrA z7qqEzKJde%>bINR!7@b;>T*I?&m+h<2c`4ruHWgmt36}C`0xsko7!h}Mp*pnphNHX z)DTQDOX#zMcUXm(BkgxCr4DJB#T%yXrNZ%XO4MGWi^PevYgUC1MA{ggOS>A}Ckw$J}J+ z4X(|h9{<{kQ6sKW^>^uW-Mq?EO-G<_&Wj?8w2QWE@&4^<_79hGNgPV^o)JCv&^>0} zRjc}6p#LAAGkX`L{x`c)D{Smc%7B7JxV2kDfup$oWV-J6v9|@HC^;vP)(eDVRigq1T9`9P}#tfrpJG zGy5VBh>=Zjl&4a`(TA7;qV16VS)O{H66+~OG>e&i|IfRuMZp8DapbZG;Cyn$I|m;v z`c(gF(5S&=xV4v1hOrOHGTZ0^{lm9agWN-t!n}rT0FRN7rUZ+rq-O{y;I)9v=~@k*#v1{9nl(XlO@^!H=sfY6Ct@e6{R%LZ>@4+NwpYTV9dxVI4Cv40N^hajUj2N>MkvM!@B(`2lWO8aKVd3j+A8rHnD+pP&bKKLF;Um3k+p zv68O=GQ5%km80zGtRcV-rbU-SgqMOyFk|6~VmzHci2Tkw*n~h4+m)^2%|HRyvjcNu zRAzW3Lr_;91MU6Fz)XxHD}0nj2#ov90i)kA6I)vMSw~@Z?w1v9!p>_eHxRTKUfqH&yyBTbi*g8xsGejWu5137aTMQVRZpdRq4qm>Nmc>ZY zR&4%zH3T&GjIl*}VkU-<`X>&c-Gi!+>!IISY>DFy{Z49&-|tDbziesN zW?GO2IXFoZ-t@WuEgGlt)SSP~F|Dqee)m`Qacv|yH$wzyo|q}lwg-H|>lh!&~8&JWRg&YLA zEacy9#16lnU@!Rq$Wm|(@F)p2#n{n~TkRj(?vt&w6c2y5B>UgS*XVyR`ej_M0;x*- zDdANsa1m>`UQio^gO_rE&g*h={{>jGKoXC<)pf7@HG&gko9KW4U`8q_`_jmRPbMrB zcLRfw<|%CZ-zY_pfmU(Y4@mLu3)5!Cm0nELhsM65!8|C6wq%(jACBD~A_-<#)}wzKM&6G{ zY0Cqir&&I!%250O$ssk*L7#$0a$6%H2g{1;jh!0Tc_MW$U`ZU%==miy%RxedFWUt1 z%h6@6&q)XpKH!~tOF6`(qrKLeGYKI0Pt=3Gym>qQywrm^&E(GR)$`Ni}o?20<$v@zBs^>th^Ch z3oLr^e$c8d6W$$zN^*Up2}-eD8Ji1VS-pjYE}^;dy$>3*CsUsp^j)ddiI*QycT;TISy6f21* zno%M8*WS{%6E$H)Hh(cwj>R(ff4FgCTU<`;HUds44MU z{Za%nLVfX4JCPK1bqceYe73JKv4a{%uIFX=^Th|v+yE3X)FJo#<@ywO(gscX?##lC zTLo*BtyT|AsFG*HuGHo^kNH-C8ik{f4Tt`03_K9vEuxt6cX8u12jKqH`;}6`+qEqE zBI1yhyV}#3A!Fb{77ohJYrmA##ZEMhjR`;Ehp_NHExh}f0Kdw^x;6vz+!4fVhn;bO zNmb_RjlRylM(4HVZ>u~-oikqL8Xp&)uO>CsWVIR~_8C%O2!v6UQISmZj{_mQXlvl$ z|KaSd!>U@lzHOB*Dd`3Uq>&UBor094bVvzE<64AtNGmDQAqdh)cPc0;Af1cul=Qu( z`;O;+_x-%zaeRNfWxFP8&UuY7&fhtn{kEunJlFK>_YsSP2oH@*d$sM9k)Y~D^yGsy zuQu^@FA0hi`B#VrkhYGB(lzrYI(>UV^Qs}{lCw6Hf-KOfTKZ8DH^F^0^s87LM7tJg%)e{MdCKRT|TN*}kHsIaWt!4n~?{&DpZdT57W?}mSM z?1LRvuXEFE+b$SA+$hKy22cOd(YH$u`+Ec&{a-IZaarN4TZac;v@wEbz3ipWoOuDv z#c?-MeBG?FY3vVFB^QGITKY%(%>@}n8x105W+rY?^riV43v%jSs;Bp2*tC&$X^kW~ zQsqh!Dei_l20Pjb787bIPjnM^F8rz!RALsKD%trK<1I zRKOWB=agXfrDzzVpDO!%PwBaD#ROXi>BeAmtW}JBQ0Y5uT$Kt&o@v+; zK9cPM|7~3JVcPDqHgn1c&lF#bdU*M;(lD>g(B~9glv*(J!3Ila6>MX+ev{VDkZ_McHK@UY#LKDe}o=$Zf z_Tb|p#b+OipdScATcA-f;L^=0rE~qpYm`oFnJ1^;uPYUF&g@v>O0B zC^K~ZE`V@DAd-4qIOzcTK+W}pdHN`NyZ%v<@bT2=5u!0nmuS?a%i3{xL~0ByB{ky# z^NVd1Ipm5Ek+r1gi0@CiU1n^jR?jTqbDB%*b!ouZadCGedIQKbkR9Z_1v{=G@0`as zR_ZML$hCQ7*$7YRWK4qB=*6c?(@{<>`Cz(iJOeUVGCZ$vS#k#K+S9)6bzGG!%v0PW zC6_p@vkuv3k%#R@KRxkZeT~WVOkE#wpm>9~qW?{s?gEh>YV3i(ni#d`T9(o5W8VZ~ zXZ4;#{ZhwF?@^M@z%spw5Yq#gsL5F(9b@a^<1JwpSC*uq|_0@F5>Md8-_@u z#xbeY1;qUYE5?^&pPnKPsK$!Fu4;BX|dAh&SnsYxrxuv zWy6*rTos^|t-kMNsx934*!IdVi#}c}WghJbgH)a}+(u(E=8XmjS@H-*7YBqWRaY@@ zTW1Ut;frK&R?o2FNgxkxmnWvcy*R~sH4~{Y%$!&qs%-ejf!-6{CUbAqbq;jM?9^YScd^L^iZiZe_`W&`AhPR^nI9}O+e(w@ z*qP>ck-TdoNr<&UxoR9rCM*6!#XY>$SuBivmeX)xmP#vTSLTcLvX$0R9wlTrCDauDk*9NeFiq6 z?Jxa_cv9|*&DHt$U+@=Uivh8IXGDx1CV~yV6b_r=x+r;4gH+L9`wk#0&DB2LZ|8Kl zcep{V>dS%qrGX(gG|bIe#f03cL0pWwZ~0er4}sk$g6F*vVf)i(-!})1zI>hxyHDWr zdk_D2>@`rKhV^p)^X5_?^MDhnoE}#khD-?RQM*U|%!rTMvIsPA zhHfo?92}mX<+6EXi}edTBlM|VAtr9ZTcu3>^=hT2z~Wx1;C1p99DO~uD9?yb(N}Qq zDs#7RgK~4ZS3lo>-b=}HCq>mJw+IR0U9r5$u$1n$n`F7edn?zJ&NyOrM=bqO$oqY6 zt&w%C?m@o&f!~({>)8=py(9Rq_5!E4`j6!1h%C~G_c6rLwj{Ld*u>^(w3@l4c1#fNZ*lh6PL0+xWM;1L3fe%HgpXk zXb-|-QfT#C7?~Wu#a3=p?45;k8p{))nkHL|6w7+k8n(~Ovlq#^Fi4Zf=xTp9IOsIs zX-qx7IfUzEvj6R2N%tdwNi4^;y^O~F7)vC>J*Q488{z$sTUyRDQCEyiJ6>a^5@*%g zg~OJJ2WAxX=9+DTb@MUuJ2O(U6RQrBB0=0mds!vyv4SpA))edhN9!LF8`$el6>bm~ zMdv>bm)yq6mKE#d>+F!l^t_o#?{NI_;&rlfq{Qz-i2;UetN2)>mq@ZIi5=Uts+QBH zUF3yoU=Ns4wCBtq*bK6kT1ML8NuhDQEb^VYeGjrF*~2kpHhWR@J41To%jPM`1P@v# znmhNza>t4aU{(4KK{$l6@$tP1xVlL5I=8~js-P+nEmMNcgb!6!w<$=ubk5>Jk}E8P ztFJg@Y&E_XZC|-J4;P#~#GE9XZ6QM6yIH53e(iAHp8h_Mv-&!xYJtun_Qln;^7=1{ z2?3v@hM18lP*4w))^5(IVUdNTXbio~t%4(N&CChSdAl|%ye2;-zFCqlab)IP$|n-Z z8Y7GKv>-M}5bj_v!}Bv~ZxFLAW-M`u=dzs^HR7;5&BD%9_{Bt0*d8oV$eCO<3U^8v zV=4irI8v#p|0~^;EP#qfUq@XwE4QOhbbTh#G{ zLzWE?`ZbQWfq5Ys;_7bP+gaCuZ*Su3r6@sa$>I>8GP|YbR7YE4hcI{~60KZN-@n6t zZ1Cv)8#ZBGywbNdnOC+ePwy)I|^$p3NJz+%pp2onj{Fcug1e+YK=E z%|(jP+Ui@cv`gs)C;9WzPNsyv|Kc`L`t35iwC*)do%-T;8(X={K6z859A?MA=)ItW7zAfwD(v=p8h}C6>_nwpm zTry%bdR7fqhW|%zEsj0xH~ZWjR=T!#D0N2? zOpvBej~mTQkRu$wds}ifn=m#I*HC1QN@HKgVzo>#m12h?D(oP{m?k>-z~ymR;^!b; zY=j(`C~jgKhx%D8^g{dyg5)h<(U{Hv^Dx@-SKmA>aHd+V;*#piX$ zpQB<}DLO*v4dW_sKuhbJ_eyp!fvI=>`*3wun=7Jwoj6tCe}^o+i7EBvXNfmuif#~L zg^Is*NI9P=6-x1_agU$vNl|#N$ngBO*?eL5NI6^{chSnH4W>qD;W*F#CC~xEVd`|B z@f(74x$FFa={x`FZ~)d)5LUCzdf%$^>|dcNGHssl)9QTfY|vZK;`BIUrerbsd-EvM z-1pw^$8Y4X^zRt5ESBcSV4sDhxMqlXdDJhzRUQUNK;mxPt=$a%$D<2}38MJn7Ob1+ z$IRb2c2#fOXGa<|G4JqAH3;rxWncE4iHAEdOSrX9Xz zh3WM^QXK~cK}ev|qxN)^QlME$U~8VZ1@{)yb6E{q{1%FvC|}MlONZpHNM8s;L$;fI z`uBHnauf!6XuG`VC8X&hW4zD4m+1|_r@C3ohfVKN2(Q{M>u~z^sYK=uJ*KDuLt0Cw zZpFk#s6)`# zvS=MlBiED%oBvIg%xf#SsF%=GbiOkZuXIF%8YBaLQrp%#g`182z2u(Z5 z^hdb7oMtp$*1j$Q6MY!0iRZCYossu`*E-<*zX#NM?zod9(ur)RROe`^!89K6^A*a- zyJ?orM^e|hj>8xVuOy!l=_EFg#q0X-(d!#2tS;+EfnGKaer*efE6lv-${n)QU9=TFRPQCoo@IEQ`ES@2*uFSz9MKrKnQ;*x-QrtQJa(CeaInPKGeJ;VXA%}ZbTO8p=hmyi>JadW=gJqb;cOGZPDaQWzI8!yE4Sn*3l z>}$KVaGs1VFui^xGQQ4psJZD(&2nL z-iCMwUyfHHZ0>DP$BVc>w}^=28hGc*v0gtO&u+*xJ}6Mhxv)8T=S?JI8Fv3mqhl42 z`B-xUjJr)!^#g6fiUOQFhlxTmYkvN$=)nute&6{d$N2p8n`Kq}%PSh|KZ$NE6zZJ# zUs21X_jZ2aeLFMsse=VYnV;|mv;1g<$VoBXKs74b(m*DWm|RuzC27dIYL#NlUZD&> z;S(7CJuNLcW6a>^#>?-@_TRIF*C?(bp}7(b`~o}cYeXAgS09tnf>aOlufiTd?JmWa zKl)lci{B@d3Kd=!=X{&49Cdh|;+b3&4pWu@Bl;746$)H(qFjAe^; zgLVSaH@UlLZ`w22$dE|GLfQb46Hc16qP@gD9YT)Y&ytrqO*hnSZ+)<-REINle1iUN^D#Ypj`VSjd#|prLJGuU zSx#^mA(?*Uq$<+}wWj*M#H>6{>;doH%!ixzV5r3goL0Z4;;aMB%*n|ID)4iA&B|`XvSltc>V?PegzOOg!TxA%}Cu?Q{#2t_`$(Q-LbzV3c8S~ z1J~rt>3B54SYZI3){)rHV)|&YvDF*#1oz%ejq@6U!0U}wvxlXMeB{p=Dy;TOFcDu3 zny3?6ZkLNN^hLNZ)yA_h$kwKMZhSOI`;R zcgRVvG%({Bo#0ww$wbwrxd?ZqWJMN5d9-R0={NH0Ep0d*tvr)3qR0oO!3cyoJg>9` zhww=|`*2y>B{5p!)a(OvwfvFA(LrA+7F|tSOFoYvaunvMwoKq5?31^y-&bj2u&du7L9f!o zYRhJKF*)nZ$vqw+^cBHoa*F4_LhDSuS_cx{sp7o%@otq#hE3?|cYdrB#9!91>P5%v-s%Z(0JCo$3VTU-k&8}k@3%De{Apu z7q`r(nmu)%vORg#rU9igj|JP|9v0~4paUnmNCJ6*Ey7PeK`BZ$5B}USe-CIuR4()@ z;ruUo!BucfmKFTrh(K9UY2)Qn_XX5k3U;glk`ohyayYop!hlueb&u?gzbY z`!$?ya_j+p@K552CD)>HJ_R$pE%Qn6o~k(YH$(5>Ix(R~z$D<-4zxbO(x^Y3rr_U)y1Se3KNCDtdfqiz<$b3+ zj;|Bh2YSF|93vrGMABL#qXf~s977E-1wfV2NN)l=m0gOA4K(>!-M%`V z8|WcB|HxP>W+vvWC6UDVcH_eL(plND9g6w1Sp$;*dXV2WeGpD5nzJPNUhAG4WV}|h zJhOg%&D%-r&x8-<8^w>SW3M^T7Cr#ULk@3E%3|Gu(fw7%?-h}LA3ywM3oy+j%+B*K zBV5R?e2(?m6q>4Ve}Nc0g;5}nK3W^4zi|}v4u){4DSfcH#@6&8iuz$i`G=Tux~hXdrEs1JUhrJN*c4* zHyt(BQR6Ki>B(No7#b-6;e}YP3By+O|kAgPzJhuJzFTDeM zf5AJI`LdZ_KtIh2f+?Ca|A@M4s`|P;VeTIXQW>C5Xd0ek<Gc&GDRgTCN}y7wV=HoZI6;&_i_z-*lNzZz1snqO$9N^f}kvR18WFY zy2EdJ)Bv+2?89si-S0MqtgLT|X`s5A7=zf87N?uzd(doW1}Q28HynD`4_96Im z=_8Jooijk1!vk3wLf>H$C@w9>srB0?R;3`Cmq+d{^Mg4j|BLC`>1i ztctA}R#kZBBshTYDBX`M%3OT0BlU9-E^}E6^E2x!gn^gidZ@&unMH}u8i1sxEmu=r zRXu@of>K$h18?QuL@+34f4D*BSIeMqf&nlfeBVUGXWjoe$>bG+ln5$wORB*{_nDOB zT`-9kT?gXMUTKAbkTtMPfv+N|=R4CUesHcl+9)brS<+^phhp4rT_n2C+MdkU6UW4} zaKU=wcfjO3Wn^*E1mHCuD7_J`MA%Nv5Ib|54}^rB|5S2)SnTQ4v~=qex+zj#gX62v z8?mWbCG&6!mw6{YeR ze({Md1t4=5^F*{mPsZ;PIyRqK?fg;-`XwB*7wLqphBknnr9QI@)_U~$2D@XTwtv9( zm&Du;iKLSd_j2zr3N@MEl=}-!(Qb8g@U7IaA=MapIQWI#U&JMz+pdI zDF3K=T(x%#i;7U=guy;6737OJyaJXL(y{2VQ>|IS*P%Kkn6s$11**rp4FL?QI6A_( zHDGKA(5SjV&VM7pYzwyQH^|**UqhZD4atJNDy<11{-A2ndHIu*_I<$Y^#3G+*Em)F zR~}d>9sU;&EJF4#9$1`!9`&*Ije%HDA2O zBgG^sr98C%Wwc8r{cLSbc41PL?#@DrrwCd)9gVd^ehb#;Z#dw=9tOTlOmzzIM-eYS zq$iH&%zWzC&^Ldt9Y)G<&piLBA^#eO;cogl$5XPo=U4&#(k+jS5rTdg8Q=F9d!UUK zR1Ln@tG?-CyxU3&zWKq~Zf`JSH#&c6rit`Cm`7eP&r-Xji_D+bzCQ-_4sN?eXQX3U za*#u#$2ZqSl8tP0glKNtHWvVTi2%_1O>s@sqNy=qiQBmG7je7v`Dj&8nv(uOR!d}A zJe5v!8q<*Mmp?S`Vj`e3-jRyCsdwPum?3bF|3Ps>6 z^pAtRV5rmnu>AioytnyR7q?)dcgjB?-cWUs#q$l%BF6>XJ4bE%X%tI~pN9#B+QCa1 z+!)fQNoAFe*!= zId@YyP>4L2SHMqJtRm+`tPca}%`sTf1E*!(00ZUzK)!^52`a5J+?3i8RZ+{&tA?EH zv_y0*<=%i$?O&&4C4{yk(e4fUsp_{{63FW{L++sePW*OW0He&Z+8))f!VPEVMo)d* zA}0iwVrWFaX>40-=P`48gN$-#(=O5~&{s1h+<%-kGl4}MXl975fAx9gre(Cy_xNtRM#!4yDF z8LNIF?|t7!FMYZ4`Qro2-gvIkFdau4p|=d}cw{g3q_KbU>PrdRffMS(D;1#`{`{`P z2jJLFJ7T`93O4@0hV#Kfm&Kr1XJTS=)2$C`8R-gWc<5Zk@WJ*zQq(}I6Xg!AB($6> z9GY@E_xGbEZf+T*y{>>cSOLIQx6gq@5{9Z5qD2ZsCJ5rt6vDOQVN_uH2%;%vOCMk7L*YUVX1{5se z5DZHOisAc6FRB9qQ^Y}cdj9h$auV{CG zcPll-TjSID&r}dFvISlogs_W`4g4i2mu5V6f8jG%rF**WAFS=Op#yz3|K^De{80ac0uIkpZBMvSl@19p3Nmurotc7K*2GbK^lBOl616F!^=#BHL&wYmM zc2I*0Yo+=j@Bd{q9fC>lRn~)83zklI{cZQziUz=BZx9Lso(J5)%zToG8aXF&s&juf3Cwc%IiZb1^o$?4 zU?~ip9Vkc8B9z_8D&)u9E}S5jKvH-_P(W%-|Cdqk%_*5LtTALJ-n^*FRO%(JYE=~~ zzz!EtpX&D!`^K?%{*V2@#chW4J2ZC?k2{Z{34;9=13#mdZ0BBudXDTZGZ}hJ!u?MK zc(RempeTL{Oh^2EPd(MU!s+Tb ze&Sx-08=Lo@WX{nW(K&*I5%DiE`wYDyN69?dr9@diMm1rX*w?1+j7B=M-(=i{(eqx zKC08r4>`XMBfPz&z0X$eeoaJyJhnN+ZMt38+ubbtwgpmJtG=I1NZ*SA>7MvkOy?<> zzvH8yXosb}wVoxom0MrTAI9OEmQw|(pwOZm`-Mq&v652ej5`u_SGi03@o+Pn1;js* zm5S70?8u!4UG!hcdDo}Qx&ZW?JXK2$=O;sZ*ZUmGeCh7l6q+vpqcm^rl)}cxJ!N3f zIzNQ!w`~PbhcyuS=m)$Y6hBybqf_6^1-)`E7)3J09d(zRY6c$O`A4ni5ZTNo{aaeL z9=D1ZHKGKsUBVLfSw#C^b(||E*%jX=w+F5pT(5CrGR^egzJJJB2>sY8LJ$D8VXF0O zdNKEc3ld~mVd5h=MZtS+&@4Xr^X_Jqfpr4_z~cnYUcFbX6>nIW(iA(kgH{MsG5lrv zU%T_eQhahFuyCX1&x_C@AXXh>?M3iR92^CfXNEHNx#M~LBF8(fI%$x^XHtWzzqEwZ zaB8)6A0^+do*G&H&FHrEFW`6SS2LZ)*eT1E&Kr-)82hg>4@-EvH&9YaR3eESFUuZy zda128e6So!x^gJ`?PqJ0AW=B(X=L_|za#mF!gqXeFWmdXk$@K$O@msvowhlTD_cU| z@_HE44CLF{rZ!sPrj&=8O3KKbUu;V0yEh|X=)r@Z_C$jD24H*1&rN%hf{H$ZL$3yN7oH4*PXi#C({xRwO_5)J8E?2P@n>YbQgUrh@0v6dd9Ww z4yrYQMuA>4=y$9*SHPHxUBIFMWVip-1~mv^uS17z3&a`GAvmYM$KbffB_B=wZl=R- z^XX-;Zu48YM-O}2K>?Qt+4(M~9`>z*5;<{oTo5w(l>{ZMLmTIyG7H8PFH#lW>)jnm zVGG_tMoZzx368!OQ*{p3=Q_|wx=l#>p!OXN7(nBFGKU*z2J1hvJ zHwY-&l)bWL)`wCq8UNyZ5#FMwzo2zj1p)<5X*zPsJ)xnI8=op#Kx+%klV;+9+cYj6 zT-nA(@*5m<~~}beIUu|sQg(K7ae_vJ^L3} zH1q|JWR@guE)lWfFk&JyInv&+*bT`A>h)b3Ipz78^-|-`?rVN{wp8Lm1p@W74hLX& zG`oBTEp&~%$ zVC||e<#qUaZ?N#SE^?gvio>DmEYWsT?8NrmE3UX&g|4w~GG~6)U1DF_rmjG5dUc1v zLcNZq2gtG%P_2K=XhKp)W1`~8gL(mfZwMFgip}@yY3FM%%I_KwhBPK~hv^t3iK_~E zU0(=DAa~w*|I6k$vZwVEJ6{0Fyt-|6?EF~P0BbGv;1Pi}W*L`@FE|_D3J}np}&W_8O;evRc>@uy3GP=9tL{gSe*8Q(@tfr9(wTT;P|k@s{gj@BST> zIL{$o_>){{V$_&%6eYc59Q)z8-{cHB7z$N1-dmikW!9hBVdr$QT%L@#+r13-HB>`5 zZ;x8HHy8_3naZ=<@0DR@E*>9!#6Yir^Id7HqCTb>uxo1eZ`5t#>1RqxfkxBJc= zF9>eLK5Md>n&hd}q(k{AR%jD?waNu|KV~O*oabmlh`k|K;@uu2$yOT@&G1R#2agE7 zTm0wqP*y6CU@*Szp0k+`ON~2!qWB3u8P8y5ZgN(pnzp7-pws?%N2V4X^@H6uWk=Je z#z6mIJMH%GvX>xLcd#t<$sleZAFzYAUE2lH>hFqbGvAI$v+f2JX(?|R*eRmz-pnCa z$s_84lHKeY1#WN)t*y>T?0y-XkO1{3wgF^{_ptEqE@a8sO5lG`dUP-Ema9Dpzg9(_ zfUU$ud?I}Cj2{PU)OXz3w}^-Q&S}zEte1AgxXWptSAzd&3Yd@U%J*FLIQa=3fV|$< zkc#QR*bh2YSD1via;B%;l`w-dYdGIXnhKM0=k`W9eGCz%;55#H&UF$JksSEZjikH- zwMspnedN=gbYyxv2>08vv2L)7{x?Hfv>O;_6;UnvSG`KJ8|HYurR1Y*(WQp;j>CoB z$DDDx=+mwaUdL}63HYOnu4C2kn66NgxJ*+QcGgpGv@r%jTs0q?Zc|S30@{LZ)4Dx1B#Ou&^ru4Ur_$;$JoE}z}QK>!7 zn$-AtoPf@7&v(?$=t?ECwp1x)?tHAK;s*PAp=E8?WJ=aOR-6@VCrkSpZO(n+7oLg`^{Y|*EucRq$iI5(G_z`sAvFJ~i70p4 z?=;7JV&-Ty-vbukxBck?>^?g=qq9x7EWQczc;<26_$MA*{+zvXi(`D z(%snz`3~b9M!x43-Su8*l-PSFx)W{M;0<@(=AGiVAJ%ulLdr^oqUiIn+{?b|-v`{k zhb`a@l(X3Aw|Aa0mk(5O>0xK77QS#_r;p6;h{_74xOQ4OC3Z<~Vk-`UX`#wG6#=v7CxH#*blQD4g}_8e;pj$ZSRJ>8+5 z=Up&6?`yg2Go%~W=;4aH=<<9?&s@g=LZS*NL~)D{lYt6i_TzJV9}*=G(EZPRJ_ z6(k@vA!*6uINNBbX!u57N2D^LH^~{wtGc48;)ZP|8eh{Yy_i{Szx$vtZ3||jou{^z zY+oAOs)Bc6!#SD$auq7*oDl^)uxTWD^rOLo_{-`HC0%7j&x7Z6*!{2a4k}t23kO@b z`--RE^I%UVsnkq*xWfw$^j+C)uIe4bbyhR*GyAWM>$)BF_C>V!2aOfHm*ouCw?>UZ;no;+N*t&2()-*kKo>-c=X=i@Bh_XCtw+lE5cV#hwJp12dR#_+cT z2i6!_BdcBbX0fx$)vO-L!o)xLU>$~0y6k&;x;E>Z??*LPI|?<{Y#U5x7;+h^PIBn3 z3d-;5y(jo-96F{ZjVY$r@(FK9`o^Ikw!a{>k)(=YdJjg+woPysv|LcfWUFkR?c;6; zGUQGg`Idp4?BE9wk~J1^{SD^YRaDZ0vpie>(-5Iu<#q{;WuYl3`roIkf2T{z{<;55 zY|$>rZi(r^Ro7}IUcwH0Sa;7JZ;JcXYtM{RLG-WzAKm@uiO&4qH;3Ug;zir+xtq6g z+97B8**2%;{AE<+;$1GL92rHnb8FizvoC-1yNhE0t-gSZ(fGwlFPAcQXY={5>} zU;%|fTS2ZYT2@(!>0>NX>GsuPA4JW>4L5WzJ}bq#va7ZpAAL?urnU3sim#bA2LeFL z+CBL>P%V(ECU-)Z%Z@m3oT4YUK}``N0#E`6rbpU^2^i4Vp9}KVcSHDWJu6B^zV4gr28Y(n#fA=2XG0^op>!?#~*gQXxL8d3ULUx?tZ}dt=y0$ zJ4+P52Rh-$SlUyeJXDe~j!HG?oh?tu*jzYoYdmmzQ4;9-(bL9zJK;tgSvvnLoyAT( ztX^T+;HhF|WT-j6VUNa%JZ5#1m$d$ghK?nS#L~vM4^Mp+2H#EfQVU#wOl28j+G>52 z5v0YM=$SPiF$qJ$oNZ1hT-yC_-Vecy3}5JV1k?#vhg>d?BrdgCt0Lr&m>^f0+S`fl z`Gw8>Ym#;#tOfl|C1;?m0n>XThG--C+RqX>D&(xJ-APV<7UZA$Y(7Nug7K_90YaKw zHK&s3RuI;bO`XeoAY@J&G@Zucy7jFt)ou6@0`c%wTRA*w2QW6?&Al4U@eI7nhVzE- zyYVqn`CvKF7sZdL5pI#e*)U8dOQ;3TLA^XA*eOrQ+1pzlq(Ysa&P~AC8S*CAER(-4 z(nYySm}Rx#8`viEZa`CHC&{cM1nH0vQK54pPAhwN|L2#uEBhr%vZ40PTxu+u)yJ8& zCe&V_onXW$`xLD1G?0&FrDK}vt+c2w89p3$cm9ry_`kGRu&$vwe@&dEj<=3ad8ti*6EZbH)Mp@;&X;fvR?YiGnsC74?8@Qf&3d^A+vnL z?VKI+Q8Av7err=4n7pO%)h(vt>9iczB=Ln?j9Y)!0UNn%*MsqF1wALz7q%j3A&rse zdR|>*N^o`|0qlfA)xqxllM{=e_Y{N>f(`GBUbow^mcF0cTXwc+d-@ zmS{usu?nL*^N7mpGqB1CaA}5gADPskwUGYuI(kVY=`eJOH3{->AWoC*G!MwZQwSF} zrkp`K>sKg*w!&FD(%b6PE(gH|P;{2lnSn7oQxaEObeo7+o3OLiE^8`A*bFyfa50VE z5H2h^$dALo(}^owiq9S56tx%ZM5rOjFB~#RjPg}x0iO6(xw5|$^$uZ=oqE^)Hv120 zY66~7)!Y+TYD;=<=J)~n!%kLqW5x?&jC(&ib2NHGOc|}}8_;DtA#Vv(aC6XNVrR?E zQg?XvFb~5Yhe-&_?++3@JFsnfaL0pvfwBD!3+@=cx4v5p30Cs) z$)Chfk`{*42Dd(9q$bLC=<(N~eLbbs_><-(Z!c6%4=$~FE z>bTILr;$lZL=>LN3QOzx-yE+PUxj+sDqz3GR=CeG8u(*5lh}HeU`2+oPvfE4Ro6u} zb?=u$>3MjAh%<*gJ}5EN3_o$kNFY38SF%we%i;p*eE ztoc(uIfOhe>3Uum+FPVr8f0RqZ0-UIh_j!)I@iltEL$veNC$@TYUA2>Ng}Z z;e~PL^V(0PdxZpCC0H8rk|JYJqjc^v1<;73blSVxd~RXE6|QNoi(Fz@iy+rlJ`3tv zW7ybE&==sVix<7sKxfZwsC&N6ANb`7I?@w*9;3t@pF2Mc2-Mi=?|WiehKY46WT9NF z?MWwEj+(svv?G)fq6O>c5{L0w@nbinGqlwXD1Cg7sJ z|HrtV#}w-?mlmpn!rwDfe?LJ%(AyyMFTV`@tx*iB?b{~zPJ+!*5MV=C75ivSC}f&^ zKi}8cj6S_Fp9nZyIx0NaUmR{JtY{i(u&pw=QhnyiFwV zmw?4T{f}|}HyM1<6{|{@M!UWNf&bf{ujNLS1Lu9ASG@>M-G>tt_O%&m953caUy6_|cAr6gVxQ~7jDgMNQ)oqUpaQMW(a_Ucv~9FD zb4XQnSWp1I3TfS$2cQZpx#tPQ8mnWhZ}uxQt~hYsn6JGi?ZeHrrkDG-MECTRm4I0u zqZyfKFx-JJkt&MY1ff|eb|qggggTtTsAEJx`y%IO&2r2yh7hH>AV`#-4~cY%gzHwt{rJJ|<9?3L26(Mvvbkul*rst6i21 zzT6^jfLGTTfWeoi&_1Y;SAbSlg|;bsf}U-?wDkEq0=8Ftv(*&9x0oIl3fin(`8WSp zI9Em!z`1?KMBssy{|#j2#ed(EO;x)R&^(o`oqTuz}s>{90$Z3Qarxb zmnF6!>jZSR4E#af%YV_j(8drdJ6)##LhNqdG(QA8alT3;!H@&94eTze?>SN4~#eWQ5-Y!c1sruF|HwI zLd*4L%k4iO%Ix3L#+Opb*=Yp-7u);pzq7rNN!>V~iGz3ka06}X&{(*7Nza7dE);96Q^DQ8#$LXglxnS-Aca=H9_aaGTczvkf}TQ;8U2;892X4>=(!O5#7Bv4b9euR96A{1}3(zVCZv4QL;^ z>%BXUs$sypH!@-nJu1%m*Mm1Rib2QQtB-}S&|=~WDH%oEBBe%Un1S+>4F8HIyAM$>XBMhUP3YmjaW!k#^mgSorJn7G*Lt-i4YkQ7Ebd z_(K7>(sT1Ux|#L2%;vHQtM^ELWRw&jo^pnvy(51X(~yeEB#$ci+!i4|Jp@m?;oG8H zv2u05((?%lsEWARoI_qij25xqmxm)1)~Q2@Svgj?;d_G+K-7phtp5P50p^|q8**HN z)u?eP9xQ8`2Pz2=Rcxmi?Lm8K)Wlxj`4_p%GVWNv{(=AH&a~U8wk}d5C92+#W=plN zK38tdtq0T(>A-NQ?w<@+-WOB4-Uk*vRRsT9rVqW%O#X~n8em|_8!O(;3S4upoALQzi(9lJ(C%y!(OdLHe`iaOB!B4g zECs;w#>VutkHH!k8)zvHsf7D9D>$c`lM2G_5C89+Zd5&I1~WtcYhsoUV23D@7HQEP z`E{|#3FKK;8Zov5W7Qr1Ns5>R9)Y@935au~ZqpF*6npMqh|tXB%l(c6nP3*k5f^o@ z2mx)~IMZ7_1+SkBFK(Q#)8Mg(pvcOe>-{fyu96yTUsk`&REiW;wjEl-168RUZbzI9 zBDKqK#5An^k!HTC3baW{+?eFnBCvBm)-`mNBjpGNL)m3`E)0UP&yt5pu-_(%3xFf` zV`vRme}pR~K;^{eAl{`117e)q*7K59kU=DBw_{?Cw%EUCiaC7;5Yxst2+tD$`H?mL zqum0{?UC%8C9^Gi?KqV-H%rB+Iu0MGzZT~t2}|10qM0;dk#7XYh`J?q!o?*Rn5Eu_ zk-(^er2kJ}XC2n$+rDuHkpUv5BPB#aqzz<_7%h#I)CfhGs7T4^6i`A@!2pJoASvA- zNJ%(4q``@dZWsc;`!T-X_jr%v{qZNq*x0i@_kCU0eV(7wtrkKS9-Qpwp@+o@9UUwW!Q80ZO{czH6Z7zc7{ou2~^6=^d-%lChf=Cf0`$nmU`Nx7NI|?Lk1GS-h_f-K~ zp6MJ88+oklmw4%+wYnZ>pqx(!Vl18|fvQxwIuI;ai4>{gu9wz`75+SIFgcmg-QJU| zEF#zTQVd_O?u)%c1SG;L`Fa(l7d|ld(=!EaqHQ?tooG9%lMZ4ZdV9aDkHN@H|Q#7@xz~uryM* zUINkt!5I*(Br^pu#5yA?b`F+SdsP~Znj6w0jBA)m zPOoQGF@8|nk}cr_)@TgeuN+H77S)zo;>$ef(YCZkJ=KHMeP2I82;>)JqzGrrtvLR{ zj8vBIK-_~qef`L}fM9wfcd^V5CTuI<@T|VG@@YMP-=o9n2v%3JPZXh2q45G;Q`7hQ z=f;7Q_{mL)>A>?p`N^6bnvnwNBvB!wB#odrG}}Jo<=COzAI3Hj);))i)OxcX2H z^;hCMnapT|AhnjdcyCXJ7G~rpu054?Iy1UWZ{am1dmmr%-Bh0>Q!X`{!gC#{t*)IE z>9prJ_a@c5e-4h|0yOESuK`!HlKGBma-3P_kZ?wdfzZ!#tAZDr7;dcvRRi&iHvH#HY-Xm8~TG%u_Uza@oZdr#_Cr7*Qm+KDfVqZDx zl?6tOR5dsSmp}Z_`QI|8-^P~j!b;!P;0wWcER1$%(ZpQ|3obLLMuNK;2AT!Td}!?6 zzNL#=YG+~2i-{^@0JwaxQC?D^Gz2DI;T!?CQn4v z>go#0W`AEJQKX!6dB5Q<%*lIUH}$ukH-hII)11%FGTu7$RR8t{f={BcR7m zkelRk09}M^M@`t=^X;Q+ggU_+le9Od!ppKc>+A+USeFMq z`D_&oSr)*$i@6iMidB#B_FF-5)TgpG9fCHt0zo3pQAJ^oO|P#-7!2@5i}<{I?Qhc= z_@&{;c!-U78UH_8-ed7V$m=WB@k)X>;Xirw2;UcWyxd~Iqv2Px55nT`L6lGx?{WX# zsl`PtGMgJ$h!_bg{u%0-x?ON&oOH__)a=>ygmUgvqKF{WQF5k% zIM~v05r!>X*IVH6K^jnM8azn^3pfUJOS90T<0k;n^|gq$*kcB!r^KB)H!zb5QeXKL zkE5SWJ(CRHdL)2*H9*A<1tp6K+=P4ycf`Z!Yd!8}YQC0CYde?RXVx6)`g5fj+R(n& z(VVqvwx^iPwrz_gtOK%sPdpsow!Jd#r5PT51%PMQ)Dl-0M#7jBnzS^-0`j5RDd))x zEM-MgC>k*{`eXM7#c1^{;1Qc*t_{Auca18$yL?Oq@epes>}pb39f9FeAm$7Ty0#18 z@&1%mx6K3#hl{j^RAH~e*vTe9U)PQcgEm(2c9GJNXHg)8b-uiO^Q%LldjqWt7_9s{ zIrO?2762V`n%t*{gQzSYPq2U&4szT4ni*Ou!@^aSWvS7WSLU({3!J}SWChbgXdt;- z(=7|sy3VFHugrshDf;uC$GX=q9SPg2az62f3or?#K|sTFoTt!M(&XrCz`hu+o|b#n z=}GKpY)fg*0^>!y?rc9`3!{l^;57e*s-JjuBkJ?!3-mqKZki4uza-ue9CM%= z_jOkrPe4p`QB`e9*h&iyqdsSx+a5>(Y+UAxEGE=tR*ArSBb{EScv@5GP%&|o{_CXm zVLY9aVfShN@_iykZ(t-K__eoJj%Lk6`jKzJEI(tGek~dckqC4~Bed1oD1Lp7n#Dlj zo^XaLUZiMa4;h+ejJp0KMGdGA3YZn_!;Ay#n3Ea2DnVFCVJ6{m97s$U<1%f4HngcU ztf}jM40_MPmAU|iQ#h~F2IVXmS!6-Zo>b6CyKm^cx8X`koWdmKdRW6w?C{?Xiv!Z& zb6%doncq9&e#H%%!<7uh?{|4l+0Ieb?}EzGv7G|an|qqBPxn5Ygrs9WdPz=gqW^Au zuH=(x`dQ)T&2h3P&ZhW+57Ey08lr@er(O~4yt71_Yux_o0gU3lb0cPHSWD<0EV@5g zFYF)os$$tbgr1JB^H+^@zB@Mk8C;CMmDY4Zgpt}=7Qt)0mg7jrlY&G8r8vFtUHS4a zGF`18?!i@*=nPT`5B07kTx+I#TpEhK=BoQmf?>c5xTOm6Lp#HC85lmbDMT_-1=9v_ zD>W1}=($LulD7>#p%ltfZ>yQ9A)E`oQX8|*h^38I{44k9oVvRemhV*d2Nb0h5N40;ns$whk+v5;4U}nyWQ^P3^XO zt3^Rpul!EI$6vLX@3hM02;;`AhW;Imn=b}ZI?k^nz6r7KGG5)61#uG6cW)be*2{^) z@^wj(KO9c~)JLcr@;p$-c_;#tOi4F)!B4lSZUD-2f?ueSp1xisV1C=zi`ERCP+NtN z689(dZ+}2n5l1-7m`9lwuWT`|%z83@{<{HMg~FH}qi)AjvwAr^;O@z^e{`6M@+?w( zBs5re77v_u^hI2t!CFuUF!>7Q?Up!I(^*ml`1O&6@E`u9DvRi?G1(d2zl3=SQYLJV zhGAB+Q=4A6;u26Buk=C{*PjNhgU5UwpItjB(K%_oa?8Ozkm8?_8Q5B(V@NPp!vdI5 zOyR-IqJ|S#+71YLnCqf*qr!2Z?du64Euq3+bE zg>Pq1g%Rc=?eL?yxNmnYe9p8>A^X?kJ#TC;PnJhT=C>cE`=4yyUG#smdC4n@-fP%n z)JZ0jZq!f=W$w?p6fMdH4MULds(8!yoX>XME@rrzPSZ;Vd9nInF;8yAD^6xhhy7lG z32AvjXN(+*#|Ex_(>EZ>^3qg8xKn0>PWEAnxN^ogN;Pjox3PEE?&V`x)qLX7?t6!dDBlCLK2dH<}WE zeojmUi+mc0v?5$3*YK&lL|NKnIRP>C3TgI%U=$;4u`7-MKIE3^5v5P}?{{hzX@Wg0 z0rGD^M@+yfeS>p6O6HBU%Plzm=*e90otxcWeyW#XxC!z~+(3sJ*9{CBzu@aHnaSk| zdJk1TUGTR-9fmJOw7LNTYFir>*P3kT{}11lr}|I$ZVQ0#NRe>eUI0 z*_o|z();l|QH!USMzGJD`m^+2+i$S=L(`BEYj04AM2eWEELOkA4@mq-A9UOE zbzQn0!=#17gWmZ={;h=@sbbr^i+rf=(uv*nT- zUr%nmQ`d3`F6(6DJ5zMX@MZ}Y==r_^NNQor^>Sxhqp)L#VO$De?G=6yoh>iSKHqhHek9*Tv-#d?HAmfTeWP=>s5Q%s)KO zcA57x`{#)lUM3DEgsJ_aNTX*yRF3E)WD|httO1_%fiJwwUq))tm`7mlV~`bK6NDCs z3zS5v(dPKsJo0xwhNGI~A#0j&#IfdF#3$^Iw-obLKWl$*wY$dRG1hvXAYJqm#5Le)Y23nHUY3v) z6;)niwLP?7xFrCyA!2|6G|%nB+a2uRS-#5*M>F?s^nRRrZ(GfPqImD)a5bxVq}`&W z3XW$@^W@0$BZpBp&Tvb|gpmELWXFbW<@dN!-UKTsj@t|Z%0_^Y_pE*J~4=-Ih@=cv7C$M?Nyh~u!Jek zWJbB>2KNEi^;7VT!I>u-;*~i^>G-mZ*$f?qL1Rsc8wLw2|G{Q9eF=Z9_2?UK*|Zd8 z4M$N27$wH~z9owAc8c$eJkZ3Ys0XtdFz7|_i#!kPw|O++Ad)F4Q`QgxXOAY&c|mM; z#ReE`a~Bv~M+$AGXqRUhatPgSHqr}_&_#Xp-OP6sziba@%sj}g_o}NL%r3Sh>4+PE z=s66WrNo8ngM%7$muV&)IlAJx>YT>i*3|Idff4uc~-Z5 z6jv+n|3Tt=$F?LKwp@5N_YS8qG|$_sS034`FUqSU2=;CZ%(=+x@b>O#eYB2RBOo2jbV zEY5RFJ2&2;6fzB?q&j$U*W)E{veou2QMtO~P%|k3x%p%^ro1f%^{D=h(x5Bg%Gn2C zSTQFJEbv15qFOOA{WOjm9)}ElImd|P<3f!Kv}i_7ww&Y5;+7N6cJglwa}DR4r&AdF z9#0YtS$oaGN0St25HA^ze!Vvy)a@I}fxG3iQ02PsUX!5x>auKYwDTr#V7hSSY%T`8 ztatecGqTP;Am;50ji=ajASn(=c?WNSnZ_F%Vm#wTVLC@pJK57zwlbv848+~|3(ns%jEs=gKHLNLmWzHu(a`Ob4)bfI&W zjoeh~gw)DmLT<#t!uWp9IW58*$J`0AXHxR5C11ujk@yZ>KhuFx(%ZsGr!%WmXnfdA zJrE4`554L(R+GF}D8ncwt?1HQiB7~ey-LC=Vp=aK|S7I zu`|1uCH%kR5evPbVo?6z%Zjihrf}!Zb2{NYkHfrkzIU4n93zsYpB!1LXG7Tc%bcwe zH8oKpJKUpry)E4ht@Axvf+_)fS950*Zjd2uK+G=$p^ zr-;v1c{DS)_QQn#keg|-_UoY%tO1Dz^==8al7YVFTcsJy*+uYVNOhyTeriB}FkIE- zvRXQEih-xdoYE&I{)x)4iCsd>TsGoM<4)U?g~tN~5UYT>vw|@f128%2qg-sq6=KGB z$F`igG{+QxGw6L#g@bvK0&p5;bQW=+cou48ZkjHgCllnv%(O|*L~c_qM4J%|rEcej zCbVQcAfBAg3AqhB^?admT0?kT)@Hb5i#wce&Xz7&snH;6^3{>Z&gk(?{Q!qObse;b zz|(nS1XnN(1_1+zxOxyR}Hz|@mUVEB|UH-7$*J>FwZ-f?Ite@n06VB~Ka;tO}Qf(oeraau9 ztR2Dn%FOl66h5}Ym#~RHWHgFa#dCD^&wIkg+5nvh$v8%ekhn=`az~68FBw;$$Af+WyZUs&cJseO2ch zx`lV8uH=oa+ISRK5iu7um|~ga-v!#T@he|vI2C0nmN*ubq&Yuqk@0Rw+U=zdk1NKo zw7Ay$rB}U=>c^dpI$fb}?hLIxe|Bp&QgDOS~3M?8Fum3*9j@p|QprHvDKi_RS&VLd$sDki!+ zATjj_iv9E%`D6And3305YDAe=^((?Z28_=^ecah#4H*dk z(wPgBHmWu2kN7>-q%Wc{?CM@|{1u&w>SQevHf`wq#CHnnC+qAeM)ejx4HVd~s3+20 zczFL8FwM$B(XsQ?x@rx3A)J!4NAN>-1(sc^A-?Hf;UQ9X(4)4v}8lWnZn7o4}-=oPhv^DOoAq4jpN`h;VQd~=mmEum(6fGLut%X3LxLeUei%TeNa4S~a-JQJr zzWM%n@6Mci=A1LLXV1QCty%lr7+q}@QX(J`1_lPHnyS((^!x`s%n9(&Z)Fl;JM@GJ zeWjv^Q8NtMK`(IZ6|@vEFzS+s?=5lBYeIKbV<-j&S?_-XbHJ_41_OgpNli)NwXfO1 zqHR3a%NhJ*#6$NYy^l{+zcHPUTCxq>3M1z86mibvuAq=`oT&c)&i;G+ZfK$D1y{q1 z(omJdZx=dJ!rzN4e%xM5PRR~tIoI6SPhlCDdi2+zDt43P9?PEJZWr!O=*U$+-Zz|V z3^4hadi}MG@_M+%zhnuzURirLvv~8gw#~EYpn2YaT<+d|*A2m7UuE#trD?b0?oeYM zM*Pg^pzUmUN?%9zP2lCM{nY6nLG!??MW3n9hGxd3jAB)D9s^93`r4Akx0my7m42I< zrE@+@ftA7|G7Y!uX*w0MR|^fdw|ljfBXX!V|Ko1_N^CNTJpa`go^iZGzc_*BvcvYP zf4kq8f+`J+Z9nfu(3_PV{@AN=%JG^mr!%q$-7ScFR?+;=e{c6{DY(}4$4-gUqSs8d zfr(@BZruR0#+~UII=tcc1y7BpyThM`N9|W;PPG=>mg6;@0@w=dfWB#_LW~|Wuc=)?BHyaDH%-8<-z=+3p!AAbFI)Z^Wb zZ`ToMm|W(#3w!FrgqEQ|({U8Xh=lQ|8~34N9YknKEaDOF3GgZa(cL! z(OEGGF8%7iTf>2i`vN;{NPH;(HFFW%8In}Lk^Z(MAZoPgTf;*1Qp1qg+ut4msE4a} z_DM-MTe)%@2Jg{`3cOfLl1n7+cKV2ps4n>_G^DO2iVEwi#F*Q2V#R;1KMyKq?Q>-} zgqi)f^Mf}aB{|-6yR{vaO*3ZF8%8x9x4S2b1C1jJ_)M?0ApJBuS7@kwJ(Wfm(_a1D zrGAim3iWu^xm!_Lqdss)4Y*qlBiBx8^FJ(kqY8iI%C2B^ZOM1m>DE>3%(N_2PEa?o zINu)Q<1^H`zHvc~sj$3jznFf@_wgSS#f)QVPP*7Zx8ycBEfZSl#(4HdIwAGv z;lzWOqQfGrGMGp_-m+Z%)Do9({AuJ`GTkg5ZQBfUtGo|e>z|uoGaak-Er2f z>I(lh7dIP=On5Exk9G_8IXvSMY5dyvmH#135| zS!9yCSx>7x8{;V2?r#tfI3i=6o91K_6$_)-;2jX0! zrNc1p*^g}r!uS))?0R~op-Jhh1!yJ28o|jeZxh;2_qOT>x&oCS4_ix}N8~JkbHh@5 zRl8_0(3shoxW)n3bUxm`PK&ghg171v3u(4Mhb4I+a#kf%lNn=KaGMBPgYDgwqem{6 zgo7FLV68KPThE-kNx6gwOUp1)F;z`llx@a41A&g~)$&}}G{L3#(@+I| zxUwWM%;2zqM0scuG5|^=NNIM!OVv*fXcS~{syJt{8=19w`K@73j@USXp>z}RGL$BN29ph2QovYVcf_RD3s{L2L66Yg1 zc}^rn$q1V8IR(~|m}xZtTm>soUq4O5m=Ka-zA)+eTxy9k-du!t9)tkjWwYcB88?;r zZoKfY>ZaLKC6|s&XFqwEPH$3gnLAo(l78}>ZTKkMZKwRoKMhO1Kd7k+%2Qys#weld z;T$W?_WWH_7<~5{$(Mz_h69I1Bj;#17S~xf=nHpC+)I9LjAJ(gLAfwG-Q! z0N?;jFw-;`YmBWEM8^@~XEI=X{m;h~`+wgz48<}`t87y?fN^_*uD%;n7U9p(?-_#? zg!=&NW?%yQtQ&TzPhTJ+MDC5)nhM(DZ*zqcQe4sW{7^O4zM;p3%-}%qxr4p;|E}Xh zPLLBW-xt#p`9Oj9CjzWR=}A@VP!gu5mWHRgeY zsm;B^=isB~;=UNC$n+F7d?2;%`gTGltSR`U^arciD*lrcTvi(WOB^ivSiLs{>j$*w zEVqHhHaZ^L`BB{5x)D!9jdaFu1pHJQl$zTfBUO-VxPDY2oZ?!X@ArMe6KNSzFY8g1 zDf(r<_R|(#YqJYti@=~$39Z@y8LDtfmtH{kr(r#<5WR>hMJB^ttA)A7D*2A5g6AT157T={qB)fTh=zGpShsHUAa`NyLO8>gxN`DZ9iJcl zxB+Az#C50HPAa|trj*`%^~%a6)*CMS2qedPxjj9~E1o~-Ad6c#r{^7}5Kp;g^*(-2 zD)Zu(z*J^L2^)r~8~<)sfvNJ(#u|?=*qW5co^z0-r;X4%NJ>2r*ogD`mW*j<`D#E4 zj=`?vkYOI=_ryZ!{ckO--%2ztW*-ll%5jumg7tsjQgA(srUK`v+ap&1XTN_zm`NRDnl&0bG%C?Zr^Tz9r)APFJ2@}tK+eP> zMOI>Yd+yDoNfy-m@8eMp5{wdmYt@AuY{JUr3=(cts&jz-y_Tu2is=3Zh#UO$BDgiT z=jeDoD2tyZ4%I8zy|1{-9s!}tico&CEv@;XLCs&dR9o)hd^c6&nWM>u#tU~i$M+Xs zlINBISj}^g>BLe0e|bwp4)iWl^objLuW_)!ST1(ZpzyvNdTS3&Fl_VBOkLmn9q}z5 zHB$pCzhKg`xzV3t!5uQQaLjC{H-FF;c1oflR<4^f7A(3oKW;ty_IJppe;q%XEVc^^Gr3fZ@%6!f;WpRDD=%QiL_~Ou*mR_6 zl@!WvCN|uRGhTrw!L#2idvBYPUs(ynV%uhaw&Y#Z1@jeE*c9q{2}CPfR@}D)`jvd~ zLzE_)?hg{9b`RE( zrKlpZ@FT$COX1PU&#TjJsiCb&@ChnUn{%#he-*AdL?_XK_7SDLZ_LPq*nDrYF_r>35yEZ*W2NWQM0*1I7V!6}G+-`vClXfmUJ zX%)5~Sor4CFg-Bw4A?D2^&mNe!qhAsxotwM0`ikt3d?ux%EF6@Y`@?-Az+T!V^Ly^ z3SVyET@r-snZYDxqs%7b{6*i149LHYm6U;N8*!=NCA!|-GK$w1?t(qmUsFn$%cLqn z1?FrFO@e#Rp1P6}xP+}#ag8;{ixXh-_TEp_%ILI}&R!Rgy9_WwAQT7t|1P?-Ib;av)(Y|`dv_`Yn^yXw(?$M-n`zE0 zdI;`Z@)2w>M1ee2{YSg(^VPi3EtZWoQ}F$XV=xl{n}d~ms*Qli;|xk6fcN73vOKKE zsQ!S6@N;9?=h)MaeNkP{+Kzip5Y90|cpf>aRBJ9cd#2g;ei+rNYsR2W=4UO-unz)u zz4w@~K1i_;pkEQ(xyTslh;#Ll(HKXCco#eD=deHBn*KZ<+*~cm7T-+y@HwRYEFt5Y zE*NO%tZ;-jYrX(Id84R@i`z<25r4@_gGD+=l8lg=!>kWC{K=x7x1@3xzX(7dFP{S^ z-g-hjl08t(a{MdJ8zWV$fh@ASbG09vdlsq|2Ln=+Cj7yEzKKS1Si@e`wjGZHjey<) zeiZ(_H`6BAK*@R7H0_JB2-OPOTYW4Va$6G>n+qFR2VPhsR2Z|!n=BADfzS2P{LFMM0g*MpU) z9?7V`=}j+rU2gSF?iL6k6;9L6>mWI&q5>Ds&rIsF+M%8->~^< z{bZZ10n7)JcyO_q#sO2UQlGxZ8_1c7KLN)r#CKxGhgn#$m?P%MxucWl#{6&8yYEeY zaS2A{xUs2r#e2H8RS=xkc}(6A!D#ZxgUAs5Nj>jHm0G7>iRNyR#n9QV6np89e+d{Ru2q=4 zUf-IN$vZHt5Mea#qG)JH@NV0ah3pA5ImS5xod16hj{V9DIcWu~J8%R6!*_a{UG09; z(tM!8Yg6j1(%=JB0EFT?|5I9~+}6}j)*YXZ8fZwwqz?O+F)77yVd^usWgEax9@he) zyxC9jCz!&$aVbubZQ!a`G=qKJA)JM$U;I%$KQ1e|f2EbBRvb2s`7Ywi>H5N1cp$6NY=g_61*@n=`Of`k>lu)i@ww0p?v#@2O!s(_Y@lfR5P@VzBMcRd0S`KMe`)( z-P5G7(T+Ispd|W3zQFQc37U}Kb-WEJzVY|!WCt!AN^$-5jKL3@ zKO()QioplD%g>~otaMj)x!_UJgsCe}vtgE_06<5BpdQS0V2u7h24}iFmCZq!Tw53JI`8Q)D3;Ks*P>b}{%FcYw zucX#nDq`;MAf66S@?D0nm>8kOA_F%foi|0s3t?`vqtU}22_12Ej?{q{fpn?KN=nOK z;4!P#Z$~-4jw};8gVI=D32rslSeZh!P3Fw}F*TEYvC2x7%yEKVXvb0FR>*4=|9y>v zBz01XAzj&$mhI2R32N61oqMuf%r73mo^qEh^`JO;f>g;ek+tgA>3J z3jpp3xuHFo3wQYB4~DA>u|ztOp}l}J$Ewzyh@G&Nw&a`;*6>K6lo*gMH_PGbj=wQ3 zO3P|f9u)oAIO?w?iU6xHxn#~jKLVp_86@q!-+2$x#B2PK!di;q9?pvk|Hl@FKmf|*PSU6Vbp$M9le{x%Byh`7drb7-n7S(99dUet`4UF*u0 zyR)vm%Oy^cp|X{8p?Iqr9N+EMz5d9f1g5a7o5kPc7T>sco8b8%Zh&+F-89{_kuLlCr>){r<+ldEvMe_*>%9pGU$hZUpXA+_yPm`_vGo(HWVDc&u@TZeInQNUGEX zw{nL8^MNkN9jAU~>V3j!Y*7_t$8Z}i*YzmQ{09SU=_b#+9XHY+oT)29fyCD8H77=N ziC~dMXA$BZMnPSXh&@5!T37F>e1V{h*1ALGV@qDA^u)MGwi+~fB_h`LqpQ%`0}qNAa&N` zk*47V>Xd7kR`=Mp=9uzY4q-P${Dmc7Xs4<^zBa1vLT&w68q8--FlMzK9kMeIzgD@& zyK>wgo?^zMmH0K3CxtA8Y?(iU9@V7GW1U&G9rA2O$dv|S#sPHT*6bFCV=|*=i9dBl zX4)uBH{Hte0&oiSc^l4x^3`VEdG4e`xls!^YqzbIwZBe+E`BJeG-EeQ? ze7fMrCRlN_T=hqVwSL77Pq^S}bY6R^%-jdxa1Kvc%kfAUeNQ4AYOl%a->i7jvveVr zqaWOi9a{gjI7(hZ-Td+Cp0T{Ea}<-e>RCCH+8aXCn775^9l;zr?B?T4$e6Ya`zf;5 z+{FKWt*BQ0{^NUy3EgC%N*_W@uxv{RS5?_F{Jr4ER8)y*(#j;qd$ko=mmpxT1RTxY zSS#_W`Icy>tQJvrH90Of({q|3f36LdjVYarEFCk5fqq9pa*Ulq- zBRlF1|CP<>%aqLoB^cyMNtVH zMerTWW_`?xz{TWHNz^=GjB5V*bH;l#g)GQ;M96*57#Lb)@QMY89vE)d6|QNy_X>$C z5Uqy|PYjX&WmX!e_pb2MFVRQDU7oBCs(~h+wt?gFD^?CX_)J-MI>Y*$7Of=)(+&5e zz-qaf1{?>sxiN$aq{DwPy>+a_UlQzrc_gG35mD< zx2G94mU`Gwn&-D5xI%jrO*YXgI97+G;@vYg8DZd{nAuX~OajcZI2*rj4MW}&j=FMs zJ@jviXv$(msIK`Dr~|C>0?#?b3oZXP6ZKORT!jo?1Xebm5>Y~$2 zIGjc&hs{{M$U9DRMIDd4XAgHET>iM|`A0yl_jgRFGjaoc&->gp5m_ghP#0M;$$Tau zpLUmnPi2HN{0CdEwY*$*#*6;i0iPPT0b>=N;2gG{d^i@J$4K_^!iQB;Y5+W1kS)t@ z*ueq1^0**~bv5Y2U5gQOc`XQkx@_j~#L~sCkrh9fps-bJ`9>)@l6>&|fE*3bU|Es> zSSAgrvGMn=>gR!KjDa|CY=St(>^4>9du4oGI~WSv;T%_k z-%pT^D$o*^x!#3=@4}9N5q#muQ8t@vzI%v^gzMuSwyms2cXH%5ukcU3)#lnUkF1D3 z+sI@q0L+A|wajQ-?=rowM|I|%&tfP0Yg}p;hjap?b8$5IU~Hr%f>3b4??)x$|3xk9 z0zxKwn#Q;ueq3CE0Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>DB9uu)K~#8N?VSg3 zRoAtK7rlc7k|?4G1VVu5y_hCUZ^0M?j_qLMJnVV#-M3NNQ@TlrLXi zX3m^xkB1W|`|Gd2E}wkziJUlb!nZdxG-=XAjKPw5^XB>XczDtgHWzNpm@$?u#*G^% zUw{2|CglPAX8$|yyd#GXAC}RhNBfRE3Iy)sk3Y8C>C>mBQ>RY8BaZ@wtyr<5@0Uo4 z5+&^Bg!^};N|mgFI0568D_2gUqob`ca0A5!9A|Tb<6MW*1MW!zrCPOWkDSl%xVSi* ztyFMtHe6rIaUN6foII~TC_8%yFM@L65)%`x+f}7X6~9}VGG*lA#f!eP)6uVAKkJ(H z>eWl`-Mc3zPo8`vWnHsoP3y`vZrs?qXQZ~BK=@j>ZY>879+aa;k6NX>bLWmc_0&_c zdGlt8kB_&4xqJ6+sa?Ca{OCtNvW(WUWlJkSTu16WB_+kWjsQ1|I1IMK73<%>zdff# zix!q0sOPO)w`9P80agGxj-Vbobja3)`xY)-SYCbgRm-9rZ_}oYZ39>lpx^@s4z$n3 zy=fcT3C2X=8#HKO??Xa`>wEO*A$<%4 z)>~EpK|(@;6~LEXdP(;0-)~tcSilsQFJFEnZfe!4m5rxxvHtqkzgj^;==SW{BX#T6 zeQ2pM1_I#ni3nq(CN!Bf<$S5Kx-pDxRmE%WWk0B$AQpa1-4`SQyz z?RDIf`jI4=J9n;(A3xqIakFO4>~nte%{SKln=)mJH3$ee?Dy%XpIUZBahf)5>bpbw z{lEI^D_gg_ckkME1l)`3cuwvQv!TSGOP4NI(b)glXP-$@Qj)!ohwvaM1-gCvwxp${ z`F>>v`#By62oQ=iZQ3*|8eT()oDF7Lkkt`$yLB`aWINkqU3-Me>}ty{M~ z638PgYuB!os#UAn^RdP-1k7>a!UfAvxIrirN(H;ssZ&RG?b>DCC$8JKZ=bzZ-6dtd zQ>RYZ-)`*PyVpKHWn8*+$+9Sd4ufGNVC{A5*2(9ee{T0Fs1Nw|+izvYj2X6`C^pwR zvy-^C)B(1{NU7V%xwHZMU@VyXyYIfUjLdVOKs*O-D9?mqdk7DLj)8b7ZlUEj`-6ch z5_s>u_iR!j)IK2;6^rbT5pZ>2G+2%}LzNQu5kW`j5NenRmj*Wo_m2@0xLRy57R;te zi^oA-QW!u1JHkk~bhxjtyz+`Q9R2{?P-j?!`l0krINMwAh2m3hr_my@f-><;9M|Vz zgArj?58*-3EsG$!m4)Bz4>yh-J0|nz&zFS@7s|G6+kAU7fZ*W5;5JR3JlSUDoI;Hi zMtKlYlnH^ujbnq2aD~{`uU{V&BW49y2o`}^Fc_5SbjzH;y&^W7IB}wN6@w9U+8bBT zne-q=A}&OE!OE2@A3hW9#cvPcK}ebMZe`J0~DEAu&K4M6v+I)dUM|MnVFCN6`>?TrTcO9Y|sX1Kc&r z;F`8=+u9BHkN1JX;O1gbNT~Ge+0%33!ibO*2sc9G0sdw+5R{jR>*6o{>IBC*FOV{# z7+FEM0hU|xY6NnlUxK){Z2Em^X}9`pBX&TJM6>4esk#Y4P~K!3>GJo#`(l+C_*6U^K9C*NfsWC3moaSMSqAc;Um4gtkNlZr(!NHVxPQgaKW zqe&IxvLSdLJmeO_J{sGpBq$O=HK}Iyxs@v%z(NljHq0^v9kRhDJv?{_1_gdT5K4-B zMCv#v$pH@@{K1f_@wYBeY3!FrYLPkQ`w^ zE+ZYB<$1_kgndS8sCe_uH?3kYI+;#ScgG^X>2QRBwAY{ioj{?sY}sNvEwS)07Ab6g zYZ{w@maqb&7&H^eU`<>`I%F9tL%$J#IXIVba$HABUOP24%ZIY4qH+$SB2Zq^=P(|I zfs{UmfzDQ#1CVBC+#D>c;CeRq94esorc9<_&>!a<>`Gm@uX|jbhwvb*3Pf*se#|36 z$q+C$Mka6%TtI%K9P~l4F+G6vIwPq$hv^NtiCw#PwF-d3F3`al@Ej!|)vh6`+WW07qdshHFNBQJ4)IHduvVpeS>HP+SEAJGsBk-*JL^ za6Qu>D3iHEC^Ia_z*CYaFe&v$IZ#Li$51w|V^jyjleFDDgb!g=AY7XKoJZsj#0{wVmpV#Nxp zm@ttOC@)IKm^;oRwn1SjlZ`f^^BKluW~?$3>;_|!h@ec^hi7n??;-aQ_B1wvsPTe= z-@y*4QANUS%I-WOCx}B{c;N->!tsAr(=l+7oa}+Z>u=mOvS%=cZp2W*%q2pJQ8wHy zC%B(8?jerhH~V2cXI=PT;~WeC>3uSkS{E1y=JF6;guR9Ha~_dDVC8YeCQO(h|M3Jme1YM1eGG1^DaEsyTsRA&@zpN2GAaS!DyGq7}#qZyu3{NJOEO7yk4_D3Avak%$7@(^k4fQAuqQ zFR!E~+rOoX`=8$6!9z|^B(7ORn7t+C5so6q#AMN{^=vQ6?Ljr zvinPyEG`$WWQ<^_Qn9?$h>P-D-+DD-rESAnvj2FRJ*Qfw3VzQkTe^hQjgOUcmonee ziJFz8rCLk{iH$02H)7TszILO|{n|H@Q|GV9?Yn_H&8b7YDI=$C+c2SulqpqQE?v7R ziSaR}4az-IW&?9hk-~-K`pw*18>7G|5Q;OpN3wKp951_0T#~6v_sPZs=j7DI8?wzP z(aPOtB&I@18P*|D8rP~QN6uY)M3KrEL45n=KDP1K?mXh#E0SC@PJTP8k8Iq1#Jr&F zO~&ifu7PxKkt7@Ugg$W&f%x61-m-r8F}Z96q+IEe^6G&0^3#DGjKWuu0UcAUA{{$> z$qEn(@?2^QS+wraR69aFsB>facFTTgpHf>oH>)elw?6vyhWe(w-@MpU*6cVeS8w^pu=j-JUp;@*jF&Fe|8 zw#l+|Q^taq*})snc991UzLz~m&&tqlO{H;SHCgrDA>UE4ZT7Zl!&7Njrf-r3n~xdx$*dgwX8*tceVrUQeMNrOH$?(<)wW8mX^(dA0}_GAEST&qe&DeUVEMyNO+YhBqWrxS6}<*X(52hOi-!xIx?3u3AZo771O9 zV=9!f%+0%k1+$9+!$3pYCCJrV_hdk;T5`__)3LN`;?8s@#FdxljVsrzc8ruORZQk> z%)Gj2aU<%2*1TT4 zoH%#cDh#e#8^c7yQkzQ`Q}^pPZ_8n0c@b{dX|Z{hWaF+PKrm2L*;4Y_;7;;VpElC1 zMP0+rcWfDp*6#_(K)6IG`J9#CS=SAB4YoGEoTM4o@3%kcEnCd9!kXP%CdnH^yGYO0 z^)2h%z5hTCoJce0SGHxqKp2vLd#;rUC%Ogn{w z0+mGguQ}Uf;^JM>+$hK&hPRUT_0zvBmuO<3Hw@#9>DfSbpS&c$`CHaY-6Cu|51*DH zogZBkp7`R0-fiUD&HLr859i3Fd28j_uFa&0aT)3x;Th7UiTw9x%Zx?-LJpoxlT;%( zYmFN;+gSdchtJ5{6X%&1c+t304W&`-D)OGO+Q0eeOY`!t$dIn-_v>Sh)izf5kCPV2 zuMNADDOEy>7EOOuHRGb?hN<6x2Ej9$I^!j)iA<{`$gHSjUe^!kYe`- zLSJ)F*zpgOzLH;?Yurp33}A~&QDq(qVYxEtOap_w*t@mNU$fJ+*=);#BaNZKFey+X zT++j*F36<0YfStVW7@g3e6f7Hy=LaJZSq2|);8{=-6-$V#hdK0<%Z=j2B8AOGVbq7 zFMfE83;K7yZIE6%!fDYdK0%I$|N17g)-2v`UdFIL0^<@XKqWc>^$ z&RvqlhCPm(xM;_r(-K|2lwUVU(a5kQf=E56vzl1vw`ERK!BlOER=SMDGMW5Bt&(u9 z=coOx>){W$X}mCJIS55bQx2B==IxA3(M=P-sRAK@)S+Ve%&`~O5V+%t0X-i)Zqx=s z!}YfuapMig{c+Fs8}~*xe@Ovu0SjW$)kr8RlqqFhR%b0xR5fHMC~*wRr-ONML(3ZG zb+*rWqd+hPj10?ZdE7gYo0%0fV+gpuv}uF#S;S}lP+%1(H-P_-`!l$6N5Igw;mJ@~3GNvurB(n_bA)M#0T(>Tb zBHqMK>cU}ZNoTh=ahMbB8YRdthIg}!7%J`?>wf<74SOGj^Zqaxb<+*EN`dfBoU__) zpDo>DpUDmCTcdK6RP-fW2;iUX*37zT0LGeZV!Gsn$}+5b`t*9(ri5YJ|D3k;VH*)A zt=xXlDiZZmMy0%JmD0c72Vged1qlfSE-~j_zMjE)xZ{dyG3Di=iPbJTSTPuQ3iKG@ z%3(n@)3bTsaXDs$m;^w#7WEz`4G@s(u~G8+kWSW@CY6k9cIHC*6mVFiih0SToKZ63 z6N2RDOps52J=_HL90RMrJ8V>OV#k_;N_wUOnvybwKm0)JYD${qp)-@^e}BrjJVQ~F$@+3CJWapG0w&YCoW{}JnPr8rp$F@ z+wj?mjjEVn_nRG=r&iz|;BUhMWUK(L0-dS|{ni5~Y{qNPk+asqrPQfrt>i2t&?p4) z$^~P|Q|eSP@lz$)W89P(-)ys)GWuw+fDO${L#a0GK4w{nOjez0F;*$D;B@kFf2=66 zP*tOJ3yqI|=iWWz_MEdBE!@lFM)>NPxT~?L1L@eW*6fnmE7Mo@A<&MADl5x;{X3Kq z8`WpE!ix0eQqO)2cywtK^Y`^ zX#Y@AV8+7Hk@`}%`nK<7&icc${b;%ZkVJU>Y41pzda-i&>@|6%N3tZxM_c9k ze9fUOhx2$S_<(_6UF}!OE(+vmY}~Y_2{F>QMNO$@nCYZpnt7X!%GSeaHt6->fpj?C z5xCtsL4lvnP_%F%xfeqE+JlEs@Wf20^WY&8k!NXa?>j#p3L?V(bq3ZlV7)XJgJ+dZ zmXTt08-BAmAWJ{7n7lu*oGpsyJkCCpPxmp^nnATgB18r|hM!E(ARMa4oQd<}uc zL|A(*Twwh;+Csm|;9jBBL*ycC3WVbP_{TrC%h$3_Gpm5I_#X>{phWA|t+Rr|qJ^x$ z!$R|JVdh%aIn&j5SY~SU=+Sm<1lD!~9A~Ls)?;%nefv1@1q4R$`di?hMGaXVmlb$e z$#lSg0rpD`^oy`c4+8F9<1Hs(;gpdhN7_Xdxi1UPqqMC3metCsj|1z&@f{`Bm-E(> z2oJ*M!i^a-#;)BwZrnJ##H`z*@|*p9VSz6Vpcrnj0uD-pzyg?o1=XF4rnBS}f~E_t zvw9B7>s}Wc%MMs!$KTiMIG^kEJbz%pLzoNJ<9|hCbD6_~pcp8>zVn0C#Dbz=EMv>( zI#}S4)jnC+5GCaMJt!LI5L>|(e8Pm)J2}o*Ah-`~$hUYnhp!l*2$Tr}!D4*phh=!V z7GXyCVITyP=Vw9q=bn4cDlbZiu)>Uiz7d4d^C=eEhPFT%Xa|<)qRy}mE1%MCl!u{U zU2Tkxu0h9#y?9RUF=WV)hxb4k_-X_|srW7s&&54pbr=U@L%pa!E2mOsZnCC_bOgmf z`SqP2l!dYp2EKZLkntO#=l)EMGi%@?`tj7M>GBfRSJUNDRo-sZ(X&zMM^&_TV2uDG)CxzrOQ>a-b|Q z9qtQkgD}Aa>?iKw99RNIV1rqRSzrN}6R?l({2;i_`l6gL0Pz`&0F$A7{-6uX1ODea zTr>VhkzpGaf#-h(ze$u}45%x=X)}y~)2-tF7hil)MvWRJFTeb4p$sV%7!^LF{0qP*w6i`KXHzNV=$cC z*m(#KLNZgh33>gUAHHUdlHl{hMgSHKj4sXL2VmoGbyOD5yKh20oC5GE*kTgZgoA5(&7( zfMf>WA|e(-aj6eX26zq<9+X4fJmfyIH5QJS;T8rra5?;a=Z7Q$899_MufFr+!9%zZ z_Q9l)@SPtI9>Rq@8I6sb775?^@!%o1k>}GHNaK3rvOE+NaV^$ z2Wf4xp>(7M>NLgxaW0*%{y;lXFXw0m_R)E*qca!{K_8p5Zo$CcJ5W$3Qu6urE^-SI zr$8u9PUjKnxH1ILnc?GwQZ{4VvQoaRzz6|4II}Z26GdU}64%l}jBqo*NWTP5=PZgu z{}IQTA4JD4BNN=99t@tPBbAZo3?pK!pBt3NTqD}SIb2DBkwQszeieO2WJ(ci?YO3V zMwHXP70Sqy5hF&}a{P?~r}LNh!D#a*84>5g<;FZB1;RilZC1e2qO^=3bA!Q}SY91h zh9%~I6xcZ*2w+W}!-1UOeCKc*1a6a4P zfRdot3i|jkN-%_i|2a>Ygm!U1D=fx-+A~n6EeRx^m(K2jf{0_F+?YqiOGI%PHGrkl zkp&1jQvv9?=N?#An2cdC43j}Qbi@L}&8*dwloWd&tiUu3%0n4Q%QH*}Wv8xKa+rw` z0l1e~UYL;Qq0fu^(ietsVaZ`1%BSuyBF{=4xsH+Ll!3y)Ah>D_<)M7;2OD#L>d3Ru z9tbZCgm5w^iY^1rXJ9M}0E_u!AleEhgY9V({^z-1KiG{Ku}r7nI763GQ&VldDVxz3 zjG$oS{DA`p+O}dy5$EF}(4H_5&%pd7n3Hq3hI@Mq6b9sU9+3iziRDD--N3zLehxyy z{2@AYbqYB`%QejJVTLH<(HLLH%f|9EbZ5$xDOQjfdq!+Fefo4eUx%@ElmieJEG~mg zxd%$4qp?v!1lm1TkNQ%!4pG9=bKgmmCfR;7+`O#7Jz+=g38T50(hcsz)C%f}fx)dp zQRurv@wh(>&3TL!L~&=#m|>rl;asq#GBoGYp48RpwqXcV2`5aLV2uWbsURR?vv4_` zN2Gv-5CANz69^0fj7x|DkYGSaP#&xeFj z5qhjJLdtnM{*C)_kH^InFdyu}UlbR{MG*smd%{H2LkG$#V2q(dhd$h3U+%BZjk3U4 zC=hk#+1by%@dS8Q;!xfVWy5|jD(3(U4eaV3l0{oHqMTS3<{<_wC=>i5R)G|%NVsvK z%p+3pGEpK}!8sQPK}3k1b9n&1JlCKwSZ`c2Cb;3U_ycYlil${?SrI_WCadP0TZF=> zJBlDt2L`V?!8MepFd^Zq1Vm|Cve zs#UA(eiR<|flX9dP#}~F1>%3;Z_X9U!#%SC$BEJK1Qd^REuR$pAQT87Fa+2tz{F4p zF>w&_jV28=dqp6Q!XePOc(`ByD~=Gc?f^I1w|sMfuQTid!B~`0Cm>6 zQ@Dt3m(2~zB95bNn43g`qo6QQ#AV?SICV9gpwSmEUhMmoK0p=Wz;dc3RbagldIUpX zuG8JXEkrmF76gQIQ3eE>7ma_9)g+$4Y9auz3b7H)g4?A^MPQ7u7R>R{n1gtVx-ol{7y{wN9c1+%ozY6{faTUW2E`?|LXc5%&Af4M>O{On9Kv%_ zch03el$PX#(}i+_cBIbE*|a=6*U{!S5UGe*1p?kY zA`g*{h*lsc@Rz-*5FR28PfQlhQyveIh#yQE+k=O2z*8U(9&!LrfjoG~0Xzlr;33-( Z`F~@U+!4^bp1A-3002ovPDHLkV1g@x40Heh literal 0 HcmV?d00001 diff --git a/com.unity.netcode.gameobjects/Documentation~/images/attachable/SpawnObjectA-B-2.png b/com.unity.netcode.gameobjects/Documentation~/images/attachable/SpawnObjectA-B-2.png new file mode 100644 index 0000000000000000000000000000000000000000..e7a2ca676275accbc4536e89d9dfcf2b565e29e1 GIT binary patch literal 4244 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�CHtC)F6XSPH*3nFM1o|=h5&Z|*o0lJr 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 GqW>?j!f{>z literal 0 HcmV?d00001 diff --git a/com.unity.netcode.gameobjects/Documentation~/images/attachable/WorldItem-Inspector-View-2.png b/com.unity.netcode.gameobjects/Documentation~/images/attachable/WorldItem-Inspector-View-2.png new file mode 100644 index 0000000000000000000000000000000000000000..b16d7c9091a6e0564655fc4dbcedc532ce88a05b GIT binary patch literal 41631 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 b%J$XF=HQ)rw^j6xfScYGW9=$U$C&>Bh&egE literal 0 HcmV?d00001 diff --git a/com.unity.netcode.gameobjects/Documentation~/learn/clientside-interpolation.md b/com.unity.netcode.gameobjects/Documentation~/learn/clientside-interpolation.md index 7781c0657e..d65c358e4d 100644 --- a/com.unity.netcode.gameobjects/Documentation~/learn/clientside-interpolation.md +++ b/com.unity.netcode.gameobjects/Documentation~/learn/clientside-interpolation.md @@ -15,7 +15,7 @@ In addition to reduced responsiveness, this approach can also directly interfere ## With client-side interpolation -When using client-side interpolation, all clients intentionally run slightly behind the server, giving them time to transition smoothly between state updates and conceal the effects of latency from users. To implement client-side interpolation, use the [NetworkTransform](../components/networktransform.md) component. +When using client-side interpolation, all clients intentionally run slightly behind the server, giving them time to transition smoothly between state updates and conceal the effects of latency from users. To implement client-side interpolation, use the [NetworkTransform](../components/helpers/networktransform.md) component. In a standard client-server [topology](../terms-concepts/network-topologies.md), clients are able to render a state that's approximately half the [round-trip time (RTT)](lagandpacketloss.md#round-trip-time-and-pings) behind the server. When using client-side interpolation, a further intentional delay is added so that by the time the client is rendering state _n_, it's already received state _n+1_, which allows the client to always smoothly transition from one state to another. This is effectively increasing latency, but in a consistent, client-universal way that can be used to reduce the negative impacts of unpredictable network jitter on gameplay. diff --git a/com.unity.netcode.gameobjects/Documentation~/learn/dealing-with-latency.md b/com.unity.netcode.gameobjects/Documentation~/learn/dealing-with-latency.md index 6d6c95c9fa..9089eaf2a6 100644 --- a/com.unity.netcode.gameobjects/Documentation~/learn/dealing-with-latency.md +++ b/com.unity.netcode.gameobjects/Documentation~/learn/dealing-with-latency.md @@ -30,7 +30,7 @@ In summary, you want accuracy, security, and responsiveness for your game. The authority is who has the right to make final gameplay decisions over objects. A server authoritative game has all its final gameplay decisions executed by the server. -![The server gets to make the final gameplay decisions](../../images/sequence_diagrams/dealing_with_latency/Example_CharPos_ServerAuthority.png) +![The server gets to make the final gameplay decisions](../images/sequence_diagrams/dealing_with_latency/Example_CharPos_ServerAuthority.png) #### Good for world consistency @@ -58,7 +58,7 @@ An issue with server authority is you're waiting for your server to tell you to In a client authoritative game using Netcode for GameObjects, you still have a server that's used to share world state, but clients will own and impose their reality. -![The client gets to make the final gameplay decisions](../../images/sequence_diagrams/dealing_with_latency/Example_CharPos_ClientAuthority.png) +![The client gets to make the final gameplay decisions](../images/sequence_diagrams/dealing_with_latency/Example_CharPos_ClientAuthority.png) #### Good for reactivity @@ -157,7 +157,7 @@ To do continuous client driven actions, there's a few more considerations to tak - You then need to make sure you don't send RPCs to the server (containing your authoritative state) when no data has changed and do dirty checks. - You'd need to send it on tick or at worst on FixedUpdate. Sending on Update() would spam your connection. -A sample for a [ClientNetworkTransform](../components/networktransform.md#clientnetworktransform) has been created, so you don't have to reimplement this yourself for transform updates. A [sample](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize/tree/main/Basic/ClientDriven) has been created on how to use it. See [movement script](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize/blob/v1.2.1/Basic/ClientDriven/Assets/Scripts/ClientPlayerMove.cs). +A sample for a [ClientNetworkTransform](../components/helpers/networktransform.md#clientnetworktransform) has been created, so you don't have to reimplement this yourself for transform updates. A [sample](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize/tree/main/Basic/ClientDriven) has been created on how to use it. See [movement script](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize/blob/v1.2.1/Basic/ClientDriven/Assets/Scripts/ClientPlayerMove.cs). > [!NOTE] > A rule of thumb here is to ask yourself: "Can the server correct me on this?". If it can, use server authority. @@ -184,7 +184,7 @@ Extrapolation is an attempt to estimate a future game state, without taking into The client will normally assume that a moving object will continue in the same direction. When a new packet is received, the position may be updated. -For Netcode for gameobjects (Netcode), a basic extrapolation implementation has been provided in [`NetworkTransform`](../components/networktransform.md) and is estimated between the time a tick advances in server-side animation and the update of the frame on the client-side. The game object extrapolates the next frame's values based on the ratio. +For Netcode for gameobjects (Netcode), a basic extrapolation implementation has been provided in [`NetworkTransform`](../components/helpers/networktransform.md) and is estimated between the time a tick advances in server-side animation and the update of the frame on the client-side. The game object extrapolates the next frame's values based on the ratio. > [!NOTE] > While Netcode for GameObjects doesn't have a full implementation of client-side prediction and reconciliation, you can build such a system on top of the existing client-side anticipation building-blocks, `AnticipatedNetworkVariable` and `AnticipatedNetworkTransform`. These components allow differentiating between the "authoritative" value and the value that is shown to the players. These components provide most of the information needed to implement prediction, but do require you to implement certain aspects yourself. Because of the complexity inherent in building a full client prediction system, the details of that are left for users, and we recommend only advanced users pursue this option. diff --git a/com.unity.netcode.gameobjects/Documentation~/network-components.md b/com.unity.netcode.gameobjects/Documentation~/network-components.md index 272f6ea8b3..dc93f61356 100644 --- a/com.unity.netcode.gameobjects/Documentation~/network-components.md +++ b/com.unity.netcode.gameobjects/Documentation~/network-components.md @@ -4,10 +4,5 @@ Understand the network components involved in a Netcode for GameObjects project. | **Topic** | **Description** | | :------------------------------ | :------------------------------- | -| **[NetworkObject](basics/networkobject.md)** | A NetworkObject is a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) with a NetworkObject component and at least one [NetworkBehaviour](basics/networkbehaviour.md) component, which enables the GameObject to respond to and interact with netcode. | -| **[NetworkObject parenting](advanced-topics/networkobject-parenting.md)** | Understand how NetworkObjects are parented in Netcode for GameObjects. | -| **[NetworkBehaviour](networkbehaviour-landing.md)**| Understand how to use NetworkBehaviour components in your Netcode for GameObjects project. | -| **[Physics](advanced-topics/physics.md)**| Netcode for GameObjects has a built in approach which allows for server-authoritative physics where the physics simulation only runs on the server. | -| **[NetworkManager](components/networkmanager.md)**| The `NetworkManager` is a required Netcode for GameObjects component that has all of your project's netcode-related settings. Think of it as the central netcode hub for your netcode-enabled project. | -| **[NetworkTransform](components/networktransform.md)**| [NetworkTransform](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.Components.NetworkTransform.html) is a concrete class that inherits from [NetworkBehaviour](basics/networkbehaviour.md) and synchronizes [Transform](https://docs.unity3d.com/Manual/class-Transform.html) properties across the network, ensuring that the position, rotation, and scale of a [GameObject](https://docs.unity3d.com/Manual/working-with-gameobjects.html) are replicated to other clients. | -| **[NetworkAnimator](components/networkanimator.md)**| The `NetworkAnimator` component provides you with a fundamental example of how to synchronize animations during a network session. Animation states are synchronized with players joining an existing network session and any client already connected before the animation state changing. | \ No newline at end of file +| **[Foundational Components](components/foundational/foundationalcomponents.md)** | Learn about the three foundational components: `NetworkObject`, `NetworkBehaviour`, and `NetworkManager`. | +| **[Helper Components](components/Helpers/helpercomponents.md)** | Helper components available to use in your Netcode for GameObjects project. | diff --git a/com.unity.netcode.gameobjects/Documentation~/networkbehaviour-landing.md b/com.unity.netcode.gameobjects/Documentation~/networkbehaviour-landing.md index bf8981fa11..b6432462cf 100644 --- a/com.unity.netcode.gameobjects/Documentation~/networkbehaviour-landing.md +++ b/com.unity.netcode.gameobjects/Documentation~/networkbehaviour-landing.md @@ -1,8 +1,9 @@ -# NetworkBehaviour +# NetworkBehaviours +`NetworkBehaviour` components are the 2nd Understand how to use NetworkBehaviour components in your project. | **Topic** | **Description** | | :------------------------------ | :------------------------------- | -| **[NetworkBehaviour](basics/networkbehaviour.md)** | [`NetworkBehaviour`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html) is an abstract class that derives from [`MonoBehaviour`](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and is primarily used to create unique netcode or game logic. To replicate any netcode-aware properties or send and receive RPCs, a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) must have a [NetworkObject](basics/networkobject.md) component and at least one `NetworkBehaviour` component. | -| **[Synchronize](basics/networkbehaviour-synchronize.md)** | You can use NetworkBehaviours to synchronize settings before, during, and after spawning NetworkObjects. | \ No newline at end of file +| **[NetworkBehaviour](components/foundational/networkbehaviour.md)** | [`NetworkBehaviour`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html) is an abstract class that derives from [`MonoBehaviour`](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and is primarily used to create unique netcode or game logic. To replicate any netcode-aware properties or send and receive RPCs, a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) must have a [NetworkObject](components/foundational/networkobject.md) component and at least one `NetworkBehaviour` component. | +| **[Synchronize](components/foundational/networkbehaviour-synchronize.md)** | You can use NetworkBehaviours to synchronize settings before, during, and after spawning NetworkObjects. | \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/samples.md b/com.unity.netcode.gameobjects/Documentation~/samples.md index b6a91e5e7d..d61850d480 100644 --- a/com.unity.netcode.gameobjects/Documentation~/samples.md +++ b/com.unity.netcode.gameobjects/Documentation~/samples.md @@ -4,5 +4,5 @@ Use the samples in this section to learn how to use Netcode for GameObjects in y | **Topic** | **Description** | | :------------------------------ | :------------------------------- | -| **[NetworkBehaviour](basics/networkbehaviour.md)** | [`NetworkBehaviour`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html) is an abstract class that derives from [`MonoBehaviour`](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and is primarily used to create unique netcode or game logic. To replicate any netcode-aware properties or send and receive RPCs, a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) must have a [NetworkObject](basics/networkobject.md) component and at least one `NetworkBehaviour` component. | -| **[Synchronize](basics/networkbehaviour-synchronize.md)** | You can use NetworkBehaviours to synchronize settings before, during, and after spawning NetworkObjects. | \ No newline at end of file +| **[NetworkBehaviour](components/foundational/networkbehaviour.md)** | [`NetworkBehaviour`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html) is an abstract class that derives from [`MonoBehaviour`](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and is primarily used to create unique netcode or game logic. To replicate any netcode-aware properties or send and receive RPCs, a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) must have a [NetworkObject](components/foundational/networkobject.md) component and at least one `NetworkBehaviour` component. | +| **[Synchronize](components/foundational/networkbehaviour-synchronize.md)** | You can use NetworkBehaviours to synchronize settings before, during, and after spawning NetworkObjects. | \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-clientdriven.md b/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-clientdriven.md index f31a3dc137..aa9445c02d 100644 --- a/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-clientdriven.md +++ b/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-clientdriven.md @@ -6,7 +6,7 @@ Making movements feel responsive while staying consistent over multiple game exe ## TLDR: -- Use [ClientNetworkTransform](../../components/networktransform.md) to sync client authoritative transform updates to the server and other clients. +- Use [ClientNetworkTransform](../../components/helpers/networktransform.md) to sync client authoritative transform updates to the server and other clients. - Make sure ownership is set properly on that NetworkObject to be able to move it. - Since your clients live on different timelines (one per player), you have to be careful about who takes decisions and keep some of those decisions centralized on the server. - DON'T FORGET to test with latency, as your game will behave differently depending on whether client or server make decisions. diff --git a/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-invaders.md b/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-invaders.md index c000aeac23..d4f6cb8d40 100644 --- a/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-invaders.md +++ b/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-invaders.md @@ -86,7 +86,7 @@ https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize/blo ## Unconventional Networked Movement -Invaders has an easy movement type - moving only on one (horizontal) axis - which allows you to only modify the transform client-side without waiting for server-side validation. You can find where we perform the move logic in *[PlayerControl.cs](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize/blob/v1.2.1/Basic/Invaders/Assets/Scripts/PlayerControl.cs)* in the `InGameUpdate` function . With the help of a [`NetworkTransform`](../../components/networktransform.md) that is attached directly to the Player Game Object, it will automatically sync up the Transform with the other clients. At the same time, it will smooth out the movement by interpolating or extrapolating for all of them. +Invaders has an easy movement type - moving only on one (horizontal) axis - which allows you to only modify the transform client-side without waiting for server-side validation. You can find where we perform the move logic in *[PlayerControl.cs](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize/blob/v1.2.1/Basic/Invaders/Assets/Scripts/PlayerControl.cs)* in the `InGameUpdate` function . With the help of a [`NetworkTransform`](../../components/helpers/networktransform.md) that is attached directly to the Player Game Object, it will automatically sync up the Transform with the other clients. At the same time, it will smooth out the movement by interpolating or extrapolating for all of them. ```csharp reference https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize/blob/v1.2.1/Basic/Invaders/Assets/Scripts/PlayerControl.cs#L176-L193 diff --git a/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/networkobject-parenting.md b/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/networkobject-parenting.md index b6c40567b1..5ab881e6c7 100644 --- a/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/networkobject-parenting.md +++ b/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/networkobject-parenting.md @@ -17,4 +17,4 @@ To accommodate the limitation highlighted at the beginning of this document, Bos A special hand bone has been added to our Character's avatar. Upon a character's successful parenting attempt, this special bone is set as the `PickUpPot`'s `PositonConstraint` target. So while the `PickUpPot` is technically parented to a player, the `PositionConstraint` component allows the `PickUpPot` to follow a bone's position, presenting the **illusion** that the `PickUpPot` is parented to the player's hand bone itself. -Once the `PickUpPot` is parented, local space simulation is enabled on its [`NetworkTransform` component](../../components/networktransform.md). +Once the `PickUpPot` is parented, local space simulation is enabled on its [`NetworkTransform` component](../../components/helpers/networktransform.md). diff --git a/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/optimizing-bossroom.md b/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/optimizing-bossroom.md index d0dc30537c..2753803ce4 100644 --- a/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/optimizing-bossroom.md +++ b/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/optimizing-bossroom.md @@ -37,7 +37,7 @@ For more examples, see [RPCs vs. NetworkVariables Examples](../../learn/rpcnetva ## NetworkTransform configuration {#networktransform-configuration} -The NetworkTransform component handles the synchronization of a NetworkObject's Transform. By default, the NetworkTransform component synchronizes every part of the transform at every tick if a change bigger than the specified [threshold](../../components/networktransform.md) occurs. However, you can configure it to only synchronize the necessary data by omitting particular axeis of the position, rotation, or scale vectors. See [Restricting synchronization](../../components/networktransform.md). +The NetworkTransform component handles the synchronization of a NetworkObject's Transform. By default, the NetworkTransform component synchronizes every part of the transform at every tick if a change bigger than the specified [threshold](../../components/helpers/networktransform.md) occurs. However, you can configure it to only synchronize the necessary data by omitting particular axeis of the position, rotation, or scale vectors. See [Restricting synchronization](../../components/helpers/networktransform.md). You can also increase the thresholds to reduce the frequency of updates if you don't mind reducing the accuracy and responsiveness of the replicated Transform. @@ -47,7 +47,7 @@ Since the characters evolve on a plane, we only synchronize their position's x a Additionally, with the changes introduced in [Netcode for GameObjects v1.4.0](https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/releases/tag/ngo%2F1.4.0), we were able to further reduce the bandwidth cost associated for some prefabs that utilized NetworkTransform. The synchronization payload was reduced by 5 bytes for the Character and the Arrow prefab inside Boss Room, for example, by enabling "Use Half Float Precision" on their respective NetworkTransforms. -See [NetworkTransform](../../components/networktransform.md) for more information on the NetworkTransform component. +See [NetworkTransform](../../components/helpers/networktransform.md) for more information on the NetworkTransform component. ## Pooling {#pooling} @@ -103,7 +103,7 @@ As a result, the maximum number of reliable packets sent or received in a single ## NetworkManager tick rate configuration {#networkmanager-tick-rate-configuration} -Netcode's [NetworkManager](../../components/networkmanager.md) provides some configuration options, one of which is the [tick rate](../../advanced-topics/networktime-ticks.md). The tick rate configuration option determines the frequency at which network ticks occur. The ideal tick rate value relies on balancing smoothness, accuracy, and bandwidth usage. +Netcode's [NetworkManager](../../components/foundational/networkmanager.md) provides some configuration options, one of which is the [tick rate](../../advanced-topics/networktime-ticks.md). The tick rate configuration option determines the frequency at which network ticks occur. The ideal tick rate value relies on balancing smoothness, accuracy, and bandwidth usage. Lowering the tick rate reduces the frequency of NetworkVariable update messages (because they're sent at each tick). However, since it reduces the frequency of updates, it also reduces the smoothness of gameplay for the clients. You can reduce the impact of lower tick rates by using interpolation to provide smoothness, such as in the NetworkTransform. However, because there are fewer updates, the interpolation will be less accurate because it has less information. diff --git a/com.unity.netcode.gameobjects/Documentation~/tutorials/get-started-with-ngo.md b/com.unity.netcode.gameobjects/Documentation~/tutorials/get-started-with-ngo.md index 3510332f7f..d4b8e87da4 100644 --- a/com.unity.netcode.gameobjects/Documentation~/tutorials/get-started-with-ngo.md +++ b/com.unity.netcode.gameobjects/Documentation~/tutorials/get-started-with-ngo.md @@ -68,7 +68,7 @@ First, create the NetworkManager component: ### Create an object to spawn for each connected player > [!NOTE] -> When you drop the prefab into the **PlayerPrefab** slot, you're telling the library that when a client connects to the game, it automatically spawns this prefab as the character for the connecting client. Netcode for GameObjects won't spawn a player object if you don't have any prefab set as the **PlayerPrefab**. Refer to [Player Objects](../basics/networkobject.md#finding-playerobjects). +> When you drop the prefab into the **PlayerPrefab** slot, you're telling the library that when a client connects to the game, it automatically spawns this prefab as the character for the connecting client. Netcode for GameObjects won't spawn a player object if you don't have any prefab set as the **PlayerPrefab**. Refer to [Player Objects](../components/foundational/networkobject.md#finding-playerobjects). This section guides you through creating an object that spawns for each connected player. diff --git a/com.unity.netcode.gameobjects/Documentation~/tutorials/helloworld.md b/com.unity.netcode.gameobjects/Documentation~/tutorials/helloworld.md index 69289e04bf..1fb4582658 100644 --- a/com.unity.netcode.gameobjects/Documentation~/tutorials/helloworld.md +++ b/com.unity.netcode.gameobjects/Documentation~/tutorials/helloworld.md @@ -22,7 +22,7 @@ In this section we will create the basic building blocks of a multiplayer game. ### Creating Network Manager and selecting the Transport -In this section we add a Network Manager and add Unity Transport to our project. The [NetworkManager](../components/networkmanager.md) is the component that has all your project's netcode-related settings. Unity Transport is the transport layer that Netcode uses for communication between the server and the clients. See [here](../advanced-topics/transports.md) for more. +In this section we add a Network Manager and add Unity Transport to our project. The [NetworkManager](../components/foundational/networkmanager.md) is the component that has all your project's netcode-related settings. Unity Transport is the transport layer that Netcode uses for communication between the server and the clients. See [here](../advanced-topics/transports.md) for more. 1. Right-click in the **Hierarchy** tab of the main Unity Window. 1. Select **Create Empty**. diff --git a/com.unity.netcode.gameobjects/Documentation~/tutorials/testing/testing_with_artificial_conditions.md b/com.unity.netcode.gameobjects/Documentation~/tutorials/testing/testing_with_artificial_conditions.md index 019f6e7b84..d5972ebe87 100644 --- a/com.unity.netcode.gameobjects/Documentation~/tutorials/testing/testing_with_artificial_conditions.md +++ b/com.unity.netcode.gameobjects/Documentation~/tutorials/testing/testing_with_artificial_conditions.md @@ -133,7 +133,7 @@ Apple's Network Link Conditioner can be downloaded from the [Additional Tools fo Download the version that's appropriate for your XCode version and then run the .dmg file. Navigate to the `Hardware` folder and install the Network Link Conditioner panel. After that you will be able to find Network Link Conditioner in the System Preferences panel of your Mac: -![nlc-in-system-preferences](../images/nlc-in-system-preferences.png) +![nlc-in-system-preferences](../../images/nlc-in-system-preferences.png) To test the builds with Network Link Conditioner: - Run Network Link Conditioner From cc04caf60b918407d274c3f7fcc5bf9ba53aa799 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 30 Jul 2025 00:53:54 -0500 Subject: [PATCH 30/43] docs (style-PVP) Removing white space at the end of lines. --- .../Documentation~/TableOfContents.md | 4 ++-- .../components/Helpers/attachablebehaviour.md | 15 +++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md b/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md index 9759fa90a7..f9c0d49b26 100644 --- a/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md +++ b/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md @@ -18,10 +18,10 @@ * [Max players](basics/maxnumberplayers.md) * [Transports](advanced-topics/transports.md) * [Relay](relay/relay.md) -* [Network components](network-components.md) +* [Network components](network-components.md) * [Foundational Components](components/foundational/foundationalcomponents.md) * [NetworkObject](components/foundational/networkobject.md) - * [NetworkObject parenting](advanced-topics/networkobject-parenting.md) + * [NetworkObject parenting](advanced-topics/networkobject-parenting.md) * [NetworkBehaviour](components/foundational/networkbehaviour.md) * [Synchronizing & Order of Operations](components/foundational/networkbehaviour-synchronize.md) * [NetworkManager](components/foundational/networkmanager.md) diff --git a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablebehaviour.md b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablebehaviour.md index 2bbc29c4a5..794aced829 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablebehaviour.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablebehaviour.md @@ -27,7 +27,7 @@ With attaching, a user would create nested `GameObject` children that represent By placing an `AttachableBehaviour` component on the NestedChild-PickedUp `GameObject` and an `AttachableNode` component on the TargetNode, a user can then invoke the `AttachableBehaviour.Attach` method while passing in the `AttachableNode` component and the NestedChild-PickedUp `GameObject` will get parented under the TargetNode while also synchronizing this action with all other clients.
![alt text](../../images/attachable/PlayerAndWorldItem-2.png) -### AttachableBehaviour +### AttachableBehaviour ![alt text](../../images/attachable/AttachableBehaviour_InspectorView-1.png) @@ -52,7 +52,7 @@ The simplest component in the bunch, this provides a valid connection point (_i. ![alt text](../../images/attachable/ComponentController_InspectorView-1.png) -Taking the above example into consideration, it would make sense that a user would want to be able to easily control whether a specific component is enabled or disabled when something is attached or detached. +Taking the above example into consideration, it would make sense that a user would want to be able to easily control whether a specific component is enabled or disabled when something is attached or detached. As an example: @@ -65,7 +65,7 @@ The `ComponentController` provides this type of functionality: - Each assigned component entry can be configured to directly or inversely follow the `ComponentController`'s current state. - Each assigned component entry can have an enable and/or disable delay. - _When invoked internally by `AttachableBehaviour`, delays are ignored when an `AttachableNode` is being destroyed and the changes are immediate._ - + The `ComponentController` could be daisy chained with minimal user script: ```csharp ///

@@ -111,7 +111,7 @@ For example purposes, we will walk through a common scenario where you might wan ![alt text](../../images/attachable/AttachableDiagram-1.png) -#### Player +#### Player The player prefab in the above diagram is not complete, includes the components of interest, and some additional children and components for example purposes. A complete diagram would most definitely have additional components and children. The `AttachableNode` components provide a "target attach point" that any other spawned network prefab with an `AttachableBehaviour` could attach itself to. @@ -151,16 +151,15 @@ We can see the `AttachableBehaviour`'s **Component Controllers** list contains ` ![alt text](../../images/attachable/AttachableDiagram-3.png) -The above diagram represents what the **Player** and **World Item** spawned objects (_including cloned/non-authority instances_) would look like once the **Attached View** object has been parented under the avatar's **Right Attach** object. The green area and arrow represent the still existing relationship that the **Attached View** has with the **World Item**'s `NetworkObject`. +The above diagram represents what the **Player** and **World Item** spawned objects (_including cloned/non-authority instances_) would look like once the **Attached View** object has been parented under the avatar's **Right Attach** object. The green area and arrow represent the still existing relationship that the **Attached View** has with the **World Item**'s `NetworkObject`. :::info **AttachableBehaviour & NetworkObject Relationship** Upon a `NetworkObject` component being spawned, all associated `NetworkBehaviour` based component instances, that are directly attached to the `NetworkObject`'s `GameObject` or are on any child `GameObject`, will be registered with the `NetworkObject` instance. This remains true even when a child `GameObject` containing one or more `NetworkBehaviour` based component instances of a spawned `NetworkObject` is parented, during runtime, under another `GameObject` that is associated with a different spawned `NetworkObject`. Of course, there are additional considerations like: - What happens when one or both of the NetworkObjects is de-spawned? - - How do you assure the child attachable will return back to its default parent? + - How do you assure the child attachable will return back to its default parent? - and several other edge case scenarios... `AttachableBehaviour` leverages from this "spawn lifetime" relationship to provide another type of "parenting" (attaching) while also taking into consideration these types of edge case scenarios. -::: - +::: \ No newline at end of file From 8ad7a824540ec6753d69428c5aa5394a3fce60c1 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 30 Jul 2025 01:06:53 -0500 Subject: [PATCH 31/43] docs(style-pvp) Removing trickier white spaces... --- .../Documentation~/TableOfContents.md | 12 ++++++------ .../components/Helpers/attachablebehaviour.md | 14 ++++++-------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md b/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md index f9c0d49b26..13e2170c03 100644 --- a/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md +++ b/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md @@ -21,7 +21,7 @@ * [Network components](network-components.md) * [Foundational Components](components/foundational/foundationalcomponents.md) * [NetworkObject](components/foundational/networkobject.md) - * [NetworkObject parenting](advanced-topics/networkobject-parenting.md) + * [NetworkObject parenting](advanced-topics/networkobject-parenting.md) * [NetworkBehaviour](components/foundational/networkbehaviour.md) * [Synchronizing & Order of Operations](components/foundational/networkbehaviour-synchronize.md) * [NetworkManager](components/foundational/networkmanager.md) @@ -43,11 +43,11 @@ * [Spawning synchronization](basics/spawning-synchronization.md) * [Deferred despawning](basics/deferred-despawning.md) * [Latency and performance](latency-performance.md) - * [Understanding latency](learn/lagandpacketloss.md) - * [Ticks and update rates](learn/ticks-and-update-rates.md) - * [Improving performance with client-side interpolation](learn/clientside-interpolation.md) - * [Client anticipation](advanced-topics/client-anticipation.md) - * [Dealing with latency](learn/dealing-with-latency.md) + * [Understanding latency](learn/lagandpacketloss.md) + * [Ticks and update rates](learn/ticks-and-update-rates.md) + * [Improving performance with client-side interpolation](learn/clientside-interpolation.md) + * [Client anticipation](advanced-topics/client-anticipation.md) + * [Dealing with latency](learn/dealing-with-latency.md) * [Network synchronization](network-synchronization.md) * [Synchronizing states and events](advanced-topics/ways-to-synchronize.md) * [NetworkVariables](networkvariables-landing.md) diff --git a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablebehaviour.md b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablebehaviour.md index 794aced829..a2b6b18c4a 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablebehaviour.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablebehaviour.md @@ -20,9 +20,9 @@ This is simple enough for many scenarios, but can become cumbersome under more s With attaching, a user would create nested `GameObject` children that represent the item when it is picked up and when it is dropped/placed somewhere in the scene (i.e. world).
![alt text](../../images/attachable/PlayerAndWorldItem-1.png) - - The WorldItemRoot is where the `NetworkObject` component is placed. - - The NestedChild-World contains the components needed for the item when it is placed in the world. - - The NestedChild-PickedUp contains the components needed for the item when it is picked up by a player. +- The WorldItemRoot is where the `NetworkObject` component is placed. +- The NestedChild-World contains the components needed for the item when it is placed in the world. +- The NestedChild-PickedUp contains the components needed for the item when it is picked up by a player. By placing an `AttachableBehaviour` component on the NestedChild-PickedUp `GameObject` and an `AttachableNode` component on the TargetNode, a user can then invoke the `AttachableBehaviour.Attach` method while passing in the `AttachableNode` component and the NestedChild-PickedUp `GameObject` will get parented under the TargetNode while also synchronizing this action with all other clients.
![alt text](../../images/attachable/PlayerAndWorldItem-2.png) @@ -65,7 +65,6 @@ The `ComponentController` provides this type of functionality: - Each assigned component entry can be configured to directly or inversely follow the `ComponentController`'s current state. - Each assigned component entry can have an enable and/or disable delay. - _When invoked internally by `AttachableBehaviour`, delays are ignored when an `AttachableNode` is being destroyed and the changes are immediate._ - The `ComponentController` could be daisy chained with minimal user script: ```csharp /// @@ -123,7 +122,6 @@ This diagram has a bit more detail to it and introduces one possible usage of a ![alt text](../../images/attachable/AttachableDiagram-2.png) - In the diagram above, we see arrows pointing from the `ComponentController` to the non-netcode standard Unity components such as a `MeshRenderer`, `Collider`, or any other component that should only be enabled when either in "World View" or "Attached View" modes. We can also see that the `AttachableBehaviour` points to the `ComponentController` with a diagram to the right that shows the `AttachableBehaviour` notifies the `ComponentController` that, in turn, enables or disables certain components. #### World Item Component Controller @@ -157,9 +155,9 @@ The above diagram represents what the **Player** and **World Item** spawned obje **AttachableBehaviour & NetworkObject Relationship** Upon a `NetworkObject` component being spawned, all associated `NetworkBehaviour` based component instances, that are directly attached to the `NetworkObject`'s `GameObject` or are on any child `GameObject`, will be registered with the `NetworkObject` instance. This remains true even when a child `GameObject` containing one or more `NetworkBehaviour` based component instances of a spawned `NetworkObject` is parented, during runtime, under another `GameObject` that is associated with a different spawned `NetworkObject`. Of course, there are additional considerations like: - - What happens when one or both of the NetworkObjects is de-spawned? - - How do you assure the child attachable will return back to its default parent? - - and several other edge case scenarios... +- What happens when one or both of the NetworkObjects is de-spawned? +- How do you assure the child attachable will return back to its default parent? +- and several other edge case scenarios... `AttachableBehaviour` leverages from this "spawn lifetime" relationship to provide another type of "parenting" (attaching) while also taking into consideration these types of edge case scenarios. ::: \ No newline at end of file From 959ccef4b049134e9968afe6a2dff3a6639acc67 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 30 Jul 2025 13:13:02 -0500 Subject: [PATCH 32/43] docs (update & add) Adding `AttachableNode` and `ComponentController` sections. Added "Synchronized RPC driven fields" section to networkbehaviour-synchronize.md. Updated various areas based on the additions. --- .../Documentation~/TableOfContents.md | 4 +- .../components/Helpers/attachablebehaviour.md | 98 +++++-------------- .../components/Helpers/attachablenode.md | 14 +++ .../components/Helpers/componentcontroller.md | 88 +++++++++++++++++ .../components/Helpers/helpercomponents.md | 6 +- .../networkbehaviour-synchronize.md | 75 +++++++++++++- .../learn/dealing-with-latency.md | 4 +- 7 files changed, 205 insertions(+), 84 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablenode.md create mode 100644 com.unity.netcode.gameobjects/Documentation~/components/Helpers/componentcontroller.md diff --git a/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md b/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md index 13e2170c03..416c29c8e3 100644 --- a/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md +++ b/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md @@ -28,8 +28,8 @@ * [PlayerObjects and player prefabs](components/foundational/playerobjects.md) * [Helper Components](components/Helpers/helpercomponents.md) * [AttachableBehaviour](components/Helpers/attachablebehaviour.md) - * AttachableNode - * ComponentController + * [AttachableNode](components/Helpers/attachablenode.md) + * [ComponentController](components/Helpers/componentcontroller.md) * [NetworkAnimator](components/helpers/networkanimator.md) * [NetworkTransform](components/helpers/networktransform.md) * [Physics](advanced-topics/physics.md) diff --git a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablebehaviour.md b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablebehaviour.md index a2b6b18c4a..d4cbab5c57 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablebehaviour.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablebehaviour.md @@ -1,5 +1,17 @@ # AttachableBehaviour -The `AttachableBehaviour` Provides "out of the box" support for attaching (i.e. parenting) a nested child `GameObject` that includes an `AttachableBehaviour` component to another nested child `GameObject` with an `AttachableNode` component that is associated with a different `NetworkObject`. + +![alt text](../../images/attachable/AttachableBehaviour_InspectorView-1.png) + +The basic functionality of the `AttachableBehaviour` component provides: +- The ability to assign (make aware) `ComponetController` components from any part of the parent-child hierarchy. + - Each `ComponentControllerEntry` provides the ability to select when the `ComponentController` should be triggered (via the **Auto Trigger** property) and whether its enabled state should be enabled or disabled upon attaching (via the **Enable On Attach** property). The default setting is to be disabled upon the `AttachableBehaviour` attaching to an `AttachableNode` and enabled upon detaching. When the **Enable On Attach** property is enabled, the `ComponentController` will be set to enabled upon the `AttachableBehaviour` attaching to an `AttachableNode` and disabled upon detaching. +- The ability to control when an `AttachableBehaviour` component will automatically detach from an `AttachableNode` via the **Auto Detach** property. + - The **Auto Detach** property can have any combination of the below flags or none (no flags): + - **On Ownership Changed:** Upon ownership changing, the `AttachableBehaviour` will detach from any `AttachableNode` it is attached to. + - **On Despawn:** Upon the `AttachableBehaviour` being despawned, it will detach from any `AttachableNode` it is attached to. + - **On Attach Node Destroy**: Just prior to the `AttachableNode` being destroyed, any attached `AttachableBehaviour` with this flag will automatically detach from the `AttachableNode`. + +_Any of the `AttachableBehaviour.AutoDetach` settings will be invoked on all instances without the need for the owner to synchronize the end result(i.e. detaching) which provides a level of redundancy for edge case scenarios like a player being disconnected abruptly by the host or by timing out or any scenario where a spawned object is being destroyed with the owner or perhaps being redistributed to another client authority in a distributed authority session. Having the ability to select or deselect any of the auto-detach flags coupled with the ability to derive from `AttachableBehaviour` provides additional levels of modularity/customization._ ## Attaching vs NetworkObject parenting @@ -27,82 +39,15 @@ With attaching, a user would create nested `GameObject` children that represent By placing an `AttachableBehaviour` component on the NestedChild-PickedUp `GameObject` and an `AttachableNode` component on the TargetNode, a user can then invoke the `AttachableBehaviour.Attach` method while passing in the `AttachableNode` component and the NestedChild-PickedUp `GameObject` will get parented under the TargetNode while also synchronizing this action with all other clients.
![alt text](../../images/attachable/PlayerAndWorldItem-2.png) -### AttachableBehaviour - -![alt text](../../images/attachable/AttachableBehaviour_InspectorView-1.png) - -The basic functionality of the `AttachableBehaviour` component provides: -- The ability to assign (make aware) `ComponetController` components from any part of the parent-child hierarchy. - - Each `ComponentControllerEntry` provides the ability to select when the `ComponentController` should be triggered (via the **Auto Trigger** property) and whether its enabled state should be enabled or disabled upon attaching (via the **Enable On Attach** property). The default setting is to be disabled upon the `AttachableBehaviour` attaching to an `AttachableNode` and enabled upon detaching. When the **Enable On Attach** property is enabled, the `ComponentController` will be set to enabled upon the `AttachableBehaviour` attaching to an `AttachableNode` and disabled upon detaching. -- The ability to control when an `AttachableBehaviour` component will automatically detach from an `AttachableNode` via the **Auto Detach** property. - - The **Auto Detach** property can have any combination of the below flags or none (no flags): - - **On Ownership Changed:** Upon ownership changing, the `AttachableBehaviour` will detach from any `AttachableNode` it is attached to. - - **On Despawn:** Upon the `AttachableBehaviour` being despawned, it will detach from any `AttachableNode` it is attached to. - - **On Attach Node Destroy**: Just prior to the `AttachableNode` being destroyed, any attached `AttachableBehaviour` with this flag will automatically detach from the `AttachableNode`. - -_Any of the `AttachableBehaviour.AutoDetach` settings will be invoked on all instances without the need for the owner to synchronize the end result(i.e. detaching) which provides a level of redundancy for edge case scenarios like a player being disconnected abruptly by the host or by timing out or any scenario where a spawned object is being destroyed with the owner or perhaps being redistributed to another client authority in a distributed authority session. Having the ability to select or deselect any of the auto-detach flags coupled with the ability to derive from `AttachableBehaviour` provides additional levels of modularity/customization._ - -### AttachableNode - -![alt text](../../images/attachable/AttachableNode_InspectorView-1.png) - -The simplest component in the bunch, this provides a valid connection point (_i.e. what an `AttachableBehaviour` can attach to_) with the ability to have it automatically detach from any attached `AttachableBehaviour` instances when it is despawned. - -### ComponentController - -![alt text](../../images/attachable/ComponentController_InspectorView-1.png) - -Taking the above example into consideration, it would make sense that a user would want to be able to easily control whether a specific component is enabled or disabled when something is attached or detached. - -As an example: - -- When the WorldItemRoot is in the "placed in the world" state, it would make sense to disable any `MeshRenderer`, `Collider`, and other components on the NestedChild-PickedUp `GameObject` while enabling similar types of components on the NestedChild-World. -- When the WorldItemRoot is in the "picked up" state, it would make sense to enable any `MeshRenderer`, `Collider`, and other components on the NestedChild-PickedUp `GameObject` while disabling similar types of components on the NestedChild-World. -- It would also make sense to synchronize the enabling or disabling of components with all instances. - -The `ComponentController` provides this type of functionality: -- Can be used with `AttachableBehaviour` or independently for another purpose. -- Each assigned component entry can be configured to directly or inversely follow the `ComponentController`'s current state. -- Each assigned component entry can have an enable and/or disable delay. - - _When invoked internally by `AttachableBehaviour`, delays are ignored when an `AttachableNode` is being destroyed and the changes are immediate._ -The `ComponentController` could be daisy chained with minimal user script: -```csharp -/// -/// Use as a component in the ComponentController that will -/// trigger the Controller (ComponentController). -/// This pattern can repeat. -/// -public class DaisyChainedController : MonoBehaviour -{ - public ComponentController Controller; - - private void OnEnable() - { - if (!Controller || !Controller.HasAuthority) - { - return; - } - Controller.SetEnabled(true); - } - - private void OnDisable() - { - if (!Controller || !Controller.HasAuthority) - { - return; - } - Controller.SetEnabled(false); - } -} -``` - -### Example of synchronized RPC driven properties +:::info +**Example of synchronized RPC driven properties** Both the `AttachableBehaviour` and the `ComponentController` provide an example of using synchronized RPC driven properties in place of `NetworkVariable`. Under certain conditions it is better to use RPCs when a specific order of operations is needed as opposed to `NetworkVariable`s which can update out of order (regarding the order in which certain states were updated) depending upon several edge case scenarios. Under this condition using reliable RPCs will assure the messages are received in the order they were generated while also reducing the latency time between the change and the non-authority instances being notified of the change. Synchronized RPC driven properties only require overriding the `NetworkBehaviour.OnSynchronize` method and serializing any properties that need to be synchronized with late joining players or handling network object visibility related scenarios. +::: -## Usage Walk Through +## Usage walk through ### Introduction @@ -118,7 +63,7 @@ The player prefab in the above diagram is not complete, includes the components This diagram has a bit more detail to it and introduces one possible usage of a `ComponentController` and `AttachableBehaviour`. The `ComponentController` will be used to control the enabling and disabling of components and synchronizing this with non-authority instances. The `AttachableBehaviour` resides on the child `AttachedView`'s `GameObject` and will be the catalyst for attaching to a player. -### World vs Attached View Modes +### World vs attached view modes ![alt text](../../images/attachable/AttachableDiagram-2.png) @@ -160,4 +105,9 @@ Upon a `NetworkObject` component being spawned, all associated `NetworkBehaviour - and several other edge case scenarios... `AttachableBehaviour` leverages from this "spawn lifetime" relationship to provide another type of "parenting" (attaching) while also taking into consideration these types of edge case scenarios. -::: \ No newline at end of file +::: + +## Additional resources + +- [AttachableNode](attachablenode.md) +- [ComponentController](componentcontroller.md) \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablenode.md b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablenode.md new file mode 100644 index 0000000000..5e8b68162f --- /dev/null +++ b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablenode.md @@ -0,0 +1,14 @@ +# AttachableNode + +![alt text](../../images/attachable/AttachableNode_InspectorView-1.png) + +This component provides a valid attach point for `AttachableBehaviour` components. It includes the `AttachableNode.DetachOnDespawn` field that, when enabled (the default), it will automatically detach any attached `AttachableBehaviour` instances when the associated `NetworkObject` of the `AttachableNode` is de-spawned. + +## AttachableBehaviour Usage + +[A usage example can be found here.](attachablebehaviour.md#usage-walk-through) + +## Additional resources + +- [AttachableBehaviour](attachablebehaviour.md) +- [ComponentController](componentcontroller.md) \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/componentcontroller.md b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/componentcontroller.md new file mode 100644 index 0000000000..916205f86b --- /dev/null +++ b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/componentcontroller.md @@ -0,0 +1,88 @@ +# ComponentController + +A `ComponentController` provides you with the ability to enable or disable one or more components by the authority instance and have those changes synchronized with non-authority/remote instances. It uses a [synchronized RPC driven field approach](../foundational/networkbehaviour-synchronize.md#synchronized-rpc-driven-fields) to synchronize its enabled state of the components it is controlling to assure optimal performance and that the order of operations of changes is relative to other `ComponentController` and/or other `AttachableBehaviour` component instances. + +The `ComponentController` can be: +- Used with `AttachableBehaviour` or independently for another purpose. +- Configured to directly or inversely follow the `ComponentController`'s current state. +- Configured to have an enable and/or disable delay. + - _When invoked internally by `AttachableBehaviour`, delays are ignored when an `AttachableNode` is being destroyed and the changes are immediate._ + + +## Configuring + +![alt text](../../images/attachable/WorldItem-Inspector-View-1.png) + +A `ComponentController` can have one or more `ComponentEntry` entries in its **Components** list. Each `ComponentEntry` has some additional fields that you can adjust based on your desired result: +- **Invert Enabled:** When enabled, this will make the associated component inversely follow the `ComponentControllers` global enabled state. This is useful if you want a set of components to be enabled when the `ComponentController` component's global enable state is set to `false` and for that same set of components to be disabled when the `ComponentController` component's global enable state is set to `true`. +- **Enable Delay:** When greater than 0 (the default), the component will delay transitioning from a disabled state to an enabled state by the amount of time (in seconds) specified. +- **Disable Delay:** When greater than 0 (the default), the component will delay transitioning from an enabled state to a disabled state by the amount of time (in seconds) specified. +- **Component:** The component to control and synchronize its enabled state. + +Both delay values (Enable & Disable) has many uses, but an example would be to prevent a `MeshRenderer` from being enabled prior to other specific events like avoiding it from rendering for a few frames while the attachable is positioned. + + +## Examples + +### Independent Usage + +While `ComponentController` can be used with an `AttachableBehaviour` without writing any script, you might find that it can be used for many other purposes. Below is a pseudo example where a `ComponentController` would have its synchronized enabled state updated when the `DaisyChainedController` is either enabled or disabled. + +```csharp +/// +/// Use as a component in the ComponentController that will +/// trigger the Controller (ComponentController). +/// This pattern can repeat/be daisy chained. +/// +public class DaisyChainedController : MonoBehaviour +{ + public ComponentController Controller; + + private void OnEnable() + { + if (!Controller || !Controller.HasAuthority) + { + return; + } + Controller.SetEnabled(true); + } + + private void OnDisable() + { + if (!Controller || !Controller.HasAuthority) + { + return; + } + Controller.SetEnabled(false); + } +} +``` +The above component could be arranged to create a chained sequence of components when the root `DaisyChainedController` component is enabled or disabled. Such a sequence could look like: + +- DaisyChainedController-A + - Controller + - Points to DaisyChainedController-B +- DaisyChainedController-B + - Controller + - Points to DaisyChainedController-C +- DaisyChainedController-C + - Controller + +When DaisyChainedController-A is enabled, then a sequence of events would occur where DaisyChainedController-B and DaisyChainedController-C would be enabled. The same sequence of events would occur when DaisyChainedController-A was then disabled. + +### AttachableBehaviour Usage + +The `AttachableBehaviour` can be assigned one or more component controllers that will be invoked, depending upon configuration, when the `AttachableBehaviour` is attached and detached from an `AttachableNode`. You can find the [usage example with an `AttachableBehaviour` here.](attachablebehaviour.md#usage-walk-through) + +:::info +**Example of synchronized RPC driven properties** + +Both the `AttachableBehaviour` and the `ComponentController` provide an example of using synchronized RPC driven properties in place of `NetworkVariable`. Under certain conditions it is better to use RPCs when a specific order of operations is needed as opposed to `NetworkVariable`s which can update out of order (regarding the order in which certain states were updated) depending upon several edge case scenarios. + +Under this condition using reliable RPCs will assure the messages are received in the order they were generated while also reducing the latency time between the change and the non-authority instances being notified of the change. Synchronized RPC driven properties only require overriding the `NetworkBehaviour.OnSynchronize` method and serializing any properties that need to be synchronized with late joining players or handling network object visibility related scenarios. +::: + +## Additional resources + +- [AttachableBehaviour](attachablebehaviour.md) +- [AttachableNode](attachablenode.md) \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/helpercomponents.md b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/helpercomponents.md index d146568479..f8a69be0d3 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/helpercomponents.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/helpercomponents.md @@ -4,9 +4,9 @@ Understand the helper components available to use in your Netcode for GameObject **Topic** | **Description** | | :------------------------------ | :------------------------------- | -| **[AttachableBehaviour](attachablebehaviour.md)**| Provides an alternative to `NetworkObject` parenting. (wip) | -| **AttachableNode**| Target parent for an `AttachableBehaviour`. (wip) | -| **ComponentController**| Provides the synchronization of and control over enabling or disabling objects. (wip) | +| **[AttachableBehaviour](attachablebehaviour.md)**| Provides an alternative to `NetworkObject` parenting. This section includes a usage example with `AttachableBehaviour`, `AttachableNode`, and `ComponentController`. | +| **[AttachableNode](attachablenode.md)**| Target parent for an `AttachableBehaviour`. | +| **[ComponentController](componentcontroller.md)**| Provides the synchronization of and control over enabling or disabling objects. | | **[NetworkAnimator](networkanimator.md)**| The `NetworkAnimator` component provides you with a fundamental example of how to synchronize animations during a network session. Animation states are synchronized with players joining an existing network session and any client already connected before the animation state changing. | | **[NetworkTransform](networktransform.md)**| [NetworkTransform](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.Components.NetworkTransform.html) is a concrete class that inherits from [NetworkBehaviour](../foundational/networkbehaviour.md) and synchronizes [Transform](https://docs.unity3d.com/Manual/class-Transform.html) properties across the network, ensuring that the position, rotation, and scale of a [GameObject](https://docs.unity3d.com/Manual/working-with-gameobjects.html) are replicated to other clients. | | **[Physics](../../advanced-topics/physics.md)**| Netcode for GameObjects has a built in approach which allows for server-authoritative physics where the physics simulation only runs on the server. | diff --git a/com.unity.netcode.gameobjects/Documentation~/components/foundational/networkbehaviour-synchronize.md b/com.unity.netcode.gameobjects/Documentation~/components/foundational/networkbehaviour-synchronize.md index 92fc4f68e2..31a5099d14 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/foundational/networkbehaviour-synchronize.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/foundational/networkbehaviour-synchronize.md @@ -97,9 +97,78 @@ Client-side: - The `NetworkObject` is spawned. - For each associated `NetworkBehaviour` component, `NetworkBehaviour.OnNetworkSpawn` is invoked. -### `OnSynchronize` example - -Now that you understand the general concept behind `NetworkBehaviour.OnSynchronize`, the question you might have is "when would you use such a thing"? `NetworkVariable`s can be useful to synchronize state, but they also are only updated every network tick and you might have some form of state that needs to be updated when it happens and not several frames later, so you decide to use RPCs. However, this becomes an issue when you want to synchronize late-joining clients as there is no way to synchronize late-joining clients based on RPC activity over the duration of a network session. This is one of many possible reasons one might want to use `NetworkBehaviour.OnSynchronize`. +### Synchronized RPC driven fields + +Now that you understand the general concept behind `NetworkBehaviour.OnSynchronize`, the question you might have is: + +_"When would you want to use `NetworkBehaviour.OnSynchronize`?"_ + + `NetworkVariable`s can be useful to synchronize state, but they also are only updated every network tick. While it is possible to adjust a `NetworkVariable`'s update frequency, `NetworkVariable`s (in general) guarantees state synchronization but does not guarantee state changes will be updated in the same order they were chanted relative to other `NetworkVariables`. + + With this in mind, you might need states to be updated in the relative order in which they were changed. In order to do this, you can combine the use of an RPC to handle updating the change in a properties state/value while using `NetworkBehaviour.OnSynchronize` to assure that any late joining client will be synchronized with the current state of said property. + +**Using a synchronized RPC driven field approach:** + +- When the authority changes a local field's value, it would then need send an RPC to all non-authority instances. + - RPC messages are immediately queued in the outbound send queue which means the order in which an RPC is invoked is the order in which they will be received (_if using the default reliable fragmented sequenced delivery_). + - Any other synchronized RPC driven fields, whether on the same `NetworkBehaviour` or not, would occur in the order they were invoked on the authority instance side. +- As long as you override the `NetworkBehaviour.OnSynchronize` method and serialize the field, then late joining clients will be synchronized with the authority's most current field value. + +:::info +**Synchronized RPC driven fields vs NetworkVariables** +When a NetworkVariable becomes dirty, the associated `NetworkObject` is add to a queue of `NetworkObject` instances to be scanned for changes to any NetworkVariable declared in a `NetworkBehaviour` associated with the `NetworkObject` instance. Towards the end of the frame, during late update, any `NetworkObject` instances marked as "having one or more dirty NetworkVariables" will be processed and the delta states will be serialized. This can lead to a scenario like the following: + +**Authority side** +- During Update: + - NetworkBehaviour-A.NetworkVariable-1 is updated on the authority instance. + - NetworkObject-A is added to the "dirty NetworkObjects" queue. + - NetworkBehaviour-B.NetworkVariable-1 is updated on the authority instance. + - NetworkObject-B is added to the "dirty NetworkObjects" queue. + - NetworkBehaviour-A.NetworkVariable-2 is updated on the authority instance. + - NetworkObject-A is determined to already be in the "dirty NetworkObjects" queue. +- During Late Update: + - NetworkObject-A is scanned for dirty `NetworkVariables` and a `NetworkVariableDeltaMessage` is generated. + - _This includes NetworkBehaviour-A.NetworkVariable-1 and NetworkBehaviour-A.NetworkVariable-2._ + - NetworkObject-B is scanned for dirty `NetworkVariables` and a `NetworkVariableDeltaMessage` is generated. + - _This includes NetworkBehaviour-B.NetworkVariable-1._ + +**Non-Authority side** +- `NetworkVariableDeltaMessage` for NetworkObject-A is processed. + - NetworkBehaviour-A.NetworkVariable-1 is updated and notifications triggered. + - NetworkBehaviour-A.NetworkVariable-2 is updated and notifications triggered. +- `NetworkVariableDeltaMessage` for NetworkObject-B is processed. + - NetworkBehaviour-B.NetworkVariable-1 is updated and notifications triggered. + +Based on the above, we can determine that there will be a different order of operations in regards to when a `NetworkVariable` is updated on non-authority instances: + +**Authority side order of operations** +- NetworkBehaviour-A.NetworkVariable-1 is changed. +- NetworkBehaviour-B.NetworkVariable-1 is changed. +- NetworkBehaviour-A.NetworkVariable-2 is changed. + +**Non-Authority side order of operations** +- NetworkBehaviour-A.NetworkVariable-1 is updated and notification triggered. +- NetworkBehaviour-A.NetworkVariable-2 is changed and notification triggered. +- NetworkBehaviour-B.NetworkVariable-1 is changed and notification triggered. + +If you depend on the authority's order of operations relative to when states should be updated on the non-authority instances, then using `NetworkVariable` has the potential to not honor the order of operations for changes to `NetworkVariables`. + +**Synchronized RPC driven fields** + +**Authority side** +- NetworkBehaviour-A.Field-1 is changed and an RPC is sent to all non-authority instances. +- NetworkBehaviour-B.Field-1 is changed and an RPC is sent to all non-authority instances. +- NetworkBehaviour-A.Field-2 is changed and an RPC is sent to all non-authority instances. + +**Non-Authority side** +- RPC for NetworkBehaviour-A.Field-1 is processed and field is updated. +- RPC for NetworkBehaviour-B.Field-1 is processed and field is updated. +- RPC for NetworkBehaviour-A.Field-2 is processed and field is updated. + +Using the synchronized RPC driven field approach preserves the order of operations in regards to when each field's state changed. The additional benefit of this approach is that it is more performant than that of a `NetworkVariable`. +::: + +### Synchronized RPC driven field example The following example uses `NetworkBehaviour.OnSynchronize` to synchronize connecting (to-be-synchronized) clients and also uses an RPC to synchronize changes in state for already synchronized and connected clients: diff --git a/com.unity.netcode.gameobjects/Documentation~/learn/dealing-with-latency.md b/com.unity.netcode.gameobjects/Documentation~/learn/dealing-with-latency.md index 9089eaf2a6..657d6f12a8 100644 --- a/com.unity.netcode.gameobjects/Documentation~/learn/dealing-with-latency.md +++ b/com.unity.netcode.gameobjects/Documentation~/learn/dealing-with-latency.md @@ -72,11 +72,11 @@ There are possible synchronizations issues with client authoritative games. If y Multiple clients with the ability to affect the same shared object can quickly become a mess. -![Multiple clients trying to impose their reality on a shared object.](../../images/sequence_diagrams/dealing_with_latency/Example_CaptureFlagPart1_ClientAuthorityIssue.png) +![Multiple clients trying to impose their reality on a shared object.](../images/sequence_diagrams/dealing_with_latency/Example_CaptureFlagPart1_ClientAuthorityIssue.png) To avoid this, it's recommended to use client **owner** authority, which allows only the owner of an object to interact with it. Since the server controls ownership in Netcode, there's no possibility of two clients running into a [race condition](https://en.wikipedia.org/wiki/Race_condition#In_software). To allow two clients to affect the same object, you must ask the server for ownership, wait for it, then execute the client authoritative logic you want. -![Multiple clients ASKING to interact with a shared object.](../../images/sequence_diagrams/dealing_with_latency/Example_CaptureFlagPart2_ServerAuthorityFix.png) +![Multiple clients ASKING to interact with a shared object.](../images/sequence_diagrams/dealing_with_latency/Example_CaptureFlagPart2_ServerAuthorityFix.png) #### Issue: Security From 7c95cc75801248a305b3884470983f573e6437ab Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 30 Jul 2025 13:25:59 -0500 Subject: [PATCH 33/43] docs (style - PVP) Resolving more white space issues. --- .../components/Helpers/componentcontroller.md | 12 +++++------- .../foundational/networkbehaviour-synchronize.md | 10 +++++----- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/componentcontroller.md b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/componentcontroller.md index 916205f86b..919a1fdc5a 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/componentcontroller.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/componentcontroller.md @@ -6,8 +6,7 @@ The `ComponentController` can be: - Used with `AttachableBehaviour` or independently for another purpose. - Configured to directly or inversely follow the `ComponentController`'s current state. - Configured to have an enable and/or disable delay. - - _When invoked internally by `AttachableBehaviour`, delays are ignored when an `AttachableNode` is being destroyed and the changes are immediate._ - + - _When invoked internally by `AttachableBehaviour`, delays are ignored when an `AttachableNode` is being destroyed and the changes are immediate._ ## Configuring @@ -21,7 +20,6 @@ A `ComponentController` can have one or more `ComponentEntry` entries in its **C Both delay values (Enable & Disable) has many uses, but an example would be to prevent a `MeshRenderer` from being enabled prior to other specific events like avoiding it from rendering for a few frames while the attachable is positioned. - ## Examples ### Independent Usage @@ -60,15 +58,15 @@ public class DaisyChainedController : MonoBehaviour The above component could be arranged to create a chained sequence of components when the root `DaisyChainedController` component is enabled or disabled. Such a sequence could look like: - DaisyChainedController-A - - Controller + - Controller - Points to DaisyChainedController-B - DaisyChainedController-B - - Controller + - Controller - Points to DaisyChainedController-C - DaisyChainedController-C - - Controller + - Controller -When DaisyChainedController-A is enabled, then a sequence of events would occur where DaisyChainedController-B and DaisyChainedController-C would be enabled. The same sequence of events would occur when DaisyChainedController-A was then disabled. +When DaisyChainedController-A is enabled, then a sequence of events would occur where DaisyChainedController-B and DaisyChainedController-C would be enabled. The same sequence of events would occur when DaisyChainedController-A was then disabled. ### AttachableBehaviour Usage diff --git a/com.unity.netcode.gameobjects/Documentation~/components/foundational/networkbehaviour-synchronize.md b/com.unity.netcode.gameobjects/Documentation~/components/foundational/networkbehaviour-synchronize.md index 31a5099d14..21c0958ea2 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/foundational/networkbehaviour-synchronize.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/foundational/networkbehaviour-synchronize.md @@ -103,9 +103,9 @@ Now that you understand the general concept behind `NetworkBehaviour.OnSynchroni _"When would you want to use `NetworkBehaviour.OnSynchronize`?"_ - `NetworkVariable`s can be useful to synchronize state, but they also are only updated every network tick. While it is possible to adjust a `NetworkVariable`'s update frequency, `NetworkVariable`s (in general) guarantees state synchronization but does not guarantee state changes will be updated in the same order they were chanted relative to other `NetworkVariables`. - - With this in mind, you might need states to be updated in the relative order in which they were changed. In order to do this, you can combine the use of an RPC to handle updating the change in a properties state/value while using `NetworkBehaviour.OnSynchronize` to assure that any late joining client will be synchronized with the current state of said property. +`NetworkVariable`s can be useful to synchronize state, but they also are only updated every network tick. While it is possible to adjust a `NetworkVariable`'s update frequency, `NetworkVariable`s (in general) guarantees state synchronization but does not guarantee state changes will be updated in the same order they were chanted relative to other `NetworkVariables`. + +With this in mind, you might need states to be updated in the relative order in which they were changed. In order to do this, you can combine the use of an RPC to handle updating the change in a properties state/value while using `NetworkBehaviour.OnSynchronize` to assure that any late joining client will be synchronized with the current state of said property. **Using a synchronized RPC driven field approach:** @@ -186,7 +186,7 @@ public class SimpleRpcState : NetworkBehaviour /// /// Late joining clients will be synchronized - /// to the most current m_ToggleState + /// to the most current m_ToggleState. /// protected override void OnSynchronize(ref BufferSerializer serializer) { @@ -201,7 +201,7 @@ public class SimpleRpcState : NetworkBehaviour /// /// Synchronizes connected clients with the - /// server-side m_ToggleState + /// server-side m_ToggleState. /// /// [Rpc(SendTo.ClientsAndHost)] From fe889232aa97af99528452eebaf177b9919e5c00 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 30 Jul 2025 13:32:04 -0500 Subject: [PATCH 34/43] docs (style) Removing a single sneaky white space. --- .../Documentation~/components/Helpers/componentcontroller.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/componentcontroller.md b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/componentcontroller.md index 919a1fdc5a..823dd15817 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/componentcontroller.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/componentcontroller.md @@ -55,6 +55,7 @@ public class DaisyChainedController : MonoBehaviour } } ``` + The above component could be arranged to create a chained sequence of components when the root `DaisyChainedController` component is enabled or disabled. Such a sequence could look like: - DaisyChainedController-A @@ -70,7 +71,7 @@ When DaisyChainedController-A is enabled, then a sequence of events would occur ### AttachableBehaviour Usage -The `AttachableBehaviour` can be assigned one or more component controllers that will be invoked, depending upon configuration, when the `AttachableBehaviour` is attached and detached from an `AttachableNode`. You can find the [usage example with an `AttachableBehaviour` here.](attachablebehaviour.md#usage-walk-through) +The `AttachableBehaviour` can be assigned one or more component controllers that will be invoked, depending upon configuration, when the `AttachableBehaviour` is attached and detached from an `AttachableNode`. You can find the [usage example with an `AttachableBehaviour` here.](attachablebehaviour.md#usage-walk-through) :::info **Example of synchronized RPC driven properties** From 5662003a0aa4c3ad4456a66b54e13182fddeb53e Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 30 Jul 2025 15:15:41 -0500 Subject: [PATCH 35/43] Update com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs Co-authored-by: Emma --- com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 754b83653d..f215abf157 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -218,7 +218,7 @@ private static void CheckPrefabStage(PrefabStage prefabStage) s_PrefabAsset = AssetDatabase.LoadAssetAtPath(s_PrefabStage.assetPath); } - if (s_PrefabAsset && s_PrefabInstance && s_PrefabInstance.GlobalObjectIdHash != s_PrefabAsset.GlobalObjectIdHash) + if (s_PrefabAsset && s_PrefabInstance.GlobalObjectIdHash != s_PrefabAsset.GlobalObjectIdHash) { s_PrefabInstance.GlobalObjectIdHash = s_PrefabAsset.GlobalObjectIdHash; // For InContext mode, we don't want to record these modifications (the in-scene GlobalObjectIdHash is serialized with the scene). From 93b645fdbd4e5dbab376f95f2e9cac4441252961 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 30 Jul 2025 16:11:47 -0500 Subject: [PATCH 36/43] update Implementing some suggested changes from PR review. --- .../Runtime/Components/Helpers/AttachableBehaviour.cs | 6 +----- .../Runtime/Components/Helpers/ComponentController.cs | 8 ++++---- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs index 8922b4ad91..2a8d968dfc 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs @@ -96,11 +96,7 @@ protected virtual void OnValidate() } foreach (var componentController in ComponentControllers) { - if (componentController == null) - { - continue; - } - componentController.OnValidate(); + componentController?.OnValidate(); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs index f135446184..181b61ebf5 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs @@ -255,7 +255,7 @@ protected virtual void OnValidate() } var propertyInfo = Components[i].Component.GetType().GetProperty("enabled", BindingFlags.Instance | BindingFlags.Public); - if (propertyInfo == null && propertyInfo.PropertyType != typeof(bool)) + if (propertyInfo == null || propertyInfo.PropertyType != typeof(bool)) { Debug.LogWarning($"{Components[i].Component.name} does not contain a public enabled property! (Removing)"); Components.RemoveAt(i); @@ -291,13 +291,13 @@ protected virtual void OnValidate() gameObjectsToScan.Clear(); // Final (third) pass is to name each list element item as the component is normally viewed in the inspector view. - for (int i = 0; i < Components.Count; i++) + foreach(var componentEntry in Components) { - if (!Components[i].Component) + if (!componentEntry.Component) { continue; } - Components[i].name = GetComponentNameFormatted(Components[i].Component); + componentEntry.name = GetComponentNameFormatted(componentEntry.Component); } } #endif From e9eec54f24aa1cad9d7553f9aca5cc7a3fe33978 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 30 Jul 2025 16:21:40 -0500 Subject: [PATCH 37/43] style Adding whitespace. --- .../Runtime/Components/Helpers/ComponentController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs index 181b61ebf5..8a99007012 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs @@ -291,7 +291,7 @@ protected virtual void OnValidate() gameObjectsToScan.Clear(); // Final (third) pass is to name each list element item as the component is normally viewed in the inspector view. - foreach(var componentEntry in Components) + foreach (var componentEntry in Components) { if (!componentEntry.Component) { From db4adabceae3c68070b78c28364c76db4e5ddc61 Mon Sep 17 00:00:00 2001 From: Amy Reeve Date: Fri, 1 Aug 2025 18:33:29 +0100 Subject: [PATCH 38/43] Lorge doc review --- .../CharacterControllerMovingBodies/Readme.md | 4 +- Examples/OverridingScenesAndPrefabs/Readme.md | 30 +-- com.unity.netcode.gameobjects/CHANGELOG.md | 250 +++++++++--------- .../Documentation~/TableOfContents.md | 24 +- .../advanced-topics/client-anticipation.md | 4 +- .../inscene_parenting_player.md | 6 +- .../message-system/reliability.md | 2 +- .../message-system/rpc-params.md | 2 +- .../advanced-topics/message-system/rpc.md | 4 +- .../advanced-topics/messaging-system.md | 4 +- .../network-update-loop-system/index.md | 4 +- .../networkobject-parenting.md | 2 +- .../advanced-topics/networktime-ticks.md | 8 +- .../advanced-topics/object-pooling.md | 2 +- .../Documentation~/advanced-topics/physics.md | 14 +- .../advanced-topics/reconnecting-mid-game.md | 4 +- .../networkobject-serialization.md | 14 +- .../advanced-topics/session-management.md | 4 +- .../advanced-topics/transports.md | 2 +- .../basics/connection-approval.md | 2 +- .../basics/custom-networkvariables.md | 10 +- .../Documentation~/basics/networkvariable.md | 30 +-- .../Documentation~/basics/object-spawning.md | 76 +++--- .../basics/object-visibility.md | 16 +- .../Documentation~/basics/ownership.md | 4 +- .../scenemanagement/custom-management.md | 16 +- .../inscene-placed-networkobjects.md | 128 ++++----- .../basics/scenemanagement/scene-events.md | 22 +- .../scene-management-overview.md | 2 +- .../scenemanagement/timing-considerations.md | 28 +- .../using-networkscenemanager.md | 28 +- .../components/Helpers/attachablebehaviour.md | 113 -------- .../components/Helpers/attachablenode.md | 14 - .../components/Helpers/componentcontroller.md | 87 ------ .../components/Helpers/helpercomponents.md | 12 - .../components/core/corecomponents.md | 36 +++ .../networkbehaviour-synchronize.md | 68 ++--- .../components/core/networkbehaviour.md | 127 +++++++++ .../{foundational => core}/networkmanager.md | 26 +- .../{foundational => core}/networkobject.md | 6 +- .../{foundational => core}/playerobjects.md | 2 +- .../foundational/foundationalcomponents.md | 34 --- .../foundational/networkbehaviour.md | 127 --------- .../components/helper/attachablebehaviour.md | 122 +++++++++ .../components/helper/attachablenode.md | 16 ++ .../components/helper/componentcontroller.md | 95 +++++++ .../components/helper/helpercomponents.md | 12 + .../{Helpers => helper}/networkanimator.md | 30 +-- .../{Helpers => helper}/networktransform.md | 12 +- .../Documentation~/integrated-management.md | 2 +- .../learn/clientside-interpolation.md | 2 +- .../learn/dealing-with-latency.md | 4 +- .../Documentation~/learn/faq.md | 4 +- .../Documentation~/learn/rpcnetvarexamples.md | 4 +- .../learn/sample-dedicated-server.md | 4 +- .../Documentation~/network-components.md | 4 +- .../Documentation~/network-synchronization.md | 2 +- .../Documentation~/network-update-loop.md | 2 +- .../networkbehaviour-landing.md | 6 +- .../Documentation~/relay/relay.md | 2 +- .../Documentation~/samples.md | 4 +- .../samples/bitesize/bitesize-clientdriven.md | 2 +- .../samples/bitesize/bitesize-invaders.md | 2 +- .../samples/bossroom/architecture.md | 22 +- .../bossroom/networkobject-parenting.md | 4 +- .../samples/bossroom/networkrigidbody.md | 4 +- .../samples/bossroom/optimizing-bossroom.md | 6 +- .../Documentation~/scene-management.md | 2 +- .../Documentation~/serialization.md | 2 +- .../Documentation~/spawn-despawn.md | 2 +- .../troubleshooting/error-messages.md | 4 +- .../troubleshooting/troubleshooting.md | 4 +- .../tutorials/get-started-with-ngo.md | 20 +- .../Documentation~/tutorials/helloworld.md | 22 +- .../testing_with_artificial_conditions.md | 2 +- .../Editor/CodeGen/NetworkBehaviourILPP.cs | 2 +- .../Bootstrap/Scripts/BootstrapPlayer.cs | 6 +- 77 files changed, 926 insertions(+), 905 deletions(-) delete mode 100644 com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablebehaviour.md delete mode 100644 com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablenode.md delete mode 100644 com.unity.netcode.gameobjects/Documentation~/components/Helpers/componentcontroller.md delete mode 100644 com.unity.netcode.gameobjects/Documentation~/components/Helpers/helpercomponents.md create mode 100644 com.unity.netcode.gameobjects/Documentation~/components/core/corecomponents.md rename com.unity.netcode.gameobjects/Documentation~/components/{foundational => core}/networkbehaviour-synchronize.md (72%) create mode 100644 com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour.md rename com.unity.netcode.gameobjects/Documentation~/components/{foundational => core}/networkmanager.md (88%) rename com.unity.netcode.gameobjects/Documentation~/components/{foundational => core}/networkobject.md (97%) rename com.unity.netcode.gameobjects/Documentation~/components/{foundational => core}/playerobjects.md (96%) delete mode 100644 com.unity.netcode.gameobjects/Documentation~/components/foundational/foundationalcomponents.md delete mode 100644 com.unity.netcode.gameobjects/Documentation~/components/foundational/networkbehaviour.md create mode 100644 com.unity.netcode.gameobjects/Documentation~/components/helper/attachablebehaviour.md create mode 100644 com.unity.netcode.gameobjects/Documentation~/components/helper/attachablenode.md create mode 100644 com.unity.netcode.gameobjects/Documentation~/components/helper/componentcontroller.md create mode 100644 com.unity.netcode.gameobjects/Documentation~/components/helper/helpercomponents.md rename com.unity.netcode.gameobjects/Documentation~/components/{Helpers => helper}/networkanimator.md (54%) rename com.unity.netcode.gameobjects/Documentation~/components/{Helpers => helper}/networktransform.md (95%) diff --git a/Examples/CharacterControllerMovingBodies/Readme.md b/Examples/CharacterControllerMovingBodies/Readme.md index 2403b27a35..539c746856 100644 --- a/Examples/CharacterControllerMovingBodies/Readme.md +++ b/Examples/CharacterControllerMovingBodies/Readme.md @@ -1,11 +1,11 @@ # Netcode for GameObjects Smooth Transform Space Transitions ## Non-Rigidbody CharacterController Parenting with Moving Bodies ![image](https://github.com/user-attachments/assets/096953ca-5d7d-40d5-916b-72212575d258) -This example provides you with the fundamental building blocks for smooth synchronized transitions between two non-rigidbody based objects. This includes transitioning from world to local, local to world, and local to local transform spaces. +This example provides you with the fundamental building blocks for smooth synchronized transitions between two non-rigidbody based objects. This includes transitioning from world to local, local to world, and local to local transform spaces. ### The `CharacterController` ![image](https://github.com/user-attachments/assets/13c627bd-920d-40c8-8947-69aa37b44ebf) -The `CharacterController` component is assigned to the `PlayerNoRigidbody` player prefab. It includes a `MoverScriptNoRigidbody` that handles all of the player's motion and includes some additional "non-rigidbody to non-rigidbody" collision handling logic that is applied when a player bumps into a rotation and/or moving body. The player prefab includes a child "PlayerBallPrime" that rotates around the player in local space (nested `NetworkTransform`), and the "PlayerBallPrime" has 3 children ("PlayerBallChild1-3") that each rotates around a different axis of the "PlayerBallPrime". While the end resulting effect is kind of cool looking, they provide a point of reference as to whether there is any deviation of each child's given axial path relative to each parent level. Additionally, it shows how tick synchronized nested `NetworkTransform` components keep synchronized with their parent and how that persists when the parent is parented or has its parent removed. +The `CharacterController` component is assigned to the `PlayerNoRigidbody` player prefab. It includes a `MoverScriptNoRigidbody` that handles all of the player's motion and includes some additional "non-rigidbody to non-rigidbody" collision handling logic that is applied when a player bumps into a rotation and/or moving body. The player prefab includes a child "PlayerBallPrime" that rotates around the player in local space (nested NetworkTransform), and the "PlayerBallPrime" has 3 children ("PlayerBallChild1-3") that each rotates around a different axis of the "PlayerBallPrime". While the end resulting effect is kind of cool looking, they provide a point of reference as to whether there is any deviation of each child's given axial path relative to each parent level. Additionally, it shows how tick synchronized nested NetworkTransform components keep synchronized with their parent and how that persists when the parent is parented or has its parent removed. ### Rotating Bodies #### StationaryBodyA&B diff --git a/Examples/OverridingScenesAndPrefabs/Readme.md b/Examples/OverridingScenesAndPrefabs/Readme.md index f411aae755..68fc73cef8 100644 --- a/Examples/OverridingScenesAndPrefabs/Readme.md +++ b/Examples/OverridingScenesAndPrefabs/Readme.md @@ -5,10 +5,10 @@ _Supports using the client-server and distributed authority network topologies._ This example, based on the [Netcode for GameObjects Smooth Transform Space Transitions](https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/tree/example/server-client-unique-scenes-and-prefabs/Examples/CharacterControllerMovingBodies), provides and overview of how to use: - [`NetworkPrefabHandler`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.0/api/Unity.Netcode.NetworkPrefabHandler.html) as a way to dynamically control overriding network prefabs and how they are instantiated. - - For this example, the prefab handler is overriding the player prefab. + - For this example, the prefab handler is overriding the player prefab. - *You will only see the end result of this portion of the example by running a server instance (i.e. not host) as that will create instances of the ServerPlayer network prefab instead of the ClientPlayer network prefab.* -- [`NetworkSceneManager.SetClientSynchronizationMode`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.0/api/Unity.Netcode.NetworkSceneManager.html#Unity_Netcode_NetworkSceneManager_SetClientSynchronizationMode_UnityEngine_SceneManagement_LoadSceneMode_) to change the default client synchronization mode (SingleMode) to an additive client synchronization mode. - - Additive client synchronization mode will prevent already existing preloaded scenes from being unloaded and will use them, as opposed to reloading the same scene, during a client's initial synchronization. +- [`NetworkSceneManager.SetClientSynchronizationMode`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.0/api/Unity.Netcode.NetworkSceneManager.html#Unity_Netcode_NetworkSceneManager_SetClientSynchronizationMode_UnityEngine_SceneManagement_LoadSceneMode_) to change the default client synchronization mode (SingleMode) to an additive client synchronization mode. + - Additive client synchronization mode will prevent already existing preloaded scenes from being unloaded and will use them, as opposed to reloading the same scene, during a client's initial synchronization. - *This is a server-side only setting that gets sent to the clients during the initial synchronization process.* - [`NetworkSceneManager.VerifySceneBeforeLoading`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.0/api/Unity.Netcode.NetworkSceneManager.html#Unity_Netcode_NetworkSceneManager_VerifySceneBeforeLoading) in order to control what scenes the server will include when sending the synchronization message to newly connecting clients. @@ -19,14 +19,14 @@ This example uses unity services. Upon loading the project for the first time, y ## Terminology ### Shared Scenes -These are scenes that will be synchronized between a server or session owner and used when a client runs through the initial synchronization process. -- You can populate these scenes with in-scene placed or dynamically spawned NetworkObjects. +These are scenes that will be synchronized between a server or session owner and used when a client runs through the initial synchronization process. +- You can populate these scenes with in-scene placed or dynamically spawned NetworkObjects. - These scenes **must be** within the scenes in build list. ### Local Scenes -These are scenes that are always only local to the application instances (server or client) and will not be synchronized. -- You should not populate these scenes with NetworkObjects. - -However, this example includes one of several ways you can associate a `MonoBehaviour` with a `NetworkBehaviour`. +These are scenes that are always only local to the application instances (server or client) and will not be synchronized. +- You should not populate these scenes with NetworkObjects. + -However, this example includes one of several ways you can associate a `MonoBehaviour` with a NetworkBehaviour. - These scenes can be dynamically created, included in the scenes in build list, or be an addressable loaded at some point prior to connecting or while connected to a session. ## Client Synchronization and Scene Validation @@ -54,16 +54,16 @@ The first scene loaded. Contains a `NetworkManagerBootstrapper` in-scene placed #### NetworkManager Bootstrapper (component) ![image](Images/NetworkManagerBootstrapperProperties.png) -Handles the pre-network session menu interface along with connect and disconnect events. Since it is derived from `NetworkManager`, it also defines the network session configuration (i.e. `NetworkConfig`). The `NetworkManagerBootstrapper` in-scene placed `GameObject` gets loaded into the DDOL scene automatically and will persist throughout the application life time. This derived class requires the `SceneBootstrapLoader` component. +Handles the pre-network session menu interface along with connect and disconnect events. Since it is derived from NetworkManager, it also defines the network session configuration (i.e. `NetworkConfig`). The `NetworkManagerBootstrapper` in-scene placed `GameObject` gets loaded into the DDOL scene automatically and will persist throughout the application life time. This derived class requires the `SceneBootstrapLoader` component. #### Scene Bootstrap Loader (component) ![image](Images/SceneBootstrapLoader.png) -This component handles preloading scenes for both the client(s) and server. Upon being started, the `NetworkManagerBootstrapper` component will invoke `SceneBootstrapLoader.LoadMainMenu` method that kicks off the scene preloading process. +This component handles preloading scenes for both the client(s) and server. Upon being started, the `NetworkManagerBootstrapper` component will invoke `SceneBootstrapLoader.LoadMainMenu` method that kicks off the scene preloading process. - **Default Active Scene Asset:** There is always an active scene. For this example, the default active scene is the same on both the client and server relative properties. *The active scene is always (and should always) be a "shared scene".* - This could represent a lobby or network session main menu (i.e. create or join session). - Both the client and the server preload this scene prior to starting a network session. -- **Local Scene Assets:** There could be times where you want to load scenes specific to the `NetworkManager` instance type (i.e. client, host, or server). +- **Local Scene Assets:** There could be times where you want to load scenes specific to the NetworkManager instance type (i.e. client, host, or server). - These scenes are not synchronized by a server (client-server) or session owner (distributed authority). - Having different locally loaded scenes is typically more common in a client-server network topology. - In a distributed authority network topology, it is more common to keep all scenes synchronized but you might want to load non-synchronized scenes (i.e. menu interface for settings etc). @@ -72,11 +72,11 @@ This component handles preloading scenes for both the client(s) and server. Upon - If the server synchronizes any scenes from the share scene assets with a client that already has those scene loaded, then those locally loaded scenes on the client side will be used during synchronization. - Depending upon how many scenes you want to synchronize and/or how large one or more scenes are, preloading scenes can reduce synchronization time for clients. The `NetworkManagerBootstrapper` uses the `SceneBootstrapLoader` component to start the creation or joining of a network session. The logical flow looks like: -- `NetworkManagerBootstrapper` invokes `SceneBootstrapLoader.StartSession` when you click one of the (very simple) main menu buttons and passes in the mode/type of `NetworkManager` to start. -- Based on the `NetworkManager` type being started, the `SceneBootstrapLoader` will then: +- `NetworkManagerBootstrapper` invokes `SceneBootstrapLoader.StartSession` when you click one of the (very simple) main menu buttons and passes in the mode/type of NetworkManager to start. +- Based on the NetworkManager type being started, the `SceneBootstrapLoader` will then: - Load the default active scene using the `UnityEngine.SceneManagement.SceneManager`. - Load the local scenes using the `UnityEngine.SceneManagement.SceneManager`. - - Then it will create or join a network session by either starting the `NetworkManager` or connecting to the sesssion via multiplayer services. + - Then it will create or join a network session by either starting the NetworkManager or connecting to the sesssion via multiplayer services. - _Server or Session Owner only:_ - If any, load the shared (i.e. synchronized) scene assets using the `NetworkSceneManager` @@ -87,7 +87,7 @@ This `MonoBehaviour` component implements the `INetworkPrefabInstanceHandler` in - Network Prefab: This is the network prefab that you want to override. In this example, it is what is used to spawn a server-side player prefab and is what is defined within the `NetworkManagerBootstrapper` component. - Network Prefab Override: This is what is used to spawn a player prefab on the client-side. -At runtime the local `NetworkManager` instance is a client/host or server and will spawn either the ClientPlayer or ServerPlayer prefab. The `NetworkPrefabOverrideHandler` does not need to be a `NetworkBehaviour` and sometimes (especially for overriding the player prefab) it is better to register prefab handlers prior to starting the `NetworkManager`. +At runtime the local NetworkManager instance is a client/host or server and will spawn either the ClientPlayer or ServerPlayer prefab. The `NetworkPrefabOverrideHandler` does not need to be a NetworkBehaviour and sometimes (especially for overriding the player prefab) it is better to register prefab handlers prior to starting the NetworkManager. ## Input Controls The following is a list of the input controls used in this project: diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 179d9a84e4..a1d14792a2 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -14,7 +14,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Added `AttachableBehaviour` helper component to provide an alternate approach to parenting items without using the `NetworkObject` parenting. (#3518) - Added `AttachableNode` helper component that is used by `AttachableBehaviour` as the target node for parenting. (#3518) - Added `ComponentController` helper component that can be used to synchronize the enabling and disabling of components and can be used in conjunction with `AttachableBehaviour`. (#3518) -- Added `NetworkBehaviour.OnNetworkPreDespawn` that is invoked before running through the despawn sequence for the `NetworkObject` and all `NetworkBehaviour` children of the `NetworkObject` being despawned. (#3518) +- Added `NetworkBehaviour.OnNetworkPreDespawn` that is invoked before running through the despawn sequence for the `NetworkObject` and all NetworkBehaviour children of the `NetworkObject` being despawned. (#3518) - Added methods `GetDefaultNetworkSettings` and `GetDefaultPipelineConfigurations` to `UnityTransport`. These can be used to retrieve the default settings and pipeline stages that are used by `UnityTransport`. This is useful when providing a custom driver constructor through `UnityTransport.s_DriverConstructor`, since it allows reusing or tuning the existing configuration instead of trying to recreate it. This means a transport with a custom driver can now easily benefit from most of the features of `UnityTransport`, like integration with the Network Simulator and Network Profiler from the multiplayer tools package. (#3501) - Added mappings between `ClientId` and `TransportId`. (#3516) - Added `NetworkPrefabInstanceHandlerWithData`, a variant of `INetworkPrefabInstanceHandler` that provides access to custom instantiation data directly within the `Instantiate()` method. (#3430) @@ -47,7 +47,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed - Fixed `NullReferenceException` on `NetworkList` when used without a NetworkManager in scene. (#3503) -- Fixed issue where `NetworkClient` could persist some settings if re-using the same `NetworkManager` instance. (#3491) +- Fixed issue where `NetworkClient` could persist some settings if re-using the same NetworkManager instance. (#3491) - Fixed issue where a pooled `NetworkObject` was not resetting the internal latest parent property when despawned. (#3491) - Fixed issue where the initial client synchronization pre-serialization process was not excluding spawned `NetworkObject` instances that already had pending visibility for the client being synchronized. (#3488) - Fixed issue where there was a potential for a small memory leak in the `ConnectionApprovedMessage`. (#3486) @@ -76,12 +76,12 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed - Fixed issue where the `NetworkObject.DontDestroyWithOwner` was not being honored. (#3477) -- Fixed issue where non-authority `NetworkTransform` instances would not allow non-synchronized axis values to be updated locally. (#3471) +- Fixed issue where non-authority NetworkTransform instances would not allow non-synchronized axis values to be updated locally. (#3471) - Fixed issue where invoking `NetworkObject.NetworkShow` and `NetworkObject.ChangeOwnership` consecutively within the same call stack location could result in an unnecessary change in ownership error message generated on the target client side. (#3468) -- Fixed issue where `NetworkVariable`s on a `NetworkBehaviour` could fail to synchronize changes if one has `NetworkVariableUpdateTraits` set and is dirty but is not ready to send. (#3466) +- Fixed issue where `NetworkVariable`s on a NetworkBehaviour could fail to synchronize changes if one has `NetworkVariableUpdateTraits` set and is dirty but is not ready to send. (#3466) - Fixed issue with the Distributed Authority connection sequence with scene management enabled where the `ClientConnected` event was fired before the client was synchronized. (#3459) - Fixed inconsistencies in the `OnSceneEvent` callback. (#3458) -- Fixed issues with the `NetworkBehaviour` and `NetworkVariable` length safety checks. (#3405) +- Fixed issues with the NetworkBehaviour and `NetworkVariable` length safety checks. (#3405) - Fixed memory leaks when domain reload is disabled. (#3427) - Fixed issue where disabling the physics or physics2D package modules could result in a compilation error. (#3422) - Fixed an exception being thrown when unregistering a custom message handler from within the registered callback. (#3417) @@ -118,23 +118,23 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed -- Fixed issue where in-scene placed `NetworkObjects` could fail to synchronize its transform properly (especially without a `NetworkTransform`) if their parenting changes from the default when the scene is loaded and if the same scene remains loaded between network sessions while the parenting is completely different from the original hierarchy. (#3387) +- Fixed issue where in-scene placed `NetworkObjects` could fail to synchronize its transform properly (especially without a NetworkTransform) if their parenting changes from the default when the scene is loaded and if the same scene remains loaded between network sessions while the parenting is completely different from the original hierarchy. (#3387) - Fixed an issue in `UnityTransport` where the transport would accept sends on invalid connections, leading to a useless memory allocation and confusing error message. (#3382) - Fixed issue where the time delta that interpolators used would not be properly updated during multiple fixed update invocations within the same player loop frame. (#3355) - Fixed issue when using a distributed authority network topology and many clients attempt to connect simultaneously the session owner could max-out the maximum in-flight reliable messages allowed, start dropping packets, and some of the connecting clients would fail to fully synchronize. (#3350) - Fixed issue when using a distributed authority network topology and scene management was disabled clients would not be able to spawn any new network prefab instances until synchronization was complete. (#3350) -- Fixed issue where an owner that changes ownership, when using a distributed authority network topology, could yield identical previous and current owner identifiers. This could also cause `NetworkTransform` to fail to change ownership which would leave the previous owner still subscribed to network tick events. (#3347) +- Fixed issue where an owner that changes ownership, when using a distributed authority network topology, could yield identical previous and current owner identifiers. This could also cause NetworkTransform to fail to change ownership which would leave the previous owner still subscribed to network tick events. (#3347) - Fixed issue where the `MaximumInterpolationTime` could not be modified from within the inspector view or runtime. (#3337) - Fixed `ChangeOwnership` changing ownership to clients that are not observers. This also happened with automated object distribution. (#3323) - Fixed issue where `AnticipatedNetworkVariable` previous value returned by `AnticipatedNetworkVariable.OnAuthoritativeValueChanged` is updated correctly on the non-authoritative side. (#3306) - Fixed `OnClientConnectedCallback` passing incorrect `clientId` when scene management is disabled. (#3312) - Fixed issue where the `NetworkObject.Ownership` custom editor did not take the default "Everything" flag into consideration. (#3305) - Fixed DestroyObject flow on non-authority game clients. (#3291) -- Fixed exception being thrown when a `GameObject` with an associated `NetworkTransform` is disabled. (#3243) +- Fixed exception being thrown when a `GameObject` with an associated NetworkTransform is disabled. (#3243) - Fixed issue where the scene migration synchronization table was not cleaned up if the `GameObject` of a `NetworkObject` is destroyed before it should have been. (#3230) -- Fixed issue where the scene migration synchronization table was not cleaned up upon `NetworkManager` shutting down. (#3230) +- Fixed issue where the scene migration synchronization table was not cleaned up upon NetworkManager shutting down. (#3230) - Fixed `NetworkObject.DeferDespawn` to respect the `DestroyGameObject` parameter. (#3219) -- Fixed issue where a `NetworkObject` with nested `NetworkTransform` components of varying authority modes was not being taken into consideration and would break both the initial `NetworkTransform` synchronization and fail to properly handle synchronized state updates of the nested `NetworkTransform` components. (#3209) +- Fixed issue where a `NetworkObject` with nested NetworkTransform components of varying authority modes was not being taken into consideration and would break both the initial NetworkTransform synchronization and fail to properly handle synchronized state updates of the nested NetworkTransform components. (#3209) - Fixed issue with distributing parented children that have the distributable and/or transferrable permissions set and have the same owner as the root parent, that has the distributable permission set, were not being distributed to the same client upon the owning client disconnecting when using a distributed authority network topology. (#3203) - Fixed issue where a spawned `NetworkObject` that was registered with a prefab handler and owned by a client would invoke destroy more than once on the host-server side if the client disconnected while the `NetworkObject` was still spawned. (#3200) - Fixed issue where `NetworkVariableBase` derived classes were not being re-initialized if the associated `NetworkObject` instance was not destroyed and re-spawned. (#3181) @@ -159,24 +159,24 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed -- Fixed issue where the server, host, or session owner would not populate the in-scene place `NetworkObject` table if the scene was loaded prior to starting the `NetworkManager`. (#3177) +- Fixed issue where the server, host, or session owner would not populate the in-scene place `NetworkObject` table if the scene was loaded prior to starting the NetworkManager. (#3177) - Fixed issue where the `NetworkObjectIdHash` value could be incorrect when entering play mode while still in prefab edit mode with pending changes and using MPPM. (#3162) -- Fixed issue where a sever only `NetworkManager` instance would spawn the actual `NetworkPrefab`'s `GameObject` as opposed to creating an instance of it. (#3160) +- Fixed issue where a sever only NetworkManager instance would spawn the actual `NetworkPrefab`'s `GameObject` as opposed to creating an instance of it. (#3160) - Fixed issue where only the session owner (as opposed to all clients) would handle spawning prefab overrides properly when using a distributed authority network topology. (#3160) - Fixed issue where an exception was thrown when calling `NetworkManager.Shutdown` after calling `UnityTransport.Shutdown`. (#3118) - Fixed issue where `NetworkList` properties on in-scene placed `NetworkObject`s could cause small memory leaks when entering playmode. (#3147) -- Fixed in-scene `NertworkObject` synchronization issue when loading a scene with currently connected clients connected to a session created by a `NetworkManager` started as a server (i.e. not as a host). (#3133) -- Fixed issue where a `NetworkManager` started as a server would not add itself as an observer to in-scene placed `NetworkObject`s instantiated and spawned by a scene loading event. (#3133) +- Fixed in-scene `NertworkObject` synchronization issue when loading a scene with currently connected clients connected to a session created by a NetworkManager started as a server (i.e. not as a host). (#3133) +- Fixed issue where a NetworkManager started as a server would not add itself as an observer to in-scene placed `NetworkObject`s instantiated and spawned by a scene loading event. (#3133) - Fixed issue where spawning a player using `NetworkObject.InstantiateAndSpawn` or `NetworkSpawnManager.InstantiateAndSpawn` would not update the `NetworkSpawnManager.PlayerObjects` or assign the newly spawned player to the `NetworkClient.PlayerObject`. (#3122) - Fixed issue where queued UnitTransport (NetworkTransport) message batches were being sent on the next frame. They are now sent at the end of the frame during `PostLateUpdate`. (#3113) - Fixed issue where `NotOwnerRpcTarget` or `OwnerRpcTarget` were not using their replacements `NotAuthorityRpcTarget` and `AuthorityRpcTarget` which would invoke a warning. (#3111) - Fixed issue where client is removed as an observer from spawned objects when their player instance is despawned. (#3110) -- Fixed issue where `NetworkAnimator` would statically allocate write buffer space for `Animator` parameters that could cause a write error if the number of parameters exceeded the space allocated. (#3108) +- Fixed issue where NetworkAnimator would statically allocate write buffer space for `Animator` parameters that could cause a write error if the number of parameters exceeded the space allocated. (#3108) ### Changed - In-scene placed `NetworkObject`s have been made distributable when balancing object distribution after a connection event. (#3175) -- Optimised `NetworkVariable` and `NetworkTransform` related packets when in Distributed Authority mode. +- Optimised `NetworkVariable` and NetworkTransform related packets when in Distributed Authority mode. - The Debug Simulator section of the Unity Transport component was removed. This section was not functional anymore and users are now recommended to use the more featureful [Network Simulator](https://docs-multiplayer.unity3d.com/tools/current/tools-network-simulator/) tool from the Multiplayer Tools package instead. (#3121) ## [2.1.1] - 2024-10-18 @@ -187,13 +187,13 @@ Additional documentation and release notes are available at [Multiplayer Documen - Added `IContactEventHandlerWithInfo` that derives from `IContactEventHandler` that can be updated per frame to provide `ContactEventHandlerInfo` information to the `RigidbodyContactEventManager` when processing collisions. (#3094) - `ContactEventHandlerInfo.ProvideNonRigidBodyContactEvents`: When set to true, non-`Rigidbody` collisions with the registered `Rigidbody` will generate contact event notifications. (#3094) - `ContactEventHandlerInfo.HasContactEventPriority`: When set to true, the `Rigidbody` will be prioritized as the instance that generates the event if the `Rigidbody` colliding does not have priority. (#3094) -- Added a static `NetworkManager.OnInstantiated` event notification to be able to track when a new `NetworkManager` instance has been instantiated. (#3088) -- Added a static `NetworkManager.OnDestroying` event notification to be able to track when an existing `NetworkManager` instance is being destroyed. (#3088) +- Added a static `NetworkManager.OnInstantiated` event notification to be able to track when a new NetworkManager instance has been instantiated. (#3088) +- Added a static `NetworkManager.OnDestroying` event notification to be able to track when an existing NetworkManager instance is being destroyed. (#3088) ### Fixed - Fixed issue where `NetworkPrefabProcessor` would not mark the prefab list as dirty and prevent saving the `DefaultNetworkPrefabs` asset when only imports or only deletes were detected.(#3103) -- Fixed an issue where nested `NetworkTransform` components in owner authoritative mode cleared their initial settings on the server, causing improper synchronization. (#3099) +- Fixed an issue where nested NetworkTransform components in owner authoritative mode cleared their initial settings on the server, causing improper synchronization. (#3099) - Fixed issue with service not getting synchronized with in-scene placed `NetworkObject` instances when a session owner starts a `SceneEventType.Load` event. (#3096) - Fixed issue with the in-scene network prefab instance update menu tool where it was not properly updating scenes when invoked on the root prefab instance. (#3092) - Fixed an issue where newly synchronizing clients would always receive current `NetworkVariable` values, potentially causing issues with collections if there were pending updates. Now, pending state updates serialize previous values to avoid duplicates on new clients. (#3081) @@ -206,7 +206,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed -- Changed `NetworkConfig.AutoSpawnPlayerPrefabClientSide` is no longer automatically set when starting `NetworkManager`. (#3097) +- Changed `NetworkConfig.AutoSpawnPlayerPrefabClientSide` is no longer automatically set when starting NetworkManager. (#3097) - Updated `NetworkVariableDeltaMessage` so the server now forwards delta state updates from clients immediately, instead of waiting until the end of the frame or the next network tick. (#3081) ## [2.0.0] - 2024-09-12 @@ -215,28 +215,28 @@ Additional documentation and release notes are available at [Multiplayer Documen - Added tooltips for all of the `NetworkObject` component's properties. (#3052) - Added message size validation to named and unnamed message sending functions for better error messages. (#3049) -- Added "Check for NetworkObject Component" property to the Multiplayer->Netcode for GameObjects project settings. When disabled, this will bypass the in-editor `NetworkObject` check on `NetworkBehaviour` components. (#3031) +- Added "Check for NetworkObject Component" property to the Multiplayer->Netcode for GameObjects project settings. When disabled, this will bypass the in-editor `NetworkObject` check on NetworkBehaviour components. (#3031) - Added `NetworkTransform.SwitchTransformSpaceWhenParented` property that, when enabled, will handle the world to local, local to world, and local to local transform space transitions when interpolation is enabled. (#3013) -- Added `NetworkTransform.TickSyncChildren` that, when enabled, will tick synchronize nested and/or child `NetworkTransform` components to eliminate any potential visual jittering that could occur if the `NetworkTransform` instances get into a state where their state updates are landing on different network ticks. (#3013) +- Added `NetworkTransform.TickSyncChildren` that, when enabled, will tick synchronize nested and/or child NetworkTransform components to eliminate any potential visual jittering that could occur if the NetworkTransform instances get into a state where their state updates are landing on different network ticks. (#3013) - Added `NetworkObject.AllowOwnerToParent` property to provide the ability to allow clients to parent owned objects when running in a client-server network topology. (#3013) - Added `NetworkObject.SyncOwnerTransformWhenParented` property to provide a way to disable applying the server's transform information in the parenting message on the client owner instance which can be useful for owner authoritative motion models. (#3013) - Added `NetcodeEditorBase` editor helper class to provide easier modification and extension of the SDK's components. (#3013) ### Fixed -- Fixed issue where `NetworkAnimator` would send updates to non-observer clients. (#3057) +- Fixed issue where NetworkAnimator would send updates to non-observer clients. (#3057) - Fixed issue where an exception could occur when receiving a universal RPC for a `NetworkObject` that has been despawned. (#3052) - Fixed issue where a NetworkObject hidden from a client that is then promoted to be session owner was not being synchronized with newly joining clients.(#3051) - Fixed issue where clients could have a wrong time delta on `NetworkVariableBase` which could prevent from sending delta state updates. (#3045) - Fixed issue where setting a prefab hash value during connection approval but not having a player prefab assigned could cause an exception when spawning a player. (#3042) - Fixed issue where the `NetworkSpawnManager.HandleNetworkObjectShow` could throw an exception if one of the `NetworkObject` components to show was destroyed during the same frame. (#3030) - Fixed issue where the `NetworkManagerHelper` was continuing to check for hierarchy changes when in play mode. (#3026) -- Fixed issue with newly/late joined clients and `NetworkTransform` synchronization of parented `NetworkObject` instances. (#3013) +- Fixed issue with newly/late joined clients and NetworkTransform synchronization of parented `NetworkObject` instances. (#3013) - Fixed issue with smooth transitions between transform spaces when interpolation is enabled (requires `NetworkTransform.SwitchTransformSpaceWhenParented` to be enabled). (#3013) ### Changed -- Changed `NetworkTransformEditor` now uses `NetworkTransform` as the base type class to assure it doesn't display a foldout group when using the base `NetworkTransform` component class. (#3052) +- Changed `NetworkTransformEditor` now uses NetworkTransform as the base type class to assure it doesn't display a foldout group when using the base NetworkTransform component class. (#3052) - Changed `NetworkAnimator.Awake` is now a protected virtual method. (#3052) - Changed when invoking `NetworkManager.ConnectionManager.DisconnectClient` during a distributed authority session a more appropriate message is logged. (#3052) - Changed `NetworkTransformEditor` so it now derives from `NetcodeEditorBase`. (#3013) @@ -252,10 +252,10 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed -- Fixed issue where nested `NetworkTransform` components were not getting updated. (#3016) -- Fixed issue by adding null checks in `NetworkVariableBase.CanClientRead` and `NetworkVariableBase.CanClientWrite` methods to ensure safe access to `NetworkBehaviour`. (#3012) +- Fixed issue where nested NetworkTransform components were not getting updated. (#3016) +- Fixed issue by adding null checks in `NetworkVariableBase.CanClientRead` and `NetworkVariableBase.CanClientWrite` methods to ensure safe access to NetworkBehaviour. (#3012) - Fixed issue where `FixedStringSerializer` was using `NetworkVariableSerialization.AreEqual` to determine if two bytes were equal causes an exception to be thrown due to no byte serializer having been defined. (#3009) -- Fixed Issue where a state with dual triggers, inbound and outbound, could cause a false layer to layer state transition message to be sent to non-authority `NetworkAnimator` instances and cause a warning message to be logged. (#3008) +- Fixed Issue where a state with dual triggers, inbound and outbound, could cause a false layer to layer state transition message to be sent to non-authority NetworkAnimator instances and cause a warning message to be logged. (#3008) - Fixed issue using collections within `NetworkVariable` where the collection would not detect changes to items or nested items. (#3004) - Fixed issue where `List`, `Dictionary`, and `HashSet` collections would not uniquely duplicate nested collections. (#3004) - Fixed issue where `NotAuthorityTarget` would include the service observer in the list of targets to send the RPC to as opposed to excluding the service observer as it should. (#3000) @@ -263,7 +263,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed -- Changed `NetworkAnimator` to automatically switch to owner authoritative mode when using a distributed authority network topology. (#3021) +- Changed NetworkAnimator to automatically switch to owner authoritative mode when using a distributed authority network topology. (#3021) - Changed permissions exception thrown in `NetworkList` to exiting early with a logged error that is now a unified permissions message within `NetworkVariableBase`. (#3004) - Changed permissions exception thrown in `NetworkVariable.Value` to exiting early with a logged error that is now a unified permissions message within `NetworkVariableBase`. (#3004) @@ -307,17 +307,17 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed -- Fixed issue when `NetworkTransform` half float precision is enabled and ownership changes the current base position was not being synchronized. (#2948) +- Fixed issue when NetworkTransform half float precision is enabled and ownership changes the current base position was not being synchronized. (#2948) - Fixed issue where `OnClientConnected` not being invoked on the session owner when connecting to a new distributed authority session. (#2948) - Fixed issue where Rigidbody micro-motion (i.e. relatively small velocities) would result in non-authority instances slightly stuttering as the body would come to a rest (i.e. no motion). Now, the threshold value can increase at higher velocities and can decrease slightly below the provided threshold to account for this. (#2948) ### Changed -- Changed `NetworkAnimator` no longer requires the `Animator` component to exist on the same `GameObject`. (#2957) +- Changed NetworkAnimator no longer requires the `Animator` component to exist on the same `GameObject`. (#2957) - Changed `NetworkObjectReference` and `NetworkBehaviourReference` to allow null references when constructing and serializing. (#2957) - Changed the client's owned objects is now returned (`NetworkClient` and `NetworkSpawnManager`) as an array as opposed to a list for performance purposes. (#2948) - Changed `NetworkTransfrom.TryCommitTransformToServer` to be internal as it will be removed by the final 2.0.0 release. (#2948) -- Changed `NetworkTransformEditor.OnEnable` to a virtual method to be able to customize a `NetworkTransform` derived class by creating a derived editor control from `NetworkTransformEditor`. (#2948) +- Changed `NetworkTransformEditor.OnEnable` to a virtual method to be able to customize a NetworkTransform derived class by creating a derived editor control from `NetworkTransformEditor`. (#2948) ## [2.0.0-exp.5] - 2024-06-03 @@ -339,15 +339,15 @@ Additional documentation and release notes are available at [Multiplayer Documen - Added `NetworkRigidbodyBase.AttachToFixedJoint` and `NetworkRigidbodyBase.DetachFromFixedJoint` to replace parenting for rigid bodies that have `NetworkRigidbodyBase.UseRigidBodyForMotion` enabled. (#2933) - Added `NetworkBehaviour.OnNetworkPreSpawn` and `NetworkBehaviour.OnNetworkPostSpawn` methods that provide the ability to handle pre and post spawning actions during the `NetworkObject` spawn sequence. (#2912) -- Added a client-side only `NetworkBehaviour.OnNetworkSessionSynchronized` convenience method that is invoked on all `NetworkBehaviour`s after a newly joined client has finished synchronizing with the network session in progress. (#2912) +- Added a client-side only `NetworkBehaviour.OnNetworkSessionSynchronized` convenience method that is invoked on all NetworkBehaviours after a newly joined client has finished synchronizing with the network session in progress. (#2912) - Added `NetworkBehaviour.OnInSceneObjectsSpawned` convenience method that is invoked when all in-scene `NetworkObject`s have been spawned after a scene has been loaded or upon a host or server starting. (#2912) ### Fixed - Fixed issue where non-authoritative rigid bodies with `NetworkRigidbodyBase.UseRigidBodyForMotion` enabled would constantly log errors about the renderTime being before `StartTimeConsumed`. (#2933) - Fixed issue where in-scene placed NetworkObjects could be destroyed if a client disconnects early and/or before approval. (#2924) -- Fixed issue where a `NetworkObject` component's associated `NetworkBehaviour` components would not be detected if scene loading is disabled in the editor and the currently loaded scene has in-scene placed `NetworkObject`s. (#2912) -- Fixed issue where an in-scene placed `NetworkObject` with `NetworkTransform` that is also parented under a `GameObject` would not properly synchronize when the parent `GameObject` had a world space position other than 0,0,0. (#2898) +- Fixed issue where a `NetworkObject` component's associated NetworkBehaviour components would not be detected if scene loading is disabled in the editor and the currently loaded scene has in-scene placed `NetworkObject`s. (#2912) +- Fixed issue where an in-scene placed `NetworkObject` with NetworkTransform that is also parented under a `GameObject` would not properly synchronize when the parent `GameObject` had a world space position other than 0,0,0. (#2898) ### Changed @@ -361,13 +361,13 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added - Added updates to all internal messages to account for a distributed authority network session connection. (#2863) -- Added `NetworkRigidbodyBase` that provides users with a more customizable network rigidbody, handles both `Rigidbody` and `Rigidbody2D`, and provides an option to make `NetworkTransform` use the rigid body for motion. (#2863) +- Added `NetworkRigidbodyBase` that provides users with a more customizable network rigidbody, handles both `Rigidbody` and `Rigidbody2D`, and provides an option to make NetworkTransform use the rigid body for motion. (#2863) - For a customized `NetworkRigidbodyBase` class: - `NetworkRigidbodyBase.AutoUpdateKinematicState` provides control on whether the kinematic setting will be automatically set or not when ownership changes. - `NetworkRigidbodyBase.AutoSetKinematicOnDespawn` provides control on whether isKinematic will automatically be set to true when the associated `NetworkObject` is despawned. - `NetworkRigidbodyBase.Initialize` is a protected method that, when invoked, will initialize the instance. This includes options to: - Set whether using a `RigidbodyTypes.Rigidbody` or `RigidbodyTypes.Rigidbody2D`. - - Includes additional optional parameters to set the `NetworkTransform`, `Rigidbody`, and `Rigidbody2d` to use. + - Includes additional optional parameters to set the NetworkTransform, `Rigidbody`, and `Rigidbody2d` to use. - Provides additional public methods: - `NetworkRigidbodyBase.GetPosition` to return the position of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). - `NetworkRigidbodyBase.GetRotation` to return the rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting). @@ -383,13 +383,13 @@ Additional documentation and release notes are available at [Multiplayer Documen - `NetworkRigidbodyBase.IsKinematic` to determine if the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting) is currently kinematic. - `NetworkRigidbodyBase.SetIsKinematic` to set the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting) current kinematic state. - `NetworkRigidbodyBase.ResetInterpolation` to reset the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting) back to its original interpolation value when initialized. - - Now includes a `MonoBehaviour.FixedUpdate` implementation that will update the assigned `NetworkTransform` when `NetworkRigidbodyBase.UseRigidBodyForMotion` is true. (#2863) + - Now includes a `MonoBehaviour.FixedUpdate` implementation that will update the assigned NetworkTransform when `NetworkRigidbodyBase.UseRigidBodyForMotion` is true. (#2863) - Added `RigidbodyContactEventManager` that provides a more optimized way to process collision enter and collision stay events as opposed to the `Monobehaviour` approach. (#2863) - Can be used in client-server and distributed authority modes, but is particularly useful in distributed authority. -- Added rigid body motion updates to `NetworkTransform` which allows users to set interolation on rigid bodies. (#2863) - - Extrapolation is only allowed on authoritative instances, but custom class derived from `NetworkRigidbodyBase` or `NetworkRigidbody` or `NetworkRigidbody2D` automatically switches non-authoritative instances to interpolation if set to extrapolation. -- Added distributed authority mode support to `NetworkAnimator`. (#2863) -- Added session mode selection to `NetworkManager` inspector view. (#2863) +- Added rigid body motion updates to NetworkTransform which allows users to set interolation on rigid bodies. (#2863) + - Extrapolation is only allowed on authoritative instances, but custom class derived from `NetworkRigidbodyBase` or NetworkRigidBody or `NetworkRigidbody2D` automatically switches non-authoritative instances to interpolation if set to extrapolation. +- Added distributed authority mode support to NetworkAnimator. (#2863) +- Added session mode selection to NetworkManager inspector view. (#2863) - Added distributed authority permissions feature. (#2863) - Added distributed authority mode specific `NetworkObject` permissions flags (Distributable, Transferable, and RequestRequired). (#2863) - Added distributed authority mode specific `NetworkObject.SetOwnershipStatus` method that applies one or more `NetworkObject` instance's ownership flags. If updated when spawned, the ownership permission changes are synchronized with the other connected clients. (#2863) @@ -409,8 +409,8 @@ Additional documentation and release notes are available at [Multiplayer Documen - Added distributed authority mode specific `NetworkConfig.AutoSpawnPlayerPrefabClientSide` property (default is true) to provide control over the automatic spawning of player prefabs on the local client side. (#2863) - Added distributed authority mode specific `NetworkManager.OnFetchLocalPlayerPrefabToSpawn` callback that, when assigned, will allow the local client to provide the player prefab to be spawned for the local client. (#2863) - This is only invoked if the `NetworkConfig.AutoSpawnPlayerPrefabClientSide` property is set to true. -- Added distributed authority mode specific `NetworkBehaviour.HasAuthority` property that determines if the local client has authority over the associated `NetworkObject` instance (typical use case is within a `NetworkBehaviour` script much like that of `IsServer` or `IsClient`). (#2863) -- Added distributed authority mode specific `NetworkBehaviour.IsSessionOwner` property that determines if the local client is the session owner (typical use case would be to determine if the local client can has scene management authority within a `NetworkBehaviour` script). (#2863) +- Added distributed authority mode specific `NetworkBehaviour.HasAuthority` property that determines if the local client has authority over the associated `NetworkObject` instance (typical use case is within a NetworkBehaviour script much like that of `IsServer` or `IsClient`). (#2863) +- Added distributed authority mode specific `NetworkBehaviour.IsSessionOwner` property that determines if the local client is the session owner (typical use case would be to determine if the local client can has scene management authority within a NetworkBehaviour script). (#2863) - Added support for distributed authority mode scene management where the currently assigned session owner can start scene events (i.e. scene loading and scene unloading). (#2863) ### Fixed @@ -423,18 +423,18 @@ Additional documentation and release notes are available at [Multiplayer Documen - Changed client side awareness of other clients is now the same as a server or host. (#2863) - Changed `NetworkManager.ConnectedClients` can now be accessed by both server and clients. (#2863) - Changed `NetworkManager.ConnectedClientsList` can now be accessed by both server and clients. (#2863) -- Changed `NetworkTransform` defaults to owner authoritative when connected to a distributed authority session. (#2863) +- Changed NetworkTransform defaults to owner authoritative when connected to a distributed authority session. (#2863) - Changed `NetworkVariable` defaults to owner write and everyone read permissions when connected to a distributed authority session (even if declared with server read or write permissions). (#2863) -- Changed `NetworkObject` no longer implements the `MonoBehaviour.Update` method in order to determine whether a `NetworkObject` instance has been migrated to a different scene. Instead, only `NetworkObjects` with the `SceneMigrationSynchronization` property set will be updated internally during the `NetworkUpdateStage.PostLateUpdate` by `NetworkManager`. (#2863) -- Changed `NetworkManager` inspector view layout where properties are now organized by category. (#2863) -- Changed `NetworkTransform` to now use `NetworkTransformMessage` as opposed to named messages for NetworkTransformState updates. (#2810) +- Changed `NetworkObject` no longer implements the `MonoBehaviour.Update` method in order to determine whether a `NetworkObject` instance has been migrated to a different scene. Instead, only `NetworkObjects` with the `SceneMigrationSynchronization` property set will be updated internally during the `NetworkUpdateStage.PostLateUpdate` by NetworkManager. (#2863) +- Changed NetworkManager inspector view layout where properties are now organized by category. (#2863) +- Changed NetworkTransform to now use `NetworkTransformMessage` as opposed to named messages for NetworkTransformState updates. (#2810) - Changed `CustomMessageManager` so it no longer attempts to register or "unregister" a null or empty string and will log an error if this condition occurs. (#2807) ## [1.9.1] - 2024-04-18 ### Added - Added `AnticipatedNetworkVariable`, which adds support for client anticipation of NetworkVariable values, allowing for more responsive game play (#2820) -- Added `AnticipatedNetworkTransform`, which adds support for client anticipation of `NetworkTransform`s (#2820) +- Added `AnticipatedNetworkTransform`, which adds support for client anticipation of NetworkTransforms (#2820) - Added `NetworkVariableBase.ExceedsDirtinessThreshold` to allow network variables to throttle updates by only sending updates when the difference between the current and previous values exceeds a threshold. (This is exposed in NetworkVariable with the callback NetworkVariable.CheckExceedsDirtinessThreshold) (#2820) - Added `NetworkVariableUpdateTraits`, which add additional throttling support: `MinSecondsBetweenUpdates` will prevent the `NetworkVariable` from sending updates more often than the specified time period (even if it exceeds the dirtiness threshold), while `MaxSecondsBetweenUpdates` will force a dirty `NetworkVariable` to send an update after the specified time period even if it has not yet exceeded the dirtiness threshold. (#2820) - Added virtual method `NetworkVariableBase.OnInitialize()` which can be used by `NetworkVariable` subclasses to add initialization code (#2820) @@ -448,7 +448,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed - Fixed issue where `NetworkTransformEditor` would throw and exception if you excluded the physics package. (#2871) -- Fixed issue where `NetworkTransform` could not properly synchronize its base position when using half float precision. (#2845) +- Fixed issue where NetworkTransform could not properly synchronize its base position when using half float precision. (#2845) - Fixed issue where the host was not invoking `OnClientDisconnectCallback` for its own local client when internally shutting down. (#2822) - Fixed issue where NetworkTransform could potentially attempt to "unregister" a named message prior to it being registered. (#2807) - Fixed issue where in-scene placed `NetworkObject`s with complex nested children `NetworkObject`s (more than one child in depth) would not synchronize properly if WorldPositionStays was set to true. (#2796) @@ -456,8 +456,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed - Changed `NetworkObjectReference` and `NetworkBehaviourReference` to allow null references when constructing and serializing. (#2874) -- Changed `NetworkAnimator` no longer requires the `Animator` component to exist on the same `GameObject`. (#2872) -- Changed `NetworkTransform` to now use `NetworkTransformMessage` as opposed to named messages for NetworkTransformState updates. (#2810) +- Changed NetworkAnimator no longer requires the `Animator` component to exist on the same `GameObject`. (#2872) +- Changed NetworkTransform to now use `NetworkTransformMessage` as opposed to named messages for NetworkTransformState updates. (#2810) - Changed `CustomMessageManager` so it no longer attempts to register or "unregister" a null or empty string and will log an error if this condition occurs. (#2807) ## [1.8.1] - 2024-02-05 @@ -490,10 +490,10 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed issue where if a host or server shutdown while a client owned NetworkObjects (other than the player) it would throw an exception. (#2789) - Fixed issue where setting values on a `NetworkVariable` or `NetworkList` within `OnNetworkDespawn` during a shutdown sequence would throw an exception. (#2789) - Fixed issue where a teleport state could potentially be overridden by a previous unreliable delta state. (#2777) -- Fixed issue where `NetworkTransform` was using the `NetworkManager.ServerTime.Tick` as opposed to `NetworkManager.NetworkTickSystem.ServerTime.Tick` during the authoritative side's tick update where it performed a delta state check. (#2777) +- Fixed issue where NetworkTransform was using the `NetworkManager.ServerTime.Tick` as opposed to `NetworkManager.NetworkTickSystem.ServerTime.Tick` during the authoritative side's tick update where it performed a delta state check. (#2777) - Fixed issue where a parented in-scene placed NetworkObject would be destroyed upon a client or server exiting a network session but not unloading the original scene in which the NetworkObject was placed. (#2737) - Fixed issue where during client synchronization and scene loading, when client synchronization or the scene loading mode are set to `LoadSceneMode.Single`, a `CreateObjectMessage` could be received, processed, and the resultant spawned `NetworkObject` could be instantiated in the client's currently active scene that could, towards the end of the client synchronization or loading process, be unloaded and cause the newly created `NetworkObject` to be destroyed (and throw and exception). (#2735) -- Fixed issue where a `NetworkTransform` instance with interpolation enabled would result in wide visual motion gaps (stuttering) under above normal latency conditions and a 1-5% or higher packet are drop rate. (#2713) +- Fixed issue where a NetworkTransform instance with interpolation enabled would result in wide visual motion gaps (stuttering) under above normal latency conditions and a 1-5% or higher packet are drop rate. (#2713) - Fixed issue where you could not have multiple source network prefab overrides targeting the same network prefab as their override. (#2710) ### Changed @@ -503,7 +503,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Changed `NetworkTransform.SetState` (and related methods) now are cumulative during a fractional tick period and sent on the next pending tick. (#2777) - `NetworkManager.ConnectedClientsIds` is now accessible on the client side and will contain the list of all clients in the session, including the host client if the server is operating in host mode (#2762) - Changed `NetworkSceneManager` to return a `SceneEventProgress` status and not throw exceptions for methods invoked when scene management is disabled and when a client attempts to access a `NetworkSceneManager` method by a client. (#2735) -- Changed `NetworkTransform` authoritative instance tick registration so a single `NetworkTransform` specific tick event update will update all authoritative instances to improve perofmance. (#2713) +- Changed NetworkTransform authoritative instance tick registration so a single NetworkTransform specific tick event update will update all authoritative instances to improve perofmance. (#2713) - Changed `NetworkPrefabs.OverrideToNetworkPrefab` dictionary is no longer used/populated due to it ending up being related to a regression bug and not allowing more than one override to be assigned to a network prefab asset. (#2710) - Changed in-scene placed `NetworkObject`s now store their source network prefab asset's `GlobalObjectIdHash` internally that is used, when scene management is disabled, by clients to spawn the correct prefab even if the `NetworkPrefab` entry has an override. This does not impact dynamically spawning the same prefab which will yield the override on both host and client. (#2710) - Changed in-scene placed `NetworkObject`s no longer require a `NetworkPrefab` entry with `GlobalObjectIdHash` override in order for clients to properly synchronize. (#2710) @@ -518,7 +518,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed - Fixed a bug where having a class with Rpcs that inherits from a class without Rpcs that inherits from NetworkVariable would cause a compile error. (#2751) -- Fixed issue where `NetworkBehaviour.Synchronize` was not truncating the write buffer if nothing was serialized during `NetworkBehaviour.OnSynchronize` causing an additional 6 bytes to be written per `NetworkBehaviour` component instance. (#2749) +- Fixed issue where `NetworkBehaviour.Synchronize` was not truncating the write buffer if nothing was serialized during `NetworkBehaviour.OnSynchronize` causing an additional 6 bytes to be written per NetworkBehaviour component instance. (#2749) ## [1.7.0] - 2023-10-11 @@ -530,7 +530,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Added `GenerateSerializationForGenericParameterAttribute`, which can be applied to user-created Network Variable types to ensure the codegen generates serialization for the generic types they wrap. (#2694) - Added `GenerateSerializationForTypeAttribute`, which can be applied to any class or method to ensure the codegen generates serialization for the specific provided type. (#2694) - Exposed `NetworkVariableSerialization.Read`, `NetworkVariableSerialization.Write`, `NetworkVariableSerialization.AreEqual`, and `NetworkVariableSerialization.Duplicate` to further support the creation of user-created network variables by allowing users to access the generated serialization methods and serialize generic types efficiently without boxing. (#2694) -- Added `NetworkVariableBase.MarkNetworkBehaviourDirty` so that user-created network variable types can mark their containing `NetworkBehaviour` to be processed by the update loop. (#2694) +- Added `NetworkVariableBase.MarkNetworkBehaviourDirty` so that user-created network variable types can mark their containing NetworkBehaviour to be processed by the update loop. (#2694) ### Fixed @@ -542,9 +542,9 @@ Additional documentation and release notes are available at [Multiplayer Documen - NetworkVariables of non-integer types will no longer break the inspector (#2714) - NetworkVariables with NonSerializedAttribute will not appear in the inspector (#2714) - Fixed issue where `UnityTransport` would attempt to establish WebSocket connections even if using UDP/DTLS Relay allocations when the build target was WebGL. This only applied to working in the editor since UDP/DTLS can't work in the browser. (#2695) -- Fixed issue where a `NetworkBehaviour` component's `OnNetworkDespawn` was not being invoked on the host-server side for an in-scene placed `NetworkObject` when a scene was unloaded (during a scene transition) and the `NetworkBehaviour` component was positioned/ordered before the `NetworkObject` component. (#2685) +- Fixed issue where a NetworkBehaviour component's `OnNetworkDespawn` was not being invoked on the host-server side for an in-scene placed `NetworkObject` when a scene was unloaded (during a scene transition) and the NetworkBehaviour component was positioned/ordered before the `NetworkObject` component. (#2685) - Fixed issue where `SpawnWithObservers` was not being honored when `NetworkConfig.EnableSceneManagement` was disabled. (#2682) -- Fixed issue where `NetworkAnimator` was not internally tracking changes to layer weights which prevented proper layer weight synchronization back to the original layer weight value. (#2674) +- Fixed issue where NetworkAnimator was not internally tracking changes to layer weights which prevented proper layer weight synchronization back to the original layer weight value. (#2674) - Fixed "writing past the end of the buffer" error when calling ResetDirty() on managed network variables that are larger than 256 bytes when serialized. (#2670) - Fixed issue where generation of the `DefaultNetworkPrefabs` asset was not enabled by default. (#2662) - Fixed issue where the `GlobalObjectIdHash` value could be updated but the asset not marked as dirty. (#2662) @@ -580,7 +580,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Bumped minimum Unity version supported to 2021.3 LTS - Fixed issue where `NetworkClient.OwnedObjects` was not returning any owned objects due to the `NetworkClient.IsConnected` not being properly set. (#2631) - Fixed a crash when calling TrySetParent with a null Transform (#2625) -- Fixed issue where a `NetworkTransform` using full precision state updates was losing transform state updates when interpolation was enabled. (#2624) +- Fixed issue where a NetworkTransform using full precision state updates was losing transform state updates when interpolation was enabled. (#2624) - Fixed issue where `NetworkObject.SpawnWithObservers` was not being honored for late joining clients. (#2623) - Fixed issue where invoking `NetworkManager.Shutdown` multiple times, depending upon the timing, could cause an exception. (#2622) - Fixed issue where removing ownership would not notify the server that it gained ownership. This also resolves the issue where an owner authoritative NetworkTransform would not properly initialize upon removing ownership from a remote client. (#2618) @@ -636,14 +636,14 @@ Additional documentation and release notes are available at [Multiplayer Documen - Added `NetworkTransform.UseQuaternionSynchronization` property that, when enabled, will synchronize the entire quaternion. (#2388) - Added `NetworkTransform.UseQuaternionCompression` property that, when enabled, will use a smallest three implementation reducing a full quaternion synchronization update to the size of an unsigned integer. (#2388) - Added `NetworkTransform.SlerpPosition` property that, when enabled along with interpolation being enabled, will interpolate using `Vector3.Slerp`. (#2388) -- Added `BufferedLinearInterpolatorVector3` that replaces the float version, is now used by `NetworkTransform`, and provides the ability to enable or disable `Slerp`. (#2388) +- Added `BufferedLinearInterpolatorVector3` that replaces the float version, is now used by NetworkTransform, and provides the ability to enable or disable `Slerp`. (#2388) - Added `HalfVector3` used for scale when half float precision is enabled. (#2388) - Added `HalfVector4` used for rotation when half float precision and quaternion synchronization is enabled. (#2388) - Added `HalfVector3DeltaPosition` used for position when half float precision is enabled. This handles loss in position precision by updating only the delta position as opposed to the full position. (#2388) - Added `NetworkTransform.GetSpaceRelativePosition` and `NetworkTransform.GetSpaceRelativeRotation` helper methods to return the proper values depending upon whether local or world space. (#2388) - Added `NetworkTransform.OnAuthorityPushTransformState` virtual method that is invoked just prior to sending the `NetworkTransformState` to non-authoritative instances. This provides users with the ability to obtain more precise delta values for prediction related calculations. (#2388) - Added `NetworkTransform.OnNetworkTransformStateUpdated` virtual method that is invoked just after the authoritative `NetworkTransformState` is applied. This provides users with the ability to obtain more precise delta values for prediction related calculations. (#2388) -- Added `NetworkTransform.OnInitialize`virtual method that is invoked after the `NetworkTransform` has been initialized or re-initialized when ownership changes. This provides for a way to make adjustments when `NetworkTransform` is initialized (i.e. resetting client prediction etc) (#2388) +- Added `NetworkTransform.OnInitialize`virtual method that is invoked after the NetworkTransform has been initialized or re-initialized when ownership changes. This provides for a way to make adjustments when NetworkTransform is initialized (i.e. resetting client prediction etc) (#2388) - Added `NetworkObject.SynchronizeTransform` property (default is true) that provides users with another way to help with bandwidth optimizations where, when set to false, the `NetworkObject`'s associated transform will not be included when spawning and/or synchronizing late joining players. (#2388) - Added `NetworkSceneManager.ActiveSceneSynchronizationEnabled` property, disabled by default, that enables client synchronization of server-side active scene changes. (#2383) - Added `NetworkObject.ActiveSceneSynchronization`, disabled by default, that will automatically migrate a `NetworkObject` to a newly assigned active scene. (#2383) @@ -652,25 +652,25 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed - Made sure the `CheckObjectVisibility` delegate is checked and applied, upon `NetworkShow` attempt. Found while supporting (#2454), although this is not a fix for this (already fixed) issue. (#2463) -- Changed `NetworkTransform` authority handles delta checks on each new network tick and no longer consumes processing cycles checking for deltas for all frames in-between ticks. (#2388) +- Changed NetworkTransform authority handles delta checks on each new network tick and no longer consumes processing cycles checking for deltas for all frames in-between ticks. (#2388) - Changed the `NetworkTransformState` structure is now public and now has public methods that provide access to key properties of the `NetworkTransformState` structure. (#2388) -- Changed `NetworkTransform` interpolation adjusts its interpolation "ticks ago" to be 2 ticks latent if it is owner authoritative and the instance is not the server or 1 tick latent if the instance is the server and/or is server authoritative. (#2388) +- Changed NetworkTransform interpolation adjusts its interpolation "ticks ago" to be 2 ticks latent if it is owner authoritative and the instance is not the server or 1 tick latent if the instance is the server and/or is server authoritative. (#2388) - Updated `NetworkSceneManager` to migrate dynamically spawned `NetworkObject`s with `DestroyWithScene` set to false into the active scene if their current scene is unloaded. (#2383) - Updated the server to synchronize its local `NetworkSceneManager.ClientSynchronizationMode` during the initial client synchronization. (#2383) ### Fixed - Fixed issue where during client synchronization the synchronizing client could receive a ObjectSceneChanged message before the client-side NetworkObject instance had been instantiated and spawned. (#2502) -- Fixed issue where `NetworkAnimator` was building client RPC parameters to exclude the host from sending itself messages but was not including it in the ClientRpc parameters. (#2492) -- Fixed issue where `NetworkAnimator` was not properly detecting and synchronizing cross fade initiated transitions. (#2481) -- Fixed issue where `NetworkAnimator` was not properly synchronizing animation state updates. (#2481) +- Fixed issue where NetworkAnimator was building client RPC parameters to exclude the host from sending itself messages but was not including it in the ClientRpc parameters. (#2492) +- Fixed issue where NetworkAnimator was not properly detecting and synchronizing cross fade initiated transitions. (#2481) +- Fixed issue where NetworkAnimator was not properly synchronizing animation state updates. (#2481) - Fixed float NetworkVariables not being rendered properly in the inspector of NetworkObjects. (#2441) - Fixed an issue where Named Message Handlers could remove themselves causing an exception when the metrics tried to access the name of the message.(#2426) -- Fixed registry of public `NetworkVariable`s in derived `NetworkBehaviour`s (#2423) -- Fixed issue where runtime association of `Animator` properties to `AnimationCurve`s would cause `NetworkAnimator` to attempt to update those changes. (#2416) -- Fixed issue where `NetworkAnimator` would not check if its associated `Animator` was valid during serialization and would spam exceptions in the editor console. (#2416) -- Fixed issue with a child's rotation rolling over when interpolation is enabled on a `NetworkTransform`. Now using half precision or full quaternion synchronization will always update all axis. (#2388) -- Fixed issue where `NetworkTransform` was not setting the teleport flag when the `NetworkTransform.InLocalSpace` value changed. This issue only impacted `NetworkTransform` when interpolation was enabled. (#2388) +- Fixed registry of public `NetworkVariable`s in derived NetworkBehaviours (#2423) +- Fixed issue where runtime association of `Animator` properties to `AnimationCurve`s would cause NetworkAnimator to attempt to update those changes. (#2416) +- Fixed issue where NetworkAnimator would not check if its associated `Animator` was valid during serialization and would spam exceptions in the editor console. (#2416) +- Fixed issue with a child's rotation rolling over when interpolation is enabled on a NetworkTransform. Now using half precision or full quaternion synchronization will always update all axis. (#2388) +- Fixed issue where NetworkTransform was not setting the teleport flag when the `NetworkTransform.InLocalSpace` value changed. This issue only impacted NetworkTransform when interpolation was enabled. (#2388) - Fixed issue when the `NetworkSceneManager.ClientSynchronizationMode` is `LoadSceneMode.Additive` and the server changes the currently active scene prior to a client connecting then upon a client connecting and being synchronized the NetworkSceneManager would clear its internal ScenePlacedObjects list that could already be populated. (#2383) - Fixed issue where a client would load duplicate scenes of already preloaded scenes during the initial client synchronization and `NetworkSceneManager.ClientSynchronizationMode` was set to `LoadSceneMode.Additive`. (#2383) @@ -700,20 +700,20 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed server side issue where, depending upon component ordering, some NetworkBehaviour components might not have their OnNetworkDespawn method invoked if the client side disconnected. (#2323) - Fixed a case where data corruption could occur when using UnityTransport when reaching a certain level of send throughput. (#2332) - Fixed an issue in `UnityTransport` where an exception would be thrown if starting a Relay host/server on WebGL. This exception should only be thrown if using direct connections (where WebGL can't act as a host/server). (#2321) -- Fixed `NetworkAnimator` issue where it was not checking for `AnimatorStateTtansition.destinationStateMachine` and any possible sub-states defined within it. (#2309) -- Fixed `NetworkAnimator` issue where the host client was receiving the ClientRpc animation updates when the host was the owner.(#2309) -- Fixed `NetworkAnimator` issue with using pooled objects and when specific properties are cleaned during despawn and destroy.(#2309) -- Fixed issue where `NetworkAnimator` was checking for animation changes when the associated `NetworkObject` was not spawned.(#2309) +- Fixed NetworkAnimator issue where it was not checking for `AnimatorStateTtansition.destinationStateMachine` and any possible sub-states defined within it. (#2309) +- Fixed NetworkAnimator issue where the host client was receiving the ClientRpc animation updates when the host was the owner.(#2309) +- Fixed NetworkAnimator issue with using pooled objects and when specific properties are cleaned during despawn and destroy.(#2309) +- Fixed issue where NetworkAnimator was checking for animation changes when the associated `NetworkObject` was not spawned.(#2309) - Corrected an issue with the documentation for BufferSerializer (#2401) ## [1.2.0] - 2022-11-21 ### Added -- Added protected method `NetworkBehaviour.OnSynchronize` which is invoked during the initial `NetworkObject` synchronization process. This provides users the ability to include custom serialization information that will be applied to the `NetworkBehaviour` prior to the `NetworkObject` being spawned. (#2298) +- Added protected method `NetworkBehaviour.OnSynchronize` which is invoked during the initial `NetworkObject` synchronization process. This provides users the ability to include custom serialization information that will be applied to the NetworkBehaviour prior to the `NetworkObject` being spawned. (#2298) - Added support for different versions of the SDK to talk to each other in circumstances where changes permit it. Starting with this version and into future versions, patch versions should be compatible as long as the minor version is the same. (#2290) - Added `NetworkObject` auto-add helper and Multiplayer Tools install reminder settings to Project Settings. (#2285) -- Added `public string DisconnectReason` getter to `NetworkManager` and `string Reason` to `ConnectionApprovalResponse`. Allows connection approval to communicate back a reason. Also added `public void DisconnectClient(ulong clientId, string reason)` allowing setting a disconnection reason, when explicitly disconnecting a client. (#2280) +- Added `public string DisconnectReason` getter to NetworkManager and `string Reason` to `ConnectionApprovalResponse`. Allows connection approval to communicate back a reason. Also added `public void DisconnectClient(ulong clientId, string reason)` allowing setting a disconnection reason, when explicitly disconnecting a client. (#2280) ### Changed @@ -723,14 +723,14 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed - Fixed `IsSpawnedObjectsPendingInDontDestroyOnLoad` is only set to true when loading a scene using `LoadSceneMode.Singleonly`. (#2330) -- Fixed issue where `NetworkTransform` components nested under a parent with a `NetworkObject` component (i.e. network prefab) would not have their associated `GameObject`'s transform synchronized. (#2298) +- Fixed issue where NetworkTransform components nested under a parent with a `NetworkObject` component (i.e. network prefab) would not have their associated `GameObject`'s transform synchronized. (#2298) - Fixed issue where `NetworkObject`s that failed to instantiate could cause the entire synchronization pipeline to be disrupted/halted for a connecting client. (#2298) - Fixed issue where in-scene placed `NetworkObject`s nested under a `GameObject` would be added to the orphaned children list causing continual console warning log messages. (#2298) - Custom messages are now properly received by the local client when they're sent while running in host mode. (#2296) - Fixed issue where the host would receive more than one event completed notification when loading or unloading a scene only when no clients were connected. (#2292) - Fixed an issue in `UnityTransport` where an error would be logged if the 'Use Encryption' flag was enabled with a Relay configuration that used a secure protocol. (#2289) - Fixed issue where in-scene placed `NetworkObjects` were not honoring the `AutoObjectParentSync` property. (#2281) -- Fixed the issue where `NetworkManager.OnClientConnectedCallback` was being invoked before in-scene placed `NetworkObject`s had been spawned when starting `NetworkManager` as a host. (#2277) +- Fixed the issue where `NetworkManager.OnClientConnectedCallback` was being invoked before in-scene placed `NetworkObject`s had been spawned when starting NetworkManager as a host. (#2277) - Creating a `FastBufferReader` with `Allocator.None` will not result in extra memory being allocated for the buffer (since it's owned externally in that scenario). (#2265) ### Removed @@ -744,7 +744,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Added `NetworkManager.IsApproved` flag that is set to `true` a client has been approved.(#2261) - `UnityTransport` now provides a way to set the Relay server data directly from the `RelayServerData` structure (provided by the Unity Transport package) throuh its `SetRelayServerData` method. This allows making use of the new APIs in UTP 1.3 that simplify integration of the Relay SDK. (#2235) - IPv6 is now supported for direct connections when using `UnityTransport`. (#2232) -- Added WebSocket support when using UTP 2.0 with `UseWebSockets` property in the `UnityTransport` component of the `NetworkManager` allowing to pick WebSockets for communication. When building for WebGL, this selection happens automatically. (#2201) +- Added WebSocket support when using UTP 2.0 with `UseWebSockets` property in the `UnityTransport` component of the NetworkManager allowing to pick WebSockets for communication. When building for WebGL, this selection happens automatically. (#2201) - Added position, rotation, and scale to the `ParentSyncMessage` which provides users the ability to specify the final values on the server-side when `OnNetworkObjectParentChanged` is invoked just before the message is created (when the `Transform` values are applied to the message). (#2146) - Added `NetworkObject.TryRemoveParent` method for convenience purposes opposed to having to cast null to either `GameObject` or `NetworkObject`. (#2146) @@ -791,9 +791,9 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed -- Fixed issue where `NetworkTransform` was not honoring the InLocalSpace property on the authority side during OnNetworkSpawn. (#2170) -- Fixed issue where `NetworkTransform` was not ending extrapolation for the previous state causing non-authoritative instances to become out of synch. (#2170) -- Fixed issue where `NetworkTransform` was not continuing to interpolate for the remainder of the associated tick period. (#2170) +- Fixed issue where NetworkTransform was not honoring the InLocalSpace property on the authority side during OnNetworkSpawn. (#2170) +- Fixed issue where NetworkTransform was not ending extrapolation for the previous state causing non-authoritative instances to become out of synch. (#2170) +- Fixed issue where NetworkTransform was not continuing to interpolate for the remainder of the associated tick period. (#2170) - Fixed issue during `NetworkTransform.OnNetworkSpawn` for non-authoritative instances where it was initializing interpolators with the replicated network state which now only contains the transform deltas that occurred during a network tick and not the entire transform state. (#2170) ## [1.0.1] - 2022-08-23 @@ -814,12 +814,12 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed Owner-written NetworkVariable infinitely write themselves (#2109) - Fixed NetworkList issue that showed when inserting at the very end of a NetworkList (#2099) - Fixed issue where a client owner of a `NetworkVariable` with both owner read and write permissions would not update the server side when changed. (#2097) -- Fixed issue when attempting to spawn a parent `GameObject`, with `NetworkObject` component attached, that has one or more child `GameObject`s, that are inactive in the hierarchy, with `NetworkBehaviour` components it will no longer attempt to spawn the associated `NetworkBehaviour`(s) or invoke ownership changed notifications but will log a warning message. (#2096) +- Fixed issue when attempting to spawn a parent `GameObject`, with `NetworkObject` component attached, that has one or more child `GameObject`s, that are inactive in the hierarchy, with NetworkBehaviour components it will no longer attempt to spawn the associated NetworkBehaviour(s) or invoke ownership changed notifications but will log a warning message. (#2096) - Fixed an issue where destroying a NetworkBehaviour would not deregister it from the parent NetworkObject, leading to exceptions when the parent was later destroyed. (#2091) - Fixed issue where `NetworkObject.NetworkHide` was despawning and destroying, as opposed to only despawning, in-scene placed `NetworkObject`s. (#2086) -- Fixed `NetworkAnimator` synchronizing transitions twice due to it detecting the change in animation state once a transition is started by a trigger. (#2084) -- Fixed issue where `NetworkAnimator` would not synchronize a looping animation for late joining clients if it was at the very end of its loop. (#2076) -- Fixed issue where `NetworkAnimator` was not removing its subscription from `OnClientConnectedCallback` when despawned during the shutdown sequence. (#2074) +- Fixed NetworkAnimator synchronizing transitions twice due to it detecting the change in animation state once a transition is started by a trigger. (#2084) +- Fixed issue where NetworkAnimator would not synchronize a looping animation for late joining clients if it was at the very end of its loop. (#2076) +- Fixed issue where NetworkAnimator was not removing its subscription from `OnClientConnectedCallback` when despawned during the shutdown sequence. (#2074) - Fixed IsServer and IsClient being set to false before object despawn during the shutdown sequence. (#2074) - Fixed NetworkList Value event on the server. PreviousValue is now set correctly when a new value is set through property setter. (#2067) - Fixed NetworkLists not populating on client. NetworkList now uses the most recent list as opposed to the list at the end of previous frame, when sending full updates to dynamically spawned NetworkObject. The difference in behaviour is required as scene management spawns those objects at a different time in the frame, relative to updates. (#2062) @@ -834,8 +834,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added -- Added a new `OnTransportFailure` callback to `NetworkManager`. This callback is invoked when the manager's `NetworkTransport` encounters an unrecoverable error. Transport failures also cause the `NetworkManager` to shut down. Currently, this is only used by `UnityTransport` to signal a timeout of its connection to the Unity Relay servers. (#1994) -- Added `NetworkEvent.TransportFailure`, which can be used by implementations of `NetworkTransport` to signal to `NetworkManager` that an unrecoverable error was encountered. (#1994) +- Added a new `OnTransportFailure` callback to NetworkManager. This callback is invoked when the manager's `NetworkTransport` encounters an unrecoverable error. Transport failures also cause the NetworkManager to shut down. Currently, this is only used by `UnityTransport` to signal a timeout of its connection to the Unity Relay servers. (#1994) +- Added `NetworkEvent.TransportFailure`, which can be used by implementations of `NetworkTransport` to signal to NetworkManager that an unrecoverable error was encountered. (#1994) - Added test to ensure a warning occurs when nesting NetworkObjects in a NetworkPrefab (#1969) - Added `NetworkManager.RemoveNetworkPrefab(...)` to remove a prefab from the prefabs list (#1950) @@ -849,7 +849,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed - Fixed issue where dynamically spawned `NetworkObject`s could throw an exception if the scene of origin handle was zero (0) and the `NetworkObject` was already spawned. (#2017) - Fixed issue where `NetworkObject.Observers` was not being cleared when despawned. (#2009) -- Fixed `NetworkAnimator` could not run in the server authoritative mode. (#2003) +- Fixed NetworkAnimator could not run in the server authoritative mode. (#2003) - Fixed issue where late joining clients would get a soft synchronization error if any in-scene placed NetworkObjects were parented under another `NetworkObject`. (#1985) - Fixed issue where `NetworkBehaviourReference` would throw a type cast exception if using `NetworkBehaviourReference.TryGet` and the component type was not found. (#1984) - Fixed `NetworkSceneManager` was not sending scene event notifications for the currently active scene and any additively loaded scenes when loading a new scene in `LoadSceneMode.Single` mode. (#1975) @@ -857,13 +857,13 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed issues when multiple `ConnectionApprovalCallback`s were registered (#1972) - Fixed a regression in serialization support: `FixedString`, `Vector2Int`, and `Vector3Int` types can now be used in NetworkVariables and RPCs again without requiring a `ForceNetworkSerializeByMemcpy<>` wrapper. (#1961) - Fixed generic types that inherit from NetworkBehaviour causing crashes at compile time. (#1976) -- Fixed endless dialog boxes when adding a `NetworkBehaviour` to a `NetworkManager` or vice-versa. (#1947) -- Fixed `NetworkAnimator` issue where it was only synchronizing parameters if the layer or state changed or was transitioning between states. (#1946) -- Fixed `NetworkAnimator` issue where when it did detect a parameter had changed it would send all parameters as opposed to only the parameters that changed. (#1946) -- Fixed `NetworkAnimator` issue where it was not always disposing the `NativeArray` that is allocated when spawned. (#1946) -- Fixed `NetworkAnimator` issue where it was not taking the animation speed or state speed multiplier into consideration. (#1946) -- Fixed `NetworkAnimator` issue where it was not properly synchronizing late joining clients if they joined while `Animator` was transitioning between states. (#1946) -- Fixed `NetworkAnimator` issue where the server was not relaying changes to non-owner clients when a client was the owner. (#1946) +- Fixed endless dialog boxes when adding a NetworkBehaviour to a NetworkManager or vice-versa. (#1947) +- Fixed NetworkAnimator issue where it was only synchronizing parameters if the layer or state changed or was transitioning between states. (#1946) +- Fixed NetworkAnimator issue where when it did detect a parameter had changed it would send all parameters as opposed to only the parameters that changed. (#1946) +- Fixed NetworkAnimator issue where it was not always disposing the `NativeArray` that is allocated when spawned. (#1946) +- Fixed NetworkAnimator issue where it was not taking the animation speed or state speed multiplier into consideration. (#1946) +- Fixed NetworkAnimator issue where it was not properly synchronizing late joining clients if they joined while `Animator` was transitioning between states. (#1946) +- Fixed NetworkAnimator issue where the server was not relaying changes to non-owner clients when a client was the owner. (#1946) - Fixed issue where the `PacketLoss` metric for tools would return the packet loss over a connection lifetime instead of a single frame. (#2004) ## [1.0.0-pre.9] - 2022-05-10 @@ -879,7 +879,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed - `unmanaged` structs are no longer universally accepted as RPC parameters because some structs (i.e., structs with pointers in them, such as `NativeList`) can't be supported by the default memcpy struct serializer. Structs that are intended to be serialized across the network must add `INetworkSerializeByMemcpy` to the interface list (i.e., `struct Foo : INetworkSerializeByMemcpy`). This interface is empty and just serves to mark the struct as compatible with memcpy serialization. For external structs you can't edit, you can pass them to RPCs by wrapping them in `ForceNetworkSerializeByMemcpy`. (#1901) -- Changed requirement to register in-scene placed NetworkObjects with `NetworkManager` in order to respawn them. In-scene placed NetworkObjects are now automatically tracked during runtime and no longer need to be registered as a NetworkPrefab. (#1898) +- Changed requirement to register in-scene placed NetworkObjects with NetworkManager in order to respawn them. In-scene placed NetworkObjects are now automatically tracked during runtime and no longer need to be registered as a NetworkPrefab. (#1898) ### Removed @@ -889,13 +889,13 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed - Fixed issue where `NetworkSceneManager` did not synchronize despawned in-scene placed NetworkObjects. (#1898) -- Fixed `NetworkTransform` generating false positive rotation delta checks when rolling over between 0 and 360 degrees. (#1890) +- Fixed NetworkTransform generating false positive rotation delta checks when rolling over between 0 and 360 degrees. (#1890) - Fixed client throwing an exception if it has messages in the outbound queue when processing the `NetworkEvent.Disconnect` event and is using UTP. (#1884) - Fixed issue during client synchronization if 'ValidateSceneBeforeLoading' returned false it would halt the client synchronization process resulting in a client that was approved but not synchronized or fully connected with the server. (#1883) - Fixed an issue where UNetTransport.StartServer would return success even if the underlying transport failed to start (#854) - Passing generic types to RPCs no longer causes a native crash (#1901) - Fixed a compile failure when compiling against com.unity.nuget.mono-cecil >= 1.11.4 (#1920) -- Fixed an issue where calling `Shutdown` on a `NetworkManager` that was already shut down would cause an immediate shutdown the next time it was started (basically the fix makes `Shutdown` idempotent). (#1877) +- Fixed an issue where calling `Shutdown` on a NetworkManager that was already shut down would cause an immediate shutdown the next time it was started (basically the fix makes `Shutdown` idempotent). (#1877) ## [1.0.0-pre.7] - 2022-04-06 @@ -1051,8 +1051,8 @@ Additional documentation and release notes are available at [Multiplayer Documen - Added `Bootstrap` sample to the SDK package (#1140) - Enhanced `NetworkSceneManager` implementation with additive scene loading capabilities (#1080, #955, #913) - `NetworkSceneManager.OnSceneEvent` provides improved scene event notificaitons -- Enhanced `NetworkTransform` implementation with per axis/component based and threshold based state replication (#1042, #1055, #1061, #1084, #1101) -- Added a jitter-resistent `BufferedLinearInterpolator` for `NetworkTransform` (#1060) +- Enhanced NetworkTransform implementation with per axis/component based and threshold based state replication (#1042, #1055, #1061, #1084, #1101) +- Added a jitter-resistent `BufferedLinearInterpolator` for NetworkTransform (#1060) - Implemented `NetworkPrefabHandler` that provides support for object pooling and `NetworkPrefab` overrides (#1073, #1004, #977, #905,#749, #727) - Implemented auto `NetworkObject` transform parent synchronization at runtime over the network (#855) - Adopted Unity C# Coding Standards in the codebase with `.editorconfig` ruleset (#666, #670) @@ -1062,12 +1062,12 @@ Additional documentation and release notes are available at [Multiplayer Documen - Added `SnapshotSystem` that would allow variables and spawn/despawn messages to be sent in blocks (#805, #852, #862, #963, #1012, #1013, #1021, #1040, #1062, #1064, #1083, #1091, #1111, #1129, #1166, #1192) - Disabled by default for now, except spawn/despawn messages - Will leverage unreliable messages with eventual consistency -- `NetworkBehaviour` and `NetworkObject`'s `NetworkManager` instances can now be overriden (#762) +- NetworkBehaviour and `NetworkObject`'s NetworkManager instances can now be overriden (#762) - Added metrics reporting for the new network profiler if the Multiplayer Tools package is present (#1104, #1089, #1096, #1086, #1072, #1058, #960, #897, #891, #878) - `NetworkBehaviour.IsSpawned` a quick (and stable) way to determine if the associated NetworkObject is spawned (#1190) -- Added `NetworkRigidbody` and `NetworkRigidbody2D` components to support networking `Rigidbody` and `Rigidbody2D` components (#1202, #1175) +- Added NetworkRigidBody and `NetworkRigidbody2D` components to support networking `Rigidbody` and `Rigidbody2D` components (#1202, #1175) - Added `NetworkObjectReference` and `NetworkBehaviourReference` structs which allow to sending `NetworkObject/Behaviours` over RPCs/`NetworkVariable`s (#1173) -- Added `NetworkAnimator` component to support networking `Animator` component (#1281, #872) +- Added NetworkAnimator component to support networking `Animator` component (#1281, #872) ### Changed @@ -1090,13 +1090,13 @@ Additional documentation and release notes are available at [Multiplayer Documen - and other `Unity.Multiplayer.MLAPI.x` variants to `Unity.Netcode.x` variants - Renamed `Prototyping` namespace and assembly definition to `Components` (#1145) - Changed `NetworkObject.Despawn(bool destroy)` API to default to `destroy = true` for better usability (#1217) -- Scene registration in `NetworkManager` is now replaced by Build Setttings → Scenes in Build List (#1080) +- Scene registration in NetworkManager is now replaced by Build Setttings → Scenes in Build List (#1080) - `NetworkSceneManager.SwitchScene` has been replaced by `NetworkSceneManager.LoadScene` (#955) - `NetworkManager, NetworkConfig, and NetworkSceneManager` scene registration replaced with scenes in build list (#1080) - `GlobalObjectIdHash` replaced `PrefabHash` and `PrefabHashGenerator` for stability and consistency (#698) - `NetworkStart` has been renamed to `OnNetworkSpawn`. (#865) - Network variable cleanup - eliminated shared mode, variables are server-authoritative (#1059, #1074) -- `NetworkManager` and other systems are no longer singletons/statics (#696, #705, #706, #737, #738, #739, #746, #747, #763, #765, #766, #783, #784, #785, #786, #787, #788) +- NetworkManager and other systems are no longer singletons/statics (#696, #705, #706, #737, #738, #739, #746, #747, #763, #765, #766, #783, #784, #785, #786, #787, #788) - Changed `INetworkSerializable.NetworkSerialize` method signature to use `BufferSerializer` instead of `NetworkSerializer` (#1187) - Changed `CustomMessagingManager`'s methods to use `FastBufferWriter` and `FastBufferReader` instead of `Stream` (#1187) - Reduced internal runtime allocations by removing LINQ calls and replacing managed lists/arrays with native collections (#1196) @@ -1119,8 +1119,8 @@ Additional documentation and release notes are available at [Multiplayer Documen - Removed `UpdateStage` parameter from `ServerRpcSendParams` and `ClientRpcSendParams` (#1187) - Removed `NetworkBuffer`, `NetworkWriter`, `NetworkReader`, `NetworkSerializer`, `PooledNetworkBuffer`, `PooledNetworkWriter`, and `PooledNetworkReader` (#1187) - Removed `EnableNetworkVariable` in `NetworkConfig`, it is always enabled now (#1179) -- Removed `NetworkTransform`'s FixedSendsPerSecond, AssumeSyncedSends, InterpolateServer, ExtrapolatePosition, MaxSendsToExtrapolate, Channel, EnableNonProvokedResendChecks, DistanceSendrate (#1060) (#826) (#1042, #1055, #1061, #1084, #1101) -- Removed `NetworkManager`'s `StopServer()`, `StopClient()` and `StopHost()` methods and replaced with single `NetworkManager.Shutdown()` method for all (#1108) +- Removed NetworkTransform's FixedSendsPerSecond, AssumeSyncedSends, InterpolateServer, ExtrapolatePosition, MaxSendsToExtrapolate, Channel, EnableNonProvokedResendChecks, DistanceSendrate (#1060) (#826) (#1042, #1055, #1061, #1084, #1101) +- Removed NetworkManager's `StopServer()`, `StopClient()` and `StopHost()` methods and replaced with single `NetworkManager.Shutdown()` method for all (#1108) ### Fixed @@ -1128,22 +1128,22 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed `NetworkObject.OwnerClientId` property changing before `NetworkBehaviour.OnGainedOwnership()` callback (#1092) - Fixed `NetworkBehaviourILPP` to iterate over all types in an assembly (#803) - Fixed cross-asmdef RPC ILPP by importing types into external assemblies (#678) -- Fixed `NetworkManager` shutdown when quitting the application or switching scenes (#1011) - - Now `NetworkManager` shutdowns correctly and despawns existing `NetworkObject`s -- Fixed Only one `PlayerPrefab` can be selected on `NetworkManager` inspector UI in the editor (#676) +- Fixed NetworkManager shutdown when quitting the application or switching scenes (#1011) + - Now NetworkManager shutdowns correctly and despawns existing `NetworkObject`s +- Fixed Only one `PlayerPrefab` can be selected on NetworkManager inspector UI in the editor (#676) - Fixed connection approval not being triggered for host (#675) - Fixed various situations where messages could be processed in an invalid order, resulting in errors (#948, #1187, #1218) - Fixed `NetworkVariable`s being default-initialized on the client instead of being initialized with the desired value (#1266) - Improved runtime performance and reduced GC pressure (#1187) - Fixed #915 - clients are receiving data from objects not visible to them (#1099) -- Fixed `NetworkTransform`'s "late join" issues, `NetworkTransform` now uses `NetworkVariable`s instead of RPCs (#826) +- Fixed NetworkTransform's "late join" issues, NetworkTransform now uses `NetworkVariable`s instead of RPCs (#826) - Throw an exception for silent failure when a client tries to get another player's `PlayerObject`, it is now only allowed on the server-side (#844) ### Known Issues - `NetworkVariable` does not serialize `INetworkSerializable` types through their `NetworkSerialize` implementation - `NetworkObjects` marked as `DontDestroyOnLoad` are disabled during some network scene transitions -- `NetworkTransform` interpolates from the origin when switching Local Space synchronization +- NetworkTransform interpolates from the origin when switching Local Space synchronization - Exceptions thrown in `OnNetworkSpawn` user code for an object will prevent the callback in other objects - Cannot send an array of `INetworkSerializable` in RPCs - ILPP generation fails with special characters in project path @@ -1186,21 +1186,21 @@ This is the initial experimental Unity MLAPI Package, v0.1.0. - [GitHub 520](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/520): MLAPI now uses the Unity Package Manager for installation management. - Added functionality and usability to `NetworkVariable`, previously called `NetworkVar`. Updates enhance options and fully replace the need for `SyncedVar`s. -- [GitHub 507](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/507): Reimplemented `NetworkAnimator`, which synchronizes animation states for networked objects. +- [GitHub 507](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/507): Reimplemented NetworkAnimator, which synchronizes animation states for networked objects. - GitHub [444](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/444) and [455](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/455): Channels are now represented as bytes instead of strings. For users of previous versions of MLAPI, this release renames APIs due to refactoring. All obsolete marked APIs have been removed as per [GitHub 513](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/513) and [GitHub 514](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/514). | Previous MLAPI Versions | V 0.1.0 Name | | -- | -- | -| `NetworkingManager` | `NetworkManager` | +| `NetworkingManager` | NetworkManager | | `NetworkedObject` | `NetworkObject` | -| `NetworkedBehaviour` | `NetworkBehaviour` | +| `NetworkedBehaviour` | NetworkBehaviour | | `NetworkedClient` | `NetworkClient` | | `NetworkedPrefab` | `NetworkPrefab` | | `NetworkedVar` | `NetworkVariable` | -| `NetworkedTransform` | `NetworkTransform` | -| `NetworkedAnimator` | `NetworkAnimator` | +| `NetworkedTransform` | NetworkTransform | +| `NetworkedAnimator` | NetworkAnimator | | `NetworkedAnimatorEditor` | `NetworkAnimatorEditor` | | `NetworkedNavMeshAgent` | `NetworkNavMeshAgent` | | `SpawnManager` | `NetworkSpawnManager` | @@ -1235,8 +1235,8 @@ With a new release of MLAPI in Unity, some features have been removed: - [GitHub 527](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/527): Lag compensation systems and `TrackedObject` have moved to the new [MLAPI Community Contributions](https://github.com/Unity-Technologies/mlapi-community-contributions/tree/master/com.mlapi.contrib.extensions) repo. - [GitHub 509](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/509): Encryption has been removed from MLAPI. The `Encryption` option in `NetworkConfig` on the `NetworkingManager` is not available in this release. This change will not block game creation or running. A current replacement for this functionality is not available, and may be developed in future releases. See the following changes: - Removed `SecuritySendFlags` from all APIs. - - Removed encryption, cryptography, and certificate configurations from APIs including `NetworkManager` and `NetworkConfig`. - - Removed "hail handshake", including `NetworkManager` implementation and `NetworkConstants` entries. + - Removed encryption, cryptography, and certificate configurations from APIs including NetworkManager and `NetworkConfig`. + - Removed "hail handshake", including NetworkManager implementation and `NetworkConstants` entries. - Modified `RpcQueue` and `RpcBatcher` internals to remove encryption and authentication from reading and writing. - Removed the previous MLAPI Profiler editor window from Unity versions 2020.2 and later. - Removed previous MLAPI Convenience and Performance RPC APIs with the new standard RPC API. See [RFC #1](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/blob/master/text/0001-std-rpc-api.md) for details. @@ -1246,16 +1246,16 @@ With a new release of MLAPI in Unity, some features have been removed: - `NetworkNavMeshAgent` does not synchronize mesh data, Agent Size, Steering, Obstacle Avoidance, or Path Finding settings. It only synchronizes the destination and velocity, not the path to the destination. - For `RPC`, methods with a `ClientRpc` or `ServerRpc` suffix which are not marked with [ServerRpc] or [ClientRpc] will cause a compiler error. -- For `NetworkAnimator`, Animator Overrides are not supported. Triggers do not work. +- For NetworkAnimator, Animator Overrides are not supported. Triggers do not work. - For `NetworkVariable`, the `NetworkDictionary` `List` and `Set` must use the `reliableSequenced` channel. - `NetworkObjects`s are supported but when spawning a prefab with nested child network objects you have to manually call spawn on them -- `NetworkTransform` have the following issues: +- NetworkTransform have the following issues: - Replicated objects may have jitter. - The owner is always authoritative about the object's position. - Scale is not synchronized. - Connection Approval is not called on the host client. - For `NamedMessages`, always use `NetworkBuffer` as the underlying stream for sending named and unnamed messages. -- For `NetworkManager`, connection management is limited. Use `IsServer`, `IsClient`, `IsConnectedClient`, or other code to check if MLAPI connected correctly. +- For NetworkManager, connection management is limited. Use `IsServer`, `IsClient`, `IsConnectedClient`, or other code to check if MLAPI connected correctly. ## [0.0.1-preview.1] - 2020-12-20 diff --git a/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md b/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md index 416c29c8e3..767fe3bad4 100644 --- a/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md +++ b/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md @@ -19,19 +19,19 @@ * [Transports](advanced-topics/transports.md) * [Relay](relay/relay.md) * [Network components](network-components.md) - * [Foundational Components](components/foundational/foundationalcomponents.md) - * [NetworkObject](components/foundational/networkobject.md) + * [Core components](components/core/corecomponents.md) + * [NetworkObject](components/core/networkobject.md) * [NetworkObject parenting](advanced-topics/networkobject-parenting.md) - * [NetworkBehaviour](components/foundational/networkbehaviour.md) - * [Synchronizing & Order of Operations](components/foundational/networkbehaviour-synchronize.md) - * [NetworkManager](components/foundational/networkmanager.md) - * [PlayerObjects and player prefabs](components/foundational/playerobjects.md) - * [Helper Components](components/Helpers/helpercomponents.md) - * [AttachableBehaviour](components/Helpers/attachablebehaviour.md) - * [AttachableNode](components/Helpers/attachablenode.md) - * [ComponentController](components/Helpers/componentcontroller.md) - * [NetworkAnimator](components/helpers/networkanimator.md) - * [NetworkTransform](components/helpers/networktransform.md) + * [NetworkBehaviour](components/core/networkbehaviour.md) + * [Synchronizing & Order of Operations](components/core/networkbehaviour-synchronize.md) + * [NetworkManager](components/core/networkmanager.md) + * [PlayerObjects and player prefabs](components/core/playerobjects.md) + * [Helper Components](components/helper/helpercomponents.md) + * [AttachableBehaviour](components/helper/attachablebehaviour.md) + * [AttachableNode](components/helper/attachablenode.md) + * [ComponentController](components/helper/componentcontroller.md) + * [NetworkAnimator](components/helper/networkanimator.md) + * [NetworkTransform](components/helper/networktransform.md) * [Physics](advanced-topics/physics.md) * [Ownership and authority](ownership-authority.md) * [Understanding ownership and authority](basics/ownership.md) diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/client-anticipation.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/client-anticipation.md index 828d5ea3ab..80aab2a550 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/client-anticipation.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/client-anticipation.md @@ -57,7 +57,7 @@ The `OnReanticipate` event can also be used for other purposes, such as "forward ## `OnReanticipate` event -`NetworkBehaviour` has a virtual method called `OnReanticipate`. When server data is received for an `AnticipatedNetworkVariable` or `AnticipatedNetworkTransform`, it's rolled back immediately, setting its anticipated state. During each frame in which a server update for any `AnticipatedNetworkVariable` or `AnticipatedNetworkTransform` is received (after **all** such operations have been performed and **all** objects are rolled back to their server state), each `NetworkObject` that had any rollbacks calls the `OnReanticipate` method on **all** of its `NetworkBehaviour`s. +NetworkBehaviour has a virtual method called `OnReanticipate`. When server data is received for an `AnticipatedNetworkVariable` or `AnticipatedNetworkTransform`, it's rolled back immediately, setting its anticipated state. During each frame in which a server update for any `AnticipatedNetworkVariable` or `AnticipatedNetworkTransform` is received (after **all** such operations have been performed and **all** objects are rolled back to their server state), each NetworkObject that had any rollbacks calls the `OnReanticipate` method on **all** of its NetworkBehaviours. If you need to do any reanticipation to update the anticipated state of any of these variables or transforms, this method is where you will do it. `OnReanticipate` takes as its only parameter a `double` providing the amount of time, in seconds, that the object has been rolled back (which corresponds to the round-trip time of the current batch of responses received from the server). This value can be used to calculate the difference between what the server value is, and what the anticipated client value should be, and apply that change. @@ -65,7 +65,7 @@ However, note that not all variables and transforms on that object may have rece ### Global `OnReanticipate` -In addition to the `NetworkBehaviour`'s `OnReanticipate` method, `NetworkManager` also has a callback that can be subscribed to for global reanticipation. This is useful if you need to run your reanticipation in a more global way, such as if you need to run it step-wise (say, anticipating one frame at a time) and need all objects to complete one step before any of them begin the second one. This callback receives the same `lastRoundTripTime` value as the `NetworkBehaviour` method, and is called after all of the `NetworkBehaviour` methods have been called. +In addition to the NetworkBehaviour's `OnReanticipate` method, NetworkManager also has a callback that can be subscribed to for global reanticipation. This is useful if you need to run your reanticipation in a more global way, such as if you need to run it step-wise (say, anticipating one frame at a time) and need all objects to complete one step before any of them begin the second one. This callback receives the same `lastRoundTripTime` value as the NetworkBehaviour method, and is called after all of the NetworkBehaviour methods have been called. > [!NOTE] >If you want to implement a full client-side prediction model in your game, the global OnReanticipate callback is likely the ideal place to incorporate your rollback and replay logic. The details of implementing this, however, are left up to users. Implementing a full, production-ready prediction loop is a complex topic and recommended for advanced users only. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/inscene_parenting_player.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/inscene_parenting_player.md index fc5e2d09dc..5f616d19a2 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/inscene_parenting_player.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/inscene_parenting_player.md @@ -8,7 +8,7 @@ When a player Prefab has a script that dynamically adds a parent to its transfor Steps to reproduce the behavior: -1. Set up basic networking game with at least one `GameObject` in a scene that isn't the player. +1. Set up basic networking game with at least one GameObject in a scene that isn't the player. 1. Add a script to the player Prefab that adds parenting to its transform via `gameObject.transform.SetParent()` in the `Start()` method. 1. Launch one instance of the game as Host. 1. Launch another instance and try to join as Client. @@ -75,8 +75,8 @@ public class ParentPlayerToInSceneNetworkObject : NetworkBehaviour } ``` -You should place this script on your in-scene placed `NetworkObject` (that is, the first `GameObject`) and do the parenting from it to avoid any timing issues of when it's spawned or the like. It only runs the script on the server-host side since parenting is server authoritative. +You should place this script on your in-scene placed NetworkObject (that is, the first GameObject) and do the parenting from it to avoid any timing issues of when it's spawned or the like. It only runs the script on the server-host side since parenting is server authoritative. > [!NOTE] -> Remove any parenting code you might have had from your player Prefab before using the above script. Depending upon your project's goals, you might be parenting all players under the same in-scene placed `NetworkObject` or you might intend to have each player parenting unique. If you want each player to be parented under a unique in-scene placed `NetworkObject` then you will need to have the same number of in-scene placed `NetworkObject`s as your maximum allowed players per game session. The above example will only parent all players under the same in-scene placed `NetworkObject`. You can extend the above example by migrating the scene event code into an in-scene placed `NetworkObject` that manages the parenting of players (i,e. name it something like `PlayerSpawnManager`) as they connect, make the `SetPlayerParent` method public, and add all in-scene placed `NetworkObject`s to a public list of GameObjects that the `PlayerSpawnManager` will reference and assign player's to as they connect while also freeing in-scene placed `NetworkObject`s as players disconnect during a game session. +> Remove any parenting code you might have had from your player Prefab before using the above script. Depending upon your project's goals, you might be parenting all players under the same in-scene placed NetworkObject or you might intend to have each player parenting unique. If you want each player to be parented under a unique in-scene placed NetworkObject then you will need to have the same number of in-scene placed NetworkObjects as your maximum allowed players per game session. The above example will only parent all players under the same in-scene placed NetworkObject. You can extend the above example by migrating the scene event code into an in-scene placed NetworkObject that manages the parenting of players (i,e. name it something like `PlayerSpawnManager`) as they connect, make the `SetPlayerParent` method public, and add all in-scene placed NetworkObjects to a public list of GameObjects that the `PlayerSpawnManager` will reference and assign player's to as they connect while also freeing in-scene placed NetworkObjects as players disconnect during a game session. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/reliability.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/reliability.md index 0363b82e68..a3f53ae955 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/reliability.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/reliability.md @@ -20,7 +20,7 @@ void MyReliableServerRpc() { /* ... */ } void MyUnreliableServerRpc() { /* ... */ } ``` -Reliable RPCs will be received on the remote end in the same order they are sent, but this in-order guarantee only applies to RPCs on the same `NetworkObject`. Different `NetworkObjects` might have reliable RPCs called but executed in different order compared to each other. To put more simply, **in-order reliable RPC execution is guaranteed per `NetworkObject` basis only**. If you determine an RPC is being updated often (that is, several times per second), it _might_ be better suited as an unreliable RPC. +Reliable RPCs will be received on the remote end in the same order they are sent, but this in-order guarantee only applies to RPCs on the same NetworkObject. Different `NetworkObjects` might have reliable RPCs called but executed in different order compared to each other. To put more simply, **in-order reliable RPC execution is guaranteed per NetworkObject basis only**. If you determine an RPC is being updated often (that is, several times per second), it _might_ be better suited as an unreliable RPC. > [!NOTE] > When testing unreliable RPCs on a local network, the chance of an unreliable packet being dropped is reduced greatly (sometimes never). As such, you might want to use [`UnityTransport`'s Simulator Pipeline](https://docs-multiplayer.unity3d.com/transport/current/pipelines#simulator-pipeline) to simulate poor network conditions to better determine how dropped unreliable RPC messages impacts your project. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc-params.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc-params.md index 9995554c8d..28d0607ef3 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc-params.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc-params.md @@ -29,7 +29,7 @@ For those accustomed to the legacy `ServerRpc` and `ClientRpc` parameters, there - `Defer`: Invokes the RPC locally on the next frame. - `SendImmediate` (default): Immediately invokes the RPC (at the current call stack) locally. -- Changing destination of an RPC is done using the properties and methods of `RpcTarget` (each `NetworkBehaviour` contains a reference to the shared `RpcTarget` object, as does `NetworkManager`.) This allows conveniently selecting various common targets (Server, NotServer, Owner, NotOwner, etc), as well as custom client lists using `RpcTarget.Single()` (to send to one client ID), `RpcTarget.Group()` (to send to multiple client IDs), and`RpcTarget.Not()` (to send to everyone except for the specified client ID or list of client IDs) +- Changing destination of an RPC is done using the properties and methods of `RpcTarget` (each NetworkBehaviour contains a reference to the shared `RpcTarget` object, as does NetworkManager.) This allows conveniently selecting various common targets (Server, NotServer, Owner, NotOwner, etc), as well as custom client lists using `RpcTarget.Single()` (to send to one client ID), `RpcTarget.Group()` (to send to multiple client IDs), and`RpcTarget.Not()` (to send to everyone except for the specified client ID or list of client IDs) - `RpcParams` do not allow overriding the destination at runtime unless either the default target is `SendTo.SpecifiedInParams` or `AllowTargetOverride = true` is passed to the attribute. ```csharp diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md index a8569b4697..c004571dbd 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md @@ -97,9 +97,9 @@ To send to a different target, you must define an additional parameter of type ` #### Built-in targets -`NetworkManager` and `NetworkBehaviour` both provide reference to a field called `RpcTarget`, which provides references to the various override targets you can pass in. The list mirrors the list of targets provided in the `SendTo` enum, and has the same behavior as described in the table above. +NetworkManager and NetworkBehaviour both provide reference to a field called `RpcTarget`, which provides references to the various override targets you can pass in. The list mirrors the list of targets provided in the `SendTo` enum, and has the same behavior as described in the table above. -The `RpcTarget` object is shared by all `NetworkBehaviours` attached to a given `NetworkManager`, so you can get access to this via any `NetworkBehaviour` or via the `NetworkManager` object itself. +The `RpcTarget` object is shared by all `NetworkBehaviours` attached to a given NetworkManager, so you can get access to this via any NetworkBehaviour or via the NetworkManager object itself. ```csharp public class SomeNetworkBehaviour : NetworkBehaviour diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/messaging-system.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/messaging-system.md index 750f034d27..36db9c787e 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/messaging-system.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/messaging-system.md @@ -36,7 +36,7 @@ Refer to [RPC migration and compatibility](message-system/rpc-compatibility.md) ## RPC method calls -A typical SDK user (Unity developer) can declare multiple RPCs under a `NetworkBehaviour` and inbound/outbound RPC calls will be replicated as a part of its replication in a network frame. +A typical SDK user (Unity developer) can declare multiple RPCs under a NetworkBehaviour and inbound/outbound RPC calls will be replicated as a part of its replication in a network frame. A method turned into an RPC is no longer a regular method; it will have its own implications on direct calls and in the network pipeline. @@ -46,7 +46,7 @@ To use RPCs, make sure: - `[Rpc]` attributes are on your method - Your method name ends with `Rpc` (for example, `DoSomethingRpc()`) -- Your method is declared in a class that inherits from `NetworkBehaviour` +- Your method is declared in a class that inherits from NetworkBehaviour - Your GameObject has a NetworkObject component attached ## Serialization types and RPCs diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/network-update-loop-system/index.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/network-update-loop-system/index.md index 0546834bf4..d7992d0480 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/network-update-loop-system/index.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/network-update-loop-system/index.md @@ -1,8 +1,8 @@ # About NetworkUpdateLoop -Often there is a need to update netcode systems like RPC queue, transport IO, and others outside the standard `MonoBehaviour` event cycle. +Often there is a need to update netcode systems like RPC queue, transport IO, and others outside the standard MonoBehaviour event cycle. -The Network Update Loop infrastructure utilizes Unity's low-level Player Loop API allowing for registering `INetworkUpdateSystems` with `NetworkUpdate()` methods to be executed at specific `NetworkUpdateStages` which may be either before or after `MonoBehaviour`-driven game logic execution. +The Network Update Loop infrastructure utilizes Unity's low-level Player Loop API allowing for registering `INetworkUpdateSystems` with `NetworkUpdate()` methods to be executed at specific `NetworkUpdateStages` which may be either before or after MonoBehaviour-driven game logic execution. Typically you will interact with `NetworkUpdateLoop` for registration and `INetworkUpdateSystem` for implementation. Systems such as network tick and future features (such as network variable snapshotting) will rely on this pipeline. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networkobject-parenting.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networkobject-parenting.md index e725f506db..5c630b60ed 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networkobject-parenting.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networkobject-parenting.md @@ -74,7 +74,7 @@ For more information, refer to: When using the `NetworkObject.TrySetParent` or `NetworkObject.TryRemoveParent` methods, the `WorldPositionStays` parameter is synchronized with currently connected and late joining clients. When removing a child from its parent, use the same `WorldPositionStays` value that you used to parent the child. More specifically, when `WorldPositionStays` is false, this applies. However, if you're using the default value of `true`, this isn't required (because it's the default). -When the `WorldPositionStays` parameter in `NetworkObject.TrySetParent` is the default value of `true`, this will preserve the world space values of the child `NetworkObject` relative to the parent. However, sometimes you might want to only preserve the local space values (pick up an object that only has some initial changes to the child's transform when parented). Through a combination of `NetworkObject.TrySetParent` and `NetworkBehaviour.OnNetworkObjectParentChanged` you can accomplish this without the need for a NetworkTransform component. To better understand how this works, it's important to understand the order of operations for both of these two methods: +When the `WorldPositionStays` parameter in `NetworkObject.TrySetParent` is the default value of `true`, this will preserve the world space values of the child NetworkObject relative to the parent. However, sometimes you might want to only preserve the local space values (pick up an object that only has some initial changes to the child's transform when parented). Through a combination of `NetworkObject.TrySetParent` and `NetworkBehaviour.OnNetworkObjectParentChanged` you can accomplish this without the need for a NetworkTransform component. To better understand how this works, it's important to understand the order of operations for both of these two methods: **Server-Side** diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networktime-ticks.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networktime-ticks.md index 4f56a900a5..157de47e79 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networktime-ticks.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networktime-ticks.md @@ -34,14 +34,14 @@ sequenceDiagram `ServerTime`: - For player objects with server authority (For example, by sending inputs to the server via RPCs) -- In sync with position updates of `NetworkTransform` for all `NetworkObjects` where the client isn't authoritative over the transform. +- In sync with position updates of NetworkTransform for all `NetworkObjects` where the client isn't authoritative over the transform. - For everything on non client controlled `NetworkObjects`. ## Examples ### Example 1: Using network time to synchronize environments -Many games have environmental objects which move in a fixed pattern. By using network time these objects can be moved without having to synchronize their positions with a `NetworkTransform`. +Many games have environmental objects which move in a fixed pattern. By using network time these objects can be moved without having to synchronize their positions with a NetworkTransform. For instance the following code can be used to create a moving elevator platform for a client authoritative game: @@ -136,11 +136,11 @@ sequenceDiagram `}/> > [!NOTE] -> Some components such as `NetworkTransform` add additional buffering. When trying to align an RPC event like in this example, an additional delay would need to be added. +> Some components such as NetworkTransform add additional buffering. When trying to align an RPC event like in this example, an additional delay would need to be added. ## Network Ticks -Network ticks are run at a fixed rate. The 'Tick Rate' field on the `NetworkManager` can be used to set the tick rate. +Network ticks are run at a fixed rate. The 'Tick Rate' field on the NetworkManager can be used to set the tick rate. What does changing the network tick affect? Changes to `NetworkVariables` aren't sent immediately. Instead during each network tick changes to `NetworkVariables` are collected and sent out to other peers. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/object-pooling.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/object-pooling.md index 32e7add573..da5c1ac150 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/object-pooling.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/object-pooling.md @@ -14,7 +14,7 @@ You can register your own spawn handlers by including the `INetworkPrefabInstanc void Destroy(NetworkObject networkObject); } ``` -Netcode will use the `Instantiate` and `Destroy` methods in place of default spawn handlers for the `NetworkObject` used during spawning and despawning. Because the message to instantiate a new `NetworkObject` originates from a Host or Server, both won't have the Instantiate method invoked. All clients (excluding a Host) will have the instantiate method invoked if the `INetworkPrefabInstanceHandler` implementation is registered with `NetworkPrefabHandler` (`NetworkManager.PrefabHandler`) and a Host or Server spawns the registered/associated `NetworkObject`. +Netcode will use the `Instantiate` and `Destroy` methods in place of default spawn handlers for the NetworkObject used during spawning and despawning. Because the message to instantiate a new NetworkObject originates from a Host or Server, both won't have the Instantiate method invoked. All clients (excluding a Host) will have the instantiate method invoked if the `INetworkPrefabInstanceHandler` implementation is registered with `NetworkPrefabHandler` (`NetworkManager.PrefabHandler`) and a Host or Server spawns the registered/associated NetworkObject. diff --git a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablebehaviour.md b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablebehaviour.md deleted file mode 100644 index d4cbab5c57..0000000000 --- a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablebehaviour.md +++ /dev/null @@ -1,113 +0,0 @@ -# AttachableBehaviour - -![alt text](../../images/attachable/AttachableBehaviour_InspectorView-1.png) - -The basic functionality of the `AttachableBehaviour` component provides: -- The ability to assign (make aware) `ComponetController` components from any part of the parent-child hierarchy. - - Each `ComponentControllerEntry` provides the ability to select when the `ComponentController` should be triggered (via the **Auto Trigger** property) and whether its enabled state should be enabled or disabled upon attaching (via the **Enable On Attach** property). The default setting is to be disabled upon the `AttachableBehaviour` attaching to an `AttachableNode` and enabled upon detaching. When the **Enable On Attach** property is enabled, the `ComponentController` will be set to enabled upon the `AttachableBehaviour` attaching to an `AttachableNode` and disabled upon detaching. -- The ability to control when an `AttachableBehaviour` component will automatically detach from an `AttachableNode` via the **Auto Detach** property. - - The **Auto Detach** property can have any combination of the below flags or none (no flags): - - **On Ownership Changed:** Upon ownership changing, the `AttachableBehaviour` will detach from any `AttachableNode` it is attached to. - - **On Despawn:** Upon the `AttachableBehaviour` being despawned, it will detach from any `AttachableNode` it is attached to. - - **On Attach Node Destroy**: Just prior to the `AttachableNode` being destroyed, any attached `AttachableBehaviour` with this flag will automatically detach from the `AttachableNode`. - -_Any of the `AttachableBehaviour.AutoDetach` settings will be invoked on all instances without the need for the owner to synchronize the end result(i.e. detaching) which provides a level of redundancy for edge case scenarios like a player being disconnected abruptly by the host or by timing out or any scenario where a spawned object is being destroyed with the owner or perhaps being redistributed to another client authority in a distributed authority session. Having the ability to select or deselect any of the auto-detach flags coupled with the ability to derive from `AttachableBehaviour` provides additional levels of modularity/customization._ - -## Attaching vs NetworkObject parenting - -Fundamentally, attaching is another way to synchronize parenting while not requiring one to use the traditional `NetworkObject` parenting. Attaching a child `GameObject` nested under a `NeworkObject` (_really the `GameObject` the `NetworkObject` component belongs to_) will only take the child `GameObject` and parent it under the `GameObject` of an `AttachableNode`. The target to parent under must be of a different spawned `NetworkObject` and the `AttachableNode` needs to be on the same or child `GameObject` of the target `NetworkObject`. - -### NetworkObject parenting - -The traditional approach has been to spawn two network prefab instances:
-![alt text](../../images/attachable/SpawnObjectA-B.png) - -Then parent one instance under the other:
-![alt text](../../images/attachable/SpawnObjectA-B-2.png) - -This is simple enough for many scenarios, but can become cumbersome under more specific scenarios where a user might want to have a "world" version of the item and a "picked up" version of the item. - -### Attaching - -With attaching, a user would create nested `GameObject` children that represent the item when it is picked up and when it is dropped/placed somewhere in the scene (i.e. world).
-![alt text](../../images/attachable/PlayerAndWorldItem-1.png) - -- The WorldItemRoot is where the `NetworkObject` component is placed. -- The NestedChild-World contains the components needed for the item when it is placed in the world. -- The NestedChild-PickedUp contains the components needed for the item when it is picked up by a player. - -By placing an `AttachableBehaviour` component on the NestedChild-PickedUp `GameObject` and an `AttachableNode` component on the TargetNode, a user can then invoke the `AttachableBehaviour.Attach` method while passing in the `AttachableNode` component and the NestedChild-PickedUp `GameObject` will get parented under the TargetNode while also synchronizing this action with all other clients.
-![alt text](../../images/attachable/PlayerAndWorldItem-2.png) - -:::info -**Example of synchronized RPC driven properties** - -Both the `AttachableBehaviour` and the `ComponentController` provide an example of using synchronized RPC driven properties in place of `NetworkVariable`. Under certain conditions it is better to use RPCs when a specific order of operations is needed as opposed to `NetworkVariable`s which can update out of order (regarding the order in which certain states were updated) depending upon several edge case scenarios. - -Under this condition using reliable RPCs will assure the messages are received in the order they were generated while also reducing the latency time between the change and the non-authority instances being notified of the change. Synchronized RPC driven properties only require overriding the `NetworkBehaviour.OnSynchronize` method and serializing any properties that need to be synchronized with late joining players or handling network object visibility related scenarios. -::: - -## Usage walk through - -### Introduction - -For example purposes, we will walk through a common scenario where you might want to have a world item that had unique visual and scripted components active while while placed in the world but then can switch to a different set of visual and scripted components when picked up by a player's avatar. Additionally, you might want to be able to easily "attach" only the portion of the item, that is active when picked up, to one of the player's avatar's child nodes. Below is a high-level diagram overview of what both the player and world item network prefabs could look like:
- -![alt text](../../images/attachable/AttachableDiagram-1.png) - -#### Player - -The player prefab in the above diagram is not complete, includes the components of interest, and some additional children and components for example purposes. A complete diagram would most definitely have additional components and children. The `AttachableNode` components provide a "target attach point" that any other spawned network prefab with an `AttachableBehaviour` could attach itself to. - -#### World Item - -This diagram has a bit more detail to it and introduces one possible usage of a `ComponentController` and `AttachableBehaviour`. The `ComponentController` will be used to control the enabling and disabling of components and synchronizing this with non-authority instances. The `AttachableBehaviour` resides on the child `AttachedView`'s `GameObject` and will be the catalyst for attaching to a player. - -### World vs attached view modes - -![alt text](../../images/attachable/AttachableDiagram-2.png) - -In the diagram above, we see arrows pointing from the `ComponentController` to the non-netcode standard Unity components such as a `MeshRenderer`, `Collider`, or any other component that should only be enabled when either in "World View" or "Attached View" modes. We can also see that the `AttachableBehaviour` points to the `ComponentController` with a diagram to the right that shows the `AttachableBehaviour` notifies the `ComponentController` that, in turn, enables or disables certain components. - -#### World Item Component Controller -Below is a screenshot of what the `ComponentController` would look like in the inspector view:
- -![alt text](../../images/attachable/WorldItem-Inspector-View-1.png) - -Looking at the `ComponentController`'s **Components** property, we can see two of the component entries have references to the `WorldItemView`'s `BoxCollider` and `MeshRenderer` that are both configured to be enabled when the `ComponentController`'s state is `true`. We can also see that the `CarryView`'s `MeshRenderer` is added and configured to be the inverse of the current `ComponentController`'s state. Since the `ComponentController`'s **Start Enabled** property is enabled we can logically deduce the **WorldItem** network prefab will start with the `WorldItemView` being active when spawned. Taking a look at the **CarryObject** child's properties: - -![alt text](../../images/attachable/WorldItem-Inspector-View-2.png) - -We can see the `AttachableBehaviour`'s **Component Controllers** list contains `ComponentControllerEntry` (WorldItem Component Controller) that references to the `WorldItem`'s `ComponentController`. We can also see that the `ComponentControllerEntry` is configured to trigger on everything (_OnAttach and OnDetach_) and will set the `ComponentController`'s state to disabled _(false)_. This means when the `AttachableBehaviour` is attached the `ComponentController` will be in the disabled state along with the `WorldItemView` components while the `CarryView`'s `MeshRenderer` will be enabled. - -**Summarized Overview:** -- `AttachableBehaviour` sets the `ComponentController` state (true/enabled or false/disabled). -- `ComponentController` states: - - Enabled (true) - - World Item View (enabled/true) - - Carry View (disabled/false) - - Disabled (false) - - World Item View (disabled/false) - - Carry View (enabled/true) - -### Attaching - -![alt text](../../images/attachable/AttachableDiagram-3.png) - -The above diagram represents what the **Player** and **World Item** spawned objects (_including cloned/non-authority instances_) would look like once the **Attached View** object has been parented under the avatar's **Right Attach** object. The green area and arrow represent the still existing relationship that the **Attached View** has with the **World Item**'s `NetworkObject`. - -:::info -**AttachableBehaviour & NetworkObject Relationship** - -Upon a `NetworkObject` component being spawned, all associated `NetworkBehaviour` based component instances, that are directly attached to the `NetworkObject`'s `GameObject` or are on any child `GameObject`, will be registered with the `NetworkObject` instance. This remains true even when a child `GameObject` containing one or more `NetworkBehaviour` based component instances of a spawned `NetworkObject` is parented, during runtime, under another `GameObject` that is associated with a different spawned `NetworkObject`. Of course, there are additional considerations like: -- What happens when one or both of the NetworkObjects is de-spawned? -- How do you assure the child attachable will return back to its default parent? -- and several other edge case scenarios... - -`AttachableBehaviour` leverages from this "spawn lifetime" relationship to provide another type of "parenting" (attaching) while also taking into consideration these types of edge case scenarios. -::: - -## Additional resources - -- [AttachableNode](attachablenode.md) -- [ComponentController](componentcontroller.md) \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablenode.md b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablenode.md deleted file mode 100644 index 5e8b68162f..0000000000 --- a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/attachablenode.md +++ /dev/null @@ -1,14 +0,0 @@ -# AttachableNode - -![alt text](../../images/attachable/AttachableNode_InspectorView-1.png) - -This component provides a valid attach point for `AttachableBehaviour` components. It includes the `AttachableNode.DetachOnDespawn` field that, when enabled (the default), it will automatically detach any attached `AttachableBehaviour` instances when the associated `NetworkObject` of the `AttachableNode` is de-spawned. - -## AttachableBehaviour Usage - -[A usage example can be found here.](attachablebehaviour.md#usage-walk-through) - -## Additional resources - -- [AttachableBehaviour](attachablebehaviour.md) -- [ComponentController](componentcontroller.md) \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/componentcontroller.md b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/componentcontroller.md deleted file mode 100644 index 823dd15817..0000000000 --- a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/componentcontroller.md +++ /dev/null @@ -1,87 +0,0 @@ -# ComponentController - -A `ComponentController` provides you with the ability to enable or disable one or more components by the authority instance and have those changes synchronized with non-authority/remote instances. It uses a [synchronized RPC driven field approach](../foundational/networkbehaviour-synchronize.md#synchronized-rpc-driven-fields) to synchronize its enabled state of the components it is controlling to assure optimal performance and that the order of operations of changes is relative to other `ComponentController` and/or other `AttachableBehaviour` component instances. - -The `ComponentController` can be: -- Used with `AttachableBehaviour` or independently for another purpose. -- Configured to directly or inversely follow the `ComponentController`'s current state. -- Configured to have an enable and/or disable delay. - - _When invoked internally by `AttachableBehaviour`, delays are ignored when an `AttachableNode` is being destroyed and the changes are immediate._ - -## Configuring - -![alt text](../../images/attachable/WorldItem-Inspector-View-1.png) - -A `ComponentController` can have one or more `ComponentEntry` entries in its **Components** list. Each `ComponentEntry` has some additional fields that you can adjust based on your desired result: -- **Invert Enabled:** When enabled, this will make the associated component inversely follow the `ComponentControllers` global enabled state. This is useful if you want a set of components to be enabled when the `ComponentController` component's global enable state is set to `false` and for that same set of components to be disabled when the `ComponentController` component's global enable state is set to `true`. -- **Enable Delay:** When greater than 0 (the default), the component will delay transitioning from a disabled state to an enabled state by the amount of time (in seconds) specified. -- **Disable Delay:** When greater than 0 (the default), the component will delay transitioning from an enabled state to a disabled state by the amount of time (in seconds) specified. -- **Component:** The component to control and synchronize its enabled state. - -Both delay values (Enable & Disable) has many uses, but an example would be to prevent a `MeshRenderer` from being enabled prior to other specific events like avoiding it from rendering for a few frames while the attachable is positioned. - -## Examples - -### Independent Usage - -While `ComponentController` can be used with an `AttachableBehaviour` without writing any script, you might find that it can be used for many other purposes. Below is a pseudo example where a `ComponentController` would have its synchronized enabled state updated when the `DaisyChainedController` is either enabled or disabled. - -```csharp -/// -/// Use as a component in the ComponentController that will -/// trigger the Controller (ComponentController). -/// This pattern can repeat/be daisy chained. -/// -public class DaisyChainedController : MonoBehaviour -{ - public ComponentController Controller; - - private void OnEnable() - { - if (!Controller || !Controller.HasAuthority) - { - return; - } - Controller.SetEnabled(true); - } - - private void OnDisable() - { - if (!Controller || !Controller.HasAuthority) - { - return; - } - Controller.SetEnabled(false); - } -} -``` - -The above component could be arranged to create a chained sequence of components when the root `DaisyChainedController` component is enabled or disabled. Such a sequence could look like: - -- DaisyChainedController-A - - Controller - - Points to DaisyChainedController-B -- DaisyChainedController-B - - Controller - - Points to DaisyChainedController-C -- DaisyChainedController-C - - Controller - -When DaisyChainedController-A is enabled, then a sequence of events would occur where DaisyChainedController-B and DaisyChainedController-C would be enabled. The same sequence of events would occur when DaisyChainedController-A was then disabled. - -### AttachableBehaviour Usage - -The `AttachableBehaviour` can be assigned one or more component controllers that will be invoked, depending upon configuration, when the `AttachableBehaviour` is attached and detached from an `AttachableNode`. You can find the [usage example with an `AttachableBehaviour` here.](attachablebehaviour.md#usage-walk-through) - -:::info -**Example of synchronized RPC driven properties** - -Both the `AttachableBehaviour` and the `ComponentController` provide an example of using synchronized RPC driven properties in place of `NetworkVariable`. Under certain conditions it is better to use RPCs when a specific order of operations is needed as opposed to `NetworkVariable`s which can update out of order (regarding the order in which certain states were updated) depending upon several edge case scenarios. - -Under this condition using reliable RPCs will assure the messages are received in the order they were generated while also reducing the latency time between the change and the non-authority instances being notified of the change. Synchronized RPC driven properties only require overriding the `NetworkBehaviour.OnSynchronize` method and serializing any properties that need to be synchronized with late joining players or handling network object visibility related scenarios. -::: - -## Additional resources - -- [AttachableBehaviour](attachablebehaviour.md) -- [AttachableNode](attachablenode.md) \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/helpercomponents.md b/com.unity.netcode.gameobjects/Documentation~/components/Helpers/helpercomponents.md deleted file mode 100644 index f8a69be0d3..0000000000 --- a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/helpercomponents.md +++ /dev/null @@ -1,12 +0,0 @@ -# Helper Components - -Understand the helper components available to use in your Netcode for GameObjects project. - - **Topic** | **Description** | -| :------------------------------ | :------------------------------- | -| **[AttachableBehaviour](attachablebehaviour.md)**| Provides an alternative to `NetworkObject` parenting. This section includes a usage example with `AttachableBehaviour`, `AttachableNode`, and `ComponentController`. | -| **[AttachableNode](attachablenode.md)**| Target parent for an `AttachableBehaviour`. | -| **[ComponentController](componentcontroller.md)**| Provides the synchronization of and control over enabling or disabling objects. | -| **[NetworkAnimator](networkanimator.md)**| The `NetworkAnimator` component provides you with a fundamental example of how to synchronize animations during a network session. Animation states are synchronized with players joining an existing network session and any client already connected before the animation state changing. | -| **[NetworkTransform](networktransform.md)**| [NetworkTransform](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.Components.NetworkTransform.html) is a concrete class that inherits from [NetworkBehaviour](../foundational/networkbehaviour.md) and synchronizes [Transform](https://docs.unity3d.com/Manual/class-Transform.html) properties across the network, ensuring that the position, rotation, and scale of a [GameObject](https://docs.unity3d.com/Manual/working-with-gameobjects.html) are replicated to other clients. | -| **[Physics](../../advanced-topics/physics.md)**| Netcode for GameObjects has a built in approach which allows for server-authoritative physics where the physics simulation only runs on the server. | diff --git a/com.unity.netcode.gameobjects/Documentation~/components/core/corecomponents.md b/com.unity.netcode.gameobjects/Documentation~/components/core/corecomponents.md new file mode 100644 index 0000000000..33947cdeeb --- /dev/null +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/corecomponents.md @@ -0,0 +1,36 @@ +# Core components + +Learn about the three core components of Netcode for GameObjects: NetworkObject, NetworkBehaviour, and NetworkManager. + +* [NetworkObject](#networkobject): This component, added to a GameObject, declares a prefab or in-scene placed object as a networked object that can have states synchronized between clients and/or a server. A NetworkObject component allows you to identify each uniquely spawned instance (dynamically or in-scene placed). _You cannot derive from the NetworkObject component class_. +* [NetworkBehaviour](#networkbehaviour): This is the fundamental networked scripting component that allows you to synchronize state and write netcode script(s). _You derive from this class to create your own netcode component scripts_. +* [NetworkManager](#networkmanager): This component is the overall network session configuration and session management component. A NetworkManager component is necessary to start or join a network session. + + +## NetworkObject + +| **Topic** | **Description** | +| :------------------------------ | :------------------------------- | +| **[NetworkObject](networkobject.md)** | A NetworkObject is a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) with a NetworkObject component and at least one [NetworkBehaviour](networkbehaviour.md) component, which enables the GameObject to respond to and interact with netcode. | +| **[NetworkObject parenting](../../advanced-topics/networkobject-parenting.md)** | Understand how NetworkObjects are parented in Netcode for GameObjects. | + + +## NetworkBehaviour + + +| **Topic** | **Description** | +| :------------------------------ | :------------------------------- | +| **[NetworkBehaviour](networkbehaviour.md)** | [NetworkBehaviour](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html) is an abstract class that derives from [MonoBehaviour](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and is primarily used to create unique netcode or game logic. To replicate any netcode-aware properties or send and receive RPCs, a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) must have a [NetworkObject](networkobject.md) component and at least one NetworkBehaviour component. | +| **[Synchronizing](networkbehaviour-synchronize.md)** | Understand a NetworkBehaviour component's order of operations when it comes to spawning, de-spawning, and adding custom synchronization data. | + + +## NetworkManager + +| **Topic** | **Description** | +| :------------------------------ | :------------------------------- | +| **[NetworkManager](networkmanager.md)**| The NetworkManager component is a required Netcode for GameObjects component that has all of your project's netcode-related settings. Think of it as the central netcode hub for your netcode-enabled project. | +| **[Player prefabs and spawning players](playerobjects.md)**| Learn about spawning player objects and creating player prefabs.| + +## Additional resources + +* [GameObjects](https://docs.unity3d.com/6000.1/Documentation/Manual/working-with-gameobjects.html) \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/components/foundational/networkbehaviour-synchronize.md b/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour-synchronize.md similarity index 72% rename from com.unity.netcode.gameobjects/Documentation~/components/foundational/networkbehaviour-synchronize.md rename to com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour-synchronize.md index 21c0958ea2..bef5662a53 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/foundational/networkbehaviour-synchronize.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour-synchronize.md @@ -1,20 +1,20 @@ # NetworkBehaviour synchronization -[`NetworkBehaviour`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html) is an abstract class that derives from [`MonoBehaviour`](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and is primarily used to create unique netcode or game logic. To replicate any netcode-aware properties or send and receive RPCs, a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) must have a [NetworkObject](networkobject.md) component and at least one `NetworkBehaviour` component. +[NetworkBehaviour](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html) is an abstract class that derives from [MonoBehaviour](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and is primarily used to create unique netcode or game logic. To replicate any netcode-aware properties or send and receive RPCs, a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) must have a [NetworkObject](networkobject.md) component and at least one NetworkBehaviour component. -You can use `NetworkBehaviour`s to synchronize settings before, during, and after spawning NetworkObjects. +You can use NetworkBehaviours to synchronize settings before, during, and after spawning NetworkObjects. -For more information about spawning and despawning `NetworkBehaviour`s, refer to the [NetworkBehaviour spawning and despawning page](networkbehaviour.md). +For more information about spawning and despawning NetworkBehaviours, refer to the [NetworkBehaviour spawning and despawning page](networkbehaviour.md). ## Pre-spawn, spawn, post-spawn and synchronization -The NetworkObject spawn process can become complicated when there are multiple `NetworkBehaviour` components attached to the same GameObject. Additionally, there can be times where you want to be able to handle pre- and post-spawn oriented tasks. +The NetworkObject spawn process can become complicated when there are multiple NetworkBehaviour components attached to the same GameObject. Additionally, there can be times where you want to be able to handle pre- and post-spawn oriented tasks. - Pre-spawn example: Instantiating a `NetworkVariable` with owner write permissions and assigning a value to that `NetworkVariable` on the server or host side. -- Spawn example: Applying a local value or setting that may be used during post spawn by another local `NetworkBehaviour` component. +- Spawn example: Applying a local value or setting that may be used during post spawn by another local NetworkBehaviour component. - Post-spawn example: Accessing a `NetworkVariable` or other property that is set during the spawn process. -Below are the three virtual methods you can override within a `NetworkBehaviour`-derived class: +Below are the three virtual methods you can override within a NetworkBehaviour-derived class: Method | Scope | Use case | Context ---------------------------- | ------------------------ | ------------------------------------------------------ | ------------- @@ -26,7 +26,7 @@ OnInSceneObjectsSpawned | In-scene NetworkObjects | New client finished sy In addition to the methods above, there are two special case convenience methods: -- `OnNetworkSessionSynchronized`: When scene management is enabled and a new client joins a session, the client starts synchronizing with the network session. During this period of time the client might need to load additional scenes as well as instantiate and spawn NetworkObjects. When a client has finished loading all scenes and all NetworkObjects are spawned, this method gets invoked on all `NetworkBehaviour`s associated with any spawned NetworkObjects. This can be useful if you want to write scripts that might require access to other spawned NetworkObjects and/or their `NetworkBehaviour` components. When this method is invoked, you are assured everything is spawned and ready to be accessed and/or to have messages sent from them. Remember that this is invoked on clients and is not invoked on a server or host. +- `OnNetworkSessionSynchronized`: When scene management is enabled and a new client joins a session, the client starts synchronizing with the network session. During this period of time the client might need to load additional scenes as well as instantiate and spawn NetworkObjects. When a client has finished loading all scenes and all NetworkObjects are spawned, this method gets invoked on all NetworkBehaviours associated with any spawned NetworkObjects. This can be useful if you want to write scripts that might require access to other spawned NetworkObjects and/or their NetworkBehaviour components. When this method is invoked, you are assured everything is spawned and ready to be accessed and/or to have messages sent from them. Remember that this is invoked on clients and is not invoked on a server or host. - `OnInSceneObjectsSpawned`: Sometimes you might want to have the same kind of assurance that any in-scene placed NetworkObjects have been spawned prior to a specific set of scripts being invoked. This method is invoked on in-scene placed NetworkObjects when: - A server or host first starts up after all in-scene placed NetworkObjects in the currently loaded scene(s) have been spawned. - A client finishes synchronizing. @@ -34,7 +34,7 @@ In addition to the methods above, there are two special case convenience methods ### Pre-spawn synchronization with `OnSynchronize` -There can be scenarios where you need to include additional configuration data or use a `NetworkBehaviour` to configure some non-netcode related component (or the like) before a `NetworkObject` is spawned. This can be particularly critical if you want specific settings applied before `NetworkBehaviour.OnNetworkSpawn` is invoked. When a client is synchronizing with an existing network session, this can become problematic as messaging requires a client to be fully synchronized before you know "it is safe" to send the message, and even if you send a message there is the latency involved in the whole process that might not be convenient and can require additional specialized code to account for this. +There can be scenarios where you need to include additional configuration data or use a NetworkBehaviour to configure some non-netcode related component (or the like) before a NetworkObject is spawned. This can be particularly critical if you want specific settings applied before `NetworkBehaviour.OnNetworkSpawn` is invoked. When a client is synchronizing with an existing network session, this can become problematic as messaging requires a client to be fully synchronized before you know "it is safe" to send the message, and even if you send a message there is the latency involved in the whole process that might not be convenient and can require additional specialized code to account for this. `NetworkBehaviour.OnSynchronize` allows you to write and read custom serialized data during the NetworkObject serialization process. @@ -53,49 +53,49 @@ The following provides you with an outline of the order of operations that occur Server-side: -- `GameObject` with `NetworkObject` component is instantiated. -- The `NetworkObject` is spawned. - - For each associated `NetworkBehaviour` component, `NetworkBehaviour.OnNetworkSpawn` is invoked. +- GameObject with NetworkObject component is instantiated. +- The NetworkObject is spawned. + - For each associated NetworkBehaviour component, `NetworkBehaviour.OnNetworkSpawn` is invoked. - The `CreateObjectMessage` is generated. - - `NetworkObject` state is serialized. + - NetworkObject state is serialized. - `NetworkVariable` state is serialized. - - `NetworkBehaviour.OnSynchronize` is invoked for each `NetworkBehaviour` component. + - `NetworkBehaviour.OnSynchronize` is invoked for each NetworkBehaviour component. - If this method isn't overridden then nothing is written to the serialization buffer. -- The `CreateObjectMessage` is sent to all clients that are observers of the `NetworkObject`. +- The `CreateObjectMessage` is sent to all clients that are observers of the NetworkObject. Client-side: - The `CreateObjectMessage` is received. - - `GameObject` with `NetworkObject` component is instantiated. + - GameObject with NetworkObject component is instantiated. - `NetworkVariable` state is deserialized and applied. - - `NetworkBehaviour.OnSynchronize` is invoked for each `NetworkBehaviour` component. + - `NetworkBehaviour.OnSynchronize` is invoked for each NetworkBehaviour component. - If this method isn't overridden then nothing is read from the serialization buffer. -- The `NetworkObject` is spawned. - - For each associated `NetworkBehaviour` component, `NetworkBehaviour.OnNetworkSpawn` is invoked. +- The NetworkObject is spawned. + - For each associated NetworkBehaviour component, `NetworkBehaviour.OnNetworkSpawn` is invoked. #### Order of operations during full (late-join) client synchronization Server-side: - The `SceneEventMessage` of type `SceneEventType.Synchronize` is created. - All spawned `NetworkObjects` that are visible to the client, already instantiated, and spawned are serialized. - - `NetworkObject` state is serialized. + - NetworkObject state is serialized. - `NetworkVariable` state is serialized. - - `NetworkBehaviour.OnSynchronize` is invoked for each `NetworkBehaviour` component. + - `NetworkBehaviour.OnSynchronize` is invoked for each NetworkBehaviour component. - If this method isn't overridden then nothing is written to the serialization buffer. - The `SceneEventMessage` is sent to the client. Client-side: - The `SceneEventMessage` of type `SceneEventType.Synchronize` is received. - Scene information is deserialized and scenes are loaded (if not already). - - In-scene placed `NetworkObject`s are instantiated when a scene is loaded. -- All `NetworkObject` oriented synchronization information is deserialized. - - Dynamically spawned `NetworkObject`s are instantiated and state is synchronized. - - For each `NetworkObject` instance: + - In-scene placed NetworkObjects are instantiated when a scene is loaded. +- All NetworkObject oriented synchronization information is deserialized. + - Dynamically spawned NetworkObjects are instantiated and state is synchronized. + - For each NetworkObject instance: - `NetworkVariable` state is deserialized and applied. - `NetworkBehaviour.OnSynchronize` is invoked. - If this method isn't overridden then nothing is read from the serialization buffer. - - The `NetworkObject` is spawned. - - For each associated `NetworkBehaviour` component, `NetworkBehaviour.OnNetworkSpawn` is invoked. + - The NetworkObject is spawned. + - For each associated NetworkBehaviour component, `NetworkBehaviour.OnNetworkSpawn` is invoked. ### Synchronized RPC driven fields @@ -111,12 +111,12 @@ With this in mind, you might need states to be updated in the relative order in - When the authority changes a local field's value, it would then need send an RPC to all non-authority instances. - RPC messages are immediately queued in the outbound send queue which means the order in which an RPC is invoked is the order in which they will be received (_if using the default reliable fragmented sequenced delivery_). - - Any other synchronized RPC driven fields, whether on the same `NetworkBehaviour` or not, would occur in the order they were invoked on the authority instance side. + - Any other synchronized RPC driven fields, whether on the same NetworkBehaviour or not, would occur in the order they were invoked on the authority instance side. - As long as you override the `NetworkBehaviour.OnSynchronize` method and serialize the field, then late joining clients will be synchronized with the authority's most current field value. :::info **Synchronized RPC driven fields vs NetworkVariables** -When a NetworkVariable becomes dirty, the associated `NetworkObject` is add to a queue of `NetworkObject` instances to be scanned for changes to any NetworkVariable declared in a `NetworkBehaviour` associated with the `NetworkObject` instance. Towards the end of the frame, during late update, any `NetworkObject` instances marked as "having one or more dirty NetworkVariables" will be processed and the delta states will be serialized. This can lead to a scenario like the following: +When a NetworkVariable becomes dirty, the associated NetworkObject is add to a queue of NetworkObject instances to be scanned for changes to any NetworkVariable declared in a NetworkBehaviour associated with the NetworkObject instance. Towards the end of the frame, during late update, any NetworkObject instances marked as "having one or more dirty NetworkVariables" will be processed and the delta states will be serialized. This can lead to a scenario like the following: **Authority side** - During Update: @@ -221,20 +221,20 @@ If your serialization code has a bug and throws an exception, then `NetworkBehav #### When writing If user-code throws an exception during `NetworkBehaviour.OnSynchronize`, it catches the exception and if: -- **LogLevel = Normal**: A warning message that includes the name of the `NetworkBehaviour` that threw an exception while writing will be logged and that part of the serialization for the given `NetworkBehaviour` is skipped. +- **LogLevel = Normal**: A warning message that includes the name of the NetworkBehaviour that threw an exception while writing will be logged and that part of the serialization for the given NetworkBehaviour is skipped. - **LogLevel = Developer**: It provides the same warning message as well as it logs an error with the exception message and stack trace. -After generating the log message(s), it rewinds the serialization stream to the point just before it invoked `NetworkBehaviour.OnSynchronize` and will continue serializing. Any data written before the exception occurred will be overwritten or dropped depending upon whether there are more `NetworkBehaviour` components to be serialized. +After generating the log message(s), it rewinds the serialization stream to the point just before it invoked `NetworkBehaviour.OnSynchronize` and will continue serializing. Any data written before the exception occurred will be overwritten or dropped depending upon whether there are more NetworkBehaviour components to be serialized. #### When reading -For exceptions this follows the exact same message logging pattern described above when writing. The distinct difference is that after it logs one or more messages to the console, it skips over only the serialization data written by the server-side when `NetworkBehaviour.OnSynchronize` was invoked and continues the deserialization process for any remaining `NetworkBehaviour` components. +For exceptions this follows the exact same message logging pattern described above when writing. The distinct difference is that after it logs one or more messages to the console, it skips over only the serialization data written by the server-side when `NetworkBehaviour.OnSynchronize` was invoked and continues the deserialization process for any remaining NetworkBehaviour components. -However, there is an additional check to assure that the total expected bytes to read were actually read from the buffer. If the total number of bytes read does not equal the expected number of bytes to be read it will log a warning that includes the name of the `NetworkBehaviour` in question, the total bytes read, the expected bytes to be read, and lets you know this `NetworkBehaviour` is being skipped. +However, there is an additional check to assure that the total expected bytes to read were actually read from the buffer. If the total number of bytes read does not equal the expected number of bytes to be read it will log a warning that includes the name of the NetworkBehaviour in question, the total bytes read, the expected bytes to be read, and lets you know this NetworkBehaviour is being skipped. > [!NOTE] > When using `NetworkBehaviour.OnSynchronize` you should be aware that you are increasing the synchronization payload size per instance. If you have 30 instances that each write 100 bytes of information you will have increased the total full client synchronization size by 3000 bytes. -## Serializing `NetworkBehaviour`s +## Serializing NetworkBehaviours -`NetworkBehaviour`s require the use of specialized [`NetworkBehaviourReference`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviourReference.html) structures to be serialized and used with RPCs and `NetworkVariable`s. +NetworkBehaviours require the use of specialized [`NetworkBehaviourReference`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviourReference.html) structures to be serialized and used with RPCs and `NetworkVariable`s. diff --git a/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour.md b/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour.md new file mode 100644 index 0000000000..26863bf673 --- /dev/null +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour.md @@ -0,0 +1,127 @@ +# NetworkBehaviour spawning and despawning + +[NetworkBehaviour](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html) is an abstract class that derives from [MonoBehaviour](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and is primarily used to create unique netcode or game logic. To replicate any netcode-aware properties or send and receive RPCs, a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) must have a [NetworkObject](networkobject.md) component and at least one NetworkBehaviour component. + +A NetworkBehaviour requires a NetworkObject component on the same relative GameObject or on a parent of the GameObject with the NetworkBehaviour component assigned to it. If you add a NetworkBehaviour to a GameObject that doesn't have a NetworkObject (or any parent), then Netcode for GameObjects automatically adds a NetworkObject component to the GameObject in which the NetworkBehaviour was added. + +NetworkBehaviours can use `NetworkVariable`s and RPCs to synchronize states and send messages over the network. When you call an RPC function, the function isn't called locally. Instead, a message is sent containing your parameters, the `networkId` of the NetworkObject associated with the same GameObject (or child) that the NetworkBehaviour is assigned to, and the index of the NetworkObject-relative NetworkBehaviour (NetworkObjects can have several `NetworkBehaviours`, the index communicates which one). + +For more information about serializing and synchronizing NetworkBehaviours, refer to the [NetworkBehaviour synchronization page](networkbehaviour-synchronize.md). + +> [!NOTE] +> It's important that the NetworkBehaviours on each NetworkObject remain the same for the server and any client connected. When using multiple projects, this becomes especially important so the server doesn't try to call a client RPC on a NetworkBehaviour that might not exist on a specific client type (or set a `NetworkVariable` that doesn't exist, and so on). + +## Spawning + +`OnNetworkSpawn` is invoked on each NetworkBehaviour associated with a NetworkObject when it's spawned. This is where all netcode-related initialization should occur. + +You can still use `Awake` and `Start` to do things like finding components and assigning them to local properties, but if `NetworkBehaviour.IsSpawned` is false then don't expect netcode-distinguishing properties (like `IsClient`, `IsServer`, `IsHost`, for example) to be accurate within `Awake` and `Start` methods. + +For reference purposes, below is a table of when `NetworkBehaviour.OnNetworkSpawn` is invoked relative to the NetworkObject type: + +Dynamically spawned | In-scene placed +------------------- | --------------- +`Awake` | `Awake` +`OnNetworkSpawn` | `Start` +`Start` | `OnNetworkSpawn` + +For more information about NetworkBehaviour methods and when they are invoked, see the [Pre-Spawn and MonoBehaviour Methods](networkbehaviour.md#pre-spawn-and-monobehaviour-methods) section. + +### Disabling NetworkBehaviours when spawning + +If you want to disable a specific NetworkBehaviour but still want it to be included in the NetworkObject spawn process (so you can still enable it at a later time), you can disable the individual NetworkBehaviour instead of the entire GameObject. + +NetworkBehaviour components that are disabled by default and are attached to in-scene placed NetworkObjects behave like NetworkBehaviour components that are attached to dynamically spawned NetworkObjects when it comes to the order of operations for the `NetworkBehaviour.Start` and `NetworkBehaviour.OnNetworkSpawn` methods. Since in-scene placed NetworkObjects are spawned when the scene is loaded, a NetworkBehaviour component (that is disabled by default) will have its `NetworkBehaviour.OnNetworkSpawn` method invoked before the `NetworkBehaviour.Start` method, since `NetworkBehaviour.Start` is invoked when a disabled NetworkBehaviour component is enabled. + +Dynamically spawned | In-scene placed (disabled NetworkBehaviour components) +------------------- | --------------- +`Awake` | `Awake` +`OnNetworkSpawn` | `OnNetworkSpawn` +`Start` | `Start` (invoked when disabled NetworkBehaviour components are enabled) + +> [!NOTE] Parenting, inactive GameObjects, and NetworkBehaviour components +> If you have child GameObjects that are not active in the hierarchy but are nested under an active GameObject with an attached NetworkObject component, then the inactive child GameObjects will not be included when the NetworkObject is spawned. This applies for the duration of the NetworkObject's spawned lifetime. If you want all child NetworkBehaviour components to be included in the spawn process, then make sure their respective GameObjects are active in the hierarchy before spawning the NetworkObject. Alternatively, you can just disable the NetworkBehaviour component(s) individually while leaving their associated GameObject active. +> It's recommended to disable a NetworkBehaviour component rather than the GameObject itself. + + +### Dynamically spawned NetworkObjects + +For dynamically spawned NetworkObjects (instantiating a network prefab during runtime) the `OnNetworkSpawn` method is invoked before the `Start` method is invoked. This means that finding and assigning components to a local property within the `Start` method exclusively will result in that property not being set in a NetworkBehaviour component's `OnNetworkSpawn` method when the NetworkObject is dynamically spawned. To circumvent this issue, you can have a common method that initializes the components and is invoked both during the `Start` method and the `OnNetworkSpawned` method like the code example below: + +```csharp +public class MyNetworkBehaviour : NetworkBehaviour +{ + private MeshRenderer m_MeshRenderer; + private void Start() + { + Initialize(); + } + + private void Initialize() + { + if (m_MeshRenderer == null) + { + m_MeshRenderer = FindObjectOfType(); + } + } + + public override void OnNetworkSpawn() + { + Initialize(); + // Do things with m_MeshRenderer + + base.OnNetworkSpawn(); + } +} +``` + +### In-scene placed NetworkObjects + +For in-scene placed NetworkObjects, the `OnNetworkSpawn` method is invoked after the `Start` method, because the SceneManager scene loading process controls when NetworkObjects are instantiated. The previous code example shows how you can design a NetworkBehaviour that ensures both in-scene placed and dynamically spawned NetworkObjects will have assigned the required properties before attempting to access them. Of course, you can always make the decision to have in-scene placed `NetworkObjects` contain unique components to that of dynamically spawned `NetworkObjects`. It all depends upon what usage pattern works best for your project. + +## Despawning + +`NetworkBehaviour.OnNetworkDespawn` is invoked on each NetworkBehaviour associated with a NetworkObject when it's despawned. This is where all netcode cleanup code should occur, but isn't to be confused with destroying ([see below](#destroying)). When a NetworkBehaviour component is destroyed, it means the associated GameObject, and all attached components, are in the middle of being destroyed. Regarding the order of operations, `NetworkBehaviour.OnNetworkDespawn` is always invoked before `NetworkBehaviour.OnDestroy`. + +### Destroying + +Each NetworkBehaviour has a virtual `OnDestroy` method that can be overridden to handle clean up that needs to occur when you know the NetworkBehaviour is being destroyed. + +If you override the virtual `OnDestroy` method it's important to always invoke the base like such: + +```csharp + public override void OnDestroy() + { + // Clean up your NetworkBehaviour + + // Always invoked the base + base.OnDestroy(); + } +``` + +NetworkBehaviour handles other destroy clean up tasks and requires that you invoke the base `OnDestroy` method to operate properly. + +## Pre-spawn and MonoBehaviour methods + +Since NetworkBehaviour is derived from MonoBehaviour, the `NetworkBehaviour.OnNetworkSpawn` method is treated similar to the `Awake`, `Start`, `FixedUpdate`, `Update`, and `LateUpdate` MonoBehaviour methods. Different methods are invoked depending on whether the GameObject is active in the hierarchy. + +- When active: `Awake`, `Start`, `FixedUpdate`, `Update`, and `LateUpdate` are invoked. +- When not active: `Awake`, `Start`, `FixedUpdate`, `Update`, and `LateUpdate` are not invoked. + +For more information about execution order, refer to [Order of execution for event functions](https://docs.unity3d.com/Manual/ExecutionOrder.html) in the main Unity Manual. + +The unique behavior of `OnNetworkSpawn`, compared to the previously listed methods, is that it's not invoked until the associated GameObject is active in the hierarchy and its associated NetworkObject is spawned. + +Additionally, the `FixedUpdate`, `Update`, and `LateUpdate` methods, if defined and the GameObject is active in the hierarchy, will still be invoked on NetworkBehaviours even when they're not yet spawned. If you want portions or all of your update methods to only execute when the associated NetworkObject component is spawned, you can use the `NetworkBehaviour.IsSpawned` flag to determine the spawned status like the below example: + +```csharp +private void Update() +{ + // If the NetworkObject is not yet spawned, exit early. + if (!IsSpawned) + { + return; + } + // Netcode specific logic executed when spawned. +} +``` diff --git a/com.unity.netcode.gameobjects/Documentation~/components/foundational/networkmanager.md b/com.unity.netcode.gameobjects/Documentation~/components/core/networkmanager.md similarity index 88% rename from com.unity.netcode.gameobjects/Documentation~/components/foundational/networkmanager.md rename to com.unity.netcode.gameobjects/Documentation~/components/core/networkmanager.md index a67560fc21..5b03fccb2d 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/foundational/networkmanager.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/networkmanager.md @@ -1,8 +1,8 @@ # NetworkManager -The `NetworkManager` is a required Netcode for GameObjects component that has all of your project's netcode-related settings. Think of it as the central netcode hub for your netcode-enabled project. +The NetworkManager is a required Netcode for GameObjects component that has all of your project's netcode-related settings. Think of it as the central netcode hub for your netcode-enabled project. -## `NetworkManager` Inspector properties +## NetworkManager Inspector properties - **LogLevel**: Sets the network logging level - **PlayerPrefab**: When a Prefab is assigned, the Prefab will be instantiated as the player object and assigned to the newly connected and authorized client. For more information about player prefabs, refer to [Player NetworkObjects](networkobject.md#player-networkobjects). @@ -19,11 +19,11 @@ The `NetworkManager` is a required Netcode for GameObjects component that has al - **Enable Scene Management**: When checked, Netcode for GameObjects will handle scene management and client synchronization for you. When not checked, you will have to create your own scene management scripts and handle client synchronization. - **Load Scene Time Out**: When Enable Scene Management is checked, this specifies the period of time the `NetworkSceneManager` will wait while a scene is being loaded asynchronously before `NetworkSceneManager` considers the load/unload scene event to have failed/timed out. -## `NetworkManager` sub-systems -`NetworkManager` is also where you can find references to other Netcode related management systems:
+## NetworkManager sub-systems +NetworkManager is also where you can find references to other Netcode related management systems:
> [!NOTE] -> All `NetworkManager` sub-systems are instantiated once the `NetworkManager` is started (that is, `NetworkManager.IsListening == true`). A good general "rule of thumb" is to not attempt to access the below sub-systems before starting the `NetworkManager`, otherwise they won't yet be initialized. +> All NetworkManager sub-systems are instantiated once the NetworkManager is started (that is, `NetworkManager.IsListening == true`). A good general "rule of thumb" is to not attempt to access the below sub-systems before starting the NetworkManager, otherwise they won't yet be initialized. - [NetworkManager.PrefabHandler](../../advanced-topics/object-pooling.md): This provides access to the NetworkPrefabHandler that is used for NetworkObject pools and to have more control overriding network prefabs. - [NetworkManager.SceneManager](../../basics/scenemanagement/using-networkscenemanager.md): When scene management is enabled, this is used to load and unload scenes, register for scene events, and other scene management related actions. @@ -34,7 +34,7 @@ The `NetworkManager` is a required Netcode for GameObjects component that has al ## Starting a server, host, or client -To perform any netcode-related action that involves sending messages, you must first have a server started and listening for connections with at least one client (_a server can send RPCs to itself when running as a host_) that is connected. To accomplish this, you must first start your `NetworkManager` as a server, host, or client. There are three `NetworkManager` methods you can invoke to accomplish this: +To perform any netcode-related action that involves sending messages, you must first have a server started and listening for connections with at least one client (_a server can send RPCs to itself when running as a host_) that is connected. To accomplish this, you must first start your NetworkManager as a server, host, or client. There are three NetworkManager methods you can invoke to accomplish this: ```csharp NetworkManager.Singleton.StartServer(); // Starts the NetworkManager as just a server (that is, no local client). @@ -45,14 +45,14 @@ NetworkManager.Singleton.StartClient(); // Starts the NetworkManager as jus > [!NOTE] > Don't start a NetworkManager within a NetworkBehaviour's Awake method as this can lead to undesirable results depending upon your project's settings! - When starting a Server or joining an already started session as client, the `NetworkManager` can spawn a "Player Object" belonging to the client. For more information about player prefabs, refer to: + When starting a Server or joining an already started session as client, the NetworkManager can spawn a "Player Object" belonging to the client. For more information about player prefabs, refer to: - [NetworkObject Player Prefab Documentation](networkobject.md) - [Connection Approval](../../basics/connection-approval) ## Connecting -When starting a client, the `NetworkManager` uses the IP and the Port provided in your `Transport` component for connecting. While you can set the IP address in the editor, many times you might want to be able to set the IP address and port during runtime. +When starting a client, the NetworkManager uses the IP and the Port provided in your `Transport` component for connecting. While you can set the IP address in the editor, many times you might want to be able to set the IP address and port during runtime. The below examples use [Unity Transport](https://docs.unity3d.com/Packages/com.unity.transport@latest?subfolder=/manual/index.html) to show a few ways you can gain access to the `UnityTransport` component via the `NetworkManager.Singleton` to configure your project's network settings programmatically: @@ -87,10 +87,10 @@ If you are using Unity Relay to handle connections, however, **don't use `SetCon ### Disconnect from a network -When you disconnect from a network you can't use or access any subsystems, for example `NetworkSceneManager`, because they become unavailable when `NetworkManager` stops. For client, host, or server modes, call the `NetworkManager.Shutdown` method to disconnect and shut down at the same time. +When you disconnect from a network you can't use or access any subsystems, for example `NetworkSceneManager`, because they become unavailable when NetworkManager stops. For client, host, or server modes, call the `NetworkManager.Shutdown` method to disconnect and shut down at the same time. > [!NOTE] -> When no network session is active and the `NetworkManager` has been shutdown, you will need to use `UnityEngine.SceneManagement` to load any non-network session related scene. +> When no network session is active and the NetworkManager has been shutdown, you will need to use `UnityEngine.SceneManagement` to load any non-network session related scene. ```csharp public void Disconnect() @@ -120,10 +120,10 @@ At times you might need to disconnect a client for various reasons without shutt - As a read only list of `NetworkClients` via the `NetworkManager.ConnectedClientsList`. - A full list of all connected client identifiers can be accessed via `NetworkManager.ConnectedClientsIds`. - The client identifier is passed as a parameter to all subscribers of the `NetworkManager.OnClientConnected` event. -- The player's `NetworkObject` has the `NetworkObject.OwnerClientId` property. +- The player's NetworkObject has the `NetworkObject.OwnerClientId` property. > [!NOTE] -> One way to get a player's primary `NetworkObject` is via `NetworkClient.PlayerObject`. +> One way to get a player's primary NetworkObject is via `NetworkClient.PlayerObject`. ```csharp @@ -154,7 +154,7 @@ You can also use the `NetworkManager.OnServerStopped` and `NetworkManager.OnClie Below is one example of how you can provide client connect and disconnect notifications to any type of NetworkBehaviour or MonoBehaviour derived component. > [!NOTE] -> The `ConnectionNotificationManager` example below should only be attached to the same GameObject as `NetworkManager` to assure it persists as long as the `NetworkManager.Singleton` instance. +> The `ConnectionNotificationManager` example below should only be attached to the same GameObject as NetworkManager to assure it persists as long as the `NetworkManager.Singleton` instance. ```csharp using System; diff --git a/com.unity.netcode.gameobjects/Documentation~/components/foundational/networkobject.md b/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject.md similarity index 97% rename from com.unity.netcode.gameobjects/Documentation~/components/foundational/networkobject.md rename to com.unity.netcode.gameobjects/Documentation~/components/core/networkobject.md index 5ea762d5af..a1f96917ef 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/foundational/networkobject.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject.md @@ -1,11 +1,11 @@ # NetworkObject -A NetworkObject is a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) with a NetworkObject component and at least one [NetworkBehaviour](networkbehaviour.md) component, which enables the GameObject to respond to and interact with netcode. NetworkObjects are session-mode agnostic and used in both [client-server](../terms-concepts/client-server.md) and [distributed authority](../../terms-concepts/distributed-authority.md) contexts. +A NetworkObject is a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) with a NetworkObject component and at least one [NetworkBehaviour](networkbehaviour.md) component, which enables the GameObject to respond to and interact with netcode. NetworkObjects are session-mode agnostic and used in both [client-server](../../terms-concepts/client-server.md) and [distributed authority](../../terms-concepts/distributed-authority.md) contexts. Netcode for GameObjects' high level components, [the RPC system](../../advanced-topics/messaging-system.md), [object spawning](../../basics/object-spawning.md), and [`NetworkVariable`](../../basics/networkvariable.md)s all rely on there being at least two Netcode components added to a GameObject: 1. NetworkObject - 2. [`NetworkBehaviour`](networkbehaviour.md) + 2. [NetworkBehaviour](networkbehaviour.md) NetworkObjects require the use of specialized [`NetworkObjectReference`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObjectReference.html) structures before you can serialize and use them with RPCs and `NetworkVariable`s @@ -70,7 +70,7 @@ To see if the server owns the NetworkObject, you can check the [`NetworkBehaviou Network prefabs are registered to a `NetworkPrefabsList` object (a type of `ScriptableObject`). By default, a default prefabs list containing every network prefab in your project. -However, when you want to limit which prefabs are available (for example, to reduce memory usage), you can disable this behavior in **Project Settings** > **Netcode For GameObjects** > **Project Settings**. You can also manually create a `NetworkPrefabsList` by right-clicking in the assets view and selecting **Create** > **Netcode** > **Network Prefabs List** and adding your prefabs to it. That prefab list can then be assigned to a `NetworkManager` to allow that `NetworkManager` to create those prefabs. +However, when you want to limit which prefabs are available (for example, to reduce memory usage), you can disable this behavior in **Project Settings** > **Netcode For GameObjects** > **Project Settings**. You can also manually create a `NetworkPrefabsList` by right-clicking in the assets view and selecting **Create** > **Netcode** > **Network Prefabs List** and adding your prefabs to it. That prefab list can then be assigned to a NetworkManager to allow that NetworkManager to create those prefabs. > [!NOTE] > You can only have one NetworkObject at the root of a prefab. Don't create prefabs with nested `NetworkObjects`. diff --git a/com.unity.netcode.gameobjects/Documentation~/components/foundational/playerobjects.md b/com.unity.netcode.gameobjects/Documentation~/components/core/playerobjects.md similarity index 96% rename from com.unity.netcode.gameobjects/Documentation~/components/foundational/playerobjects.md rename to com.unity.netcode.gameobjects/Documentation~/components/core/playerobjects.md index 6650aee374..e7ae88353f 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/foundational/playerobjects.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/playerobjects.md @@ -8,7 +8,7 @@ PlayerObjects are instantiated by reference to a player prefab, which defines th If you're using `UnityEngine.InputSystem.PlayerInput` or `UnityEngine.PhysicsModule.CharacterController` components on your player prefab(s), you should disable them by default and only enable them for the local client's PlayerObject. Otherwise, you may get events from the most recently instantiated player prefab instance, even if it isn't the local client instance. -You can disable these components in the **Inspector** view on the prefab itself, or disable them during `Awake` in one of your `NetworkBehaviour` components. Then you can enable the components only on the owner's instance using code like the example below: +You can disable these components in the **Inspector** view on the prefab itself, or disable them during `Awake` in one of your NetworkBehaviour components. Then you can enable the components only on the owner's instance using code like the example below: ```csharp PlayerInput m_PlayerInput; diff --git a/com.unity.netcode.gameobjects/Documentation~/components/foundational/foundationalcomponents.md b/com.unity.netcode.gameobjects/Documentation~/components/foundational/foundationalcomponents.md deleted file mode 100644 index 09b8f52274..0000000000 --- a/com.unity.netcode.gameobjects/Documentation~/components/foundational/foundationalcomponents.md +++ /dev/null @@ -1,34 +0,0 @@ -# Foundational Components - -While there are many classes within the Netcode for GameObjects SDK, there are really only three foundational components: - -* **NetworkObject:** This component declares a prefab or in-scene placed object as "networked object" that can have states synchronized between clients and/or a server. A `NetworkObject` component provides the means to identify each uniquely spawned instance (dynamically or in-scene placed). _You cannot derive from the `NetworkObject` component class_. - -* **NetworkBehaviour:** This is the fundamental "netcode" scripting component that provides the ability to synchronize state and write netcode script(s). _You derive from this class to create your own netcode component scripts_. - -* **NetworkManager:** This component is the over-all network session configuration and session management component. The `NetworkManager` component is required in order to start or join a network session. - - -## NetworkObject - -| **Topic** | **Description** | -| :------------------------------ | :------------------------------- | -| **[NetworkObject](networkobject.md)** | A NetworkObject is a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) with a NetworkObject component and at least one [NetworkBehaviour](networkbehaviour.md) component, which enables the GameObject to respond to and interact with netcode. | -| **[NetworkObject parenting](../../advanced-topics/networkobject-parenting.md)** | Understand how NetworkObjects are parented in Netcode for GameObjects. | - - -## NetworkBehaviour - - -| **Topic** | **Description** | -| :------------------------------ | :------------------------------- | -| **[NetworkBehaviour](networkbehaviour.md)** | [`NetworkBehaviour`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html) is an abstract class that derives from [`MonoBehaviour`](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and is primarily used to create unique netcode or game logic. To replicate any netcode-aware properties or send and receive RPCs, a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) must have a [NetworkObject](networkobject.md) component and at least one `NetworkBehaviour` component. | -| **[Synchronizing](networkbehaviour-synchronize.md)** | Understanding a `NetworkBehaviour` component's order of operations when it comes to spawning, de-spawning, and adding custom synchronization data. | - - -## NetworkManager - -| **Topic** | **Description** | -| :------------------------------ | :------------------------------- | -| **[NetworkManager](networkmanager.md)**| The `NetworkManager` is a required Netcode for GameObjects component that has all of your project's netcode-related settings. Think of it as the central netcode hub for your netcode-enabled project. | -| **[Player prefabs and spawning players](playerobjects.md)**| Learn about spawning player objects and creating player prefabs.| \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/components/foundational/networkbehaviour.md b/com.unity.netcode.gameobjects/Documentation~/components/foundational/networkbehaviour.md deleted file mode 100644 index dde4efc5d7..0000000000 --- a/com.unity.netcode.gameobjects/Documentation~/components/foundational/networkbehaviour.md +++ /dev/null @@ -1,127 +0,0 @@ -# NetworkBehaviour spawning and despawning - -[`NetworkBehaviour`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html) is an abstract class that derives from [`MonoBehaviour`](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and is primarily used to create unique netcode or game logic. To replicate any netcode-aware properties or send and receive RPCs, a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) must have a [NetworkObject](networkobject.md) component and at least one `NetworkBehaviour` component. - -A `NetworkBehaviour` requires a `NetworkObject` component on the same relative GameObject or on a parent of the GameObject with the `NetworkBehaviour` component assigned to it. If you add a `NetworkBehaviour` to a GameObject that doesn't have a `NetworkObject` (or any parent), then Netcode for GameObjects automatically adds a `NetworkObject` component to the `GameObject` in which the `NetworkBehaviour` was added. - -`NetworkBehaviour`s can use `NetworkVariable`s and RPCs to synchronize states and send messages over the network. When you call an RPC function, the function isn't called locally. Instead, a message is sent containing your parameters, the `networkId` of the NetworkObject associated with the same GameObject (or child) that the `NetworkBehaviour` is assigned to, and the index of the NetworkObject-relative `NetworkBehaviour` (NetworkObjects can have several `NetworkBehaviours`, the index communicates which one). - -For more information about serializing and synchronizing `NetworkBehaviour`s, refer to the [NetworkBehaviour synchronization page](networkbehaviour-synchronize.md). - -> [!NOTE] -> It's important that the `NetworkBehaviour`s on each NetworkObject remain the same for the server and any client connected. When using multiple projects, this becomes especially important so the server doesn't try to call a client RPC on a `NetworkBehaviour` that might not exist on a specific client type (or set a `NetworkVariable` that doesn't exist, and so on). - -## Spawning - -`OnNetworkSpawn` is invoked on each `NetworkBehaviour` associated with a NetworkObject when it's spawned. This is where all netcode-related initialization should occur. - -You can still use `Awake` and `Start` to do things like finding components and assigning them to local properties, but if `NetworkBehaviour.IsSpawned` is false then don't expect netcode-distinguishing properties (like `IsClient`, `IsServer`, `IsHost`, for example) to be accurate within `Awake` and `Start` methods. - -For reference purposes, below is a table of when `NetworkBehaviour.OnNetworkSpawn` is invoked relative to the `NetworkObject` type: - -Dynamically spawned | In-scene placed -------------------- | --------------- -`Awake` | `Awake` -`OnNetworkSpawn` | `Start` -`Start` | `OnNetworkSpawn` - -For more information about `NetworkBehaviour` methods and when they are invoked, see the [Pre-Spawn and MonoBehaviour Methods](networkbehaviour.md#pre-spawn-and-monobehaviour-methods) section. - -### Disabling `NetworkBehaviour`s when spawning - -If you want to disable a specific `NetworkBehaviour` but still want it to be included in the `NetworkObject` spawn process (so you can still enable it at a later time), you can disable the individual `NetworkBehaviour` instead of the entire `GameObject`. - -`NetworkBehaviour` components that are disabled by default and are attached to in-scene placed NetworkObjects behave like `NetworkBehaviour` components that are attached to dynamically spawned NetworkObjects when it comes to the order of operations for the `NetworkBehaviour.Start` and `NetworkBehaviour.OnNetworkSpawn` methods. Since in-scene placed NetworkObjects are spawned when the scene is loaded, a `NetworkBehaviour` component (that is disabled by default) will have its `NetworkBehaviour.OnNetworkSpawn` method invoked before the `NetworkBehaviour.Start` method, since `NetworkBehaviour.Start` is invoked when a disabled `NetworkBehaviour` component is enabled. - -Dynamically spawned | In-scene placed (disabled `NetworkBehaviour` components) -------------------- | --------------- -`Awake` | `Awake` -`OnNetworkSpawn` | `OnNetworkSpawn` -`Start` | `Start` (invoked when disabled `NetworkBehaviour` components are enabled) - -> [!NOTE] Parenting, inactive GameObjects, and `NetworkBehaviour` components -> If you have child GameObjects that are not active in the hierarchy but are nested under an active GameObject with an attached NetworkObject component, then the inactive child GameObjects will not be included when the NetworkObject is spawned. This applies for the duration of the NetworkObject's spawned lifetime. If you want all child `NetworkBehaviour` components to be included in the spawn process, then make sure their respective GameObjects are active in the hierarchy before spawning the `NetworkObject`. Alternatively, you can just disable the NetworkBehaviour component(s) individually while leaving their associated GameObject active. -> It's recommended to disable a `NetworkBehaviour` component rather than the GameObject itself. - - -### Dynamically spawned NetworkObjects - -For dynamically spawned NetworkObjects (instantiating a network prefab during runtime) the `OnNetworkSpawn` method is invoked before the `Start` method is invoked. This means that finding and assigning components to a local property within the `Start` method exclusively will result in that property not being set in a `NetworkBehaviour` component's `OnNetworkSpawn` method when the NetworkObject is dynamically spawned. To circumvent this issue, you can have a common method that initializes the components and is invoked both during the `Start` method and the `OnNetworkSpawned` method like the code example below: - -```csharp -public class MyNetworkBehaviour : NetworkBehaviour -{ - private MeshRenderer m_MeshRenderer; - private void Start() - { - Initialize(); - } - - private void Initialize() - { - if (m_MeshRenderer == null) - { - m_MeshRenderer = FindObjectOfType(); - } - } - - public override void OnNetworkSpawn() - { - Initialize(); - // Do things with m_MeshRenderer - - base.OnNetworkSpawn(); - } -} -``` - -### In-scene placed NetworkObjects - -For in-scene placed NetworkObjects, the `OnNetworkSpawn` method is invoked after the `Start` method, because the SceneManager scene loading process controls when NetworkObjects are instantiated. The previous code example shows how you can design a `NetworkBehaviour` that ensures both in-scene placed and dynamically spawned NetworkObjects will have assigned the required properties before attempting to access them. Of course, you can always make the decision to have in-scene placed `NetworkObjects` contain unique components to that of dynamically spawned `NetworkObjects`. It all depends upon what usage pattern works best for your project. - -## Despawning - -`NetworkBehaviour.OnNetworkDespawn` is invoked on each `NetworkBehaviour` associated with a `NetworkObject` when it's despawned. This is where all netcode cleanup code should occur, but isn't to be confused with destroying ([see below](#destroying)). When a `NetworkBehaviour` component is destroyed, it means the associated GameObject, and all attached components, are in the middle of being destroyed. Regarding the order of operations, `NetworkBehaviour.OnNetworkDespawn` is always invoked before `NetworkBehaviour.OnDestroy`. - -### Destroying - -Each `NetworkBehaviour` has a virtual `OnDestroy` method that can be overridden to handle clean up that needs to occur when you know the `NetworkBehaviour` is being destroyed. - -If you override the virtual `OnDestroy` method it's important to always invoke the base like such: - -```csharp - public override void OnDestroy() - { - // Clean up your NetworkBehaviour - - // Always invoked the base - base.OnDestroy(); - } -``` - -`NetworkBehaviour` handles other destroy clean up tasks and requires that you invoke the base `OnDestroy` method to operate properly. - -## Pre-spawn and `MonoBehaviour` methods - -Since `NetworkBehaviour` is derived from `MonoBehaviour`, the `NetworkBehaviour.OnNetworkSpawn` method is treated similar to the `Awake`, `Start`, `FixedUpdate`, `Update`, and `LateUpdate` `MonoBehaviour` methods. Different methods are invoked depending on whether the GameObject is active in the hierarchy. - -- When active: `Awake`, `Start`, `FixedUpdate`, `Update`, and `LateUpdate` are invoked. -- When not active: `Awake`, `Start`, `FixedUpdate`, `Update`, and `LateUpdate` are not invoked. - -For more information about execution order, refer to [Order of execution for event functions](https://docs.unity3d.com/Manual/ExecutionOrder.html) in the main Unity Manual. - -The unique behavior of `OnNetworkSpawn`, compared to the previously listed methods, is that it's not invoked until the associated GameObject is active in the hierarchy and its associated NetworkObject is spawned. - -Additionally, the `FixedUpdate`, `Update`, and `LateUpdate` methods, if defined and the GameObject is active in the hierarchy, will still be invoked on `NetworkBehaviour`s even when they're not yet spawned. If you want portions or all of your update methods to only execute when the associated `NetworkObject` component is spawned, you can use the `NetworkBehaviour.IsSpawned` flag to determine the spawned status like the below example: - -```csharp -private void Update() -{ - // If the NetworkObject is not yet spawned, exit early. - if (!IsSpawned) - { - return; - } - // Netcode specific logic executed when spawned. -} -``` diff --git a/com.unity.netcode.gameobjects/Documentation~/components/helper/attachablebehaviour.md b/com.unity.netcode.gameobjects/Documentation~/components/helper/attachablebehaviour.md new file mode 100644 index 0000000000..29688da041 --- /dev/null +++ b/com.unity.netcode.gameobjects/Documentation~/components/helper/attachablebehaviour.md @@ -0,0 +1,122 @@ +# AttachableBehaviour + +Use the AttachableBehaviour component to manage [ComponentController](componentcontroller.md) components and to attach a child GameObject to an [AttachableNode](attachablenode.md). The AttachableBehaviour component provides an alternative to NetworkObject parenting, allowing you to attach and detach child objects dynamically during runtime. + +![Inspector UI view with AttachableBehaviour component added](../../images/attachable/AttachableBehaviour_InspectorView-1.png) + +The basic functionality of the AttachableBehaviour component provides: + +- The ability to assign ComponentController components from any part of the parent-child hierarchy. + - Each **Component Controller** entry provides the ability to select when the ComponentController should be triggered (via the **Auto Trigger** property) and whether its enabled state should be enabled or disabled upon attaching (via the **Enable On Attach** property). The default setting is to be disabled when the AttachableBehaviour attaches to an AttachableNode and enabled when detaching. When the **Enable On Attach** property is enabled, the ComponentController is enabled when the AttachableBehaviour attaches to an AttachableNode and disabled when detaching. +- The ability to control when an AttachableBehaviour component will automatically detach from an AttachableNode via the **Auto Detach** property. + - The **Auto Detach** property can have any combination of the following flags or none (no flags): + - **On Ownership Changed:** When ownership changes, the AttachableBehaviour will detach from any AttachableNode it's attached to. + - **On Despawn:** When the AttachableBehaviour is despawned, it will detach from any AttachableNode it's attached to. + - **On Attach Node Destroy**: Just before the AttachableNode is destroyed, any attached AttachableBehaviour with this flag will automatically detach from the AttachableNode. + +Any of the `AttachableBehaviour.AutoDetach` settings will be invoked on all instances without the need for the owner to synchronize the end result (by detaching), which provides a level of redundancy for edge case scenarios like a player being disconnected abruptly by the host, timing out, or any scenario where a spawned object is being destroyed with the owner (or perhaps being redistributed to another client authority in a [distributed authority](../../terms-concepts/distributed-authority.md) session). Having the ability to select or deselect any of the auto-detach flags coupled with the ability to derive from AttachableBehaviour provides additional levels of modularity and customization. + +## Attaching and NetworkObject parenting + +Fundamentally, attaching is an alternative method of synchronizing parenting that doesn't involve traditional [NetworkObject parenting](../../advanced-topics/networkobject-parenting.md). Attaching a child GameObject nested under a NetworkObject only takes the child GameObject and parents it under the GameObject of an AttachableNode. The target to parent under must be of a different spawned NetworkObject and the AttachableNode needs to be on the same or child GameObject of the target NetworkObject. + +### NetworkObject parenting + +The traditional approach is to spawn two NetworkPrefabs instances: + +![Tree view of two spawned NetworkPrefabs](../../images/attachable/SpawnObjectA-B.png) + +Then parent one instance under the other: + +![Tree view of two spawned NetworkPrefabs with one parented under the other](../../images/attachable/SpawnObjectA-B-2.png) + +This is simple enough for many scenarios, but can become cumbersome in situations where you might want to have a world version of the item and a picked up version of the item. + +### Attaching + +When attaching, you create nested GameObject children that represent the item when it's picked up and when it's placed somewhere in the world, effectively creating a GameObject for each item state. + +![Tree view of nested GameObject children](../../images/attachable/PlayerAndWorldItem-1.png) + +- The WorldItemRoot is where the NetworkObject component is placed. +- The NestedChild-World contains the components needed for the item when it's placed in the world. +- The NestedChild-PickedUp contains the components needed for the item when it's picked up by a player. + +By placing an AttachableBehaviour component on the NestedChild-PickedUp GameObject and an AttachableNode component on the TargetNode, a user can then invoke the `AttachableBehaviour.Attach` method while passing in the AttachableNode component and the NestedChild-PickedUp GameObject will get parented under the TargetNode while also synchronizing this action with all other clients. + +![Tree view of attached child GameObject](../../images/attachable/PlayerAndWorldItem-2.png) + +## AttachableBehaviour example + +### Introduction + +This example walks through a common scenario where you want to have a world item that has unique visual and scripted components active while placed in the world, but that switches to a different set of visual and scripted components when picked up by a player's avatar. It also covers attaching only the portion of the item that is active when picked up to one of the player's avatar's child nodes. + +Below is a high-level diagram overview of what both the player and world item NetworkPrefabs could look like: + +![Hierarchy diagram of Player and WorldItem NetworkPrefabs](../../images/attachable/AttachableDiagram-1.png) + +#### Player + +The player prefab in the above diagram is not complete, and only includes the components relevant to this example plus some additional children and components for example purposes. The AttachableNode components provide an attachment point that any other spawned network prefab with an AttachableBehaviour could attach itself to. + +#### WorldItem + +This diagram has more detail and introduces one possible method of using a ComponentController and AttachableBehaviour. The ComponentController is used to control the enabling and disabling of components and synchronizing this with non-authority instances. The AttachableBehaviour resides on the child AttachedView's GameObject and is the catalyst for attaching to a player. + +### WorldView and AttachedView modes + +![Hierachy diagram of WorldItem NetworkPrefab with arrows indicating relationships between components](../../images/attachable/AttachableDiagram-2.png) + +The diagram above has arrows pointing from the ComponentController to the components that are enabled or disabled depending on the ComponentController's state. These are components that are only relevant when the item is in the world (WorldView mode) or when it's attached to a player (AttachedView mode). + +The AttachableBehaviour also has an arrow that points to the ComponentController, indicating a relationship between the two. The right-hand diagram indicates the flow of events: the AttachableBehaviour notifies the ComponentController that an item has been attached or detached and the ComponentController, in turn, enables or disables certain components. + +#### WorldItem ComponentController + +Below is a screenshot of what the ComponentController would look like in the Inspector view: + +![Inspector view UI with details of ComponentController](../../images/attachable/WorldItem-Inspector-View-1.png) + +The ComponentController's **Components** list has three entries, two of which have references to the WorldItemView's BoxCollider and MeshRenderer that are both configured to be enabled when the ComponentController's state is `true`. There's also the CarryView's MeshRenderer that is added and configured to be the inverse of the current ComponentController's state. + + The ComponentController's **Start Enabled** property is enabled, which means that the WorldItem NetworkPrefab starts with the WorldItemView being active when spawned. + +The CarryObject child's properties are as follows: + +![Inspector view UI with details of CarryObject's AttachableBehaviour component](../../images/attachable/WorldItem-Inspector-View-2.png) + +The AttachableBehaviour's **Component Controllers** list contains the WorldItem ComponentController that's configured to trigger on everything (`OnAttach` and `OnDetach`) and sets the ComponentController's state to disabled (`false`). This means that when the AttachableBehaviour is attached, the ComponentController is in the disabled state along with the WorldItemView components, while the CarryView's MeshRenderer will be enabled. + +#### Summary + +- AttachableBehaviour sets the ComponentController state (true/enabled or false/disabled). +- ComponentController states: + - Enabled (true) + - World Item View (enabled/true) + - Carry View (disabled/false) + - Disabled (false) + - World Item View (disabled/false) + - Carry View (enabled/true) + +### Attaching + +![Hierarchy diagram of Player and WorldItem NetworkPrefabs when attached](../../images/attachable/AttachableDiagram-3.png) + +The above diagram represents what the Player and WorldItem spawned objects (including non-authority instances) look like once the AttachedView object has been parented under the avatar's RightAttach object. The green area and arrow represent the still existing relationship that the AttachedView has with the WorldItem's NetworkObject. + +#### AttachableBehaviour and NetworkObject relationship + +When a NetworkObject component is spawned, it registers all NetworkBehaviour-based component instances that are directly attached to the NetworkObject's GameObject or that are on any child GameObject. This remains true even when a child GameObject containing one or more NetworkBehaviour-based component instances of a spawned NetworkObject is parented, during runtime, under another GameObject that is associated with a different spawned NetworkObject. + +There are additional considerations like: + +- What happens when one or both of the NetworkObjects is de-spawned? +- How do you ensure the child attachable will return back to its default parent? + +AttachableBehaviour addresses these issues by leveraging the spawn lifetime relationship to provide another type of parenting (attaching) while also taking into consideration edge case scenarios. + +## Additional resources + +- [AttachableNode](attachablenode.md) +- [ComponentController](componentcontroller.md) \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/components/helper/attachablenode.md b/com.unity.netcode.gameobjects/Documentation~/components/helper/attachablenode.md new file mode 100644 index 0000000000..0c63fb2c56 --- /dev/null +++ b/com.unity.netcode.gameobjects/Documentation~/components/helper/attachablenode.md @@ -0,0 +1,16 @@ +# AttachableNode + +Use an AttachableNode component to provide an attachment point for an [AttachableBehaviour](attachablebehaviour.md) component. + +AttachableNodes include a **Detach On Despawn** field that, when enabled (the default setting), automatically detaches and destroys any attached AttachableBehaviour instances when the associated NetworkObject of the AttachableNode is despawned. + +![Inspector view UI with details of AttachableNode component](../../images/attachable/AttachableNode_InspectorView-1.png) + +## AttachableBehaviour example + +Refer to the [AttachableBehaviour example](attachablebehaviour.md#attachablebehaviour-example) for an example of how to use the AttachableNode component with an AttachableBehaviour component. + +## Additional resources + +- [AttachableBehaviour](attachablebehaviour.md) +- [ComponentController](componentcontroller.md) \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/components/helper/componentcontroller.md b/com.unity.netcode.gameobjects/Documentation~/components/helper/componentcontroller.md new file mode 100644 index 0000000000..26465b9d6e --- /dev/null +++ b/com.unity.netcode.gameobjects/Documentation~/components/helper/componentcontroller.md @@ -0,0 +1,95 @@ +# ComponentController + +Use a [ComponentController](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.ComponentController.html) component to enable or disable one or more components depending on the authority state of the ComponentController and have those changes synchronized with non-authority instances. + +For example, you can use a ComponentController to enable or disable a MeshRenderer component on the owner of the ComponentController instance, while disabling it for all other clients. This is useful for controlling visibility of objects that should only be visible to the owner, such as a player's avatar or a weapon they are holding. + +The ComponentController can be: + +* Used with an [AttachableBehaviour component](attachablebehaviour.md) or independently for another purpose. +* Configured to directly or inversely follow the ComponentController's current state. +* Configured to have an enable and/or disable delay. + * Note that when invoked internally by AttachableBehaviour, delays are ignored when an [AttachableNode](attachablenode.md) is being destroyed and the changes are immediate. + +ComponentControllers use a [synchronized RPC-driven field approach](#synchronized-rpc-driven-properties) to synchronize the states of the components its controlling. This ensures optimal performance and that the order of operations of changes is relative to other ComponentController or AttachableBehaviour component instances. + +## Configuring a ComponentController + +![Inspector UI view with ComponentController component added](../../images/attachable/WorldItem-Inspector-View-1.png) + +A ComponentController can have one or more entries in its **Components** list. Each entry has some additional fields that you can adjust: + +- **Invert Enabled:** When enabled, this makes the associated component inversely follow the ComponentController's global enabled state. Use this if you want a set of components to be enabled when the ComponentController component's global enable state is set to `false` and for that same set of components to be disabled when the ComponentController component's global enable state is set to `true`. +- **Enable Delay:** When greater than 0 (the default), the associated component will delay transitioning from a disabled state to an enabled state by the amount of time (in seconds) specified. +- **Disable Delay:** When greater than 0 (the default), the associated component will delay transitioning from an enabled state to a disabled state by the amount of time (in seconds) specified. +- **Component:** The component to control and synchronize its enabled state. + +You can use the delay values to, for example, prevent a MeshRenderer from being enabled prior to other events (such as while waiting for the attachable to be positioned). The ComponentController automatically handles the synchronization of these delays across the network, ensuring that all clients see the same behavior. + +## Examples + +### Independent usage + +A ComponentController can be [used with an AttachableBehaviour](#attachablebehaviour-usage) without writing any scripts, but you can also write scripts to use it independently. Below is a pseudo example where a ComponentController has its synchronized state updated when the `DaisyChainedController` is either enabled or disabled. + +```csharp +/// +/// Use as a component in the ComponentController that will +/// trigger the Controller (ComponentController). +/// This pattern can repeat/be daisy chained. +/// +public class DaisyChainedController : MonoBehaviour +{ + public ComponentController Controller; + + private void OnEnable() + { + if (!Controller || !Controller.HasAuthority) + { + return; + } + Controller.SetEnabled(true); + } + + private void OnDisable() + { + if (!Controller || !Controller.HasAuthority) + { + return; + } + Controller.SetEnabled(false); + } +} +``` + +The above component could be arranged to create a chained sequence of components when the root `DaisyChainedController` component is enabled or disabled. Such a sequence could look like: + +- DaisyChainedController-A + - Controller + - Points to DaisyChainedController-B +- DaisyChainedController-B + - Controller + - Points to DaisyChainedController-C +- DaisyChainedController-C + - Controller + +When DaisyChainedController-A is enabled, then a sequence of events would occur where DaisyChainedController-B and DaisyChainedController-C would be enabled. The same sequence of events would occur when DaisyChainedController-A was then disabled. + +### AttachableBehaviour usage + +An [AttachableBehaviour component](attachablebehaviour.md) can be assigned one or more ComponentControllers that will be invoked, depending on configuration, when the AttachableBehaviour is attached and detached from an [AttachableNode](attachablenode.md). + +For more information, refer to the [AttachableBehaviour example](attachablebehaviour.md#attachablebehaviour-example). + +## Synchronized RPC-driven properties + +Both AttachableBehaviour and ComponentController provide an example of using synchronized RPC-driven properties instead of [NetworkVariables](../../basics/networkvariable.md). Under certain conditions, it can be preferable to use RPCs when a specific order of operations is needed, because NetworkVariables can update out of order (relative to the order in which certain states were updated) in some edge case scenarios. + +Under such conditions, using reliable RPCs ensures that messages are received in the order they're generated, while also reducing the latency time between the change and non-authority instances being notified of the change. Synchronized RPC-driven properties only require overriding the `NetworkBehaviour.OnSynchronize` method and serializing any properties that need to be synchronized with late joining players or handling network object visibility related scenarios. + +For more information, refer to [NetworkBehaviour synchronization page](../core/networkbehaviour-synchronize.md#synchronized-rpc-driven-fields). + +## Additional resources + +- [AttachableBehaviour](attachablebehaviour.md) +- [AttachableNode](attachablenode.md) \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/components/helper/helpercomponents.md b/com.unity.netcode.gameobjects/Documentation~/components/helper/helpercomponents.md new file mode 100644 index 0000000000..8f9d13ec9f --- /dev/null +++ b/com.unity.netcode.gameobjects/Documentation~/components/helper/helpercomponents.md @@ -0,0 +1,12 @@ +# Helper Components + +Understand the helper components available to use in your Netcode for GameObjects project. + + **Topic** | **Description** | +| :------------------------------ | :------------------------------- | +| **[AttachableBehaviour](attachablebehaviour.md)**| Use the AttachableBehaviour component to manage [ComponentController](componentcontroller.md) components and to attach a child GameObject to an [AttachableNode](attachablenode.md). The AttachableBehaviour component provides an alternative to NetworkObject parenting, allowing you to attach and detach child objects dynamically during runtime. | +| **[AttachableNode](attachablenode.md)**| Use an AttachableNode component to provide an attachment point for an [AttachableBehaviour](attachablebehaviour.md) component. | +| **[ComponentController](componentcontroller.md)**| Use a [ComponentController](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.ComponentController.html) component to enable or disable one or more components depending on the authority state of the ComponentController and have those changes synchronized with non-authority instances. | +| **[NetworkAnimator](networkanimator.md)**| The NetworkAnimator component provides you with a fundamental example of how to synchronize animations during a network session. Animation states are synchronized with players joining an existing network session and any client already connected before the animation state changing. | +| **[NetworkTransform](networktransform.md)**| [NetworkTransform](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.Components.NetworkTransform.html) is a concrete class that inherits from [NetworkBehaviour](../core/networkbehaviour.md) and synchronizes [Transform](https://docs.unity3d.com/Manual/class-Transform.html) properties across the network, ensuring that the position, rotation, and scale of a [GameObject](https://docs.unity3d.com/Manual/working-with-gameobjects.html) are replicated to other clients. | +| **[Physics](../../advanced-topics/physics.md)**| Netcode for GameObjects has a built in approach which allows for server-authoritative physics where the physics simulation only runs on the server. | diff --git a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/networkanimator.md b/com.unity.netcode.gameobjects/Documentation~/components/helper/networkanimator.md similarity index 54% rename from com.unity.netcode.gameobjects/Documentation~/components/Helpers/networkanimator.md rename to com.unity.netcode.gameobjects/Documentation~/components/helper/networkanimator.md index 51b8d52eed..62b0162374 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/networkanimator.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/helper/networkanimator.md @@ -1,6 +1,6 @@ # NetworkAnimator -The `NetworkAnimator` component provides you with a fundamental example of how to synchronize animations during a network session. Animation states are synchronized with players joining an existing network session and any client already connected before the animation state changing. +The NetworkAnimator component provides you with a fundamental example of how to synchronize animations during a network session. Animation states are synchronized with players joining an existing network session and any client already connected before the animation state changing. * Players joining an existing network session will be synchronized with: * All the `Animator`'s current properties and states. @@ -10,10 +10,10 @@ The `NetworkAnimator` component provides you with a fundamental example of how t * States * Transitions * Properties - * `NetworkAnimator` will only synchronize properties that have changed since the earlier frame property values. + * NetworkAnimator will only synchronize properties that have changed since the earlier frame property values. * Since triggers are similar to an "event," when an `Animator` property is set to `true` it will always be synchronized. -`NetworkAnimator` can operate in two authoritative modes: +NetworkAnimator can operate in two authoritative modes: * Server Authoritative (default): Server initiates animation state changes. * Owner's can still invoke `NetworkAnimator.SetTrigger`. @@ -21,7 +21,7 @@ The `NetworkAnimator` component provides you with a fundamental example of how t > [!NOTE] -> You need to use `Unity.Netcode.Components` to reference components such as `NetworkAnimator`. +> You need to use `Unity.Netcode.Components` to reference components such as NetworkAnimator. ## Animator Trigger Property @@ -32,11 +32,11 @@ The `Animator` trigger property type ("trigger") is basically nothing more than ## Server Authoritative Mode -The default setting for `NetworkAnimator` is server authoritative mode. When operating in server authoritative mode, any animation state changes that are set (triggers) or detected (change in layer, state, or any `Animator` properties excluding triggers) on the server side will be synchronized with all clients. Because the server initiates any synchronization of changes to an `Animator` 's state, a client that's the owner of the `NetworkObject` associated with the `NetworkAnimator` can lag by roughly the full round trip time (RTT). Below is a timing diagram to show this: +The default setting for NetworkAnimator is server authoritative mode. When operating in server authoritative mode, any animation state changes that are set (triggers) or detected (change in layer, state, or any `Animator` properties excluding triggers) on the server side will be synchronized with all clients. Because the server initiates any synchronization of changes to an `Animator` 's state, a client that's the owner of the NetworkObject associated with the NetworkAnimator can lag by roughly the full round trip time (RTT). Below is a timing diagram to show this: ![ServerAuthMode](../../images/NetworkAnimatorServerAuthTiming.png) -In the above diagram, a client might be sending the server an RPC to tell the server that the player is performing some kind of action that can change the player's animations (including setting a trigger). Under this scenario, the client sends an RPC to the server (half RTT), the server processes the RPC, the associated `Animator` state changes are detected by the `NetworkAnimator` (server-side), and then all clients (including the owner client) are synchronized with the changed. +In the above diagram, a client might be sending the server an RPC to tell the server that the player is performing some kind of action that can change the player's animations (including setting a trigger). Under this scenario, the client sends an RPC to the server (half RTT), the server processes the RPC, the associated `Animator` state changes are detected by the NetworkAnimator (server-side), and then all clients (including the owner client) are synchronized with the changed. **Server authoritative model benefits:** @@ -49,7 +49,7 @@ In the above diagram, a client might be sending the server an RPC to tell the se ## Owner Authoritative Mode -Usually, your project's design (or personal preference) might require that owners are immediately updated to any `Animator` state changes. The most typical reason would be to give the local player with instantaneous visual (animation) feedback. To create an owner authoritative `NetworkAnimator` you need to create a new class that's derived from `NetworkAnimator`, override the `NetworkAnimator.OnIsServerAuthoritative` method, and within the overridden `OnIsServerAuthoritative` method you should return false like in the example provided below: +Usually, your project's design (or personal preference) might require that owners are immediately updated to any `Animator` state changes. The most typical reason would be to give the local player with instantaneous visual (animation) feedback. To create an owner authoritative NetworkAnimator you need to create a new class that's derived from NetworkAnimator, override the `NetworkAnimator.OnIsServerAuthoritative` method, and within the overridden `OnIsServerAuthoritative` method you should return false like in the example provided below: ```csharp public class OwnerNetworkAnimator : NetworkAnimator @@ -61,11 +61,11 @@ Usually, your project's design (or personal preference) might require that owner } ``` -Looking at the timing for an owner authoritative `NetworkAnimator`, in the diagram below, you can see that while the owner client gets "immediate visual animation response" the non-owner clients end up being roughly one full RTT behind the owner client and a host would be half RTT behind the owner client. +Looking at the timing for an owner authoritative NetworkAnimator, in the diagram below, you can see that while the owner client gets "immediate visual animation response" the non-owner clients end up being roughly one full RTT behind the owner client and a host would be half RTT behind the owner client. ![ServerAuthMode](../../images/NetworkAnimatorOwnerAuthTiming.png) -In the above diagram, it shows that the owner client has an `Animator` state change that's detected by the `NetworkAnimator` ( `OwnerNetworkAnimator`) which automatically synchronizes the server with the changed state. The server applies the change(s) locally and then broadcasts this state change to all non-owner clients. +In the above diagram, it shows that the owner client has an `Animator` state change that's detected by the NetworkAnimator ( `OwnerNetworkAnimator`) which automatically synchronizes the server with the changed state. The server applies the change(s) locally and then broadcasts this state change to all non-owner clients. **Owner authoritative model benefits:** @@ -81,25 +81,25 @@ In the above diagram, it shows that the owner client has an `Animator` state cha ## Using NetworkAnimator -Using `NetworkAnimator` is a pretty straight forward approach with the only subtle difference being whether you are using a server or owner authoritative model. +Using NetworkAnimator is a pretty straight forward approach with the only subtle difference being whether you are using a server or owner authoritative model. > [!NOTE] -> `NetworkAnimator` is one of several possible ways to synchronize animations during a network session. Netcode for GameObjects provides you with the building blocks (RPCs, NetworkVariables, and Custom Messages) needed to create a completely unique animation synchronization system that has a completely different and potentially more optimized approach. `NetworkAnimator` is a straight forward approach provided for users already familiar with the `Animator` component and, depending upon your project's design requirements, might be all that you need. +> NetworkAnimator is one of several possible ways to synchronize animations during a network session. Netcode for GameObjects provides you with the building blocks (RPCs, NetworkVariables, and Custom Messages) needed to create a completely unique animation synchronization system that has a completely different and potentially more optimized approach. NetworkAnimator is a straight forward approach provided for users already familiar with the `Animator` component and, depending upon your project's design requirements, might be all that you need. ### Server Authoritative -If you decide you want to use the server authoritative model, then you can add a `NetworkAnimator` component to either the same `GameObject` that has the `NetworkObject` component attached to it or any child `GameObject`. In the below screenshot, you can see a network Prefab that houses two authoritative models. The `NetworkAnimatorCube-Server` `GameObject` has an `Animator` component, an `AnimatedCubeController` component (used for manual testing), and the `NetworkAnimator` component that has a reference to the `Animator` component. +If you decide you want to use the server authoritative model, then you can add a NetworkAnimator component to either the same GameObject that has the NetworkObject component attached to it or any child GameObject. In the below screenshot, you can see a network Prefab that houses two authoritative models. The `NetworkAnimatorCube-Server` GameObject has an `Animator` component, an `AnimatedCubeController` component (used for manual testing), and the NetworkAnimator component that has a reference to the `Animator` component. ![Usage-1](../../images/NetworkAnimatorUsage-1.png) ### Owner Authoritative -If you decide you want to use the owner authoritative model, then (for example purposes) you would use your derived `OwnerNetworkAnimator` component as opposed to the default `NetworkAnimator` component like in the screenshot below: +If you decide you want to use the owner authoritative model, then (for example purposes) you would use your derived `OwnerNetworkAnimator` component as opposed to the default NetworkAnimator component like in the screenshot below: ![Usage-1](../../images/NetworkAnimatorUsage-2.png) > [!NOTE] -> While it isn't advised to have different `NetworkAnimator` authoritative models "under the same root network Prefab `GameObject`, " you can have multiple children that each have their own `Animator` and `NetworkAnimator` all housed under a single `NetworkObject` and all use the same authoritative model. However, you should always consider the balance between performance (CPU or bandwidth consumption) and convenience/modularity. +> While it isn't advised to have different NetworkAnimator authoritative models "under the same root network Prefab GameObject, " you can have multiple children that each have their own `Animator` and NetworkAnimator all housed under a single NetworkObject and all use the same authoritative model. However, you should always consider the balance between performance (CPU or bandwidth consumption) and convenience/modularity. ### Changing Animator Properties @@ -112,7 +112,7 @@ public void ApplyMotion(Vector3 playerVelocity) } ``` -For triggers you always want to use `NetworkAnimator`. One example might be that you use a trigger, called it " `IsJumping`, " to start a blended transition between the player's walking/running animation and the jumping animation: +For triggers you always want to use NetworkAnimator. One example might be that you use a trigger, called it " `IsJumping`, " to start a blended transition between the player's walking/running animation and the jumping animation: ```csharp public void SetPlayerJumping(bool isJumping) diff --git a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/networktransform.md b/com.unity.netcode.gameobjects/Documentation~/components/helper/networktransform.md similarity index 95% rename from com.unity.netcode.gameobjects/Documentation~/components/Helpers/networktransform.md rename to com.unity.netcode.gameobjects/Documentation~/components/helper/networktransform.md index c3b3456148..0ad8366692 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/Helpers/networktransform.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/helper/networktransform.md @@ -1,6 +1,6 @@ # NetworkTransform -[NetworkTransform](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.Components.NetworkTransform.html) is a concrete class that inherits from [NetworkBehaviour](../foundational/networkbehaviour.md) and synchronizes [Transform](https://docs.unity3d.com/Manual/class-Transform.html) properties across the network, ensuring that the position, rotation, and scale of a [GameObject](https://docs.unity3d.com/Manual/working-with-gameobjects.html) are replicated to other clients. +[NetworkTransform](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.Components.NetworkTransform.html) is a concrete class that inherits from [NetworkBehaviour](../core/networkbehaviour.md) and synchronizes [Transform](https://docs.unity3d.com/Manual/class-Transform.html) properties across the network, ensuring that the position, rotation, and scale of a [GameObject](https://docs.unity3d.com/Manual/working-with-gameobjects.html) are replicated to other clients. The synchronization of a GameObject's Transform is a key netcode task, and usually proceeds in the following order: @@ -20,7 +20,7 @@ There are other considerations when synchronizing Transform values, however, suc ## Add a NetworkTransform component to a GameObject -Because a NetworkTransform component is derived from the NetworkBehaviour class, it has many of the [same requirements](../foundational/networkbehaviour.md). For example, when adding a NetworkTransform component to a GameObject, it should be added to the same or any hierarchy generation relative to the `NetworkObject` component. +Because a NetworkTransform component is derived from the NetworkBehaviour class, it has many of the [same requirements](../core/networkbehaviour.md). For example, when adding a NetworkTransform component to a GameObject, it should be added to the same or any hierarchy generation relative to the NetworkObject component. In the following image both NetworkTransform and NetworkObject components are on the same GameObject: @@ -33,7 +33,7 @@ Alternatively, the parent GameObject can have multiple children where any child Theoretically, you can have a NetworkTransform on every child object of a 100 leaf deep hierarchy. However, it's recommended to exercise caution with the amount of nested NetworkTransforms in a network prefab, particularly if there will be many instances of the network prefab. > [!NOTE] -> Generally, as long as there's at least one [NetworkObject](../foundational/networkobject.md) at the same GameObject hierarchy level or above, you can attach a NetworkTransform component to a GameObject. You could have a single root-parent GameObject that has a NetworkObject component and under the root-parent several levels of nested child GameObjects that all have NetworkTransform components attached to them. Each child GameObject doesn't require a NetworkObject component in order for the respective NetworkTransform component to synchronize properly. +> Generally, as long as there's at least one [NetworkObject](../core/networkobject.md) at the same GameObject hierarchy level or above, you can attach a NetworkTransform component to a GameObject. You could have a single root-parent GameObject that has a NetworkObject component and under the root-parent several levels of nested child GameObjects that all have NetworkTransform components attached to them. Each child GameObject doesn't require a NetworkObject component in order for the respective NetworkTransform component to synchronize properly. ### Nesting NetworkTransforms @@ -56,7 +56,7 @@ Some NetworkTransform properties are automatically synchronized by the authorita #### Properties that cause a full state update -The following are a list of `NetworkTransform` properties that will cause a full state update (effectively a teleport) when changed during runtime by the authority instance: +The following are a list of NetworkTransform properties that will cause a full state update (effectively a teleport) when changed during runtime by the authority instance: - [UseUnreliableDeltas](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.3/api/Unity.Netcode.Components.NetworkTransform.html#Unity_Netcode_Components_NetworkTransform_UseUnreliableDeltas) - [InLocalSpace](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.3/api/Unity.Netcode.Components.NetworkTransform.html#Unity_Netcode_Components_NetworkTransform_InLocalSpace) @@ -89,7 +89,7 @@ You can have [nested NetworkTransforms](#nesting-networktransforms) as long as t If you don't plan on changing the Transform's scale after the initial synchronization (that occurs when joining a network session or when a network prefab instance is spawned for the first time), then disabling the X, Y, and Z properties for __Scale__ synchronization removes some additional processing overhead per instance. However, if you have a nested NetworkTransform and want to apply a unique scale (per instance) that's applied to that nested NetworkTransform's Transform when it's first spawned, then the adjusted scale won't be synchronized. -Fortunately, the authority of the `NetworkTransform` instance can make changes to any of the __Axis to Synchronize__ properties during runtime and non-authoritative instances will receive updates for the axis marked for synchronization. +Fortunately, the authority of the NetworkTransform instance can make changes to any of the __Axis to Synchronize__ properties during runtime and non-authoritative instances will receive updates for the axis marked for synchronization. When disabling an axis to be synchronized for performance purposes, you should always consider that NetworkTransform won't send updates as long as the axis in question doesn't have a change that exceeds its [threshold](#thresholds) value. So, taking the scale example into consideration, it can be simpler to leave those axes enabled if you only ever plan on changing them once or twice because the CPU cost for checking that change isn't as expensive as the serialization and state update sending process. The associated axis threshold values can make the biggest impact on frequency of sending state updates that, in turn, will reduce the number of state updates sent per second at the cost of losing some motion resolution. @@ -166,7 +166,7 @@ When unreliable state updates are enabled, NetworkTransform instances are assign By default, NetworkTransform synchronizes the Transform of an object in world space. The __In Local Space__ configuration option allows you to change to synchronizing the transform in local space instead. A child's local space axis values (position and rotation primarily) are always relative offsets from the parent transform, where a child's world space axis values include the parent's axis values. Where a transform with no parent really is "parented" to the root of the scene hierarchy which results in its world and local space positions to always be the same. -Enabling the __In Local Space__ property on a parented NetworkTransform can improve the synchronization of the transform when the object gets re-parented because the re-parenting won't change the local space Transform of the object (but does change the world space position) and you only need to update motion of the parented `NetworkTransform` relative to its parent (if the parent is moving and the child has no motion then there are no delta states to detect for the child but the child moves with the parent). +Enabling the __In Local Space__ property on a parented NetworkTransform can improve the synchronization of the transform when the object gets re-parented because the re-parenting won't change the local space Transform of the object (but does change the world space position) and you only need to update motion of the parented NetworkTransform relative to its parent (if the parent is moving and the child has no motion then there are no delta states to detect for the child but the child moves with the parent). :::info The authority instance does synchronize changes to the __In Local Space__ property. As such, you can make adjustments to this property on the authoritative side during runtime and the non-authoritative instances will automatically be updated. diff --git a/com.unity.netcode.gameobjects/Documentation~/integrated-management.md b/com.unity.netcode.gameobjects/Documentation~/integrated-management.md index 6e4994ade1..de76dd38d7 100644 --- a/com.unity.netcode.gameobjects/Documentation~/integrated-management.md +++ b/com.unity.netcode.gameobjects/Documentation~/integrated-management.md @@ -8,4 +8,4 @@ Understand Netcode for GameObject's integrated scene management system. | **[Scene events](basics/scenemanagement/scene-events.md)** | Use scene events to manage sychronization in a scene. | | **[Client synchronization mode](basics/scenemanagement/client-synchronization-mode.md)** | Netcode for GameObjects provides you with the ability to select the client synchronization mode that best suits your project's needs. | | **[Timing considerations](basics/scenemanagement/timing-considerations.md)** | Netcode for GameObjects handles many of the more complicated aspects of scene management. This section is tailored towards those who want to better understand the client-server communication sequence for scene events as they occur over time. | -| **[In-scene placed NetworkObjects](basics/scenemanagement/inscene-placed-networkobjects.md)** | In-scene placed `NetworkObject`s are GameObjects with a `NetworkObject` component that was added to a scene from within the Editor. | \ No newline at end of file +| **[In-scene placed NetworkObjects](basics/scenemanagement/inscene-placed-networkobjects.md)** | In-scene placed NetworkObjects are GameObjects with a NetworkObject component that was added to a scene from within the Editor. | \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/learn/clientside-interpolation.md b/com.unity.netcode.gameobjects/Documentation~/learn/clientside-interpolation.md index d65c358e4d..f6c6e51829 100644 --- a/com.unity.netcode.gameobjects/Documentation~/learn/clientside-interpolation.md +++ b/com.unity.netcode.gameobjects/Documentation~/learn/clientside-interpolation.md @@ -15,7 +15,7 @@ In addition to reduced responsiveness, this approach can also directly interfere ## With client-side interpolation -When using client-side interpolation, all clients intentionally run slightly behind the server, giving them time to transition smoothly between state updates and conceal the effects of latency from users. To implement client-side interpolation, use the [NetworkTransform](../components/helpers/networktransform.md) component. +When using client-side interpolation, all clients intentionally run slightly behind the server, giving them time to transition smoothly between state updates and conceal the effects of latency from users. To implement client-side interpolation, use the [NetworkTransform](../components/helper/networktransform.md) component. In a standard client-server [topology](../terms-concepts/network-topologies.md), clients are able to render a state that's approximately half the [round-trip time (RTT)](lagandpacketloss.md#round-trip-time-and-pings) behind the server. When using client-side interpolation, a further intentional delay is added so that by the time the client is rendering state _n_, it's already received state _n+1_, which allows the client to always smoothly transition from one state to another. This is effectively increasing latency, but in a consistent, client-universal way that can be used to reduce the negative impacts of unpredictable network jitter on gameplay. diff --git a/com.unity.netcode.gameobjects/Documentation~/learn/dealing-with-latency.md b/com.unity.netcode.gameobjects/Documentation~/learn/dealing-with-latency.md index 657d6f12a8..a14cc6d0da 100644 --- a/com.unity.netcode.gameobjects/Documentation~/learn/dealing-with-latency.md +++ b/com.unity.netcode.gameobjects/Documentation~/learn/dealing-with-latency.md @@ -157,7 +157,7 @@ To do continuous client driven actions, there's a few more considerations to tak - You then need to make sure you don't send RPCs to the server (containing your authoritative state) when no data has changed and do dirty checks. - You'd need to send it on tick or at worst on FixedUpdate. Sending on Update() would spam your connection. -A sample for a [ClientNetworkTransform](../components/helpers/networktransform.md#clientnetworktransform) has been created, so you don't have to reimplement this yourself for transform updates. A [sample](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize/tree/main/Basic/ClientDriven) has been created on how to use it. See [movement script](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize/blob/v1.2.1/Basic/ClientDriven/Assets/Scripts/ClientPlayerMove.cs). +A sample for a [ClientNetworkTransform](../components/helper/networktransform.md#clientnetworktransform) has been created, so you don't have to reimplement this yourself for transform updates. A [sample](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize/tree/main/Basic/ClientDriven) has been created on how to use it. See [movement script](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize/blob/v1.2.1/Basic/ClientDriven/Assets/Scripts/ClientPlayerMove.cs). > [!NOTE] > A rule of thumb here is to ask yourself: "Can the server correct me on this?". If it can, use server authority. @@ -184,7 +184,7 @@ Extrapolation is an attempt to estimate a future game state, without taking into The client will normally assume that a moving object will continue in the same direction. When a new packet is received, the position may be updated. -For Netcode for gameobjects (Netcode), a basic extrapolation implementation has been provided in [`NetworkTransform`](../components/helpers/networktransform.md) and is estimated between the time a tick advances in server-side animation and the update of the frame on the client-side. The game object extrapolates the next frame's values based on the ratio. +For Netcode for gameobjects (Netcode), a basic extrapolation implementation has been provided in [NetworkTransform](../components/helper/networktransform.md) and is estimated between the time a tick advances in server-side animation and the update of the frame on the client-side. The game object extrapolates the next frame's values based on the ratio. > [!NOTE] > While Netcode for GameObjects doesn't have a full implementation of client-side prediction and reconciliation, you can build such a system on top of the existing client-side anticipation building-blocks, `AnticipatedNetworkVariable` and `AnticipatedNetworkTransform`. These components allow differentiating between the "authoritative" value and the value that is shown to the players. These components provide most of the information needed to implement prediction, but do require you to implement certain aspects yourself. Because of the complexity inherent in building a full client prediction system, the details of that are left for users, and we recommend only advanced users pursue this option. diff --git a/com.unity.netcode.gameobjects/Documentation~/learn/faq.md b/com.unity.netcode.gameobjects/Documentation~/learn/faq.md index 81d5536282..46a5f40879 100644 --- a/com.unity.netcode.gameobjects/Documentation~/learn/faq.md +++ b/com.unity.netcode.gameobjects/Documentation~/learn/faq.md @@ -49,7 +49,7 @@ Spawning can always be done on the host/server. If you want to give a client con ### What are best practices for handing physics with Netcode? -Networked physics is complicated, with many different ways to handle them. Currently, physics can be a little difficult to handle with Netcode and the built-in `NetworkTransform`. +Networked physics is complicated, with many different ways to handle them. Currently, physics can be a little difficult to handle with Netcode and the built-in NetworkTransform. The Multiplayer Technology Team recommends the following: @@ -90,7 +90,7 @@ See [Apple Support](https://support.apple.com/guide/mac-help/open-a-mac-app-from ### Why is there an `InitBootStrap` scene as the startup scene for Boss Room and Bitesize Samples? -The initial reason is that in Netcode the `NetworkManager` is a singleton class. The Bitesize Samples Team initially created it in the main menu, but when the host was leaving the in-game/networked scene, the Network Manager was getting destroyed, which led to not being able to host a game again without restarting the game instance. +The initial reason is that in Netcode the NetworkManager is a singleton class. The Bitesize Samples Team initially created it in the main menu, but when the host was leaving the in-game/networked scene, the Network Manager was getting destroyed, which led to not being able to host a game again without restarting the game instance. The Bootstrap scene ensures that the NetworkManager and other singletons are initialized first and will be there when you get back to main menu. diff --git a/com.unity.netcode.gameobjects/Documentation~/learn/rpcnetvarexamples.md b/com.unity.netcode.gameobjects/Documentation~/learn/rpcnetvarexamples.md index 9e4ee6e21f..fd328d147a 100644 --- a/com.unity.netcode.gameobjects/Documentation~/learn/rpcnetvarexamples.md +++ b/com.unity.netcode.gameobjects/Documentation~/learn/rpcnetvarexamples.md @@ -16,7 +16,7 @@ Boss Room wants the full history of inputs sent, not just the latest value. Ther ## Arrow's GameObject vs Fireball's VFX -The archer's arrows use a standalone `GameObject` that's replicated over time. Since this object's movements are slow, the Boss Room development team decided to use state (via the `NetworkTransform`) to replicate the ability's status (in case a client connected while the arrow was flying). +The archer's arrows use a standalone GameObject that's replicated over time. Since this object's movements are slow, the Boss Room development team decided to use state (via the NetworkTransform) to replicate the ability's status (in case a client connected while the arrow was flying). ```csharp reference https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/v2.2.0/Assets/Scripts/Gameplay/GameplayObjects/Projectiles/PhysicsProjectile.cs @@ -32,7 +32,7 @@ https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/v2 ## Breakable state -Boss Room might have used a "break" `RPC` to set a breakable object as broken and play the appropriate visual effects. Applying the "replicate information when a player joins the game mid-game" rule of thumb, the Boss Room development team used `NetworkVariable`s instead. Boss Room uses the `OnValueChanged` callback on those values to play the visual effects (and an initial check when spawning the `NetworkBehaviour`). +Boss Room might have used a "break" `RPC` to set a breakable object as broken and play the appropriate visual effects. Applying the "replicate information when a player joins the game mid-game" rule of thumb, the Boss Room development team used `NetworkVariable`s instead. Boss Room uses the `OnValueChanged` callback on those values to play the visual effects (and an initial check when spawning the NetworkBehaviour). ```csharp reference diff --git a/com.unity.netcode.gameobjects/Documentation~/learn/sample-dedicated-server.md b/com.unity.netcode.gameobjects/Documentation~/learn/sample-dedicated-server.md index 7e4f5abb75..3fc0335690 100644 --- a/com.unity.netcode.gameobjects/Documentation~/learn/sample-dedicated-server.md +++ b/com.unity.netcode.gameobjects/Documentation~/learn/sample-dedicated-server.md @@ -52,14 +52,14 @@ This sample strips other components and GameObjects from the server manually. To This sample splits the logic of the Player Character and the AI Character into separate scripts so that you can use the Content Selection window to run each character separately on the client, server and network. For example, the `PlayerCharacter` script logic is split into the following scripts: * Client Player Character. This script only exists on the clients. * Server Player Character.This script only exists on the server. -* Networked Player Character: This script inherits from `NetworkBehaviour`.It synchronizes data and sends messages between the server and clients. This script exists on both clients and the server. +* Networked Player Character: This script inherits from NetworkBehaviour.It synchronizes data and sends messages between the server and clients. This script exists on both clients and the server. These scripts separate the logic of each player which means you can strip the components that each script uses. For example, Client Player Character is the only script that uses the Player Character’s `CharacterController` component, so you can safely strip the `CharacterController` component from the server. To learn where the scripts in this sample exist, do the following: 1. Select a Player Character GameObject. 2. Open the GameObject’s Inspector window. 3. Refer to the script component’s [Multiplayer role icon](https://docs.unity3d.com/Packages/com.unity.dedicated-server@1.0/manual/mutliplayer-roles-icons.html). -The logic for scripts that contain a small class, like the doors and switches in this sample scene, exist in a single script that inherits from ``NetworkBehaviour``. +The logic for scripts that contain a small class, like the doors and switches in this sample scene, exist in a single script that inherits from `NetworkBehaviour`. #### Synchronize animations between clients diff --git a/com.unity.netcode.gameobjects/Documentation~/network-components.md b/com.unity.netcode.gameobjects/Documentation~/network-components.md index dc93f61356..b4df3986e3 100644 --- a/com.unity.netcode.gameobjects/Documentation~/network-components.md +++ b/com.unity.netcode.gameobjects/Documentation~/network-components.md @@ -4,5 +4,5 @@ Understand the network components involved in a Netcode for GameObjects project. | **Topic** | **Description** | | :------------------------------ | :------------------------------- | -| **[Foundational Components](components/foundational/foundationalcomponents.md)** | Learn about the three foundational components: `NetworkObject`, `NetworkBehaviour`, and `NetworkManager`. | -| **[Helper Components](components/Helpers/helpercomponents.md)** | Helper components available to use in your Netcode for GameObjects project. | +| **[Core components](components/core/corecomponents.md)** | Learn about the three core components of Netcode for GameObjects: NetworkObject, NetworkBehaviour, and NetworkManager. | +| **[Helper Components](components/helper/helpercomponents.md)** | Helper components available to use in your Netcode for GameObjects project. | diff --git a/com.unity.netcode.gameobjects/Documentation~/network-synchronization.md b/com.unity.netcode.gameobjects/Documentation~/network-synchronization.md index 30d5576881..aff9529a82 100644 --- a/com.unity.netcode.gameobjects/Documentation~/network-synchronization.md +++ b/com.unity.netcode.gameobjects/Documentation~/network-synchronization.md @@ -9,5 +9,5 @@ Manage latency and performance in your Netcode for GameObjects project. | **[Remote procedure calls (RPCs)](rpc-landing.md)** | Any process can communicate with any other process by sending a remote procedure call (RPC). | | **[Custom messages](advanced-topics/message-system/custom-messages.md)** | Create a custom message system for your Netcode for GameObjects project. | | **[Connection events](advanced-topics/connection-events.md)** | When you need to react to connection or disconnection events for yourself or other clients, you can use `NetworkManager.OnConnectionEvent` as a unified source of information about changes in the network. | -| **[Network update loop](network-update-loop.md)** | The Network Update Loop infrastructure utilizes Unity's low-level Player Loop API allowing for registering `INetworkUpdateSystems` with `NetworkUpdate()` methods to be executed at specific `NetworkUpdateStages` which may be either before or after `MonoBehaviour`-driven game logic execution. | +| **[Network update loop](network-update-loop.md)** | The Network Update Loop infrastructure utilizes Unity's low-level Player Loop API allowing for registering `INetworkUpdateSystems` with `NetworkUpdate()` methods to be executed at specific `NetworkUpdateStages` which may be either before or after MonoBehaviour-driven game logic execution. | | **[Network time and ticks](advanced-topics/networktime-ticks.md)** | Understand how network time and ticks work while synchronizing your project. | \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/network-update-loop.md b/com.unity.netcode.gameobjects/Documentation~/network-update-loop.md index d705e8e073..323b8d1403 100644 --- a/com.unity.netcode.gameobjects/Documentation~/network-update-loop.md +++ b/com.unity.netcode.gameobjects/Documentation~/network-update-loop.md @@ -4,5 +4,5 @@ Understand how network information is updated in Netcode for GameObjects project | **Topic** | **Description** | | :------------------------------ | :------------------------------- | -| **[About network update loop](advanced-topics/network-update-loop-system/index.md)** | The Network Update Loop infrastructure utilizes Unity's low-level Player Loop API allowing for registering `INetworkUpdateSystems` with `NetworkUpdate()` methods to be executed at specific `NetworkUpdateStages` which may be either before or after `MonoBehaviour`-driven game logic execution. | +| **[About network update loop](advanced-topics/network-update-loop-system/index.md)** | The Network Update Loop infrastructure utilizes Unity's low-level Player Loop API allowing for registering `INetworkUpdateSystems` with `NetworkUpdate()` methods to be executed at specific `NetworkUpdateStages` which may be either before or after MonoBehaviour-driven game logic execution. | | **[Network update loop reference](advanced-topics/network-update-loop-system/network-update-loop-reference.md)** | Diagrams that provide insight into the Network Update Loop process and APIs. | \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/networkbehaviour-landing.md b/com.unity.netcode.gameobjects/Documentation~/networkbehaviour-landing.md index b6432462cf..c690759906 100644 --- a/com.unity.netcode.gameobjects/Documentation~/networkbehaviour-landing.md +++ b/com.unity.netcode.gameobjects/Documentation~/networkbehaviour-landing.md @@ -1,9 +1,9 @@ # NetworkBehaviours -`NetworkBehaviour` components are the 2nd +NetworkBehaviour components are the 2nd Understand how to use NetworkBehaviour components in your project. | **Topic** | **Description** | | :------------------------------ | :------------------------------- | -| **[NetworkBehaviour](components/foundational/networkbehaviour.md)** | [`NetworkBehaviour`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html) is an abstract class that derives from [`MonoBehaviour`](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and is primarily used to create unique netcode or game logic. To replicate any netcode-aware properties or send and receive RPCs, a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) must have a [NetworkObject](components/foundational/networkobject.md) component and at least one `NetworkBehaviour` component. | -| **[Synchronize](components/foundational/networkbehaviour-synchronize.md)** | You can use NetworkBehaviours to synchronize settings before, during, and after spawning NetworkObjects. | \ No newline at end of file +| **[NetworkBehaviour](components/core/networkbehaviour.md)** | [NetworkBehaviour](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html) is an abstract class that derives from [MonoBehaviour](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and is primarily used to create unique netcode or game logic. To replicate any netcode-aware properties or send and receive RPCs, a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) must have a [NetworkObject](components/core/networkobject.md) component and at least one NetworkBehaviour component. | +| **[Synchronize](components/core/networkbehaviour-synchronize.md)** | You can use NetworkBehaviours to synchronize settings before, during, and after spawning NetworkObjects. | \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/relay/relay.md b/com.unity.netcode.gameobjects/Documentation~/relay/relay.md index 844a9d3b24..15f4fe66a6 100644 --- a/com.unity.netcode.gameobjects/Documentation~/relay/relay.md +++ b/com.unity.netcode.gameobjects/Documentation~/relay/relay.md @@ -105,7 +105,7 @@ For more information about the join code connection process, refer to [Connectio When an allocation exists, you need to make all traffic that comes from Netcode for GameObjects go through the Relay. To do this, perform the following actions to pass the allocation parameters to UnityTransport: -1. Retrieve Unity transport from your `NetworkManager`: +1. Retrieve Unity transport from your NetworkManager: ```csharp //Retrieve the Unity transport used by the NetworkManager UnityTransport transport = NetworkManager.Singleton.gameObject.GetComponent(); diff --git a/com.unity.netcode.gameobjects/Documentation~/samples.md b/com.unity.netcode.gameobjects/Documentation~/samples.md index d61850d480..8c799629a9 100644 --- a/com.unity.netcode.gameobjects/Documentation~/samples.md +++ b/com.unity.netcode.gameobjects/Documentation~/samples.md @@ -4,5 +4,5 @@ Use the samples in this section to learn how to use Netcode for GameObjects in y | **Topic** | **Description** | | :------------------------------ | :------------------------------- | -| **[NetworkBehaviour](components/foundational/networkbehaviour.md)** | [`NetworkBehaviour`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html) is an abstract class that derives from [`MonoBehaviour`](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and is primarily used to create unique netcode or game logic. To replicate any netcode-aware properties or send and receive RPCs, a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) must have a [NetworkObject](components/foundational/networkobject.md) component and at least one `NetworkBehaviour` component. | -| **[Synchronize](components/foundational/networkbehaviour-synchronize.md)** | You can use NetworkBehaviours to synchronize settings before, during, and after spawning NetworkObjects. | \ No newline at end of file +| **[NetworkBehaviour](components/core/networkbehaviour.md)** | [NetworkBehaviour](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html) is an abstract class that derives from [MonoBehaviour](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and is primarily used to create unique netcode or game logic. To replicate any netcode-aware properties or send and receive RPCs, a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) must have a [NetworkObject](components/core/networkobject.md) component and at least one NetworkBehaviour component. | +| **[Synchronize](components/core/networkbehaviour-synchronize.md)** | You can use NetworkBehaviours to synchronize settings before, during, and after spawning NetworkObjects. | \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-clientdriven.md b/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-clientdriven.md index aa9445c02d..bf614d50b8 100644 --- a/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-clientdriven.md +++ b/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-clientdriven.md @@ -6,7 +6,7 @@ Making movements feel responsive while staying consistent over multiple game exe ## TLDR: -- Use [ClientNetworkTransform](../../components/helpers/networktransform.md) to sync client authoritative transform updates to the server and other clients. +- Use [ClientNetworkTransform](../../components/helper/networktransform.md) to sync client authoritative transform updates to the server and other clients. - Make sure ownership is set properly on that NetworkObject to be able to move it. - Since your clients live on different timelines (one per player), you have to be careful about who takes decisions and keep some of those decisions centralized on the server. - DON'T FORGET to test with latency, as your game will behave differently depending on whether client or server make decisions. diff --git a/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-invaders.md b/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-invaders.md index d4f6cb8d40..2d7604fd6a 100644 --- a/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-invaders.md +++ b/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-invaders.md @@ -86,7 +86,7 @@ https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize/blo ## Unconventional Networked Movement -Invaders has an easy movement type - moving only on one (horizontal) axis - which allows you to only modify the transform client-side without waiting for server-side validation. You can find where we perform the move logic in *[PlayerControl.cs](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize/blob/v1.2.1/Basic/Invaders/Assets/Scripts/PlayerControl.cs)* in the `InGameUpdate` function . With the help of a [`NetworkTransform`](../../components/helpers/networktransform.md) that is attached directly to the Player Game Object, it will automatically sync up the Transform with the other clients. At the same time, it will smooth out the movement by interpolating or extrapolating for all of them. +Invaders has an easy movement type - moving only on one (horizontal) axis - which allows you to only modify the transform client-side without waiting for server-side validation. You can find where we perform the move logic in *[PlayerControl.cs](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize/blob/v1.2.1/Basic/Invaders/Assets/Scripts/PlayerControl.cs)* in the `InGameUpdate` function . With the help of a [NetworkTransform](../../components/helper/networktransform.md) that is attached directly to the Player Game Object, it will automatically sync up the Transform with the other clients. At the same time, it will smooth out the movement by interpolating or extrapolating for all of them. ```csharp reference https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize/blob/v1.2.1/Basic/Invaders/Assets/Scripts/PlayerControl.cs#L176-L193 diff --git a/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/architecture.md b/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/architecture.md index 0f0928e5a7..d3e64ea4d3 100644 --- a/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/architecture.md +++ b/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/architecture.md @@ -32,7 +32,7 @@ After Boss Room starts and the initial bootstrap logic completes, the `Applicati The MainMenu scene only has the `MainMenuClientState`, whereas scenes that contain networked logic also have the server counterparts to the client state components. In the latter case, both the server and client components exist on the same GameObject. -The `NetworkManager` starts when the `CharSelect` scene loads, which happens when a player joins or hosts a game. The host drives game state transitions and controls the set of loaded scenes. Having the host manage the state transitions and scenes indirectly forces all clients to load the same scenes as the server they're connected to (via Netcode's networked scene management). +The NetworkManager starts when the `CharSelect` scene loads, which happens when a player joins or hosts a game. The host drives game state transitions and controls the set of loaded scenes. Having the host manage the state transitions and scenes indirectly forces all clients to load the same scenes as the server they're connected to (via Netcode's networked scene management). ### Application Flow Diagram @@ -57,7 +57,7 @@ With the Unity Relay network transport, clients don't need to worry about sharin See the [Multiplayer over the internet](getting-started-boss-room.md) section of the Boss Room README for more information about using the two network transport mechanisms. -Boss Room uses the Unity Transport package. Boss Room's assigns its instance of Unity Transport to the `transport` field of the `NetworkManager`. +Boss Room uses the Unity Transport package. Boss Room's assigns its instance of Unity Transport to the `transport` field of the NetworkManager. The Unity Transport Package is a network transport layer with network simulation tools that help spot networking issues early during development. Boss Room has both buttons to start a game in the two modes and will setup Unity Transport automatically to use either one of them at runtime. @@ -100,7 +100,7 @@ To keep a single source of truth for service access (and avoid scattering of ser An `Avatar` is at the same level as an `Imp` and lives in a scene. A `Persistent Player` lives across scenes. ::: -A `Persistent Player` Prefab goes into the `Player` Prefab slot in the `NetworkManager` of Boss Room. As a result, Boss Room spawns a single `Persistent Player` Prefab per client, and each client owns their respective `Persistent Player` instances. +A `Persistent Player` Prefab goes into the `Player` Prefab slot in the NetworkManager of Boss Room. As a result, Boss Room spawns a single `Persistent Player` Prefab per client, and each client owns their respective `Persistent Player` instances. :::note There is no need to mark `Persistent Player` instances as `DontDestroyOnLoad`. Netcode for GameObjects automatically keeps these prefabs alive between scene loads while the connections are live. @@ -117,19 +117,19 @@ Inside the Boss Room scene, `ServerBossRoomState` spawns a `PlayerAvatar` per `P The following example of a selected “Archer Boy” class shows the `PlayerAvatar` GameObject hierarchy: * `PlayerAvatar` is a NetworkObject that Boss Room destroys when the scene unloads. -* `PlayerGraphics` is a child `GameObject` containing a `NetworkAnimator` component responsible for replicating animations invoked on the server. +* `PlayerGraphics` is a child GameObject containing a NetworkAnimator component responsible for replicating animations invoked on the server. * `PlayerGraphics_Archer_Boy` is a purely graphical representation of the selected avatar class. -`ClientAvatarGuidHandler`, a `NetworkBehaviour` component residing on the `PlayerAvatar` Prefab instance, fetches the validated avatar GUID from `NetworkAvatarGuidState` and spawns a local, non-networked graphics GameObject corresponding to the avatar GUID. +`ClientAvatarGuidHandler`, a NetworkBehaviour component residing on the `PlayerAvatar` Prefab instance, fetches the validated avatar GUID from `NetworkAvatarGuidState` and spawns a local, non-networked graphics GameObject corresponding to the avatar GUID. ### Characters `ServerCharacter` exists on a `PlayerAvatar` (or another NPC character) and has server RPCs and `NetworkVariables` that store the state of a given character. It's responsible for executing the server-side logic for the characters. This server-side logic includes the following: -* Movement and pathfinding via `ServerCharacterMovement` use `NavMeshAgent,` which exists on the server to translate the character's transform (synchronized using the `NetworkTransform` component); +* Movement and pathfinding via `ServerCharacterMovement` use `NavMeshAgent,` which exists on the server to translate the character's transform (synchronized using the NetworkTransform component); * Player action queueing and execution via `ServerActionPlayer`; * AI logic via `AIBrain` (applies to NPCs); -* Character animations via `ServerAnimationHandler`, which uses `NetworkAnimator` to synchronize; +* Character animations via `ServerAnimationHandler`, which uses NetworkAnimator to synchronize; * `ClientCharacter` is primarily a host for the `ClientActionPlayer` class and has the client RPCs for the character gameplay logic. ### Game config setup @@ -176,7 +176,7 @@ The following list describes the movement flow of a player character. 4. There's network latency before the server receives the RPC. 5. The server receives the RPC (containing the target destination). 6. The server performs pathfinding calculations. -7. The server completes the pathfinding, and the server representation of the entity starts updating its `NetworkTransform` at the same cadence as `FixedUpdate`. +7. The server completes the pathfinding, and the server representation of the entity starts updating its NetworkTransform at the same cadence as `FixedUpdate`. 8. There's network latency before clients receive replication data. 9. The client representation of the entity updates its NetworkVariables. @@ -186,7 +186,7 @@ The Visuals GameObject never outpaces the simulation GameObject and is always sl ### Navigation system -Each scene with navigation (or dynamic navigation objects) should have a `NavigationSystem` component on a scene GameObject. The `GameObject` containing the `NavigationSystem` component must have the `NavigationSystem` tag. +Each scene with navigation (or dynamic navigation objects) should have a `NavigationSystem` component on a scene GameObject. The GameObject containing the `NavigationSystem` component must have the `NavigationSystem` tag. #### Building a navigation mesh @@ -202,7 +202,7 @@ You should also ensure each scene has exactly one `navmesh` file. You can find ` Boss Room implements the [Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection) (DI) pattern using the [`VContainer`](https://vcontainer.hadashikick.jp/) library. DI allows Boss Room to clearly define its dependencies in code instead of using static access, pervasive singletons, or scriptable object references (Scriptable Object Architecture). Code is easy to version-control and comparatively easy to understand for a programmer, unlike Unity YAML-based objects, such as scenes, scriptable object instances, and prefabs. -DI also allows Boss Room to circumvent the problem of cross-scene references to common dependencies, even though it still has to manage the lifecycle of `MonoBehaviour`-based dependencies by marking them with `DontDestroyOnLoad` and destroying them manually when appropriate. +DI also allows Boss Room to circumvent the problem of cross-scene references to common dependencies, even though it still has to manage the lifecycle of MonoBehaviour-based dependencies by marking them with `DontDestroyOnLoad` and destroying them manually when appropriate. :::note `ApplicationController` inherits from the `VContainer`'s `LifetimeScope`, a class that serves as a dependency injection scope and bootstrapper that facilitates binding dependencies. Scene-specific State classes inherit from `LifetimeScope`, too. @@ -229,7 +229,7 @@ After investigation, the Boss Room development team determined that client/serve * It's not completely necessary to ifdef classes because it's only compile-time insurance that certain parts of client-side code never run. You can still disable the component on Awake at runtime if it's not mean to run on the server or client. * The added complexity outweighed the pros that'd help with stripping whole assemblies. * Most `Client`/`Server` class pairs are tightly coupled and call one another; they have split implementations of the same logical object. Separating them into different assemblies forces you to create “bridge classes” to avoid circular dependencies between your client and server assemblies. By putting your client and server classes in the same assemblies, you allow those circular dependencies in those tightly coupled classes and remove unnecessary bridging and abstractions. -* Whole assembly stripping is incompatible with Netcode for GameObjects because Netcode for GameObjects doesn't support NetworkBehaviour stripping. Components related to a NetworkObject must match on the client and server sides. If these components aren't identical, it creates undefined runtime errors (the errors will change from one use to another; they range from no issue, to silent errors, to buffer exceptions) with Netcode for GameObjects' `NetworkBehaviour` indexing. +* Whole assembly stripping is incompatible with Netcode for GameObjects because Netcode for GameObjects doesn't support NetworkBehaviour stripping. Components related to a NetworkObject must match on the client and server sides. If these components aren't identical, it creates undefined runtime errors (the errors will change from one use to another; they range from no issue, to silent errors, to buffer exceptions) with Netcode for GameObjects' NetworkBehaviour indexing. After those experiments, the Boss Room development team established new rules for the codebase: diff --git a/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/networkobject-parenting.md b/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/networkobject-parenting.md index 5ab881e6c7..8c20b1a0d5 100644 --- a/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/networkobject-parenting.md +++ b/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/networkobject-parenting.md @@ -8,7 +8,7 @@ Before detailing Boss Room's approach to NetworkObject parenting, it's important Boss Room leverages NetworkObject parenting through the server-driven `PickUp` action (see [`PickUpAction.cs`](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/main/Assets/Scripts/Gameplay/Action/ConcreteActions/PickUpAction.cs)), where a character has the ability to pick up a specially-tagged, in-scene placed NetworkObject (see [`PickUpPot` prefab](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/main/Assets/Prefabs/Game/PickUpPot.prefab)]. -At its root, `PickUpPot` has a NetworkObject, a `NetworkTransform`, and a `PositionConstraint` component. `AutoObjectParentSync` is enabled on its `NetworkTransform` (as is by default) so that: +At its root, `PickUpPot` has a NetworkObject, a NetworkTransform, and a `PositionConstraint` component. `AutoObjectParentSync` is enabled on its NetworkTransform (as is by default) so that: 1. The NetworkObject can verify server-side if parenting a Heavy object to another NetworkObject is allowed. 2. The NetworkObject can notify us when the parent has successfully been modified server-side. @@ -17,4 +17,4 @@ To accommodate the limitation highlighted at the beginning of this document, Bos A special hand bone has been added to our Character's avatar. Upon a character's successful parenting attempt, this special bone is set as the `PickUpPot`'s `PositonConstraint` target. So while the `PickUpPot` is technically parented to a player, the `PositionConstraint` component allows the `PickUpPot` to follow a bone's position, presenting the **illusion** that the `PickUpPot` is parented to the player's hand bone itself. -Once the `PickUpPot` is parented, local space simulation is enabled on its [`NetworkTransform` component](../../components/helpers/networktransform.md). +Once the `PickUpPot` is parented, local space simulation is enabled on its [NetworkTransform component](../../components/helper/networktransform.md). diff --git a/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/networkrigidbody.md b/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/networkrigidbody.md index dcb0339cb9..35a7530d6e 100644 --- a/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/networkrigidbody.md +++ b/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/networkrigidbody.md @@ -4,6 +4,6 @@ Required reading: [Physics](../..//advanced-topics/physics.md) ::: -Boss Room leverages `NetworkRigidbody` to simulate physics-based projectiles. See the Vandal Imp's tossed projectile, the [`ImpTossedItem` prefab](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/main/Assets/Prefabs/Game/ImpTossedItem.prefab). At its root, this Prefab has a NetworkObject, a `NetworkTransform`, a `Rigidbody`, and a `NetworkRigidbody` component. Refer to [`TossAction.cs`](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/main/Assets/Scripts/Gameplay/Action/ConcreteActions/TossAction.cs) for more implementation details. +Boss Room leverages NetworkRigidBody to simulate physics-based projectiles. See the Vandal Imp's tossed projectile, the [`ImpTossedItem` prefab](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/main/Assets/Prefabs/Game/ImpTossedItem.prefab). At its root, this Prefab has a NetworkObject, a NetworkTransform, a `Rigidbody`, and a NetworkRigidBody component. Refer to [`TossAction.cs`](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/main/Assets/Scripts/Gameplay/Action/ConcreteActions/TossAction.cs) for more implementation details. -An important note: You must do any modifications to a `Rigidbody` that involve Physics (modifying velocity, applying forces, applying torque, and the like) **after** the NetworkObject spawns since `NetworkRigidbody` forces the `Rigidbody`'s `isKinematic` flag to be true on `Awake()`. Once spawned, this flag is modified depending on the ownership status of the NetworkObject. +An important note: You must do any modifications to a `Rigidbody` that involve Physics (modifying velocity, applying forces, applying torque, and the like) **after** the NetworkObject spawns since NetworkRigidBody forces the `Rigidbody`'s `isKinematic` flag to be true on `Awake()`. Once spawned, this flag is modified depending on the ownership status of the NetworkObject. diff --git a/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/optimizing-bossroom.md b/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/optimizing-bossroom.md index 2753803ce4..f5325cb5b8 100644 --- a/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/optimizing-bossroom.md +++ b/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/optimizing-bossroom.md @@ -37,7 +37,7 @@ For more examples, see [RPCs vs. NetworkVariables Examples](../../learn/rpcnetva ## NetworkTransform configuration {#networktransform-configuration} -The NetworkTransform component handles the synchronization of a NetworkObject's Transform. By default, the NetworkTransform component synchronizes every part of the transform at every tick if a change bigger than the specified [threshold](../../components/helpers/networktransform.md) occurs. However, you can configure it to only synchronize the necessary data by omitting particular axeis of the position, rotation, or scale vectors. See [Restricting synchronization](../../components/helpers/networktransform.md). +The NetworkTransform component handles the synchronization of a NetworkObject's Transform. By default, the NetworkTransform component synchronizes every part of the transform at every tick if a change bigger than the specified [threshold](../../components/helper/networktransform.md) occurs. However, you can configure it to only synchronize the necessary data by omitting particular axeis of the position, rotation, or scale vectors. See [Restricting synchronization](../../components/helper/networktransform.md). You can also increase the thresholds to reduce the frequency of updates if you don't mind reducing the accuracy and responsiveness of the replicated Transform. @@ -47,7 +47,7 @@ Since the characters evolve on a plane, we only synchronize their position's x a Additionally, with the changes introduced in [Netcode for GameObjects v1.4.0](https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/releases/tag/ngo%2F1.4.0), we were able to further reduce the bandwidth cost associated for some prefabs that utilized NetworkTransform. The synchronization payload was reduced by 5 bytes for the Character and the Arrow prefab inside Boss Room, for example, by enabling "Use Half Float Precision" on their respective NetworkTransforms. -See [NetworkTransform](../../components/helpers/networktransform.md) for more information on the NetworkTransform component. +See [NetworkTransform](../../components/helper/networktransform.md) for more information on the NetworkTransform component. ## Pooling {#pooling} @@ -103,7 +103,7 @@ As a result, the maximum number of reliable packets sent or received in a single ## NetworkManager tick rate configuration {#networkmanager-tick-rate-configuration} -Netcode's [NetworkManager](../../components/foundational/networkmanager.md) provides some configuration options, one of which is the [tick rate](../../advanced-topics/networktime-ticks.md). The tick rate configuration option determines the frequency at which network ticks occur. The ideal tick rate value relies on balancing smoothness, accuracy, and bandwidth usage. +Netcode's [NetworkManager](../../components/core/networkmanager.md) provides some configuration options, one of which is the [tick rate](../../advanced-topics/networktime-ticks.md). The tick rate configuration option determines the frequency at which network ticks occur. The ideal tick rate value relies on balancing smoothness, accuracy, and bandwidth usage. Lowering the tick rate reduces the frequency of NetworkVariable update messages (because they're sent at each tick). However, since it reduces the frequency of updates, it also reduces the smoothness of gameplay for the clients. You can reduce the impact of lower tick rates by using interpolation to provide smoothness, such as in the NetworkTransform. However, because there are fewer updates, the interpolation will be less accurate because it has less information. diff --git a/com.unity.netcode.gameobjects/Documentation~/scene-management.md b/com.unity.netcode.gameobjects/Documentation~/scene-management.md index e2c9772d2d..2c6cae5e08 100644 --- a/com.unity.netcode.gameobjects/Documentation~/scene-management.md +++ b/com.unity.netcode.gameobjects/Documentation~/scene-management.md @@ -6,4 +6,4 @@ Understand scene management in Netcode for GameObjects. | :------------------------------ | :------------------------------- | | **[Scene management overview](basics/scenemanagement/scene-management-overview.md)** | Introduction to scene management in Netcode for GameObjects. | | **[Integrated management](integrated-management.md)** | The Netcode for GameObjects scene management solution is enabled by default and provides you with a fully functional netcode scene management solution. | -| **[Custom management](basics/scenemanagement/custom-management.md)** | If your project has needs that go beyond the scope of the Netcode integrated scene management solution, you can disable scene management in the Editor through `NetworkManager`'s properties. | \ No newline at end of file +| **[Custom management](basics/scenemanagement/custom-management.md)** | If your project has needs that go beyond the scope of the Netcode integrated scene management solution, you can disable scene management in the Editor through NetworkManager's properties. | \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/serialization.md b/com.unity.netcode.gameobjects/Documentation~/serialization.md index 370183ef56..fb39fad334 100644 --- a/com.unity.netcode.gameobjects/Documentation~/serialization.md +++ b/com.unity.netcode.gameobjects/Documentation~/serialization.md @@ -10,5 +10,5 @@ Netcode for GameObjects has built-in serialization support for C# and Unity prim | **[Arrays](advanced-topics/serialization/serialization-arrays.md)** | Netcode for GameObjects has built-in serialization code for arrays of [C# value-type primitives](advanced-topics/serialization/cprimitives.md), like `int[]`, and [Unity primitive types](advanced-topics/serialization/unity-primitives.md). Any arrays of types that aren't handled by the built-in serialization code, such as `string[]`, need to be handled using a container class or structure that implements the [`INetworkSerializable`](advanced-topics/serialization/inetworkserializable.md) interface. | | **[INetworkSerializable](advanced-topics/serialization/inetworkserializable.md)** | You can use the `INetworkSerializable` interface to define custom serializable types. | | **[Custom serialization](advanced-topics/custom-serialization.md)** | Create custom serialization types. | -| **[NetworkObject serialization](advanced-topics/serialization/networkobject-serialization.md)** | `GameObjects`, `NetworkObjects` and `NetworkBehaviour` aren't serializable types so they can't be used in `RPCs` or `NetworkVariables` by default. There are two convenience wrappers which can be used to send a reference to a `NetworkObject` or a `NetworkBehaviour` over RPCs or `NetworkVariables`. | +| **[NetworkObject serialization](advanced-topics/serialization/networkobject-serialization.md)** | `GameObjects`, `NetworkObjects` and NetworkBehaviour aren't serializable types so they can't be used in `RPCs` or `NetworkVariables` by default. There are two convenience wrappers which can be used to send a reference to a NetworkObject or a NetworkBehaviour over RPCs or `NetworkVariables`. | | **[FastBufferWriter and FastBufferReader](advanced-topics/fastbufferwriter-fastbufferreader.md)** | The serialization and deserialization is done via `FastBufferWriter` and `FastBufferReader`. These have methods for serializing individual types and methods for serializing packed numbers, but in particular provide a high-performance method called `WriteValue()/ReadValue()` (for Writers and Readers, respectively) that can extremely quickly write an entire unmanaged struct to a buffer. | \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/spawn-despawn.md b/com.unity.netcode.gameobjects/Documentation~/spawn-despawn.md index fda545daff..f06ba35154 100644 --- a/com.unity.netcode.gameobjects/Documentation~/spawn-despawn.md +++ b/com.unity.netcode.gameobjects/Documentation~/spawn-despawn.md @@ -6,5 +6,5 @@ Spawn and despawn objects in your project. | :------------------------------ | :------------------------------- | | **[Object spawning](basics/object-spawning.md)** | Spawning in Netcode for GameObjects means to instantiate and/or spawn the object that is synchronized between all clients by the server. | | **[Object pooling](advanced-topics/object-pooling.md)** | Netcode for GameObjects provides built-in support for Object Pooling, which allows you to override the default Netcode destroy and spawn handlers with your own logic. | -| **[Object visibility](basics/object-visibility.md)** | Object (NetworkObject) visibility is a Netcode for GameObjects term used to describe whether a `NetworkObject` is visible to one or more clients as it pertains to a netcode/network perspective. | +| **[Object visibility](basics/object-visibility.md)** | Object (NetworkObject) visibility is a Netcode for GameObjects term used to describe whether a NetworkObject is visible to one or more clients as it pertains to a netcode/network perspective. | | **[Spawning synchronization](basics/spawning-synchronization.md)** | Ensuring that objects spawn in a synchronized manner across clients can be difficult, although the challenges differ depending on which [network topology](terms-concepts/network-topologies.md) you're using. | \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/troubleshooting/error-messages.md b/com.unity.netcode.gameobjects/Documentation~/troubleshooting/error-messages.md index 461da3a2a5..ca40666460 100644 --- a/com.unity.netcode.gameobjects/Documentation~/troubleshooting/error-messages.md +++ b/com.unity.netcode.gameobjects/Documentation~/troubleshooting/error-messages.md @@ -15,7 +15,7 @@ Error messages are captured and returned through Unity Editor Diagnostics (requi * `Can't find pending soft sync object. Is the projects the same? UnityEngine.Debug:LogError(Object)` * `ArgumentNullException: Can't spawn null object Parameter name: netObject` -This exception should only occur if your scenes aren't the same, for example if the scene of your server has a `NetworkObject` which isn't present in the client scene. Verify the scene objects work correctly by entering playmode in both editors. +This exception should only occur if your scenes aren't the same, for example if the scene of your server has a NetworkObject which isn't present in the client scene. Verify the scene objects work correctly by entering playmode in both editors. ## ServerRPC errors @@ -25,4 +25,4 @@ This exception should only occur if your scenes aren't the same, for example if The ServerRPC should only be used on the server. Make sure to add `isServer` check before calling. -If the call is added correctly but still returns a `nullreferenceexception`, `NetworkManager.Singleton` may be returning `null`. Verify you created the `GameObject` with a `NetworkManager` component, which handles all initialization. `NetworkManager.Singleton` is a reference to a instance of the `NetworkManager` component. +If the call is added correctly but still returns a `nullreferenceexception`, `NetworkManager.Singleton` may be returning `null`. Verify you created the GameObject with a NetworkManager component, which handles all initialization. `NetworkManager.Singleton` is a reference to a instance of the NetworkManager component. diff --git a/com.unity.netcode.gameobjects/Documentation~/troubleshooting/troubleshooting.md b/com.unity.netcode.gameobjects/Documentation~/troubleshooting/troubleshooting.md index 6704a33d63..e78de950fb 100644 --- a/com.unity.netcode.gameobjects/Documentation~/troubleshooting/troubleshooting.md +++ b/com.unity.netcode.gameobjects/Documentation~/troubleshooting/troubleshooting.md @@ -18,7 +18,7 @@ The following exception is thrown: NullReferenceException: Object reference not set to an instance of an object ``` -**Solution:** You most likely forgot to add the `NetworkManager` component to a game object in your scene. +**Solution:** You most likely forgot to add the NetworkManager component to a game object in your scene. ## NullReferenceException when trying to send an RPC to the server @@ -28,7 +28,7 @@ NullReferenceException: Object reference not set to an instance of an object NullReferenceException: Object reference not set to an instance of an object ``` -**Solution:** You most likely forgot to `Spawn()` your object. Run `Spawn()` on your `NetworkObject` component as the server to fix this issue. +**Solution:** You most likely forgot to `Spawn()` your object. Run `Spawn()` on your NetworkObject component as the server to fix this issue. ## Server build is using 100% CPU diff --git a/com.unity.netcode.gameobjects/Documentation~/tutorials/get-started-with-ngo.md b/com.unity.netcode.gameobjects/Documentation~/tutorials/get-started-with-ngo.md index d4b8e87da4..90be9d8f14 100644 --- a/com.unity.netcode.gameobjects/Documentation~/tutorials/get-started-with-ngo.md +++ b/com.unity.netcode.gameobjects/Documentation~/tutorials/get-started-with-ngo.md @@ -68,7 +68,7 @@ First, create the NetworkManager component: ### Create an object to spawn for each connected player > [!NOTE] -> When you drop the prefab into the **PlayerPrefab** slot, you're telling the library that when a client connects to the game, it automatically spawns this prefab as the character for the connecting client. Netcode for GameObjects won't spawn a player object if you don't have any prefab set as the **PlayerPrefab**. Refer to [Player Objects](../components/foundational/networkobject.md#finding-playerobjects). +> When you drop the prefab into the **PlayerPrefab** slot, you're telling the library that when a client connects to the game, it automatically spawns this prefab as the character for the connecting client. Netcode for GameObjects won't spawn a player object if you don't have any prefab set as the **PlayerPrefab**. Refer to [Player Objects](../components/core/networkobject.md#finding-playerobjects). This section guides you through creating an object that spawns for each connected player. @@ -101,7 +101,7 @@ This section guides you through creating an object that spawns for each connecte ### Scene management and the scenes in build list -Netcode for GameObjects comes with an integrated scene management solution that helps you synchronize what scenes should be loaded by all connected clients. The `NetworkManager` **Enable Scene Management** property, enabled by default, determines whether the integrated scene management solution will be used for your project (or not). In order for the integrated scene management solution to work properly, you must add any scene you want to be synchronized to the scenes in build list. This section guides you through adding your current scene to the scenes in build list. +Netcode for GameObjects comes with an integrated scene management solution that helps you synchronize what scenes should be loaded by all connected clients. The NetworkManager **Enable Scene Management** property, enabled by default, determines whether the integrated scene management solution will be used for your project (or not). In order for the integrated scene management solution to work properly, you must add any scene you want to be synchronized to the scenes in build list. This section guides you through adding your current scene to the scenes in build list. 1. Open the Build Settings window by selecting **File** > **Build Settings**. 2. Select **Add Open Scenes**. @@ -109,7 +109,7 @@ Netcode for GameObjects comes with an integrated scene management solution that ## Test starting a host in the Unity Editor -Now that you have a **NetworkManager**, assigned a **PlayerPrefab**, and added your current scene to the scenes in build test, you can quickly verify everything is functioning/configured correctly via entering play mode in the Unity Editor. By starting a host, you are starting `NetworkManager` as both a server and a client at the same time. +Now that you have a **NetworkManager**, assigned a **PlayerPrefab**, and added your current scene to the scenes in build test, you can quickly verify everything is functioning/configured correctly via entering play mode in the Unity Editor. By starting a host, you are starting NetworkManager as both a server and a client at the same time. You can test your Hello World project using the Unity Editor or a command-line helper. If you choose the latter, refer to [Create a command line helper](../tutorials/command-line-helper/). Otherwise, refer to the following instructions to test using the Unity Editor. Only the Plane appears on the server until the first client connects. Then, Netcode for GameObjects spawns a new Player prefab for each connected client; however, they overlap in the Game view. @@ -130,10 +130,10 @@ If it works correctly, the option to **Stop Host** displays in the **Inspector** ### The `HelloWorldManager.cs` script -Now that you have verified everything is configured correctly, you will want to have the ability to start the `NetworkManager` whether in play mode, as a stand alone build, or in another MPPM instance. This section will walk you through creating the `HelloWorldManager.cs` component script. +Now that you have verified everything is configured correctly, you will want to have the ability to start the NetworkManager whether in play mode, as a stand alone build, or in another MPPM instance. This section will walk you through creating the `HelloWorldManager.cs` component script. 1. Create a new script in the `Scripts` folder named `HelloWorldManager.cs`. -2. Add this component to the `NetworkManager` `GameObject` in your scene. +2. Add this component to the NetworkManager GameObject in your scene. 3. Copy the following code into the `HelloWorldManager.cs` script: ```csharp @@ -206,7 +206,7 @@ namespace HelloWorld } ``` -In your Hello World project, you created a NetworkManager by adding the pre-created NetworkManager component to a `GameObject`. This component allows you to start a Host, Client, or Server in Play Mode via the inspector view. The `HelloWorldManager.cs` script simplifies and extends this functionality by creating a runtime/play mode UI menu that allows you to select the three different `NetworkManager` modes you can start: +In your Hello World project, you created a NetworkManager by adding the pre-created NetworkManager component to a GameObject. This component allows you to start a Host, Client, or Server in Play Mode via the inspector view. The `HelloWorldManager.cs` script simplifies and extends this functionality by creating a runtime/play mode UI menu that allows you to select the three different NetworkManager modes you can start: - The **Host** starts the server and joins as a client. - The **Client** joins the server as a client player. @@ -327,7 +327,7 @@ Client Received the RPC #3 on NetworkObject #1 ... ``` -Only the client owning the `NetworkObject` owning the `RpcTest` script will send RPCs on the server, but they will all receive RPCs from the server. This means that if you test with multiple clients the consoles will log RPCs received once per `NetworkObject` per iteration on the server and all clients. If testing with a host and a client, you will see the following on the host's **Console**. This is because as a server it will receive the other client's server RPCs and as a client it will also receive its own client RPCs. +Only the client owning the NetworkObject owning the `RpcTest` script will send RPCs on the server, but they will all receive RPCs from the server. This means that if you test with multiple clients the consoles will log RPCs received once per NetworkObject per iteration on the server and all clients. If testing with a host and a client, you will see the following on the host's **Console**. This is because as a server it will receive the other client's server RPCs and as a client it will also receive its own client RPCs. ```log Server Received the RPC #0 on NetworkObject #2 @@ -399,7 +399,7 @@ namespace HelloWorld The `HelloWorldPlayer.cs` script adds some basic movement to the Hello World project player. Both the server player and the client player can start player movement. However, the movement occurs through the server's position NetworkVariable, which means the server player can move immediately, but the client player must request a movement from the server, wait for the server to update the position NetworkVariable, then replicate the change locally. -The `HelloWorldPlayer` class inherits from `Unity.Netcode`'s `NetworkBehaviour` instead of `MonoBehaviour`. This allows you to customize the networking code as you override what happens when the Player spawns. +The `HelloWorldPlayer` class inherits from `Unity.Netcode`'s NetworkBehaviour instead of MonoBehaviour. This allows you to customize the networking code as you override what happens when the Player spawns. ```csharp public class HelloWorldPlayer : NetworkBehaviour @@ -417,7 +417,7 @@ For multiplayer games, every object runs on at least two machines: player one an } ``` -Any `MonoBehaviour` implementing a NetworkBehaviour component can override the Netcode for GameObjects method `OnNetworkSpawn()`. The `OnNetworkSpawn()` method fires in response to the `NetworkObject` spawning. The `HelloWorldPlayer` class overrides `OnNetworkSpawn` because clients and the server run different logic. You can override this behavior on any NetworkBehaviour component. +Any MonoBehaviour implementing a NetworkBehaviour component can override the Netcode for GameObjects method `OnNetworkSpawn()`. The `OnNetworkSpawn()` method fires in response to the NetworkObject spawning. The `HelloWorldPlayer` class overrides `OnNetworkSpawn` because clients and the server run different logic. You can override this behavior on any NetworkBehaviour component. Because the server and client can be the same machine and the Player's owner (aka Host), you want further to differentiate the two and have different Move behavior for each. @@ -517,7 +517,7 @@ Add the `HelloWorldPlayer.cs` script to the Player prefab as a component: ## Add a NetworkTransform -This section guides you through adding a `NetworkTransform` component that moves the player. `NetworkTransform` is a component used to synchronize the position, rotation, and scale of objects across the network. +This section guides you through adding a NetworkTransform component that moves the player. NetworkTransform is a component used to synchronize the position, rotation, and scale of objects across the network. Add a NetworkTransform component to the Player prefab: diff --git a/com.unity.netcode.gameobjects/Documentation~/tutorials/helloworld.md b/com.unity.netcode.gameobjects/Documentation~/tutorials/helloworld.md index 1fb4582658..e9a162f184 100644 --- a/com.unity.netcode.gameobjects/Documentation~/tutorials/helloworld.md +++ b/com.unity.netcode.gameobjects/Documentation~/tutorials/helloworld.md @@ -22,16 +22,16 @@ In this section we will create the basic building blocks of a multiplayer game. ### Creating Network Manager and selecting the Transport -In this section we add a Network Manager and add Unity Transport to our project. The [NetworkManager](../components/foundational/networkmanager.md) is the component that has all your project's netcode-related settings. Unity Transport is the transport layer that Netcode uses for communication between the server and the clients. See [here](../advanced-topics/transports.md) for more. +In this section we add a Network Manager and add Unity Transport to our project. The [NetworkManager](../components/core/networkmanager.md) is the component that has all your project's netcode-related settings. Unity Transport is the transport layer that Netcode uses for communication between the server and the clients. See [here](../advanced-topics/transports.md) for more. 1. Right-click in the **Hierarchy** tab of the main Unity Window. 1. Select **Create Empty**. -1. Rename the `GameObject` **NetworkManager**. +1. Rename the GameObject **NetworkManager**. 2. Select **NetworkManager**. 3. Click **Add Component** in the Inspector Tab. 4. Select **Netcode** from the list shown. -5. Select `NetworkManager` Component from the list displayed. -6. Inside the `NetworkManager` component tab, locate the `NetworkTransport` field. +5. Select NetworkManager Component from the list displayed. +6. Inside the NetworkManager component tab, locate the `NetworkTransport` field. 7. Click "Select Transport". 8. Select `UnityTransport`. 9. Save your scene. @@ -42,18 +42,18 @@ This section adds in a player object and spawns it for each connected player. 1. Right-click in the **Hierarchy** tab of the Unity Window to create a **3D Object > Capsule** 1. Rename it **Player**. -1. While **Player** is selected, add a **Netcode** > `NetworkObject` component in the Inspector Tab. +1. While **Player** is selected, add a **Netcode** > NetworkObject component in the Inspector Tab. 1. Click the **Assets** folder under the **Project** tab. 2. Right-click inside the **Assets** folder to **Create** > **Folder** and call it **Prefabs**. 3. Make **Player** a Prefab by dragging it to **Prefabs** folder you just created. 4. Delete **Player** from scene. > [!NOTE] -> We remove the **Player** object from the scene because we assign this network Prefab to the `Player Prefab` property in the `NetworkManager` component. The library does not support defining a player object as an in-scene placed `NetworkObject`. +> We remove the **Player** object from the scene because we assign this network Prefab to the `Player Prefab` property in the NetworkManager component. The library does not support defining a player object as an in-scene placed NetworkObject. -5. Select `NetworkManager`. -6. Inside the `NetworkManager` component tab, locate the `Player Prefab` field. +5. Select NetworkManager. +6. Inside the NetworkManager component tab, locate the `Player Prefab` field. 7. Drag this player Prefab from above into this field. > [!NOTE] @@ -77,11 +77,11 @@ This command line helper will allow us to launch builds with a command line argu 1. Right-click the **Assets** folder and create a new folder by hovering over **Create** and selecting **Folder**. Name it **Scripts**. 2. Create a script called `NetworkCommandLine` by right-clicking on your **Scripts** folder, hovering over **Create** and selecting **C# Script**. -3. In the **Hierarchy** menu, right-click on the `NetworkManager` and choose **Create Empty**. +3. In the **Hierarchy** menu, right-click on the NetworkManager and choose **Create Empty**. - This will create an empty `GameObject` with `NetworkManager` as its parent. + This will create an empty GameObject with NetworkManager as its parent. -4. Rename this child `GameObject` `NetworkCommandLine`. +4. Rename this child GameObject `NetworkCommandLine`. 5. With the new `NetworkCommandLine` object selected, click **Add Component** from the **Inspector** tab. 6. Select **Scripts** from the drop-down and click on the `NetworkCommandLine.cs` script you created earlier. 7. Open the `NetworkCommandLine.cs` script by double-clicking from the **Project** tab > **Assets** > **Scripts**. It will open in your text editor diff --git a/com.unity.netcode.gameobjects/Documentation~/tutorials/testing/testing_with_artificial_conditions.md b/com.unity.netcode.gameobjects/Documentation~/tutorials/testing/testing_with_artificial_conditions.md index d5972ebe87..f3c0646638 100644 --- a/com.unity.netcode.gameobjects/Documentation~/tutorials/testing/testing_with_artificial_conditions.md +++ b/com.unity.netcode.gameobjects/Documentation~/tutorials/testing/testing_with_artificial_conditions.md @@ -78,7 +78,7 @@ To combine the benefits of Simulator Tools Window with ParrelSync - create or op Debug builds do allow for the possibility of applying artificial network conditions to the Unity Transport driver, but the Simulator Tools window itself only sets these values in the Editor. -To set the latency, jitter and packet-loss percentage values for develop builds we need the following code to execute before `NetworkManager` attempts to connect (changing the values of the parameters as desired): +To set the latency, jitter and packet-loss percentage values for develop builds we need the following code to execute before NetworkManager attempts to connect (changing the values of the parameters as desired): ``` #if DEVELOPMENT_BUILD && !UNITY_EDITOR diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index a95c9ec1d6..894aaf76d5 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -109,7 +109,7 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) if (ImportReferences(mainModule, compiledAssembly.Defines)) { - // process `NetworkBehaviour` types + // process NetworkBehaviour types try { mainModule.GetTypes() diff --git a/com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts/BootstrapPlayer.cs b/com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts/BootstrapPlayer.cs index af68e5037a..4e4cc97995 100644 --- a/com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts/BootstrapPlayer.cs +++ b/com.unity.netcode.gameobjects/Samples~/Bootstrap/Scripts/BootstrapPlayer.cs @@ -3,7 +3,7 @@ namespace Unity.Netcode.Samples { /// - /// Component attached to the "Player Prefab" on the `NetworkManager`. + /// Component attached to the "Player Prefab" on the NetworkManager. /// public class BootstrapPlayer : NetworkBehaviour { @@ -12,8 +12,8 @@ public class BootstrapPlayer : NetworkBehaviour /// If this method is invoked on the server instance of this player, it will teleport player to a random position. ///
/// - /// Since a `NetworkTransform` component is attached to this player, and the authority on that component is set to "Server", - /// this transform's position modification can only be performed on the server, where it will then be replicated down to all clients through `NetworkTransform`. + /// Since a NetworkTransform component is attached to this player, and the authority on that component is set to "Server", + /// this transform's position modification can only be performed on the server, where it will then be replicated down to all clients through NetworkTransform. /// [ServerRpc] public void RandomTeleportServerRpc() From dd7d4c91a846d5a1f5de3028f06d22280b0d7fcb Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 6 Aug 2025 16:09:45 -0500 Subject: [PATCH 39/43] Update AttachableBehaviour.cs Applying modifications suggested by Emma. --- .../Components/Helpers/AttachableBehaviour.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs index 2a8d968dfc..6907f2a1bc 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs @@ -499,18 +499,16 @@ public void Detach() if (!m_AttachableNode) { NetworkLog.LogError($"[{name}][Detach] Invalid state detected! {name}'s state is still {m_AttachState} but has no {nameof(AttachableNode)} assigned!"); + // Developer only notification for the most likely scenario where this method is invoked but the instance is not attached to anything. + if (NetworkManager && NetworkManager.LogLevel <= LogLevel.Developer) + { + NetworkLog.LogWarning($"[{name}][Detach] Cannot detach! {name} is not attached to anything!"); + } } - - // Developer only notification for the most likely scenario where this method is invoked but the instance is not attached to anything. - if (!m_AttachableNode && NetworkManager && NetworkManager.LogLevel <= LogLevel.Developer) - { - NetworkLog.LogWarning($"[{name}][Detach] Cannot detach! {name} is not attached to anything!"); - } - - // If we have the attachable node set and we are not in the middle of detaching, then log an error and note - // this could potentially occur if inoked more than once for the same instance in the same frame. - if (m_AttachableNode) + else { + // If we have the attachable node set and we are not in the middle of detaching, then log an error and note + // this could potentially occur if inoked more than once for the same instance in the same frame. NetworkLog.LogError($"[{name}][Detach] Invalid state detected! {name} is still referencing {nameof(AttachableNode)} {m_AttachableNode.name}! Could {nameof(Detach)} be getting invoked more than once for the same instance?"); } return; From 44c5ee79c59231e08b48daaccfea80dbe51e4a68 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 6 Aug 2025 16:50:16 -0500 Subject: [PATCH 40/43] style Remove whitespace --- .../Runtime/Components/Helpers/AttachableBehaviour.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs index 6907f2a1bc..d2cde01968 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs @@ -41,7 +41,6 @@ internal class ComponentControllerEntry #if UNITY_EDITOR - internal void OnValidate() { if (!HasInitialized) @@ -503,7 +502,7 @@ public void Detach() if (NetworkManager && NetworkManager.LogLevel <= LogLevel.Developer) { NetworkLog.LogWarning($"[{name}][Detach] Cannot detach! {name} is not attached to anything!"); - } + } } else { From 9122d3bf500749b0a772950d955e087ec4eee080 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 7 Aug 2025 09:40:43 -0500 Subject: [PATCH 41/43] Update com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs Co-authored-by: Emma --- .../Runtime/Components/Helpers/ComponentController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs index 8a99007012..d3d6e678b9 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs @@ -178,7 +178,7 @@ internal PendingStateUpdate(ComponentEntry componentControllerEntry, bool isEnab /// /// Determines whether the selected s will start enabled or disabled when spawned. /// - [Tooltip("The initial state of the component controllers enabled status when instnatiated.")] + [Tooltip("The initial state of the component controllers enabled status when instantiated.")] public bool StartEnabled = true; /// From aa8b2f4278c0127b8461d1fc936c968fb588acef Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 7 Aug 2025 14:50:52 -0500 Subject: [PATCH 42/43] update Modifications from 1:1 PR review session with Emma. Removed some redundant logic. Re-organized a few areas for better readability. Renamed the `AttachableBehaviour.AttachableNode` to `AttachableBehaviour.InternalAttachableNode`. Removed the authority check within `AttachableNode.OnNetworkPreDespawn`. --- .../Components/Helpers/AttachableBehaviour.cs | 35 ++++++++++--------- .../Components/Helpers/AttachableNode.cs | 4 +-- .../Tests/Runtime/AttachableBehaviourTests.cs | 6 ++-- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs index d2cde01968..ef99a265fa 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs @@ -198,7 +198,7 @@ public enum AttachState /// If attached, attaching, or detaching this will be the this instance is attached to. /// protected AttachableNode m_AttachableNode { get; private set; } - internal AttachableNode AttachableNode => m_AttachableNode; + internal AttachableNode InternalAttachableNode => m_AttachableNode; private NetworkBehaviourReference m_AttachedNodeReference = new NetworkBehaviourReference(null); private Vector3 m_OriginalLocalPosition; @@ -287,36 +287,37 @@ public override void OnNetworkPreDespawn() private void UpdateAttachedState() { var attachableNode = (AttachableNode)null; - var isAttaching = m_AttachedNodeReference.TryGet(out attachableNode, NetworkManager); - var preState = isAttaching ? AttachState.Attaching : AttachState.Detaching; + var referenceHasNode = m_AttachedNodeReference.TryGet(out attachableNode, NetworkManager); + ///////////////////////////////////////////////////////////// // Exit early if we are already in the correct attached state and the incoming // AttachableNode reference is the same as the local AttachableNode property. if (attachableNode == m_AttachableNode && - ((isAttaching && m_AttachState == AttachState.Attached) || - (!isAttaching && m_AttachState == AttachState.Detached))) + ((referenceHasNode && m_AttachState == AttachState.Attached) || + (!referenceHasNode && m_AttachState == AttachState.Detached))) { return; } - var preNode = isAttaching ? attachableNode : m_AttachableNode; - isAttaching = isAttaching && attachableNode != null; + // If we are in an attaching state but the node is null then we are still not attaching. + var isAttaching = referenceHasNode && attachableNode != null; + // If we are attached to some other AttachableNode, then detach from that before attaching to a new one. if (isAttaching && m_AttachableNode != null && m_AttachState == AttachState.Attached) { - // If we are attached to some other AttachableNode, then detach from that before attaching to a new one. - if (m_AttachableNode != attachableNode) - { - // Run through the same process without being triggerd by a NetVar update. - UpdateAttachState(AttachState.Detaching, m_AttachableNode); - InternalDetach(); - UpdateAttachState(AttachState.Detached, m_AttachableNode); + // Run through the same process without being triggerd by a NetVar update. + UpdateAttachState(AttachState.Detaching, m_AttachableNode); + InternalDetach(); + UpdateAttachState(AttachState.Detached, m_AttachableNode); - m_AttachableNode.Detach(this); - m_AttachableNode = null; - } + m_AttachableNode.Detach(this); + m_AttachableNode = null; } + // Used for attaching or detatching notifications + var preNode = referenceHasNode ? attachableNode : m_AttachableNode; + var preState = referenceHasNode ? AttachState.Attaching : AttachState.Detaching; + // Change the state to attaching or detaching UpdateAttachState(preState, preNode); diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs index d768b17fcf..d1a2ca9c16 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs @@ -51,7 +51,7 @@ protected override void OnOwnershipChanged(ulong previous, ulong current) var attachables = NetworkObject.transform.GetComponentsInChildren(); foreach (var attachable in attachables) { - if (attachable.AttachableNode == this) + if (attachable.InternalAttachableNode == this) { m_AttachedBehaviours.Add(attachable); } @@ -67,7 +67,7 @@ protected override void OnOwnershipChanged(ulong previous, ulong current) /// public override void OnNetworkPreDespawn() { - if (IsSpawned && HasAuthority && DetachOnDespawn) + if (IsSpawned && DetachOnDespawn) { for (int i = m_AttachedBehaviours.Count - 1; i >= 0; i--) { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/AttachableBehaviourTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/AttachableBehaviourTests.cs index 1d3d3778cf..4616525896 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/AttachableBehaviourTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/AttachableBehaviourTests.cs @@ -387,10 +387,10 @@ private bool AllInstancesDetached() m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}][{attachable.name}] Did not have its event invoked!"); } - if (attachable.AttachableNode != null) + if (attachable.InternalAttachableNode != null) { - var nodeHasAttachments = attachable.AttachableNode.HasAttachments ? $" {attachable.AttachableNode.name} still has attachments!" : ""; - m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}][{attachable.name}] Still refers to {attachable.AttachableNode.name}!{nodeHasAttachments}"); + var nodeHasAttachments = attachable.InternalAttachableNode.HasAttachments ? $" {attachable.InternalAttachableNode.name} still has attachments!" : ""; + m_ErrorLog.AppendLine($"[Client-{networkManager.LocalClientId}][{attachable.name}] Still refers to {attachable.InternalAttachableNode.name}!{nodeHasAttachments}"); } } return m_ErrorLog.Length == 0; From 48bc4c049e300eda29c69897a19e06847974bac6 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 7 Aug 2025 16:14:27 -0500 Subject: [PATCH 43/43] update Final suggested modifications from 1:1 with Emma during PR review. --- .../Components/Helpers/AttachableBehaviour.cs | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs index ef99a265fa..0d1a85d516 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; #if UNITY_EDITOR using UnityEditor; @@ -261,7 +262,7 @@ internal void ForceDetach() InternalDetach(); // Notify of the changed attached state - UpdateAttachState(m_AttachState, m_AttachableNode); + NotifyAttachedStateChanged(m_AttachState, m_AttachableNode); m_AttachedNodeReference = new NetworkBehaviourReference(null); @@ -284,42 +285,39 @@ public override void OnNetworkPreDespawn() base.OnNetworkDespawn(); } + /// + /// This will apply the final attach or detatch state based on the current value of . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void UpdateAttachedState() { - var attachableNode = (AttachableNode)null; - var referenceHasNode = m_AttachedNodeReference.TryGet(out attachableNode, NetworkManager); + // Process the NetworkBehaviourReference to get the new AttachableNode or null. + // If null, then isAttaching will always be false. + var isAttaching = m_AttachedNodeReference.TryGet(out AttachableNode attachableNode, NetworkManager); - ///////////////////////////////////////////////////////////// // Exit early if we are already in the correct attached state and the incoming // AttachableNode reference is the same as the local AttachableNode property. if (attachableNode == m_AttachableNode && - ((referenceHasNode && m_AttachState == AttachState.Attached) || - (!referenceHasNode && m_AttachState == AttachState.Detached))) + ((isAttaching && m_AttachState == AttachState.Attached) || + (!isAttaching && m_AttachState == AttachState.Detached))) { return; } - // If we are in an attaching state but the node is null then we are still not attaching. - var isAttaching = referenceHasNode && attachableNode != null; - // If we are attached to some other AttachableNode, then detach from that before attaching to a new one. if (isAttaching && m_AttachableNode != null && m_AttachState == AttachState.Attached) { // Run through the same process without being triggerd by a NetVar update. - UpdateAttachState(AttachState.Detaching, m_AttachableNode); + NotifyAttachedStateChanged(AttachState.Detaching, m_AttachableNode); InternalDetach(); - UpdateAttachState(AttachState.Detached, m_AttachableNode); + NotifyAttachedStateChanged(AttachState.Detached, m_AttachableNode); m_AttachableNode.Detach(this); m_AttachableNode = null; } - // Used for attaching or detatching notifications - var preNode = referenceHasNode ? attachableNode : m_AttachableNode; - var preState = referenceHasNode ? AttachState.Attaching : AttachState.Detaching; - // Change the state to attaching or detaching - UpdateAttachState(preState, preNode); + NotifyAttachedStateChanged(isAttaching ? AttachState.Attaching : AttachState.Detaching, isAttaching ? attachableNode : m_AttachableNode); ForceComponentChange(isAttaching, false); if (isAttaching) @@ -332,7 +330,7 @@ private void UpdateAttachedState() } // Notify of the changed attached state - UpdateAttachState(m_AttachState, m_AttachableNode); + NotifyAttachedStateChanged(m_AttachState, m_AttachableNode); // When detaching, we want to make our final action // the invocation of the AttachableNode's Detach method. @@ -357,7 +355,7 @@ protected virtual void OnAttachStateChanged(AttachState attachState, AttachableN /// /// Update the attached state. /// - private void UpdateAttachState(AttachState attachState, AttachableNode attachableNode) + private void NotifyAttachedStateChanged(AttachState attachState, AttachableNode attachableNode) { try { @@ -432,7 +430,7 @@ public void Attach(AttachableNode attachableNode) return; } - if (!HasAuthority) + if (!OnHasAuthority()) { NetworkLog.LogError($"[{name}][Attach][Not Authority] Client-{NetworkManager.LocalClientId} is not the authority!"); return;