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.7.0] - 2023-10-11 ### Added - exposed NetworkObject.GetNetworkBehaviourAtOrderIndex as a public API (#2724) - Added context menu tool that provides users with the ability to quickly update the GlobalObjectIdHash value for all in-scene placed prefab instances that were created prior to adding a NetworkObject component to it. (#2707) - Added methods NetworkManager.SetPeerMTU and NetworkManager.GetPeerMTU to be able to set MTU sizes per-peer (#2676) - Added `GenerateSerializationForGenericParameterAttribute`, which can be applied to user-created Network Variable types to ensure the codegen generates serialization for the generic types they wrap. (#2694) - Added `GenerateSerializationForTypeAttribute`, which can be applied to any class or method to ensure the codegen generates serialization for the specific provided type. (#2694) - Exposed `NetworkVariableSerialization<T>.Read`, `NetworkVariableSerialization<T>.Write`, `NetworkVariableSerialization<T>.AreEqual`, and `NetworkVariableSerialization<T>.Duplicate` to further support the creation of user-created network variables by allowing users to access the generated serialization methods and serialize generic types efficiently without boxing. (#2694) - Added `NetworkVariableBase.MarkNetworkBehaviourDirty` so that user-created network variable types can mark their containing `NetworkBehaviour` to be processed by the update loop. (#2694) ### Fixed - Fixed issue where the server side `NetworkSceneManager` instance was not adding the currently active scene to its list of scenes loaded. (#2723) - Generic NetworkBehaviour types no longer result in compile errors or runtime errors (#2720) - Rpcs within Generic NetworkBehaviour types can now serialize parameters of the class's generic types (but may not have generic types of their own) (#2720) - Errors are no longer thrown when entering play mode with domain reload disabled (#2720) - NetworkSpawn is now correctly called each time when entering play mode with scene reload disabled (#2720) - NetworkVariables of non-integer types will no longer break the inspector (#2714) - NetworkVariables with NonSerializedAttribute will not appear in the inspector (#2714) - Fixed issue where `UnityTransport` would attempt to establish WebSocket connections even if using UDP/DTLS Relay allocations when the build target was WebGL. This only applied to working in the editor since UDP/DTLS can't work in the browser. (#2695) - Fixed issue where a `NetworkBehaviour` component's `OnNetworkDespawn` was not being invoked on the host-server side for an in-scene placed `NetworkObject` when a scene was unloaded (during a scene transition) and the `NetworkBehaviour` component was positioned/ordered before the `NetworkObject` component. (#2685) - Fixed issue where `SpawnWithObservers` was not being honored when `NetworkConfig.EnableSceneManagement` was disabled. (#2682) - Fixed issue where `NetworkAnimator` was not internally tracking changes to layer weights which prevented proper layer weight synchronization back to the original layer weight value. (#2674) - Fixed "writing past the end of the buffer" error when calling ResetDirty() on managed network variables that are larger than 256 bytes when serialized. (#2670) - Fixed issue where generation of the `DefaultNetworkPrefabs` asset was not enabled by default. (#2662) - Fixed issue where the `GlobalObjectIdHash` value could be updated but the asset not marked as dirty. (#2662) - Fixed issue where the `GlobalObjectIdHash` value of a (network) prefab asset could be assigned an incorrect value when editing the prefab in a temporary scene. (#2662) - Fixed issue where the `GlobalObjectIdHash` value generated after creating a (network) prefab from an object constructed within the scene would not be the correct final value in a stand alone build. (#2662) ### Changed - Updated dependency on `com.unity.transport` to version 1.4.0. (#2716)
229 lines
9.5 KiB
C#
229 lines
9.5 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;
|
|
}
|
|
|
|
private const int k_MaxThresholdFailures = 4;
|
|
private int m_ExceededThresholdCount;
|
|
|
|
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)
|
|
{
|
|
// Temporary work around for this test.
|
|
// Really, this test needs to be completely re-written.
|
|
m_ExceededThresholdCount++;
|
|
// If we haven't corrected ourselves within the maximum number of updates then throw an error.
|
|
if (m_ExceededThresholdCount > k_MaxThresholdFailures)
|
|
{
|
|
Debug.LogError($"Interpolation failure. transform.position.y is {transform.position.y}. Should be between 0.0 and 100.0. Current threshold is [+/- {MinThreshold}].");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If corrected, then reset our count
|
|
m_ExceededThresholdCount = 0;
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
}
|