This repository has been archived on 2025-04-22. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs
Unity Technologies b3bd4727ab com.unity.netcode.gameobjects@1.6.0
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).

## [1.6.0] - 2023-08-09

### Added

- Added a protected virtual method `NetworkTransform.OnInitialize(ref NetworkTransformState replicatedState)` that just returns the replicated state reference.

### Fixed

- Fixed  issue where invoking `NetworkManager.Shutdown` within `NetworkManager.OnClientStopped` or `NetworkManager.OnServerStopped` would force `NetworkManager.ShutdownInProgress` to remain true after completing the shutdown process. (#2661)
- Fixed issue with client synchronization of position when using half precision and the delta position reaches the maximum value and is collapsed on the host prior to being forwarded to the non-owner clients. (#2636)
- Fixed issue with scale not synchronizing properly depending upon the spawn order of NetworkObjects. (#2636)
- Fixed issue position was not properly transitioning between ownership changes with an owner authoritative NetworkTransform. (#2636)
- Fixed issue where a late joining non-owner client could update an owner authoritative NetworkTransform if ownership changed without any updates to position prior to the non-owner client joining. (#2636)

### Changed
2023-08-09 00:00:00 +00:00

214 lines
8.8 KiB
C#

using System.Collections;
using NUnit.Framework;
using Unity.Netcode.Components;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{
public class TransformInterpolationObject : NetworkTransform
{
public static bool TestComplete = false;
// Set the minimum threshold which we will use as our margin of error
#if UNITY_EDITOR
public const float MinThreshold = 0.005f;
#else
// Add additional room for error on console tests
public const float MinThreshold = 0.009999f;
#endif
private const int k_TargetLocalSpaceToggles = 10;
public bool CheckPosition;
public bool IsMoving;
public bool IsFixed;
private float m_FrameRateFractional;
private bool m_CurrentLocalSpace;
private int m_LocalSpaceToggles;
private int m_LastFrameCount;
public bool ReachedTargetLocalSpaceTransitionCount()
{
TestComplete = m_LocalSpaceToggles >= k_TargetLocalSpaceToggles;
return TestComplete;
}
protected override void OnInitialize(ref NetworkTransformState replicatedState)
{
m_LocalSpaceToggles = 0;
m_FrameRateFractional = 1.0f / Application.targetFrameRate;
PositionThreshold = MinThreshold;
SetMaxInterpolationBound(1.0f);
base.OnInitialize(ref replicatedState);
}
private int m_StartFrameCount;
public void StartMoving()
{
m_StartFrameCount = Time.frameCount;
IsMoving = true;
}
public void StopMoving()
{
IsMoving = false;
}
protected override void Update()
{
base.Update();
if (!IsSpawned || TestComplete)
{
return;
}
// Check the position of the nested object on the client
if (CheckPosition)
{
if (transform.position.y < -MinThreshold || transform.position.y > Application.targetFrameRate + MinThreshold)
{
Debug.LogError($"Interpolation failure. transform.position.y is {transform.position.y}. Should be between 0.0 and 100.0. Current threshold is [+/- {MinThreshold}].");
}
}
// Move the nested object on the server
if (IsMoving)
{
Assert.True(CanCommitToTransform, $"Using non-authority instance to update transform!");
if (m_LastFrameCount == Time.frameCount)
{
Debug.Log($"Detected duplicate frame update count {Time.frameCount}. Ignoring this update.");
return;
}
m_LastFrameCount = Time.frameCount;
// Leaving this here for reference.
// If a system is running at a slower frame rate than expected, then the below code could toggle
// the local to world space value at a higher frequency which might not provide enough updates to
// handle interpolating between the transitions.
//var y = Time.realtimeSinceStartup % 10.0f;
//// change the space between local and global every second
//GetComponent<NetworkTransform>().InLocalSpace = ((int)y % 2 == 0);
// Reduce the total frame count down to the frame rate
var y = (Time.frameCount - m_StartFrameCount) % Application.targetFrameRate;
// change the space between local and global every time we hit the expected number of frames
// (or every second if running at the target frame rate)
InLocalSpace = y == 0 ? !InLocalSpace : InLocalSpace;
if (m_CurrentLocalSpace != InLocalSpace)
{
m_LocalSpaceToggles++;
m_CurrentLocalSpace = InLocalSpace;
}
transform.position = new Vector3(0.0f, (y * m_FrameRateFractional), 0.0f);
}
// On the server, make sure to keep the parent object at a fixed position
if (IsFixed)
{
Assert.True(CanCommitToTransform, $"Using non-authority instance to update transform!");
transform.position = new Vector3(1000.0f, 1000.0f, 1000.0f);
}
}
}
public class TransformInterpolationTests : NetcodeIntegrationTest
{
protected override int NumberOfClients => 1;
private GameObject m_PrefabToSpawn;
private NetworkObject m_SpawnedAsNetworkObject;
private NetworkObject m_SpawnedObjectOnClient;
private NetworkObject m_BaseAsNetworkObject;
private NetworkObject m_BaseOnClient;
protected override void OnServerAndClientsCreated()
{
m_PrefabToSpawn = CreateNetworkObjectPrefab("InterpTestObject");
var networkTransform = m_PrefabToSpawn.AddComponent<TransformInterpolationObject>();
}
private IEnumerator RefreshNetworkObjects()
{
var clientId = m_ClientNetworkManagers[0].LocalClientId;
yield return WaitForConditionOrTimeOut(() => s_GlobalNetworkObjects.ContainsKey(clientId) &&
s_GlobalNetworkObjects[clientId].ContainsKey(m_BaseAsNetworkObject.NetworkObjectId) &&
s_GlobalNetworkObjects[clientId].ContainsKey(m_SpawnedAsNetworkObject.NetworkObjectId));
Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for client side {nameof(NetworkObject)} ID of {m_SpawnedAsNetworkObject.NetworkObjectId}");
m_BaseOnClient = s_GlobalNetworkObjects[clientId][m_BaseAsNetworkObject.NetworkObjectId];
// make sure the objects are set with the right network manager
m_BaseOnClient.NetworkManagerOwner = m_ClientNetworkManagers[0];
m_SpawnedObjectOnClient = s_GlobalNetworkObjects[clientId][m_SpawnedAsNetworkObject.NetworkObjectId];
// make sure the objects are set with the right network manager
m_SpawnedObjectOnClient.NetworkManagerOwner = m_ClientNetworkManagers[0];
}
[UnityTest]
public IEnumerator TransformInterpolationTest()
{
TransformInterpolationObject.TestComplete = false;
// create an object
var spawnedObject = Object.Instantiate(m_PrefabToSpawn);
var baseObject = Object.Instantiate(m_PrefabToSpawn);
baseObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
baseObject.GetComponent<NetworkObject>().Spawn();
m_SpawnedAsNetworkObject = spawnedObject.GetComponent<NetworkObject>();
m_SpawnedAsNetworkObject.NetworkManagerOwner = m_ServerNetworkManager;
m_BaseAsNetworkObject = baseObject.GetComponent<NetworkObject>();
m_BaseAsNetworkObject.NetworkManagerOwner = m_ServerNetworkManager;
m_SpawnedAsNetworkObject.TrySetParent(baseObject);
m_SpawnedAsNetworkObject.Spawn();
yield return RefreshNetworkObjects();
m_SpawnedAsNetworkObject.TrySetParent(baseObject);
var spawnedObjectNetworkTransform = spawnedObject.GetComponent<TransformInterpolationObject>();
baseObject.GetComponent<TransformInterpolationObject>().IsFixed = true;
spawnedObject.GetComponent<TransformInterpolationObject>().StartMoving();
const float maxPlacementError = 0.01f;
// Wait for the base object to place itself on both instances
while (m_BaseOnClient.transform.position.y < 1000 - maxPlacementError ||
m_BaseOnClient.transform.position.y > 1000 + maxPlacementError ||
baseObject.transform.position.y < 1000 - maxPlacementError ||
baseObject.transform.position.y > 1000 + maxPlacementError)
{
yield return new WaitForSeconds(0.01f);
}
m_SpawnedObjectOnClient.GetComponent<TransformInterpolationObject>().CheckPosition = true;
// Test that interpolation works correctly for ~10 seconds or 10 local to world space transitions while moving
// Increasing this duration gives you the opportunity to go check in the Editor how the objects are setup
// and how they move
var timeOutHelper = new TimeoutFrameCountHelper(10);
yield return WaitForConditionOrTimeOut(spawnedObjectNetworkTransform.ReachedTargetLocalSpaceTransitionCount, timeOutHelper);
Debug.Log($"[TransformInterpolationTest] Wait condition reached or timed out. Frame Count ({timeOutHelper.GetFrameCount()}) | Time Elapsed ({timeOutHelper.GetTimeElapsed()})");
AssertOnTimeout($"Failed to reach desired local to world space transitions in the given time!", timeOutHelper);
}
}
}