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