Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
472ba7f
fix: remove the NetworkObjectBridge when a hybrid prefab stops being …
NoelStephensUnity Oct 22, 2026
e81a81c
update
NoelStephensUnity Oct 22, 2026
6c44457
fix: revalidate after GhostObject removal and guard deferred editor c…
NoelStephensUnity Oct 23, 2026
943d58c
Merge branch 'develop-3.x.x' into fix/hybrid-prefab-component-removal
NoelStephensUnity Oct 23, 2026
a182f27
chore: retrigger CI
NoelStephensUnity Oct 23, 2026
3454143
fix: drop the GhostObject removal callback and keep the user's ghost …
NoelStephensUnity Oct 23, 2026
9dd10d8
Merge branch 'develop-3.x.x' into fix/hybrid-prefab-component-removal
NoelStephensUnity Oct 26, 2026
110f8bc
Clean up NetworkObjectBridge by removing physics code
NoelStephensUnity Oct 26, 2026
68bab5e
Refactor HybridParentUpdate and remove ApplyScale method
NoelStephensUnity Oct 26, 2026
f5b1323
Merge branch 'develop-3.x.x' into fix/hybrid-prefab-component-removal
NoelStephensUnity Oct 26, 2026
46ae0a9
Merge develop-3.x.x into fix/hybrid-prefab-component-removal
netcode-ci-service Oct 26, 2026
06fe8a2
Merge develop-3.x.x into fix/hybrid-prefab-component-removal
netcode-ci-service Oct 26, 2026
c043168
Merge develop-3.x.x into fix/hybrid-prefab-component-removal
netcode-ci-service Oct 27, 2026
25c1115
Merge branch 'develop-3.x.x' into fix/hybrid-prefab-component-removal
NoelStephensUnity Oct 27, 2026
ee08a6b
Merge develop-3.x.x into fix/hybrid-prefab-component-removal
netcode-ci-service Oct 27, 2026
f8e8e75
style
NoelStephensUnity Oct 27, 2026
6c4d6c2
Merge develop-3.x.x into fix/hybrid-prefab-component-removal
netcode-ci-service Oct 27, 2026
fac0d6f
fix: keep the bridge when a script adds it before the NetworkObject
NoelStephensUnity Oct 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
31 changes: 0 additions & 31 deletions com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using System.Collections.Generic;
#if UNIFIED_NETCODE
using Unity.Netcode.Editor;
#endif
using UnityEditor;
using UnityEngine;
using UnityEngine.Scripting.APIUpdating;
Expand All @@ -25,34 +22,6 @@ public class NetworkObjectEditor : UnityEditor.Editor

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

#if UNIFIED_NETCODE
/// <summary>
/// Register for the GhostObject removal event.
/// </summary>
[InitializeOnLoadMethod]
private static void OnApplicationStart()
{
GhostObjectEditor.OnGhostObjectPreRemoval = OnGhostObjectPreRemoval;
}

/// <summary>
/// Callback to remove the GhostBehaviours prior to removing GhostObject.
/// </summary>
/// <param name="gameObject">The <see cref="GameObject"/> with the <see cref="GhostObject"/> component being removed.</param>
private static void OnGhostObjectPreRemoval(GameObject gameObject)
{
var ghostBehaviours = gameObject.GetComponentsInChildren<GhostBehaviour>();
for (int i = ghostBehaviours.Length - 1; i >= 0; i--)
{
DestroyImmediate(ghostBehaviours[i], true);
}
var networkObject = gameObject.GetComponent<NetworkObject>();
networkObject.GhostObject = null;
networkObject.HasGhost = false;
networkObject.HadBridge = true;
}
#endif

private void Initialize()
{
if (m_Initialized)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#if UNIFIED_NETCODE
using Unity.Mathematics;
using Unity.Transforms;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;

namespace Unity.Netcode
Expand All @@ -15,18 +18,6 @@
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<GhostObject>();
if (ghostAdapter != null)
{
ghostAdapter.SupportedGhostModes = GhostModeMask.Interpolated;
}
}

private void OnValidate()
{
hideFlags = HideFlags.HideInInspector;
Expand All @@ -37,29 +28,23 @@
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<Rigidbody>();
var ghostRigidBody = GetComponent<GhostRigidbody>();
if (rigidBody != null)
var networkObject = GetComponent<NetworkObject>();
if (networkObject == null)

Check warning on line 32 in com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs#L31-L32

Added lines #L31 - L32 were not covered by tests
{
// 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 += () =>

Check warning on line 35 in com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs#L35

Added line #L35 was not covered by tests
{
gameObject.AddComponent<GhostRigidbody>();
}
if (this != null && GetComponent<NetworkObject>() == null)
{
DestroyImmediate(this, true);
}
};
}

