com.unity.netcode.gameobjects@2.0.0-exp.2

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).

## [2.0.0-exp.2] - 2024-04-02

### Added
- Added updates to all internal messages to account for a distributed authority network session connection.  (#2863)
- Added `NetworkRigidbodyBase` that provides users with a more customizable network rigidbody, handles both `Rigidbody` and `Rigidbody2D`, and provides an option to make `NetworkTransform` use the rigid body for motion.  (#2863)
  - For a customized `NetworkRigidbodyBase` class:
    - `NetworkRigidbodyBase.AutoUpdateKinematicState` provides control on whether the kinematic setting will be automatically set or not when ownership changes.
    - `NetworkRigidbodyBase.AutoSetKinematicOnDespawn` provides control on whether isKinematic will automatically be set to true when the associated `NetworkObject` is despawned.
    - `NetworkRigidbodyBase.Initialize` is a protected method that, when invoked, will initialize the instance. This includes options to:
      - Set whether using a `RigidbodyTypes.Rigidbody` or `RigidbodyTypes.Rigidbody2D`.
      - Includes additional optional parameters to set the `NetworkTransform`, `Rigidbody`, and `Rigidbody2d` to use.
  - Provides additional public methods:
    - `NetworkRigidbodyBase.GetPosition` to return the position of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting).
    - `NetworkRigidbodyBase.GetRotation` to return the rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting).
    - `NetworkRigidbodyBase.MovePosition` to move to the position of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting).
    - `NetworkRigidbodyBase.MoveRotation` to move to the rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting).
    - `NetworkRigidbodyBase.Move` to move to the position and rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting).
    - `NetworkRigidbodyBase.Move` to move to the position and rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting).
    - `NetworkRigidbodyBase.SetPosition` to set the position of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting).
    - `NetworkRigidbodyBase.SetRotation` to set the rotation of the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting).
    - `NetworkRigidbodyBase.ApplyCurrentTransform` to set the position and rotation of the `Rigidbody` or `Rigidbody2d` based on the associated `GameObject` transform (depending upon its initialized setting).
    - `NetworkRigidbodyBase.WakeIfSleeping` to wake up the rigid body if sleeping.
    - `NetworkRigidbodyBase.SleepRigidbody` to put the rigid body to sleep.
    - `NetworkRigidbodyBase.IsKinematic` to determine if the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting) is currently kinematic.
    - `NetworkRigidbodyBase.SetIsKinematic` to set the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting) current kinematic state.
    - `NetworkRigidbodyBase.ResetInterpolation` to reset the `Rigidbody` or `Rigidbody2d` (depending upon its initialized setting) back to its original interpolation value when initialized.
  - Now includes a `MonoBehaviour.FixedUpdate` implementation that will update the assigned `NetworkTransform` when `NetworkRigidbodyBase.UseRigidBodyForMotion` is true. (#2863)
- Added `RigidbodyContactEventManager` that provides a more optimized way to process collision enter and collision stay events as opposed to the `Monobehaviour` approach. (#2863)
  - Can be used in client-server and distributed authority modes, but is particularly useful in distributed authority.
- Added rigid body motion updates to `NetworkTransform` which allows users to set interolation on rigid bodies. (#2863)
  - Extrapolation is only allowed on authoritative instances, but custom class derived from `NetworkRigidbodyBase` or `NetworkRigidbody` or `NetworkRigidbody2D` automatically switches non-authoritative instances to interpolation if set to extrapolation.
- Added distributed authority mode support to `NetworkAnimator`. (#2863)
- Added session mode selection to `NetworkManager` inspector view. (#2863)
- Added distributed authority permissions feature. (#2863)
- Added distributed authority mode specific `NetworkObject` permissions flags (Distributable, Transferable, and RequestRequired). (#2863)
- Added distributed authority mode specific `NetworkObject.SetOwnershipStatus` method that applies one or more `NetworkObject` instance's ownership flags. If updated when spawned, the ownership permission changes are synchronized with the other connected clients. (#2863)
- Added distributed authority mode specific `NetworkObject.RemoveOwnershipStatus` method that removes one or more `NetworkObject` instance's ownership flags. If updated when spawned, the ownership permission changes are synchronized with the other connected clients. (#2863)
- Added distributed authority mode specific `NetworkObject.HasOwnershipStatus` method that will return (true or false) whether one or more ownership flags is set. (#2863)
- Added distributed authority mode specific `NetworkObject.SetOwnershipLock` method that locks ownership of a `NetworkObject` to prevent ownership from changing until the current owner releases the lock. (#2863)
- Added distributed authority mode specific `NetworkObject.RequestOwnership` method that sends an ownership request to the current owner of a spawned `NetworkObject` instance. (#2863)
- Added distributed authority mode specific `NetworkObject.OnOwnershipRequested` callback handler that is invoked on the owner/authoritative side when a non-owner requests ownership. Depending upon the boolean returned value depends upon whether the request is approved or denied. (#2863)
- Added distributed authority mode specific `NetworkObject.OnOwnershipRequestResponse` callback handler that is invoked when a non-owner's request has been processed. This callback includes a `NetworkObjet.OwnershipRequestResponseStatus` response parameter that describes whether the request was approved or the reason why it was not approved. (#2863)
- Added distributed authority mode specific `NetworkObject.DeferDespawn` method that defers the despawning of `NetworkObject` instances on non-authoritative clients based on the tick offset parameter. (#2863)
- Added distributed authority mode specific `NetworkObject.OnDeferredDespawnComplete` callback handler that can be used to further control when deferring the despawning of a `NetworkObject` on non-authoritative instances. (#2863)
- Added `NetworkClient.SessionModeType` as one way to determine the current session mode of the network session a client is connected to. (#2863)
- Added distributed authority mode specific `NetworkClient.IsSessionOwner` property to determine if the current local client is the current session owner of a distributed authority session. (#2863)
- Added distributed authority mode specific client side spawning capabilities. When running in distributed authority mode, clients can instantiate and spawn `NetworkObject` instances (the local client is authomatically the owner of the spawned object). (#2863)
  - This is useful to better visually synchronize owner authoritative motion models and newly spawned `NetworkObject` instances (i.e. projectiles for example).
- Added distributed authority mode specific client side player spawning capabilities. Clients will automatically spawn their associated player object locally. (#2863)
- Added distributed authority mode specific `NetworkConfig.AutoSpawnPlayerPrefabClientSide` property (default is true) to provide control over the automatic spawning of player prefabs on the local client side. (#2863)
- Added distributed authority mode specific `NetworkManager.OnFetchLocalPlayerPrefabToSpawn` callback that, when assigned, will allow the local client to provide the player prefab to be spawned for the local client. (#2863)
  - This is only invoked if the `NetworkConfig.AutoSpawnPlayerPrefabClientSide` property is set to true.
- Added distributed authority mode specific `NetworkBehaviour.HasAuthority` property that determines if the local client has authority over the associated `NetworkObject` instance (typical use case is within a `NetworkBehaviour` script much like that of `IsServer` or `IsClient`). (#2863)
- Added distributed authority mode specific `NetworkBehaviour.IsSessionOwner` property that determines if the local client is the session owner (typical use case would be to determine if the local client can has scene management authority within a `NetworkBehaviour` script). (#2863)
- Added support for distributed authority mode scene management where the currently assigned session owner can start scene events (i.e. scene loading and scene unloading). (#2863)

### Fixed

- Fixed issue where the host was not invoking `OnClientDisconnectCallback` for its own local client when internally shutting down. (#2822)
- Fixed issue where NetworkTransform could potentially attempt to "unregister" a named message prior to it being registered. (#2807)
- Fixed issue where in-scene placed `NetworkObject`s with complex nested children `NetworkObject`s (more than one child in depth) would not synchronize properly if WorldPositionStays was set to true. (#2796)

### Changed
- Changed client side awareness of other clients is now the same as a server or host. (#2863)
- Changed `NetworkManager.ConnectedClients` can now be accessed by both server and clients. (#2863)
- Changed `NetworkManager.ConnectedClientsList` can now be accessed by both server and clients. (#2863)
- Changed `NetworkTransform` defaults to owner authoritative when connected to a distributed authority session. (#2863)
- Changed `NetworkVariable` defaults to owner write and everyone read permissions when connected to a distributed authority session (even if declared with server read or write permissions).  (#2863)
- Changed `NetworkObject` no longer implements the `MonoBehaviour.Update` method in order to determine whether a `NetworkObject` instance has been migrated to a different scene. Instead, only `NetworkObjects` with the `SceneMigrationSynchronization` property set will be updated internally during the `NetworkUpdateStage.PostLateUpdate` by `NetworkManager`. (#2863)
- Changed `NetworkManager` inspector view layout where properties are now organized by category. (#2863)
- Changed `NetworkTransform` to now use `NetworkTransformMessage` as opposed to named messages for NetworkTransformState updates. (#2810)
- Changed `CustomMessageManager` so it no longer attempts to register or "unregister" a null or empty string and will log an error if this condition occurs. (#2807)
This commit is contained in:
Unity Technologies
2024-04-02 00:00:00 +00:00
parent f8ebf679ec
commit 143a6cbd34
140 changed files with 18009 additions and 2672 deletions

View File

@@ -12,12 +12,12 @@ namespace Unity.Netcode.RuntimeTests
public class NetworkTransformBase : IntegrationTestWithApproximation
{
// The number of iterations to change position, rotation, and scale for NetworkTransformMultipleChangesOverTime
// The number of iterations to change position, rotation, and scale for NetworkTransformMultipleChangesOverTime
protected const int k_PositionRotationScaleIterations = 3;
protected const int k_PositionRotationScaleIterations3Axis = 8;
protected float m_CurrentHalfPrecision = 0.0f;
protected const float k_HalfPrecisionPosScale = 0.115f;
protected const float k_HalfPrecisionPosScale = 0.1256f;
protected const float k_HalfPrecisionRot = 0.725f;
@@ -126,7 +126,7 @@ namespace Unity.Netcode.RuntimeTests
{
return m_CurrentHalfPrecision;
}
return 0.045f;
return 0.055f;
}
/// <summary>
@@ -473,12 +473,12 @@ namespace Unity.Netcode.RuntimeTests
}
if (!Approximately(childLocalPosition, authorityObjectLocalPosition))
{
m_InfoMessage.AppendLine($"[{childParentName}][{childInstance.name}] Child's Local Position ({childLocalPosition}) | Authority Local Position ({authorityObjectLocalPosition})");
m_InfoMessage.AppendLine($"[{childParentName}][{childInstance.name}] Child's Local Position ({GetVector3Values(childLocalPosition)}) | Authority Local Position ({GetVector3Values(authorityObjectLocalPosition)})");
success = false;
}
if (!Approximately(childLocalScale, authorityObjectLocalScale))
{
m_InfoMessage.AppendLine($"[{childParentName}][{childInstance.name}] Child's Local Scale ({childLocalScale}) | Authority Local Scale ({authorityObjectLocalScale})");
m_InfoMessage.AppendLine($"[{childParentName}][{childInstance.name}] Child's Local Scale ({GetVector3Values(childLocalScale)}) | Authority Local Scale ({GetVector3Values(authorityObjectLocalScale)})");
success = false;
}
@@ -489,7 +489,7 @@ namespace Unity.Netcode.RuntimeTests
}
if (!ApproximatelyEuler(childLocalRotation, authorityObjectLocalRotation))
{
m_InfoMessage.AppendLine($"[{childParentName}][{childInstance.name}] Child's Local Rotation ({childLocalRotation}) | Authority Local Rotation ({authorityObjectLocalRotation})");
m_InfoMessage.AppendLine($"[{childParentName}][{childInstance.name}] Child's Local Rotation ({GetVector3Values(childLocalRotation)}) | Authority Local Rotation ({GetVector3Values(authorityObjectLocalRotation)})");
success = false;
}
}

View File

@@ -9,13 +9,28 @@ using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{
[TestFixture(HostOrServer.DAHost, MotionModels.UseTransform)]
[TestFixture(HostOrServer.DAHost, MotionModels.UseRigidbody)]
[TestFixture(HostOrServer.Host, MotionModels.UseTransform)]
public class NetworkTransformOwnershipTests : IntegrationTestWithApproximation
{
public enum MotionModels
{
UseRigidbody,
UseTransform
}
protected override int NumberOfClients => 1;
private GameObject m_ClientNetworkTransformPrefab;
private GameObject m_NetworkTransformPrefab;
private MotionModels m_MotionModel;
public NetworkTransformOwnershipTests(HostOrServer hostOrServer, MotionModels motionModel) : base(hostOrServer)
{
m_MotionModel = motionModel;
}
protected override void OnServerAndClientsCreated()
{
VerifyObjectIsSpawnedOnClient.ResetObjectTable();
@@ -25,20 +40,26 @@ namespace Unity.Netcode.RuntimeTests
clientNetworkTransform.UseHalfFloatPrecision = false;
var rigidBody = m_ClientNetworkTransformPrefab.AddComponent<Rigidbody>();
rigidBody.useGravity = false;
rigidBody.interpolation = RigidbodyInterpolation.None;
rigidBody.maxLinearVelocity = 0;
// NOTE: We don't use a sphere collider for this integration test because by the time we can
// assure they don't collide and skew the results the NetworkObjects are already synchronized
// with skewed results
m_ClientNetworkTransformPrefab.AddComponent<NetworkRigidbody>();
var networkRigidbody = m_ClientNetworkTransformPrefab.AddComponent<NetworkRigidbody>();
networkRigidbody.UseRigidBodyForMotion = m_MotionModel == MotionModels.UseRigidbody;
m_ClientNetworkTransformPrefab.AddComponent<VerifyObjectIsSpawnedOnClient>();
m_NetworkTransformPrefab = CreateNetworkObjectPrefab("ServerAuthorityTest");
var networkTransform = m_NetworkTransformPrefab.AddComponent<NetworkTransform>();
rigidBody = m_NetworkTransformPrefab.AddComponent<Rigidbody>();
rigidBody.useGravity = false;
rigidBody.interpolation = RigidbodyInterpolation.None;
rigidBody.maxLinearVelocity = 0;
// NOTE: We don't use a sphere collider for this integration test because by the time we can
// assure they don't collide and skew the results the NetworkObjects are already synchronized
// with skewed results
m_NetworkTransformPrefab.AddComponent<NetworkRigidbody>();
networkRigidbody = m_NetworkTransformPrefab.AddComponent<NetworkRigidbody>();
networkRigidbody.UseRigidBodyForMotion = m_MotionModel == MotionModels.UseRigidbody;
m_NetworkTransformPrefab.AddComponent<VerifyObjectIsSpawnedOnClient>();
networkTransform.Interpolate = false;
networkTransform.UseHalfFloatPrecision = false;
@@ -99,10 +120,14 @@ namespace Unity.Netcode.RuntimeTests
// Wait until the client gains ownership
yield return WaitForConditionOrTimeOut(ClientIsOwner);
AssertOnTimeout($"Timed out waiting for the {nameof(ClientIsOwner)} condition to be met!");
// Spawn a new client
yield return CreateAndStartNewClient();
yield return WaitForConditionOrTimeOut(() => VerifyObjectIsSpawnedOnClient.NetworkManagerRelativeSpawnedObjects.ContainsKey(m_ClientNetworkManagers[1].LocalClientId));
AssertOnTimeout($"Timed out waiting for late joing client VerifyObjectIsSpawnedOnClient entry to be created!");
// Get the instance of the object relative to the newly joined client
var newClientObjectInstance = VerifyObjectIsSpawnedOnClient.GetClientInstance(m_ClientNetworkManagers[1].LocalClientId);
@@ -117,7 +142,16 @@ namespace Unity.Netcode.RuntimeTests
// Wait one frame so the NetworkTransform can apply the owner's last state received on the late joining client side
// (i.e. prevent the non-owner from changing the transform)
yield return null;
if (m_MotionModel == MotionModels.UseRigidbody)
{
// Allow fixed update to run twice for values to propogate to Unity transform
yield return new WaitForFixedUpdate();
yield return new WaitForFixedUpdate();
}
else
{
yield return null;
}
// Get the owner instance
var ownerInstance = VerifyObjectIsSpawnedOnClient.GetClientInstance(m_ClientNetworkManagers[0].LocalClientId);
@@ -139,6 +173,23 @@ namespace Unity.Netcode.RuntimeTests
ClientStartsAsOwner,
}
private bool ClientAndServerSpawnedInstance()
{
return VerifyObjectIsSpawnedOnClient.NetworkManagerRelativeSpawnedObjects.ContainsKey(m_ServerNetworkManager.LocalClientId) && VerifyObjectIsSpawnedOnClient.NetworkManagerRelativeSpawnedObjects.ContainsKey(m_ClientNetworkManagers[0].LocalClientId);
}
private bool m_UseAdjustedVariance;
private const float k_AdjustedVariance = 0.025f;
protected override float GetDeltaVarianceThreshold()
{
if (m_UseAdjustedVariance)
{
return k_AdjustedVariance;
}
return base.GetDeltaVarianceThreshold();
}
/// <summary>
/// This verifies that when authority is owner authoritative the owner's
/// Rigidbody is kinematic and the non-owner's is not.
@@ -155,28 +206,90 @@ namespace Unity.Netcode.RuntimeTests
// Spawn the m_ClientNetworkTransformPrefab and wait for the client-side to spawn the object
var serverSideInstance = SpawnObject(m_ClientNetworkTransformPrefab, networkManagerOwner);
yield return WaitForConditionOrTimeOut(() => VerifyObjectIsSpawnedOnClient.GetClientsThatSpawnedThisPrefab().Contains(m_ClientNetworkManagers[0].LocalClientId));
yield return WaitForConditionOrTimeOut(ClientAndServerSpawnedInstance);
AssertOnTimeout($"Timed out waiting for all object instances to be spawned!");
// Get owner relative instances
var ownerInstance = VerifyObjectIsSpawnedOnClient.GetClientInstance(networkManagerOwner.LocalClientId);
var nonOwnerInstance = VerifyObjectIsSpawnedOnClient.GetClientInstance(networkManagerNonOwner.LocalClientId);
Assert.NotNull(ownerInstance);
Assert.NotNull(nonOwnerInstance);
Assert.True(networkManagerOwner.LocalClientId != networkManagerNonOwner.LocalClientId);
Assert.True(nonOwnerInstance.OwnerClientId != networkManagerNonOwner.LocalClientId);
Assert.True(nonOwnerInstance.NetworkManager.LocalClientId == networkManagerNonOwner.LocalClientId);
Vector3 GetNonOwnerPosition()
{
if (m_MotionModel == MotionModels.UseRigidbody)
{
return nonOwnerInstance.GetComponent<Rigidbody>().position;
}
else
{
return nonOwnerInstance.transform.position;
}
}
Quaternion GetNonOwnerRotation()
{
if (m_MotionModel == MotionModels.UseRigidbody)
{
return nonOwnerInstance.GetComponent<Rigidbody>().rotation;
}
else
{
return nonOwnerInstance.transform.rotation;
}
}
void LogNonOwnerRigidBody(int stage)
{
if (m_MotionModel == MotionModels.UseRigidbody && m_EnableVerboseDebug)
{
var rigidbody = nonOwnerInstance.GetComponent<Rigidbody>();
Debug.Log($"[{stage}][Rigidbody-NonOwner][Owner:{nonOwnerInstance.OwnerClientId} [Client-{nonOwnerInstance.NetworkManager.LocalClientId}][Gravity: {rigidbody.useGravity}][Kinematic: {rigidbody.isKinematic}][RB-Pos: {rigidbody.position}][RB-Rotation: {rigidbody.rotation}]");
}
}
void LogOwnerRigidBody(int stage)
{
if (m_MotionModel == MotionModels.UseRigidbody && m_EnableVerboseDebug)
{
var rigidbody = ownerInstance.GetComponent<Rigidbody>();
Debug.Log($"[{stage}][Rigidbody-Owner][Owner:{ownerInstance.OwnerClientId} [Client-{ownerInstance.NetworkManager.LocalClientId}][Gravity: {rigidbody.useGravity}][Kinematic: {rigidbody.isKinematic}][RB-Pos: {rigidbody.position}][RB-Rotation: {rigidbody.rotation}]");
}
}
// Make sure the owner is not kinematic and the non-owner(s) are kinematic
Assert.True(nonOwnerInstance.GetComponent<Rigidbody>().isKinematic, $"{networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} is not kinematic when it should be!");
Assert.False(ownerInstance.GetComponent<Rigidbody>().isKinematic, $"{networkManagerOwner.name}'s object instance {ownerInstance.name} is kinematic when it should not be!");
if (m_MotionModel == MotionModels.UseRigidbody)
{
nonOwnerInstance.GetComponent<NetworkTransform>().LogStateUpdate = m_EnableVerboseDebug;
}
// Owner changes transform values
var valueSetByOwner = Vector3.one * 2;
ownerInstance.transform.position = valueSetByOwner;
ownerInstance.transform.localScale = valueSetByOwner;
var rotation = new Quaternion
{
eulerAngles = valueSetByOwner
};
ownerInstance.transform.rotation = rotation;
if (m_MotionModel == MotionModels.UseRigidbody)
{
var ownerRigidbody = ownerInstance.GetComponent<Rigidbody>();
ownerRigidbody.Move(valueSetByOwner, rotation);
ownerRigidbody.linearVelocity = Vector3.zero;
yield return s_DefaultWaitForTick;
ownerInstance.transform.localScale = valueSetByOwner;
}
else
{
ownerInstance.transform.position = valueSetByOwner;
ownerInstance.transform.rotation = rotation;
ownerInstance.transform.localScale = valueSetByOwner;
}
var transformToTest = nonOwnerInstance.transform;
LogNonOwnerRigidBody(1);
yield return WaitForConditionOrTimeOut(() => Approximately(transformToTest.position, valueSetByOwner) && Approximately(transformToTest.localScale, valueSetByOwner) && Approximately(transformToTest.rotation, rotation));
Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} to change its transform!\n" +
$"Expected Position: {valueSetByOwner} | Current Position: {transformToTest.position}\n" +
@@ -186,14 +299,29 @@ namespace Unity.Netcode.RuntimeTests
// Verify non-owners cannot change transform values
nonOwnerInstance.transform.position = Vector3.zero;
yield return s_DefaultWaitForTick;
Assert.True(Approximately(nonOwnerInstance.transform.position, valueSetByOwner), $"{networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} was allowed to change its position! Expected: {valueSetByOwner} Is Currently:{nonOwnerInstance.transform.position}");
if (m_MotionModel == MotionModels.UseRigidbody)
{
yield return new WaitForFixedUpdate();
}
LogNonOwnerRigidBody(2);
Assert.True(Approximately(GetNonOwnerPosition(), valueSetByOwner), $"{networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} was allowed to change its position! Expected: {valueSetByOwner} Is Currently:{GetNonOwnerPosition()}");
// Change ownership and wait for the non-owner to reflect the change
VerifyObjectIsSpawnedOnClient.ResetObjectTable();
m_ServerNetworkManager.SpawnManager.ChangeOwnership(serverSideInstance.GetComponent<NetworkObject>(), networkManagerNonOwner.LocalClientId);
if (m_DistributedAuthority)
{
ownerInstance.NetworkObject.ChangeOwnership(networkManagerNonOwner.LocalClientId);
}
else
{
m_ServerNetworkManager.SpawnManager.ChangeOwnership(serverSideInstance.GetComponent<NetworkObject>(), networkManagerNonOwner.LocalClientId, true);
}
LogNonOwnerRigidBody(3);
yield return WaitForConditionOrTimeOut(() => nonOwnerInstance.GetComponent<NetworkObject>().OwnerClientId == networkManagerNonOwner.LocalClientId);
Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} to change ownership!");
LogNonOwnerRigidBody(4);
// Re-assign the ownership references and wait for the non-owner instance to be notified of ownership change
networkManagerOwner = startingOwnership == StartingOwnership.HostStartsAsOwner ? m_ClientNetworkManagers[0] : m_ServerNetworkManager;
networkManagerNonOwner = startingOwnership == StartingOwnership.HostStartsAsOwner ? m_ServerNetworkManager : m_ClientNetworkManagers[0];
@@ -206,15 +334,48 @@ namespace Unity.Netcode.RuntimeTests
// Make sure the owner is not kinematic and the non-owner(s) are kinematic
Assert.False(ownerInstance.GetComponent<Rigidbody>().isKinematic, $"{networkManagerOwner.name}'s object instance {ownerInstance.name} is kinematic when it should not be!");
Assert.True(nonOwnerInstance.GetComponent<Rigidbody>().isKinematic, $"{networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} is not kinematic when it should be!");
transformToTest = nonOwnerInstance.transform;
Assert.True(networkManagerOwner.LocalClientId != networkManagerNonOwner.LocalClientId);
Assert.True(nonOwnerInstance.OwnerClientId != networkManagerNonOwner.LocalClientId);
Assert.True(nonOwnerInstance.NetworkManager.LocalClientId == networkManagerNonOwner.LocalClientId);
yield return WaitForConditionOrTimeOut(() => Approximately(transformToTest.position, valueSetByOwner) && Approximately(transformToTest.localScale, valueSetByOwner) && Approximately(transformToTest.rotation, rotation));
Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} to change its transform!\n" +
$"Expected Position: {valueSetByOwner} | Current Position: {transformToTest.position}\n" +
$"Expected Rotation: {valueSetByOwner} | Current Rotation: {transformToTest.rotation.eulerAngles}\n" +
$"Expected Scale: {valueSetByOwner} | Current Scale: {transformToTest.localScale}");
LogNonOwnerRigidBody(5);
// Have the new owner change transform values and wait for those values to be applied on the non-owner side.
valueSetByOwner = Vector3.one * 10;
ownerInstance.transform.position = valueSetByOwner;
ownerInstance.transform.localScale = valueSetByOwner;
rotation.eulerAngles = valueSetByOwner;
ownerInstance.transform.rotation = rotation;
transformToTest = nonOwnerInstance.transform;
yield return WaitForConditionOrTimeOut(() => Approximately(transformToTest.position, valueSetByOwner) && Approximately(transformToTest.localScale, valueSetByOwner) && Approximately(transformToTest.rotation, rotation));
LogOwnerRigidBody(1);
if (m_MotionModel == MotionModels.UseRigidbody)
{
m_UseAdjustedVariance = true;
var ownerRigidbody = ownerInstance.GetComponent<Rigidbody>();
ownerRigidbody.Move(valueSetByOwner, rotation);
LogOwnerRigidBody(2);
ownerInstance.GetComponent<NetworkTransform>().LogMotion = m_EnableVerboseDebug;
nonOwnerInstance.GetComponent<NetworkTransform>().LogMotion = m_EnableVerboseDebug;
ownerRigidbody.linearVelocity = Vector3.zero;
}
else
{
m_UseAdjustedVariance = false;
ownerInstance.transform.position = valueSetByOwner;
ownerInstance.transform.rotation = rotation;
}
LogOwnerRigidBody(3);
LogNonOwnerRigidBody(6);
yield return WaitForConditionOrTimeOut(() => Approximately(GetNonOwnerPosition(), valueSetByOwner) && Approximately(transformToTest.localScale, valueSetByOwner) && Approximately(GetNonOwnerRotation(), rotation));
if (s_GlobalTimeoutHelper.TimedOut)
{
LogOwnerRigidBody(4);
LogNonOwnerRigidBody(7);
}
Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} to change its transform!\n" +
$"Expected Position: {valueSetByOwner} | Current Position: {transformToTest.position}\n" +
$"Expected Rotation: {valueSetByOwner} | Current Rotation: {transformToTest.rotation.eulerAngles}\n" +
@@ -223,7 +384,11 @@ namespace Unity.Netcode.RuntimeTests
// The last check is to verify non-owners cannot change transform values after ownership has changed
nonOwnerInstance.transform.position = Vector3.zero;
yield return s_DefaultWaitForTick;
Assert.True(Approximately(nonOwnerInstance.transform.position, valueSetByOwner), $"{networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} was allowed to change its position! Expected: {Vector3.one} Is Currently:{nonOwnerInstance.transform.position}");
if (m_MotionModel == MotionModels.UseRigidbody)
{
yield return new WaitForFixedUpdate();
}
Assert.True(Approximately(GetNonOwnerPosition(), valueSetByOwner), $"{networkManagerNonOwner.name}'s object instance {nonOwnerInstance.name} was allowed to change its position! Expected: {valueSetByOwner} Is Currently:{GetNonOwnerPosition()}");
}
/// <summary>
@@ -255,6 +420,12 @@ namespace Unity.Netcode.RuntimeTests
eulerAngles = valueSetByOwner
};
ownerInstance.transform.rotation = rotation;
// Allow scale to update first when using rigid body motion
if (m_MotionModel == MotionModels.UseRigidbody)
{
yield return new WaitForFixedUpdate();
}
var transformToTest = nonOwnerInstance.transform;
yield return WaitForConditionOrTimeOut(() => transformToTest.position == valueSetByOwner && transformToTest.localScale == valueSetByOwner && transformToTest.rotation == rotation);
Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for {m_ClientNetworkManagers[0].name}'s object instance {nonOwnerInstance.name} to change its transform!\n" +
@@ -265,6 +436,11 @@ namespace Unity.Netcode.RuntimeTests
// The last check is to verify clients cannot change transform values
nonOwnerInstance.transform.position = Vector3.zero;
yield return s_DefaultWaitForTick;
// Allow scale to update first when using rigid body motion
if (m_MotionModel == MotionModels.UseRigidbody)
{
yield return new WaitForFixedUpdate();
}
Assert.True(nonOwnerInstance.transform.position == valueSetByOwner, $"{m_ClientNetworkManagers[0].name}'s object instance {nonOwnerInstance.name} was allowed to change its position! Expected: {Vector3.one} Is Currently:{nonOwnerInstance.transform.position}");
}
@@ -273,59 +449,59 @@ namespace Unity.Netcode.RuntimeTests
/// </summary>
public class VerifyObjectIsSpawnedOnClient : NetworkBehaviour
{
private static Dictionary<ulong, VerifyObjectIsSpawnedOnClient> s_NetworkManagerRelativeSpawnedObjects = new Dictionary<ulong, VerifyObjectIsSpawnedOnClient>();
public static Dictionary<ulong, VerifyObjectIsSpawnedOnClient> NetworkManagerRelativeSpawnedObjects = new Dictionary<ulong, VerifyObjectIsSpawnedOnClient>();
public static void ResetObjectTable()
{
s_NetworkManagerRelativeSpawnedObjects.Clear();
NetworkManagerRelativeSpawnedObjects.Clear();
}
public override void OnGainedOwnership()
{
if (!s_NetworkManagerRelativeSpawnedObjects.ContainsKey(NetworkManager.LocalClientId))
if (!NetworkManagerRelativeSpawnedObjects.ContainsKey(NetworkManager.LocalClientId))
{
s_NetworkManagerRelativeSpawnedObjects.Add(NetworkManager.LocalClientId, this);
NetworkManagerRelativeSpawnedObjects.Add(NetworkManager.LocalClientId, this);
}
base.OnGainedOwnership();
}
public override void OnLostOwnership()
{
if (!s_NetworkManagerRelativeSpawnedObjects.ContainsKey(NetworkManager.LocalClientId))
if (!NetworkManagerRelativeSpawnedObjects.ContainsKey(NetworkManager.LocalClientId))
{
s_NetworkManagerRelativeSpawnedObjects.Add(NetworkManager.LocalClientId, this);
NetworkManagerRelativeSpawnedObjects.Add(NetworkManager.LocalClientId, this);
}
base.OnLostOwnership();
}
public static List<ulong> GetClientsThatSpawnedThisPrefab()
{
return s_NetworkManagerRelativeSpawnedObjects.Keys.ToList();
return NetworkManagerRelativeSpawnedObjects.Keys.ToList();
}
public static VerifyObjectIsSpawnedOnClient GetClientInstance(ulong clientId)
{
if (s_NetworkManagerRelativeSpawnedObjects.ContainsKey(clientId))
if (NetworkManagerRelativeSpawnedObjects.ContainsKey(clientId))
{
return s_NetworkManagerRelativeSpawnedObjects[clientId];
return NetworkManagerRelativeSpawnedObjects[clientId];
}
return null;
}
public override void OnNetworkSpawn()
{
if (!s_NetworkManagerRelativeSpawnedObjects.ContainsKey(NetworkManager.LocalClientId))
if (!NetworkManagerRelativeSpawnedObjects.ContainsKey(NetworkManager.LocalClientId))
{
s_NetworkManagerRelativeSpawnedObjects.Add(NetworkManager.LocalClientId, this);
NetworkManagerRelativeSpawnedObjects.Add(NetworkManager.LocalClientId, this);
}
base.OnNetworkSpawn();
}
public override void OnNetworkDespawn()
{
if (s_NetworkManagerRelativeSpawnedObjects.ContainsKey(NetworkManager.LocalClientId))
if (NetworkManagerRelativeSpawnedObjects.ContainsKey(NetworkManager.LocalClientId))
{
s_NetworkManagerRelativeSpawnedObjects.Remove(NetworkManager.LocalClientId);
NetworkManagerRelativeSpawnedObjects.Remove(NetworkManager.LocalClientId);
}
base.OnNetworkDespawn();
}
@@ -338,24 +514,24 @@ namespace Unity.Netcode.RuntimeTests
[DisallowMultipleComponent]
public class TestClientNetworkTransform : NetworkTransform
{
public override void OnNetworkSpawn()
{
base.OnNetworkSpawn();
CanCommitToTransform = IsOwner;
}
//public override void OnNetworkSpawn()
//{
// base.OnNetworkSpawn();
// CanCommitToTransform = IsOwner;
//}
protected override void Update()
{
CanCommitToTransform = IsOwner;
base.Update();
if (NetworkManager.Singleton != null && (NetworkManager.Singleton.IsConnectedClient || NetworkManager.Singleton.IsListening))
{
if (CanCommitToTransform)
{
TryCommitTransformToServer(transform, NetworkManager.LocalTime.Time);
}
}
}
//protected override void Update()
//{
// CanCommitToTransform = IsOwner;
// base.Update();
// if (NetworkManager.Singleton != null && (NetworkManager.Singleton.IsConnectedClient || NetworkManager.Singleton.IsListening))
// {
// if (CanCommitToTransform)
// {
// TryCommitTransformToServer(transform, NetworkManager.LocalTime.Time);
// }
// }
//}
protected override bool OnIsServerAuthoritative()
{

View File

@@ -14,6 +14,12 @@ namespace Unity.Netcode.RuntimeTests
/// models for each operating mode when packet loss and latency is
/// present.
/// </summary>
[TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full)]
[TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half)]
[TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full)]
[TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half)]
[TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full)]
[TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half)]
[TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full)]
[TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half)]
[TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full)]
@@ -54,13 +60,18 @@ namespace Unity.Netcode.RuntimeTests
// We don't assert on timeout here because we want to log this information during PostAllChildrenLocalTransformValuesMatch
yield return WaitForConditionOrTimeOut(() => AllInstancesKeptLocalTransformValues(useSubChild));
var success = true;
m_InfoMessage.AppendLine($"[{checkType}][{useSubChild}] Timed out waiting for all children to have the correct local space values:\n");
if (s_GlobalTimeoutHelper.TimedOut)
{
var waitForMs = new WaitForSeconds(0.001f);
var waitForMs = new WaitForSeconds(0.0025f);
if (m_Precision == Precision.Half)
{
m_CurrentHalfPrecision = 0.2156f;
}
// If we timed out, then wait for a full range of ticks to assure all data has been synchronized before declaring this a failed test.
for (int j = 0; j < m_ServerNetworkManager.NetworkConfig.TickRate; j++)
{
m_InfoMessage.Clear();
m_InfoMessage.AppendLine($"[{checkType}][{useSubChild}] Timed out waiting for all children to have the correct local space values:\n");
var instances = useSubChild ? ChildObjectComponent.SubInstances : ChildObjectComponent.Instances;
success = PostAllChildrenLocalTransformValuesMatch(useSubChild);
yield return waitForMs;
@@ -100,6 +111,11 @@ namespace Unity.Netcode.RuntimeTests
var serverSideChild = SpawnObject(m_ChildObject.gameObject, authorityNetworkManager).GetComponent<NetworkObject>();
var serverSideSubChild = SpawnObject(m_SubChildObject.gameObject, authorityNetworkManager).GetComponent<NetworkObject>();
yield return s_DefaultWaitForTick;
yield return s_DefaultWaitForTick;
yield return s_DefaultWaitForTick;
yield return s_DefaultWaitForTick;
// Assure all of the child object instances are spawned before proceeding to parenting
yield return WaitForConditionOrTimeOut(AllChildObjectInstancesAreSpawned);
AssertOnTimeout("Timed out waiting for all child instances to be spawned!");

View File

@@ -1,3 +1,4 @@
#if !MULTIPLAYER_TOOLS
using NUnit.Framework;
using Unity.Netcode.Components;
using UnityEngine;
@@ -233,6 +234,8 @@ namespace Unity.Netcode.RuntimeTests
var manager = new GameObject($"Test-{nameof(NetworkManager)}.{nameof(TestSyncAxes)}");
var networkManager = manager.AddComponent<NetworkManager>();
networkManager.NetworkConfig = new NetworkConfig();
networkObject.NetworkManagerOwner = networkManager;
networkTransform.enabled = false; // do not tick `FixedUpdate()` or `Update()`
@@ -905,3 +908,4 @@ namespace Unity.Netcode.RuntimeTests
}
}
}
#endif

View File

@@ -1,3 +1,4 @@
using System.Collections;
using NUnit.Framework;
using UnityEngine;
@@ -8,25 +9,37 @@ namespace Unity.Netcode.RuntimeTests
/// server and host operating modes and will test both authoritative
/// models for each operating mode.
/// </summary>
[TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full)]
#if !MULTIPLAYER_TOOLS
[TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half)]
[TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full)]
[TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half)]
[TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full)]
[TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half)]
#endif
[TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full)]
#if !MULTIPLAYER_TOOLS
[TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half)]
[TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full)]
[TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half)]
[TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full)]
[TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half)]
#endif
[TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full)]
#if !MULTIPLAYER_TOOLS
[TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half)]
[TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full)]
[TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half)]
[TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full)]
[TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half)]
#endif
public class NetworkTransformTests : NetworkTransformBase
{
protected const int k_TickRate = 60;
/// <summary>
/// Constructor
/// </summary>
/// <param name="testWithHost">Determines if we are running as a server or host</param>
/// <param name="hostOrServer">Determines if we are running as a server or host</param>
/// <param name="authority">Determines if we are using server or owner authority</param>
public NetworkTransformTests(HostOrServer testWithHost, Authority authority, RotationCompression rotationCompression, Rotation rotation, Precision precision) :
base(testWithHost, authority, rotationCompression, rotation, precision)
@@ -41,6 +54,24 @@ namespace Unity.Netcode.RuntimeTests
return k_TickRate;
}
private bool m_UseParentingThreshold;
private const float k_ParentingThreshold = 0.25f;
protected override float GetDeltaVarianceThreshold()
{
if (m_UseParentingThreshold)
{
return k_ParentingThreshold;
}
return base.GetDeltaVarianceThreshold();
}
protected override IEnumerator OnSetup()
{
m_UseParentingThreshold = false;
return base.OnSetup();
}
/// <summary>
/// Handles validating the local space values match the original local space values.
/// If not, it generates a message containing the axial values that did not match
@@ -69,6 +100,7 @@ namespace Unity.Netcode.RuntimeTests
}
}
#if !MULTIPLAYER_TOOLS
/// <summary>
/// Validates that transform values remain the same when a NetworkTransform is
/// parented under another NetworkTransform under all of the possible axial conditions
@@ -77,6 +109,7 @@ namespace Unity.Netcode.RuntimeTests
[Test]
public void ParentedNetworkTransformTest([Values] Interpolation interpolation, [Values] bool worldPositionStays, [Values(0.5f, 1.0f, 5.0f)] float scale)
{
m_UseParentingThreshold = true;
// Get the NetworkManager that will have authority in order to spawn with the correct authority
var isServerAuthority = m_Authority == Authority.ServerAuthority;
var authorityNetworkManager = m_ServerNetworkManager;
@@ -134,6 +167,10 @@ namespace Unity.Netcode.RuntimeTests
Assert.True(success, "All transform values did not match prior to parenting!");
success = WaitForConditionOrTimeOutWithTimeTravel(PositionRotationScaleMatches);
Assert.True(success, "All transform values did not match prior to parenting!");
// Parent the child under the parent with the current world position stays setting
Assert.True(serverSideChild.TrySetParent(serverSideParent.transform, worldPositionStays), "[Server-Side Child] Failed to set child's parent!");
@@ -349,6 +386,7 @@ namespace Unity.Netcode.RuntimeTests
}
}
}
#endif
/// <summary>
/// Tests changing all axial values one at a time.