Skip to content

Commit 81dd1e4

Browse files
authored
fix: network transform pooled objects reset (#1533)
1 parent 3eb810f commit 81dd1e4

File tree

3 files changed

+176
-5
lines changed

3 files changed

+176
-5
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
2727
- Fixed NullReferenceException on ImportReferences call in NetworkBehaviourILPP (#1434)
2828
- Fixed NetworkObjects not being despawned before they are destroyed during shutdown for client, host, and server instances. (#1390)
2929
- Fixed KeyNotFound exception when removing ownership of a newly spawned NetworkObject that is already owned by the server. (#1500)
30+
- Fixed issue where pooled NetworkObjects using NetworkTransform would interpolate from their last de-spawned position to the newly spawned position (#1505)
3031

3132
### Changed
3233

com.unity.netcode.gameobjects/Components/NetworkTransform.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,15 @@ internal struct NetworkTransformState : INetworkSerializable
3838
// 11-15: <unused>
3939
private ushort m_Bitset;
4040

41+
public void Reset()
42+
{
43+
m_Bitset = 0;
44+
PositionX = PositionY = PositionZ = 0.0f;
45+
RotAngleX = RotAngleY = RotAngleZ = 0.0f;
46+
ScaleX = ScaleY = ScaleZ = 0.0f;
47+
SentTime = 0.0f;
48+
}
49+
4150
public bool InLocalSpace
4251
{
4352
get => (m_Bitset & (1 << k_InLocalSpaceBit)) != 0;
@@ -704,15 +713,18 @@ public override void OnNetworkSpawn()
704713
{
705714
TryCommitTransformToServer(m_Transform, m_CachedNetworkManager.LocalTime.Time);
706715
}
707-
m_LocalAuthoritativeNetworkState = m_ReplicatedNetworkState.Value;
708716

709-
// crucial we do this to reset the interpolators so that recycled objects when using a pool will
710-
// not have leftover interpolator state from the previous object
717+
// We do this to reset the interpolators so that pooled NetworkObjects will initialize prior
718+
// to assigning the current NetworkState -- helps prevent pooled objects from interpolating from the last state
711719
Initialize();
720+
721+
m_LocalAuthoritativeNetworkState = m_ReplicatedNetworkState.Value;
712722
}
713723

714724
public override void OnNetworkDespawn()
715725
{
726+
// Reset the network state once despawned -- helps prevent pooled objects from interpolating from the last state
727+
m_LocalAuthoritativeNetworkState.Reset();
716728
m_ReplicatedNetworkState.OnValueChanged -= OnNetworkStateChanged;
717729
}
718730

com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs

Lines changed: 160 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
// using Unity.Netcode.Samples;
99
using UnityEngine;
1010
using UnityEngine.TestTools;
11+
using Object = UnityEngine.Object;
1112

1213
namespace Unity.Netcode.RuntimeTests
1314
{
@@ -188,14 +189,171 @@ public IEnumerator TestCantChangeTransformFromOtherSideAuthority([Values] bool t
188189
* ownership change
189190
* test teleport with interpolation
190191
* test teleport without interpolation
191-
* test dynamic spawning
192+
* test dynamic spawning -- done with NetworkTransformRespawnTests
192193
*/
193194

194195
[UnityTearDown]
195196
public override IEnumerator Teardown()
196197
{
197198
yield return base.Teardown();
198-
UnityEngine.Object.DestroyImmediate(m_PlayerPrefab);
199+
Object.DestroyImmediate(m_PlayerPrefab);
200+
}
201+
}
202+
203+
/// <summary>
204+
/// This test simulates a pooled NetworkObject being re-used over time with a NetworkTransform
205+
/// This test validates that pooled NetworkObjects' NetworkTransforms are completely reset in
206+
/// order to properly start interpolating from the new spawn position and not the previous position
207+
/// when the registered Network Prefab was despawned. This specifically tests the client side.
208+
/// </summary>
209+
public class NetworkTransformRespawnTests : BaseMultiInstanceTest, INetworkPrefabInstanceHandler
210+
{
211+
/// <summary>
212+
/// Our test object mover NetworkBehaviour
213+
/// </summary>
214+
public class DynamicObjectMover : NetworkBehaviour
215+
{
216+
public Vector3 SpawnedPosition;
217+
private Rigidbody m_Rigidbody;
218+
private Vector3 m_MoveTowardsPosition = new Vector3(20, 0, 20);
219+
220+
private void OnEnable()
221+
{
222+
SpawnedPosition = transform.position;
223+
}
224+
225+
private void Update()
226+
{
227+
if (!IsSpawned || !IsServer)
228+
{
229+
return;
230+
}
231+
232+
if (m_Rigidbody == null)
233+
{
234+
m_Rigidbody = GetComponent<Rigidbody>();
235+
}
236+
if (m_Rigidbody != null)
237+
{
238+
m_Rigidbody.MovePosition(transform.position + (m_MoveTowardsPosition * Time.fixedDeltaTime));
239+
}
240+
}
241+
}
242+
243+
protected override int NbClients => 1;
244+
private GameObject m_ObjectToSpawn;
245+
private GameObject m_ClientSideObject;
246+
private NetworkObject m_DefaultNetworkObject;
247+
private Vector3 m_LastClientSidePosition;
248+
private bool m_ClientSideSpawned;
249+
250+
public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation)
251+
{
252+
m_ClientSideSpawned = true;
253+
m_ClientSideObject.SetActive(true);
254+
return m_ClientSideObject.GetComponent<NetworkObject>();
255+
}
256+
257+
public void Destroy(NetworkObject networkObject)
258+
{
259+
m_ClientSideSpawned = false;
260+
networkObject.gameObject.SetActive(false);
261+
m_LastClientSidePosition = networkObject.transform.position;
262+
}
263+
264+
public override IEnumerator Setup()
265+
{
266+
m_BypassStartAndWaitForClients = true;
267+
yield return StartSomeClientsAndServerWithPlayers(true, NbClients);
268+
269+
m_ObjectToSpawn = new GameObject("NetworkTransformDynamicObject");
270+
m_DefaultNetworkObject = m_ObjectToSpawn.AddComponent<NetworkObject>();
271+
m_ObjectToSpawn.AddComponent<NetworkTransform>();
272+
var rigidBody = m_ObjectToSpawn.AddComponent<Rigidbody>();
273+
rigidBody.useGravity = false;
274+
m_ObjectToSpawn.AddComponent<NetworkRigidbody>();
275+
m_ObjectToSpawn.AddComponent<DynamicObjectMover>();
276+
MultiInstanceHelpers.MakeNetworkObjectTestPrefab(m_DefaultNetworkObject);
277+
278+
var networkPrefab = new NetworkPrefab();
279+
networkPrefab.Prefab = m_ObjectToSpawn;
280+
m_ServerNetworkManager.NetworkConfig.NetworkPrefabs.Add(networkPrefab);
281+
m_ServerNetworkManager.NetworkConfig.EnableSceneManagement = false;
282+
283+
foreach (var client in m_ClientNetworkManagers)
284+
{
285+
client.NetworkConfig.NetworkPrefabs.Add(networkPrefab);
286+
client.NetworkConfig.EnableSceneManagement = false;
287+
// Add a client side prefab handler for this NetworkObject
288+
client.PrefabHandler.AddHandler(m_ObjectToSpawn, this);
289+
}
290+
m_DefaultNetworkObject.NetworkManagerOwner = m_ServerNetworkManager;
291+
m_ClientSideObject = Object.Instantiate(m_ObjectToSpawn);
292+
m_ClientSideObject.SetActive(false);
293+
}
294+
295+
[UnityTest]
296+
public IEnumerator RespawnedPositionTest()
297+
{
298+
if (!MultiInstanceHelpers.Start(true, m_ServerNetworkManager, m_ClientNetworkManagers))
299+
{
300+
Debug.LogError("Failed to start instances");
301+
Assert.Fail("Failed to start instances");
302+
}
303+
304+
// Wait for connection on client side
305+
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientsConnected(m_ClientNetworkManagers));
306+
307+
// Wait for connection on server side
308+
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientsConnectedToServer(m_ServerNetworkManager, NbClients + 1));
309+
310+
Assert.True(m_ObjectToSpawn != null);
311+
Assert.True(m_DefaultNetworkObject != null);
312+
m_DefaultNetworkObject.Spawn();
313+
yield return new WaitUntil(() => m_ClientSideSpawned);
314+
315+
// Let the object move a bit
316+
yield return new WaitForSeconds(0.5f);
317+
318+
// Make sure it moved on the client side
319+
Assert.IsTrue(m_ClientSideObject.transform.position != Vector3.zero);
320+
321+
m_ServerNetworkManager.SpawnManager.DespawnObject(m_DefaultNetworkObject);
322+
yield return new WaitUntil(() => !m_ClientSideSpawned);
323+
324+
// Re-spawn the same NetworkObject
325+
m_DefaultNetworkObject.Spawn();
326+
yield return new WaitUntil(() => m_ClientSideSpawned);
327+
328+
// !!! This is the primary element for this particular test !!!
329+
// If NetworkTransform.OnNetworkDespawn did not have m_LocalAuthoritativeNetworkState.Reset();
330+
// then this will always fail. To verify this will fail you can comment out that line of code
331+
// in NetworkTransform.OnNetworkDespawn and run this test again.
332+
Assert.IsTrue(m_ClientSideObject.transform.position == Vector3.zero);
333+
334+
// Next we make sure the last spawn instance position was anything but zero
335+
// (i.e. it moved prior to despawning and respawning the object)
336+
Assert.IsTrue(m_LastClientSidePosition != Vector3.zero);
337+
338+
// Done
339+
m_DefaultNetworkObject.Despawn();
340+
}
341+
342+
public override IEnumerator Teardown()
343+
{
344+
if (m_ClientSideObject != null)
345+
{
346+
Object.Destroy(m_ClientSideObject);
347+
m_ClientSideObject = null;
348+
}
349+
350+
if (m_ObjectToSpawn != null)
351+
{
352+
Object.Destroy(m_ObjectToSpawn);
353+
m_ObjectToSpawn = null;
354+
}
355+
356+
return base.Teardown();
199357
}
200358
}
201359
}

0 commit comments

Comments
 (0)