Check warning on line 42 in com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs#L37-L42

Added lines #L37 - L42 were not covered by tests
else
{

Check warning on line 44 in com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs#L44

Added line #L44 was not covered by tests
// NGO spawns the ghost when the NetworkObject spawns. This assures the ghost spawns in that NetworkManager's world.
ghostAdapter.DelaySpawning = true;

Check warning on line 46 in com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs#L46

Added line #L46 was not covered by tests
}
#endif
#if COM_UNITY_MODULES_PHYSICS2D
// TODO: Fill out a similar script as above but for the 2D version
#endif
}
#endif

Expand All @@ -79,17 +64,12 @@
/// instance's entity's PostTransformMatrix.</param>
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);
}

/// <summary>
/// Keeps the ghost's owner in step with the <see cref="NetworkObject"/> owner, so an owner-predicted
/// ghost is predicted by the NGO owner.
Expand Down
74 changes: 62 additions & 12 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -369,14 +369,57 @@
[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;

Check warning on line 378 in com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs#L375-L378

Added lines #L375 - L378 were not covered by tests
}
NetworkObjectBridge = gameObject.AddComponent<NetworkObjectBridge>();
HadBridge = true;
// Transform synchronization is handled by unified netcode
SynchronizeTransform = false;
}

Check warning on line 384 in com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs#L384

Added line #L384 was not covered by tests

private void OnRemoveNetworkObjectBridge()
{
EditorApplication.update -= OnRemoveNetworkObjectBridge;
if (this == null)
{
return;

Check warning on line 391 in com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs#L387-L391

Added lines #L387 - L391 were not covered by tests
}
var networkObjectBridge = gameObject.GetComponent<NetworkObjectBridge>();
if (networkObjectBridge != null)
{
DestroyImmediate(networkObjectBridge, true);
}
var ghostRigidbodies = gameObject.GetComponentsInChildren<GhostRigidbody>(true);
for (int i = 0; i < ghostRigidbodies.Length; i++)
{
DestroyImmediate(ghostRigidbodies[i], true);
}

Check warning on line 402 in com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs#L393-L402

Added lines #L393 - L402 were not covered by tests

EditorApplication.update -= OnApplicationUpdate;
NetworkObjectBridge = null;
HadBridge = false;
HasGhost = false;

Check warning on line 406 in com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs#L404-L406

Added lines #L404 - L406 were not covered by tests

// NGO synchronizes the transform again once the prefab is no longer hybrid
SynchronizeTransform = true;
}

Check warning on line 410 in com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs#L409-L410

Added lines #L409 - L410 were not covered by tests

private void OnRemoveGhostObject()
{
EditorApplication.update -= OnRemoveGhostObject;
if (this == null)
{
return;

Check warning on line 417 in com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs#L413-L417

Added lines #L413 - L417 were not covered by tests
}
OnRemoveNetworkObjectBridge();
DestroyImmediate(GhostObject);
GhostObject = null;
HasGhost = false;

Check warning on line 422 in com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs#L419-L422

Added lines #L419 - L422 were not covered by tests
}

internal void UnifiedValidation()
Expand All @@ -390,22 +433,29 @@
//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;

Check warning on line 438 in com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs#L436-L438

Added lines #L436 - L438 were not covered by tests
return;
}

if (NetworkObjectBridge == null)
else if (NetworkObjectBridge == null)

Check warning on line 441 in com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs#L441

Added line #L441 was not covered by tests
{
EditorApplication.update -= OnApplicationUpdate;
EditorApplication.update += OnApplicationUpdate;
EditorApplication.update -= OnAddNetworkObjectBridge;
EditorApplication.update += OnAddNetworkObjectBridge;

Check warning on line 444 in com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs#L443-L444

Added lines #L443 - L444 were not covered by tests
}
}
else if (HadBridge && !HasGhost && !NetworkObjectBridge)
else if (HadBridge && !HasGhost)
{
HadBridge = false;
SynchronizeTransform = true;
if (NetworkObjectBridge != null)
Comment thread
NoelStephensUnity marked this conversation as resolved.
{
EditorApplication.update -= OnRemoveNetworkObjectBridge;
EditorApplication.update += OnRemoveNetworkObjectBridge;
}

Check warning on line 453 in com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs#L449-L453

Added lines #L449 - L453 were not covered by tests
else
{
HadBridge = false;
SynchronizeTransform = true;
}

Check warning on line 458 in com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs#L455-L458

Added lines #L455 - L458 were not covered by tests
}
}
#endif
Expand Down
Loading