Skip to content

Commit 8e8d766

Browse files
fix: remove the NetworkObjectBridge when a hybrid prefab stops being hybrid (#4211)
* fix: remove the NetworkObjectBridge when a hybrid prefab stops being hybrid Removing GhostObject from a hybrid prefab now removes only the hidden NetworkObjectBridge and any GhostRigidbody, instead of every GhostBehaviour, and restores SynchronizeTransform. Removing NetworkObject from a hybrid prefab also removes the bridge. The bridge now sets FullTickSmoothing in place of the obsolete SingleWorldHostInterpolationSmoothing. * update Clearing HasGhost. * fix: revalidate after GhostObject removal and guard deferred editor callbacks The GhostObject removal hook now re-runs UnifiedValidation on the next editor tick, so GhostObject and HasGhost match the components on the prefab stage root, and the bridge comes back when the hook fired for a different GhostBehaviour. The deferred add/remove callbacks unsubscribe first and return when their NetworkObject was destroyed. * chore: retrigger CI * fix: drop the GhostObject removal callback and keep the user's ghost modes UnifiedValidation removes a leftover NetworkObjectBridge, so the GhostObjectEditor pre-removal callback is no longer needed. It also fired when the inspection component was removed, and the rebuilt bridge reset SupportedGhostModes. The bridge no longer forces SupportedGhostModes to Interpolated. * Clean up NetworkObjectBridge by removing physics code Removed unused physics-related code and comments. * Refactor HybridParentUpdate and remove ApplyScale method Removed the ApplyScale method and commented out the current position and rotation logging.
1 parent 212a491 commit 8e8d766

4 files changed

Lines changed: 81 additions & 82 deletions

File tree

‎com.unity.netcode.gameobjects/Documentation~/ghostobjects.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ A hybrid object synchronizes its data through two separate systems:
1919

2020
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.
2121

22-
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).
22+
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).
2323

2424
### Effect on existing transform and physics components
2525

‎com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs‎

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
using System.Collections.Generic;
2-
#if UNIFIED_NETCODE
3-
using Unity.Netcode.Editor;
4-
#endif
52
using UnityEditor;
63
using UnityEngine;
74
using UnityEngine.Scripting.APIUpdating;
@@ -25,34 +22,6 @@ public class NetworkObjectEditor : UnityEditor.Editor
2522

2623
private static readonly string[] k_HiddenFields = { "m_Script" };
2724

28-
#if UNIFIED_NETCODE
29-
/// <summary>
30-
/// Register for the GhostObject removal event.
31-
/// </summary>
32-
[InitializeOnLoadMethod]
33-
private static void OnApplicationStart()
34-
{
35-
GhostObjectEditor.OnGhostObjectPreRemoval = OnGhostObjectPreRemoval;
36-
}
37-
38-
/// <summary>
39-
/// Callback to remove the GhostBehaviours prior to removing GhostObject.
40-
/// </summary>
41-
/// <param name="gameObject">The <see cref="GameObject"/> with the <see cref="GhostObject"/> component being removed.</param>
42-
private static void OnGhostObjectPreRemoval(GameObject gameObject)
43-
{
44-
var ghostBehaviours = gameObject.GetComponentsInChildren<GhostBehaviour>();
45-
for (int i = ghostBehaviours.Length - 1; i >= 0; i--)
46-
{
47-
DestroyImmediate(ghostBehaviours[i], true);
48-
}
49-
var networkObject = gameObject.GetComponent<NetworkObject>();
50-
networkObject.GhostObject = null;
51-
networkObject.HasGhost = false;
52-
networkObject.HadBridge = true;
53-
}
54-
#endif
55-
5625
private void Initialize()
5726
{
5827
if (m_Initialized)

‎com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs‎

Lines changed: 18 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#if UNIFIED_NETCODE
22
using Unity.Mathematics;
33
using Unity.Transforms;
4+
#if UNITY_EDITOR
5+
using UnityEditor;
6+
#endif
47
using UnityEngine;
58

69
namespace Unity.Netcode
@@ -15,18 +18,6 @@ namespace Unity.Netcode
1518
internal partial class NetworkObjectBridge : GhostBehaviour
1619
{
1720
#if UNITY_EDITOR
18-
private void Reset()
19-
{
20-
// Start users with just interpolation (they can adjust this if they want prediction)
21-
// to make the initial transition less problematic for users.
22-
// Only set when the bridge is first added, so a user's prediction setting is kept.
23-
var ghostAdapter = GetComponent<GhostObject>();
24-
if (ghostAdapter != null)
25-
{
26-
ghostAdapter.SupportedGhostModes = GhostModeMask.Interpolated;
27-
}
28-
}
29-
3021
private void OnValidate()
3122
{
3223
hideFlags = HideFlags.HideInInspector;
@@ -37,29 +28,23 @@ private void OnValidate()
3728
return;
3829
}
3930

40-
// NGO spawns the ghost when the NetworkObject spawns. This assures the ghost spawns in that NetworkManager's world.
41-
ghostAdapter.DelaySpawning = true;
42-
43-
#if COM_UNITY_MODULES_PHYSICS
44-
var rigidBody = GetComponent<Rigidbody>();
45-
var ghostRigidBody = GetComponent<GhostRigidbody>();
46-
if (rigidBody != null)
31+
var networkObject = GetComponent<NetworkObject>();
32+
if (networkObject == null)
4733
{
48-
// This must be enabled when replicating the rigid body.
49-
50-
ghostAdapter.SingleWorldHostInterpolationSmoothing = SingleWorldHostInterpolationMode.Interpolate;
51-
// TODO: Currently, this is added only if you enable replication of the rigid body.
52-
// There is a bug where if you don't add this component it doesn't synchronize the transform.
53-
// Remove this once the issue is resolved.
54-
if (ghostRigidBody == null)
34+
// Check again once the current operation finishes, since a script can add the bridge before the NetworkObject.
35+
EditorApplication.delayCall += () =>
5536
{
56-
gameObject.AddComponent<GhostRigidbody>();
57-
}
37+
if (this != null && GetComponent<NetworkObject>() == null)
38+
{
39+
DestroyImmediate(this, true);
40+
}
41+
};
42+
}
43+
else
44+
{
45+
// NGO spawns the ghost when the NetworkObject spawns. This assures the ghost spawns in that NetworkManager's world.
46+
ghostAdapter.DelaySpawning = true;
5847
}
59-
#endif
60-
#if COM_UNITY_MODULES_PHYSICS2D
61-
// TODO: Fill out a similar script as above but for the 2D version
62-
#endif
6348
}
6449
#endif
6550

