diff --git a/com.unity.netcode.gameobjects/Documentation~/ghostobjects.md b/com.unity.netcode.gameobjects/Documentation~/ghostobjects.md index 5e135b699a..333d3e6f8d 100644 --- a/com.unity.netcode.gameobjects/Documentation~/ghostobjects.md +++ b/com.unity.netcode.gameobjects/Documentation~/ghostobjects.md @@ -19,7 +19,7 @@ A hybrid object synchronizes its data through two separate systems: Because Netcode for GameObjects data isn't part of the snapshot, Unity Netcode doesn't roll back or replay `NetworkVariable`s, RPCs, or spawn data as part of its prediction loop. To use prediction on a hybrid object, write your prediction code in `GhostBehaviour`s. Prediction code in `GhostBehaviour`s on hybrid objects works the same way as on any other GhostObject. -When you convert a NetworkObject into a hybrid object, Netcode for GameObjects sets the GhostObject's supported ghost modes to interpolated only. To use prediction, change the `SupportedGhostModes` and `DefaultGhostMode` settings on the **GhostObject** component to include a predicted mode. For more information about ghost modes, refer to the [Unity Netcode documentation](https://docs.unity3d.com/Packages/com.unity.netcode@latest). +When you convert a NetworkObject into a hybrid object, Netcode for GameObjects doesn't change the GhostObject's ghost mode settings. By default, a GhostObject spawns interpolated and supports all ghost modes. To use prediction, set `DefaultGhostMode` on the **GhostObject** component to a predicted mode. To reduce bandwidth, limit `SupportedGhostModes` to the modes the object uses. For more information about ghost modes, refer to the [Unity Netcode documentation](https://docs.unity3d.com/Packages/com.unity.netcode@latest). ### Effect on existing transform and physics components diff --git a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs index 312e875850..256baa72c6 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs @@ -1,7 +1,4 @@ using System.Collections.Generic; -#if UNIFIED_NETCODE -using Unity.Netcode.Editor; -#endif using UnityEditor; using UnityEngine; using UnityEngine.Scripting.APIUpdating; @@ -25,34 +22,6 @@ public class NetworkObjectEditor : UnityEditor.Editor private static readonly string[] k_HiddenFields = { "m_Script" }; -#if UNIFIED_NETCODE - /// - /// Register for the GhostObject removal event. - /// - [InitializeOnLoadMethod] - private static void OnApplicationStart() - { - GhostObjectEditor.OnGhostObjectPreRemoval = OnGhostObjectPreRemoval; - } - - /// - /// Callback to remove the GhostBehaviours prior to removing GhostObject. - /// - /// The with the component being removed. - private static void OnGhostObjectPreRemoval(GameObject gameObject) - { - var ghostBehaviours = gameObject.GetComponentsInChildren(); - for (int i = ghostBehaviours.Length - 1; i >= 0; i--) - { - DestroyImmediate(ghostBehaviours[i], true); - } - var networkObject = gameObject.GetComponent(); - networkObject.GhostObject = null; - networkObject.HasGhost = false; - networkObject.HadBridge = true; - } -#endif - private void Initialize() { if (m_Initialized) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs index 601c0f1b34..e54543af5d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs @@ -1,6 +1,9 @@ #if UNIFIED_NETCODE using Unity.Mathematics; using Unity.Transforms; +#if UNITY_EDITOR +using UnityEditor; +#endif using UnityEngine; namespace Unity.Netcode @@ -15,18 +18,6 @@ namespace Unity.Netcode internal partial class NetworkObjectBridge : GhostBehaviour { #if UNITY_EDITOR - private void Reset() - { - // Start users with just interpolation (they can adjust this if they want prediction) - // to make the initial transition less problematic for users. - // Only set when the bridge is first added, so a user's prediction setting is kept. - var ghostAdapter = GetComponent(); - if (ghostAdapter != null) - { - ghostAdapter.SupportedGhostModes = GhostModeMask.Interpolated; - } - } - private void OnValidate() { hideFlags = HideFlags.HideInInspector; @@ -37,29 +28,23 @@ private void OnValidate() return; } - // NGO spawns the ghost when the NetworkObject spawns. This assures the ghost spawns in that NetworkManager's world. - ghostAdapter.DelaySpawning = true; - -#if COM_UNITY_MODULES_PHYSICS - var rigidBody = GetComponent(); - var ghostRigidBody = GetComponent(); - if (rigidBody != null) + var networkObject = GetComponent(); + if (networkObject == null) { - // This must be enabled when replicating the rigid body. - - ghostAdapter.SingleWorldHostInterpolationSmoothing = SingleWorldHostInterpolationMode.Interpolate; - // TODO: Currently, this is added only if you enable replication of the rigid body. - // There is a bug where if you don't add this component it doesn't synchronize the transform. - // Remove this once the issue is resolved. - if (ghostRigidBody == null) + // Check again once the current operation finishes, since a script can add the bridge before the NetworkObject. + EditorApplication.delayCall += () => { - gameObject.AddComponent(); - } + if (this != null && GetComponent() == null) + { + DestroyImmediate(this, true); + } + }; + } + else + { + // NGO spawns the ghost when the NetworkObject spawns. This assures the ghost spawns in that NetworkManager's world. + ghostAdapter.DelaySpawning = true; } -#endif -#if COM_UNITY_MODULES_PHYSICS2D - // TODO: Fill out a similar script as above but for the 2D version -#endif } #endif @@ -79,17 +64,12 @@ private void OnValidate() /// instance's entity's PostTransformMatrix. internal void HybridParentUpdate(Vector3 scale) { - var current = Ghost.GetPositionAndRotation(); + //var current = Ghost.GetPositionAndRotation(); //Debug.Log($"---- Current LT: {current.Position} | {current.Rotation.eulerAngles}"); //Debug.Log($"---- New LT: {transform.localPosition} | {transform.localRotation}"); Ghost.ApplyPostTransformMatrixScale(scale); } - internal void ApplyScale(Vector3 scale) - { - Ghost.ApplyPostTransformMatrixScale(scale); - } - /// /// Keeps the ghost's owner in step with the owner, so an owner-predicted /// ghost is predicted by the NGO owner. diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index f5f90c0a39..96e765f86d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -369,14 +369,57 @@ private void CheckForInScenePlaced() [SerializeField] internal bool HadBridge; #if UNITY_EDITOR - private void OnApplicationUpdate() + // The update callbacks unsubscribe first so one whose NetworkObject was destroyed doesn't throw every frame. + private void OnAddNetworkObjectBridge() { + EditorApplication.update -= OnAddNetworkObjectBridge; + if (this == null) + { + return; + } NetworkObjectBridge = gameObject.AddComponent(); HadBridge = true; // Transform synchronization is handled by unified netcode SynchronizeTransform = false; + } + + private void OnRemoveNetworkObjectBridge() + { + EditorApplication.update -= OnRemoveNetworkObjectBridge; + if (this == null) + { + return; + } + var networkObjectBridge = gameObject.GetComponent(); + if (networkObjectBridge != null) + { + DestroyImmediate(networkObjectBridge, true); + } + var ghostRigidbodies = gameObject.GetComponentsInChildren(true); + for (int i = 0; i < ghostRigidbodies.Length; i++) + { + DestroyImmediate(ghostRigidbodies[i], true); + } - EditorApplication.update -= OnApplicationUpdate; + NetworkObjectBridge = null; + HadBridge = false; + HasGhost = false; + + // NGO synchronizes the transform again once the prefab is no longer hybrid + SynchronizeTransform = true; + } + + private void OnRemoveGhostObject() + { + EditorApplication.update -= OnRemoveGhostObject; + if (this == null) + { + return; + } + OnRemoveNetworkObjectBridge(); + DestroyImmediate(GhostObject); + GhostObject = null; + HasGhost = false; } internal void UnifiedValidation() @@ -390,22 +433,29 @@ internal void UnifiedValidation() //TODO: Needs to be validated once develop-2.0.0 is merged. if (InScenePlaced) { - Debug.LogError($"This experimental version of NGO does not support hybrid in-scene placed objects."); - Destroy(GhostObject); - HasGhost = false; + Debug.LogError($"In-scene placed Hybrid instances are not allowed."); + EditorApplication.update -= OnRemoveGhostObject; + EditorApplication.update += OnRemoveGhostObject; return; } - - if (NetworkObjectBridge == null) + else if (NetworkObjectBridge == null) { - EditorApplication.update -= OnApplicationUpdate; - EditorApplication.update += OnApplicationUpdate; + EditorApplication.update -= OnAddNetworkObjectBridge; + EditorApplication.update += OnAddNetworkObjectBridge; } } - else if (HadBridge && !HasGhost && !NetworkObjectBridge) + else if (HadBridge && !HasGhost) { - HadBridge = false; - SynchronizeTransform = true; + if (NetworkObjectBridge != null) + { + EditorApplication.update -= OnRemoveNetworkObjectBridge; + EditorApplication.update += OnRemoveNetworkObjectBridge; + } + else + { + HadBridge = false; + SynchronizeTransform = true; + } } } #endif