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.4.0] - 2023-04-10 ### Added - Added a way to access the GlobalObjectIdHash via PrefabIdHash for use in the Connection Approval Callback. (#2437) - Added `OnServerStarted` and `OnServerStopped` events that will trigger only on the server (or host player) to notify that the server just started or is no longer active (#2420) - Added `OnClientStarted` and `OnClientStopped` events that will trigger only on the client (or host player) to notify that the client just started or is no longer active (#2420) - Added `NetworkTransform.UseHalfFloatPrecision` property that, when enabled, will use half float values for position, rotation, and scale. This yields a 50% bandwidth savings a the cost of precision. (#2388) - Added `NetworkTransform.UseQuaternionSynchronization` property that, when enabled, will synchronize the entire quaternion. (#2388) - Added `NetworkTransform.UseQuaternionCompression` property that, when enabled, will use a smallest three implementation reducing a full quaternion synchronization update to the size of an unsigned integer. (#2388) - Added `NetworkTransform.SlerpPosition` property that, when enabled along with interpolation being enabled, will interpolate using `Vector3.Slerp`. (#2388) - Added `BufferedLinearInterpolatorVector3` that replaces the float version, is now used by `NetworkTransform`, and provides the ability to enable or disable `Slerp`. (#2388) - Added `HalfVector3` used for scale when half float precision is enabled. (#2388) - Added `HalfVector4` used for rotation when half float precision and quaternion synchronization is enabled. (#2388) - Added `HalfVector3DeltaPosition` used for position when half float precision is enabled. This handles loss in position precision by updating only the delta position as opposed to the full position. (#2388) - Added `NetworkTransform.GetSpaceRelativePosition` and `NetworkTransform.GetSpaceRelativeRotation` helper methods to return the proper values depending upon whether local or world space. (#2388) - Added `NetworkTransform.OnAuthorityPushTransformState` virtual method that is invoked just prior to sending the `NetworkTransformState` to non-authoritative instances. This provides users with the ability to obtain more precise delta values for prediction related calculations. (#2388) - Added `NetworkTransform.OnNetworkTransformStateUpdated` virtual method that is invoked just after the authoritative `NetworkTransformState` is applied. This provides users with the ability to obtain more precise delta values for prediction related calculations. (#2388) - Added `NetworkTransform.OnInitialize`virtual method that is invoked after the `NetworkTransform` has been initialized or re-initialized when ownership changes. This provides for a way to make adjustments when `NetworkTransform` is initialized (i.e. resetting client prediction etc) (#2388) - Added `NetworkObject.SynchronizeTransform` property (default is true) that provides users with another way to help with bandwidth optimizations where, when set to false, the `NetworkObject`'s associated transform will not be included when spawning and/or synchronizing late joining players. (#2388) - Added `NetworkSceneManager.ActiveSceneSynchronizationEnabled` property, disabled by default, that enables client synchronization of server-side active scene changes. (#2383) - Added `NetworkObject.ActiveSceneSynchronization`, disabled by default, that will automatically migrate a `NetworkObject` to a newly assigned active scene. (#2383) - Added `NetworkObject.SceneMigrationSynchronization`, enabled by default, that will synchronize client(s) when a `NetworkObject` is migrated into a new scene on the server side via `SceneManager.MoveGameObjectToScene`. (#2383) ### Changed - Made sure the `CheckObjectVisibility` delegate is checked and applied, upon `NetworkShow` attempt. Found while supporting (#2454), although this is not a fix for this (already fixed) issue. (#2463) - Changed `NetworkTransform` authority handles delta checks on each new network tick and no longer consumes processing cycles checking for deltas for all frames in-between ticks. (#2388) - Changed the `NetworkTransformState` structure is now public and now has public methods that provide access to key properties of the `NetworkTransformState` structure. (#2388) - Changed `NetworkTransform` interpolation adjusts its interpolation "ticks ago" to be 2 ticks latent if it is owner authoritative and the instance is not the server or 1 tick latent if the instance is the server and/or is server authoritative. (#2388) - Updated `NetworkSceneManager` to migrate dynamically spawned `NetworkObject`s with `DestroyWithScene` set to false into the active scene if their current scene is unloaded. (#2383) - Updated the server to synchronize its local `NetworkSceneManager.ClientSynchronizationMode` during the initial client synchronization. (#2383) ### Fixed - Fixed issue where during client synchronization the synchronizing client could receive a ObjectSceneChanged message before the client-side NetworkObject instance had been instantiated and spawned. (#2502) - Fixed issue where `NetworkAnimator` was building client RPC parameters to exclude the host from sending itself messages but was not including it in the ClientRpc parameters. (#2492) - Fixed issue where `NetworkAnimator` was not properly detecting and synchronizing cross fade initiated transitions. (#2481) - Fixed issue where `NetworkAnimator` was not properly synchronizing animation state updates. (#2481) - Fixed float NetworkVariables not being rendered properly in the inspector of NetworkObjects. (#2441) - Fixed an issue where Named Message Handlers could remove themselves causing an exception when the metrics tried to access the name of the message.(#2426) - Fixed registry of public `NetworkVariable`s in derived `NetworkBehaviour`s (#2423) - Fixed issue where runtime association of `Animator` properties to `AnimationCurve`s would cause `NetworkAnimator` to attempt to update those changes. (#2416) - Fixed issue where `NetworkAnimator` would not check if its associated `Animator` was valid during serialization and would spam exceptions in the editor console. (#2416) - Fixed issue with a child's rotation rolling over when interpolation is enabled on a `NetworkTransform`. Now using half precision or full quaternion synchronization will always update all axis. (#2388) - Fixed issue where `NetworkTransform` was not setting the teleport flag when the `NetworkTransform.InLocalSpace` value changed. This issue only impacted `NetworkTransform` when interpolation was enabled. (#2388) - Fixed issue when the `NetworkSceneManager.ClientSynchronizationMode` is `LoadSceneMode.Additive` and the server changes the currently active scene prior to a client connecting then upon a client connecting and being synchronized the NetworkSceneManager would clear its internal ScenePlacedObjects list that could already be populated. (#2383) - Fixed issue where a client would load duplicate scenes of already preloaded scenes during the initial client synchronization and `NetworkSceneManager.ClientSynchronizationMode` was set to `LoadSceneMode.Additive`. (#2383)
267 lines
11 KiB
C#
267 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using NUnit.Framework;
|
|
using Unity.Netcode.Transports.UTP;
|
|
using UnityEngine;
|
|
|
|
namespace Unity.Netcode.TestHelpers.Runtime
|
|
{
|
|
/// <summary>
|
|
/// Helper class to instantiate a NetworkManager
|
|
/// This also provides the ability to:
|
|
/// --- instantiate GameObjects with NetworkObject components that returns a Guid for accessing it later.
|
|
/// --- add NetworkBehaviour components to the instantiated GameObjects
|
|
/// --- spawn a NetworkObject using its parent GameObject's Guid
|
|
/// Call StartNetworkManager in the constructor of your runtime unit test class.
|
|
/// Call ShutdownNetworkManager in the destructor of your runtime unit test class.
|
|
///
|
|
/// Includes a useful "BuffersMatch" method that allows you to compare two buffers (returns true if they match false if not)
|
|
/// </summary>
|
|
public static class NetworkManagerHelper
|
|
{
|
|
public static NetworkManager NetworkManagerObject { get; internal set; }
|
|
public static GameObject NetworkManagerGameObject { get; internal set; }
|
|
|
|
public static Dictionary<Guid, GameObject> InstantiatedGameObjects = new Dictionary<Guid, GameObject>();
|
|
public static Dictionary<Guid, NetworkObject> InstantiatedNetworkObjects = new Dictionary<Guid, NetworkObject>();
|
|
public static NetworkManagerOperatingMode CurrentNetworkManagerMode;
|
|
|
|
/// <summary>
|
|
/// This provides the ability to start NetworkManager in various modes
|
|
/// </summary>
|
|
public enum NetworkManagerOperatingMode
|
|
{
|
|
None,
|
|
Host,
|
|
Server,
|
|
Client,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called upon the RpcQueueTests being instantiated.
|
|
/// This creates an instance of the NetworkManager to be used during unit tests.
|
|
/// Currently, the best method to run unit tests is by starting in host mode as you can
|
|
/// send messages to yourself (i.e. Host-Client to Host-Server and vice versa).
|
|
/// As such, the default setting is to start in Host mode.
|
|
/// </summary>
|
|
/// <param name="managerMode">parameter to specify which mode you want to start the NetworkManager</param>
|
|
/// <param name="networkConfig">parameter to specify custom NetworkConfig settings</param>
|
|
/// <returns>true if it was instantiated or is already instantiate otherwise false means it failed to instantiate</returns>
|
|
public static bool StartNetworkManager(out NetworkManager networkManager, NetworkManagerOperatingMode managerMode = NetworkManagerOperatingMode.Host, NetworkConfig networkConfig = null)
|
|
{
|
|
// If we are changing the current manager mode and the current manager mode is not "None", then stop the NetworkManager mode
|
|
if (CurrentNetworkManagerMode != managerMode && CurrentNetworkManagerMode != NetworkManagerOperatingMode.None)
|
|
{
|
|
StopNetworkManagerMode();
|
|
}
|
|
|
|
if (NetworkManagerGameObject == null)
|
|
{
|
|
NetworkManagerGameObject = new GameObject(nameof(NetworkManager));
|
|
NetworkManagerObject = NetworkManagerGameObject.AddComponent<NetworkManager>();
|
|
|
|
if (NetworkManagerObject == null)
|
|
{
|
|
networkManager = null;
|
|
return false;
|
|
}
|
|
|
|
Debug.Log($"{nameof(NetworkManager)} Instantiated.");
|
|
|
|
var unityTransport = NetworkManagerGameObject.AddComponent<UnityTransport>();
|
|
if (networkConfig == null)
|
|
{
|
|
networkConfig = new NetworkConfig
|
|
{
|
|
EnableSceneManagement = false,
|
|
};
|
|
}
|
|
|
|
NetworkManagerObject.NetworkConfig = networkConfig;
|
|
NetworkManagerObject.NetworkConfig.NetworkTransport = unityTransport;
|
|
|
|
// Starts the network manager in the mode specified
|
|
StartNetworkManagerMode(managerMode);
|
|
}
|
|
|
|
networkManager = NetworkManagerObject;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a GameObject with a NetworkObject component
|
|
/// </summary>
|
|
/// <param name="nameOfGameObject">the name of the object</param>
|
|
/// <returns></returns>
|
|
public static Guid AddGameNetworkObject(string nameOfGameObject)
|
|
{
|
|
var gameObjectId = Guid.NewGuid();
|
|
|
|
// Create the player object that we will spawn as a host
|
|
var gameObject = new GameObject(nameOfGameObject);
|
|
|
|
Assert.IsNotNull(gameObject);
|
|
|
|
var networkObject = gameObject.AddComponent<NetworkObject>();
|
|
|
|
Assert.IsNotNull(networkObject);
|
|
|
|
Assert.IsFalse(InstantiatedGameObjects.ContainsKey(gameObjectId));
|
|
Assert.IsFalse(InstantiatedNetworkObjects.ContainsKey(gameObjectId));
|
|
|
|
InstantiatedGameObjects.Add(gameObjectId, gameObject);
|
|
InstantiatedNetworkObjects.Add(gameObjectId, networkObject);
|
|
|
|
return gameObjectId;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper class to add a component to the GameObject with a NetoworkObject component
|
|
/// </summary>
|
|
/// <typeparam name="T">NetworkBehaviour component being added to the GameObject</typeparam>
|
|
/// <param name="gameObjectIdentifier">ID returned to reference the game object</param>
|
|
/// <returns></returns>
|
|
public static T AddComponentToObject<T>(Guid gameObjectIdentifier) where T : NetworkBehaviour
|
|
{
|
|
Assert.IsTrue(InstantiatedGameObjects.ContainsKey(gameObjectIdentifier));
|
|
return InstantiatedGameObjects[gameObjectIdentifier].AddComponent<T>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Spawn the NetworkObject, so Rpcs can flow
|
|
/// </summary>
|
|
/// <param name="gameObjectIdentifier">ID returned to reference the game object</param>
|
|
public static void SpawnNetworkObject(Guid gameObjectIdentifier)
|
|
{
|
|
Assert.IsTrue(InstantiatedNetworkObjects.ContainsKey(gameObjectIdentifier));
|
|
if (!InstantiatedNetworkObjects[gameObjectIdentifier].IsSpawned)
|
|
{
|
|
InstantiatedNetworkObjects[gameObjectIdentifier].Spawn();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts the NetworkManager in the current mode specified by managerMode
|
|
/// </summary>
|
|
/// <param name="managerMode">the mode to start the NetworkManager as</param>
|
|
private static void StartNetworkManagerMode(NetworkManagerOperatingMode managerMode)
|
|
{
|
|
CurrentNetworkManagerMode = managerMode;
|
|
switch (CurrentNetworkManagerMode)
|
|
{
|
|
case NetworkManagerOperatingMode.Host:
|
|
{
|
|
// Starts the host
|
|
NetworkManagerObject.StartHost();
|
|
break;
|
|
}
|
|
case NetworkManagerOperatingMode.Server:
|
|
{
|
|
// Starts the server
|
|
NetworkManagerObject.StartServer();
|
|
break;
|
|
}
|
|
case NetworkManagerOperatingMode.Client:
|
|
{
|
|
// Starts the client
|
|
NetworkManagerObject.StartClient();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we started an netcode session
|
|
if (CurrentNetworkManagerMode != NetworkManagerOperatingMode.None)
|
|
{
|
|
// With some unit tests the Singleton can still be from a previous unit test
|
|
// depending upon the order of operations that occurred.
|
|
if (NetworkManager.Singleton != NetworkManagerObject)
|
|
{
|
|
NetworkManagerObject.SetSingleton();
|
|
}
|
|
|
|
// Only log this if we started an netcode session
|
|
Debug.Log($"{CurrentNetworkManagerMode} started.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stops the current mode of the NetworkManager
|
|
/// </summary>
|
|
private static void StopNetworkManagerMode()
|
|
{
|
|
NetworkManagerObject.Shutdown();
|
|
|
|
Debug.Log($"{CurrentNetworkManagerMode} stopped.");
|
|
CurrentNetworkManagerMode = NetworkManagerOperatingMode.None;
|
|
}
|
|
|
|
// This is called, even if we assert and exit early from a test
|
|
public static void ShutdownNetworkManager()
|
|
{
|
|
// clean up any game objects created with custom unit testing components
|
|
foreach (var entry in InstantiatedGameObjects)
|
|
{
|
|
UnityEngine.Object.DestroyImmediate(entry.Value);
|
|
}
|
|
|
|
InstantiatedGameObjects.Clear();
|
|
|
|
if (NetworkManagerGameObject != null)
|
|
{
|
|
Debug.Log($"{nameof(NetworkManager)} shutdown.");
|
|
|
|
StopNetworkManagerMode();
|
|
UnityEngine.Object.DestroyImmediate(NetworkManagerGameObject);
|
|
Debug.Log($"{nameof(NetworkManager)} destroyed.");
|
|
}
|
|
NetworkManagerGameObject = null;
|
|
NetworkManagerObject = null;
|
|
}
|
|
|
|
public static bool BuffersMatch(int indexOffset, long targetSize, byte[] sourceArray, byte[] originalArray)
|
|
{
|
|
long largeInt64Blocks = targetSize >> 3; // Divide by 8
|
|
int originalArrayOffset = 0;
|
|
// process by 8 byte blocks if we can
|
|
for (long i = 0; i < largeInt64Blocks; i++)
|
|
{
|
|
if (BitConverter.ToInt64(sourceArray, indexOffset) != BitConverter.ToInt64(originalArray, originalArrayOffset))
|
|
{
|
|
return false;
|
|
}
|
|
indexOffset += 8;
|
|
originalArrayOffset += 8;
|
|
}
|
|
|
|
long offset = largeInt64Blocks * 8;
|
|
long remainder = targetSize - offset;
|
|
|
|
// 4 byte block
|
|
if (remainder >= 4)
|
|
{
|
|
if (BitConverter.ToInt32(sourceArray, indexOffset) != BitConverter.ToInt32(originalArray, originalArrayOffset))
|
|
{
|
|
return false;
|
|
}
|
|
indexOffset += 4;
|
|
originalArrayOffset += 4;
|
|
offset += 4;
|
|
}
|
|
|
|
// Remainder of bytes < 4
|
|
if (targetSize - offset > 0)
|
|
{
|
|
for (long i = 0; i < (targetSize - offset); i++)
|
|
{
|
|
if (sourceArray[indexOffset + i] != originalArray[originalArrayOffset + i])
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|