@@ -79,17 +64,12 @@ private void OnValidate()
7964
/// instance's entity's PostTransformMatrix.</param>
8065
internal void HybridParentUpdate(Vector3 scale)
8166
{
82-
var current = Ghost.GetPositionAndRotation();
67+
//var current = Ghost.GetPositionAndRotation();
8368
//Debug.Log($"---- Current LT: {current.Position} | {current.Rotation.eulerAngles}");
8469
//Debug.Log($"---- New LT: {transform.localPosition} | {transform.localRotation}");
8570
Ghost.ApplyPostTransformMatrixScale(scale);
8671
}
8772

88-
internal void ApplyScale(Vector3 scale)
89-
{
90-
Ghost.ApplyPostTransformMatrixScale(scale);
91-
}
92-
9373
/// <summary>
9474
/// Keeps the ghost's owner in step with the <see cref="NetworkObject"/> owner, so an owner-predicted
9575
/// ghost is predicted by the NGO owner.

‎com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs‎

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -369,14 +369,57 @@ private void CheckForInScenePlaced()
369369
[SerializeField]
370370
internal bool HadBridge;
371371
#if UNITY_EDITOR
372-
private void OnApplicationUpdate()
372+
// The update callbacks unsubscribe first so one whose NetworkObject was destroyed doesn't throw every frame.
373+
private void OnAddNetworkObjectBridge()
373374
{
375+
EditorApplication.update -= OnAddNetworkObjectBridge;
376+
if (this == null)
377+
{
378+
return;
379+
}
374380
NetworkObjectBridge = gameObject.AddComponent<NetworkObjectBridge>();
375381
HadBridge = true;
376382
// Transform synchronization is handled by unified netcode
377383
SynchronizeTransform = false;
384+
}
385+
386+
private void OnRemoveNetworkObjectBridge()
387+
{
388+
EditorApplication.update -= OnRemoveNetworkObjectBridge;
389+
if (this == null)
390+
{
391+
return;
392+
}
393+
var networkObjectBridge = gameObject.GetComponent<NetworkObjectBridge>();
394+
if (networkObjectBridge != null)
395+
{
396+
DestroyImmediate(networkObjectBridge, true);
397+
}
398+
var ghostRigidbodies = gameObject.GetComponentsInChildren<GhostRigidbody>(true);
399+
for (int i = 0; i < ghostRigidbodies.Length; i++)
400+
{
401+
DestroyImmediate(ghostRigidbodies[i], true);
402+
}
378403

379-
EditorApplication.update -= OnApplicationUpdate;
404+
NetworkObjectBridge = null;
405+
HadBridge = false;
406+
HasGhost = false;
407+
408+
// NGO synchronizes the transform again once the prefab is no longer hybrid
409+
SynchronizeTransform = true;
410+
}
411+
412+
private void OnRemoveGhostObject()
413+
{
414+
EditorApplication.update -= OnRemoveGhostObject;
415+
if (this == null)
416+
{
417+
return;
418+
}
419+
OnRemoveNetworkObjectBridge();
420+
DestroyImmediate(GhostObject);
421+
GhostObject = null;
422+
HasGhost = false;
380423
}
381424

382425
internal void UnifiedValidation()
@@ -390,22 +433,29 @@ internal void UnifiedValidation()
390433
//TODO: Needs to be validated once develop-2.0.0 is merged.
391434
if (InScenePlaced)
392435
{
393-
Debug.LogError($"This experimental version of NGO does not support hybrid in-scene placed objects.");
394-
Destroy(GhostObject);
395-
HasGhost = false;
436+
Debug.LogError($"In-scene placed Hybrid instances are not allowed.");
437+
EditorApplication.update -= OnRemoveGhostObject;
438+
EditorApplication.update += OnRemoveGhostObject;
396439
return;
397440
}
398-
399-
if (NetworkObjectBridge == null)
441+
else if (NetworkObjectBridge == null)
400442
{
401-
EditorApplication.update -= OnApplicationUpdate;
402-
EditorApplication.update += OnApplicationUpdate;
443+
EditorApplication.update -= OnAddNetworkObjectBridge;
444+
EditorApplication.update += OnAddNetworkObjectBridge;
403445
}
404446
}
405-
else if (HadBridge && !HasGhost && !NetworkObjectBridge)
447+
else if (HadBridge && !HasGhost)
406448
{
407-
HadBridge = false;
408-
SynchronizeTransform = true;
449+
if (NetworkObjectBridge != null)
450+
{
451+
EditorApplication.update -= OnRemoveNetworkObjectBridge;
452+
EditorApplication.update += OnRemoveNetworkObjectBridge;
453+
}
454+
else
455+
{
456+
HadBridge = false;
457+
SynchronizeTransform = true;
458+
}
409459
}
410460
}
411461
#endif

0 commit comments

Comments
 (0)