com.unity.netcode.gameobjects@2.0.0-pre.1

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-pre.1] - 2024-06-17

### Added

- Added event `NetworkManager.OnSessionOwnerPromoted` that is invoked when a new session owner promotion occurs. (#2948)
- Added `NetworkRigidBodyBase.GetLinearVelocity` and `NetworkRigidBodyBase.SetLinearVelocity` convenience/helper methods. (#2948)
- Added `NetworkRigidBodyBase.GetAngularVelocity` and `NetworkRigidBodyBase.SetAngularVelocity` convenience/helper methods. (#2948)

### Fixed

- Fixed issue when `NetworkTransform` half float precision is enabled and ownership changes the current base position was not being synchronized. (#2948)
- Fixed issue where `OnClientConnected` not being invoked on the session owner when connecting to a new distributed authority session. (#2948)
- Fixed issue where Rigidbody micro-motion (i.e. relatively small velocities) would result in non-authority instances slightly stuttering as the body would come to a rest (i.e. no motion). Now, the threshold value can increase at higher velocities and can decrease slightly below the provided threshold to account for this. (#2948)

### Changed

- Changed the client's owned objects is now returned (`NetworkClient` and `NetworkSpawnManager`) as an array as opposed to a list for performance purposes. (#2948)
- Changed `NetworkTransfrom.TryCommitTransformToServer` to be internal as it will be removed by the final 2.0.0 release. (#2948)
- Changed `NetworkTransformEditor.OnEnable` to a virtual method to be able to customize a `NetworkTransform` derived class by creating a derived editor control from `NetworkTransformEditor`. (#2948)
This commit is contained in:
Unity Technologies
2024-06-17 00:00:00 +00:00
parent 36d539e265
commit ed38a4dcc2
47 changed files with 2758 additions and 2248 deletions

View File

@@ -45,6 +45,9 @@ namespace Unity.Netcode.Components
private Rigidbody m_Rigidbody;
private Rigidbody2D m_Rigidbody2D;
internal NetworkTransform NetworkTransform;
private float m_TickFrequency;
private float m_TickRate;
private enum InterpolationTypes
{
None,
@@ -120,6 +123,129 @@ namespace Unity.Netcode.Components
}
}
internal Vector3 GetAdjustedPositionThreshold()
{
// Since the threshold is a measurement of unity world space units per tick, we will allow for the maximum threshold
// to be no greater than the threshold measured in unity world space units per second
var thresholdMax = NetworkTransform.PositionThreshold * m_TickRate;
// Get the velocity in unity world space units per tick
var perTickVelocity = GetLinearVelocity() * m_TickFrequency;
// Since a rigid body can have "micro-motion" when allowed to come to rest (based on friction etc), we will allow for
// no less than 1/10th the threshold value.
var minThreshold = NetworkTransform.PositionThreshold * 0.1f;
// Finally, we adjust the threshold based on the body's current velocity
perTickVelocity.x = Mathf.Clamp(Mathf.Abs(perTickVelocity.x), minThreshold, thresholdMax);
perTickVelocity.y = Mathf.Clamp(Mathf.Abs(perTickVelocity.y), minThreshold, thresholdMax);
// 2D Rigidbody only moves on x & y axis
if (!m_IsRigidbody2D)
{
perTickVelocity.z = Mathf.Clamp(Mathf.Abs(perTickVelocity.z), minThreshold, thresholdMax);
}
return perTickVelocity;
}
internal Vector3 GetAdjustedRotationThreshold()
{
// Since the rotation threshold is a measurement pf degrees per tick, we get the maximum threshold
// by calculating the threshold in degrees per second.
var thresholdMax = NetworkTransform.RotAngleThreshold * m_TickRate;
// Angular velocity is expressed in radians per second where as the rotation being checked is in degrees.
// Convert the angular velocity to degrees per second and then convert that to degrees per tick.
var rotationPerTick = (GetAngularVelocity() * Mathf.Rad2Deg) * m_TickFrequency;
var minThreshold = NetworkTransform.RotAngleThreshold * m_TickFrequency;
// 2D Rigidbody only rotates around Z axis
if (!m_IsRigidbody2D)
{
rotationPerTick.x = Mathf.Clamp(Mathf.Abs(rotationPerTick.x), minThreshold, thresholdMax);
rotationPerTick.y = Mathf.Clamp(Mathf.Abs(rotationPerTick.y), minThreshold, thresholdMax);
}
rotationPerTick.z = Mathf.Clamp(Mathf.Abs(rotationPerTick.z), minThreshold, thresholdMax);
return rotationPerTick;
}
/// <summary>
/// Sets the linear velocity of the Rigidbody.
/// </summary>
/// <remarks>
/// For <see cref="Rigidbody2D"/>, only the x and y components of the <see cref="Vector3"/> are applied.
/// </remarks>
public void SetLinearVelocity(Vector3 linearVelocity)
{
if (m_IsRigidbody2D)
{
m_Rigidbody2D.velocity = linearVelocity;
}
else
{
m_Rigidbody.linearVelocity = linearVelocity;
}
}
/// <summary>
/// Gets the linear velocity of the Rigidbody.
/// </summary>
/// <remarks>
/// For <see cref="Rigidbody2D"/>, the <see cref="Vector3"/> velocity returned is only applied to the x and y components.
/// </remarks>
/// <returns><see cref="Vector3"/> as the linear velocity</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 GetLinearVelocity()
{
if (m_IsRigidbody2D)
{
return m_Rigidbody2D.velocity;
}
else
{
return m_Rigidbody.linearVelocity;
}
}
/// <summary>
/// Sets the angular velocity for the Rigidbody.
/// </summary>
/// <remarks>
/// For <see cref="Rigidbody2D"/>, the z component of <param name="angularVelocity"/> is only used to set the angular velocity.
/// A quick way to pass in a 2D angular velocity component is: <see cref="Vector3.forward"/> * angularVelocity (where angularVelocity is a float)
/// </remarks>
/// <param name="angularVelocity">the angular velocity to apply to the body</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetAngularVelocity(Vector3 angularVelocity)
{
if (m_IsRigidbody2D)
{
m_Rigidbody2D.angularVelocity = angularVelocity.z;
}
else
{
m_Rigidbody.angularVelocity = angularVelocity;
}
}
/// <summary>
/// Gets the angular velocity for the Rigidbody.
/// </summary>
/// <remarks>
/// For <see cref="Rigidbody2D"/>, the z component of the <see cref="Vector3"/> returned is the angular velocity of the object.
/// </remarks>
/// <returns>angular velocity as a <see cref="Vector3"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 GetAngularVelocity()
{
if (m_IsRigidbody2D)
{
return Vector3.forward * m_Rigidbody2D.velocity;
}
else
{
return m_Rigidbody.angularVelocity;
}
}
/// <summary>
/// Gets the position of the Rigidbody
/// </summary>
@@ -210,6 +336,9 @@ namespace Unity.Netcode.Components
}
}
// Used for Rigidbody only (see info on normalized below)
private Vector4 m_QuaternionCheck = Vector4.zero;
/// <summary>
/// Rotatates the Rigidbody towards a specified rotation
/// </summary>
@@ -227,6 +356,17 @@ namespace Unity.Netcode.Components
}
else
{
// Evidently we need to check to make sure the quaternion is a perfect
// magnitude of 1.0f when applying the rotation to a rigid body.
m_QuaternionCheck.x = rotation.x;
m_QuaternionCheck.y = rotation.y;
m_QuaternionCheck.z = rotation.z;
m_QuaternionCheck.w = rotation.w;
// If the magnitude is greater than 1.0f (even by a very small fractional value), then normalize the quaternion
if (m_QuaternionCheck.magnitude != 1.0f)
{
rotation.Normalize();
}
m_Rigidbody.MoveRotation(rotation);
}
}
@@ -501,6 +641,8 @@ namespace Unity.Netcode.Components
/// <inheritdoc />
public override void OnNetworkSpawn()
{
m_TickFrequency = 1.0f / NetworkManager.NetworkConfig.TickRate;
m_TickRate = NetworkManager.NetworkConfig.TickRate;
UpdateOwnershipAuthority();
}

View File

@@ -920,6 +920,22 @@ namespace Unity.Netcode.Components
#endregion
#region PROPERTIES AND GENERAL METHODS
public enum AuthorityModes
{
Server,
Owner,
}
#if MULTIPLAYER_SERVICES_SDK_INSTALLED
[Tooltip("Selects who has authority (sends state updates) over the transform. When the network topology is set to distributed authority, this always defaults to owner authority. If server (the default), then only server-side adjustments to the " +
"transform will be synchronized with clients. If owner (or client), then only the owner-side adjustments to the transform will be synchronized with both the server and other clients.")]
#else
[Tooltip("Selects who has authority (sends state updates) over the transform. If server (the default), then only server-side adjustments to the transform will be synchronized with clients. If owner (or client), " +
"then only the owner-side adjustments to the transform will be synchronized with both the server and other clients.")]
#endif
public AuthorityModes AuthorityMode;
/// <summary>
/// The default position change threshold value.
/// Any changes above this threshold will be replicated.
@@ -1487,7 +1503,7 @@ namespace Unity.Netcode.Components
/// </remarks>
/// <param name="transformToCommit">the transform to be committed</param>
/// <param name="dirtyTime">time it was marked dirty</param>
protected void TryCommitTransformToServer(Transform transformToCommit, double dirtyTime)
internal void TryCommitTransformToServer(Transform transformToCommit, double dirtyTime)
{
if (!IsSpawned)
{
@@ -1568,8 +1584,6 @@ namespace Unity.Netcode.Components
// If the transform has deltas (returns dirty) or if an explicitly set state is pending
if (m_LocalAuthoritativeNetworkState.ExplicitSet || CheckForStateChange(ref m_LocalAuthoritativeNetworkState, ref transformToCommit, synchronize))
{
m_LocalAuthoritativeNetworkState.LastSerializedSize = m_OldState.LastSerializedSize;
// If the state was explicitly set, then update the network tick to match the locally calculate tick
if (m_LocalAuthoritativeNetworkState.ExplicitSet)
{
@@ -1699,9 +1713,20 @@ namespace Unity.Netcode.Components
#if COM_UNITY_MODULES_PHYSICS
var position = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetPosition() : InLocalSpace ? transformToUse.localPosition : transformToUse.position;
var rotation = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetRotation() : InLocalSpace ? transformToUse.localRotation : transformToUse.rotation;
var positionThreshold = Vector3.one * PositionThreshold;
var rotationThreshold = Vector3.one * RotAngleThreshold;
if (m_UseRigidbodyForMotion)
{
positionThreshold = m_NetworkRigidbodyInternal.GetAdjustedPositionThreshold();
rotationThreshold = m_NetworkRigidbodyInternal.GetAdjustedRotationThreshold();
}
#else
var position = InLocalSpace ? transformToUse.localPosition : transformToUse.position;
var rotation = InLocalSpace ? transformToUse.localRotation : transformToUse.rotation;
var positionThreshold = Vector3.one * PositionThreshold;
var rotationThreshold = Vector3.one * RotAngleThreshold;
#endif
var rotAngles = rotation.eulerAngles;
var scale = transformToUse.localScale;
@@ -1828,21 +1853,21 @@ namespace Unity.Netcode.Components
// Begin delta checks against last sent state update
if (!UseHalfFloatPrecision)
{
if (SyncPositionX && (Mathf.Abs(networkState.PositionX - position.x) >= PositionThreshold || networkState.IsTeleportingNextFrame || isAxisSync))
if (SyncPositionX && (Mathf.Abs(networkState.PositionX - position.x) >= positionThreshold.x || networkState.IsTeleportingNextFrame || isAxisSync))
{
networkState.PositionX = position.x;
networkState.HasPositionX = true;
isPositionDirty = true;
}
if (SyncPositionY && (Mathf.Abs(networkState.PositionY - position.y) >= PositionThreshold || networkState.IsTeleportingNextFrame || isAxisSync))
if (SyncPositionY && (Mathf.Abs(networkState.PositionY - position.y) >= positionThreshold.y || networkState.IsTeleportingNextFrame || isAxisSync))
{
networkState.PositionY = position.y;
networkState.HasPositionY = true;
isPositionDirty = true;
}
if (SyncPositionZ && (Mathf.Abs(networkState.PositionZ - position.z) >= PositionThreshold || networkState.IsTeleportingNextFrame || isAxisSync))
if (SyncPositionZ && (Mathf.Abs(networkState.PositionZ - position.z) >= positionThreshold.z || networkState.IsTeleportingNextFrame || isAxisSync))
{
networkState.PositionZ = position.z;
networkState.HasPositionZ = true;
@@ -1863,7 +1888,7 @@ namespace Unity.Netcode.Components
{
for (int i = 0; i < 3; i++)
{
if (Math.Abs(position[i] - m_HalfPositionState.PreviousPosition[i]) >= PositionThreshold)
if (Math.Abs(position[i] - m_HalfPositionState.PreviousPosition[i]) >= positionThreshold[i])
{
isPositionDirty = i == 0 ? SyncPositionX : i == 1 ? SyncPositionY : SyncPositionZ;
if (!isPositionDirty)
@@ -1958,21 +1983,21 @@ namespace Unity.Netcode.Components
if (!UseQuaternionSynchronization)
{
if (SyncRotAngleX && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleX, rotAngles.x)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame || isAxisSync))
if (SyncRotAngleX && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleX, rotAngles.x)) >= rotationThreshold.x || networkState.IsTeleportingNextFrame || isAxisSync))
{
networkState.RotAngleX = rotAngles.x;
networkState.HasRotAngleX = true;
isRotationDirty = true;
}
if (SyncRotAngleY && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleY, rotAngles.y)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame || isAxisSync))
if (SyncRotAngleY && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleY, rotAngles.y)) >= rotationThreshold.y || networkState.IsTeleportingNextFrame || isAxisSync))
{
networkState.RotAngleY = rotAngles.y;
networkState.HasRotAngleY = true;
isRotationDirty = true;
}
if (SyncRotAngleZ && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleZ, rotAngles.z)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame || isAxisSync))
if (SyncRotAngleZ && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleZ, rotAngles.z)) >= rotationThreshold.z || networkState.IsTeleportingNextFrame || isAxisSync))
{
networkState.RotAngleZ = rotAngles.z;
networkState.HasRotAngleZ = true;
@@ -1989,7 +2014,7 @@ namespace Unity.Netcode.Components
var previousRotation = networkState.Rotation.eulerAngles;
for (int i = 0; i < 3; i++)
{
if (Mathf.Abs(Mathf.DeltaAngle(previousRotation[i], rotAngles[i])) >= RotAngleThreshold)
if (Mathf.Abs(Mathf.DeltaAngle(previousRotation[i], rotAngles[i])) >= rotationThreshold[i])
{
isRotationDirty = true;
break;
@@ -2984,7 +3009,13 @@ namespace Unity.Netcode.Components
return;
}
m_CachedNetworkObject = NetworkObject;
if (m_CachedNetworkManager && m_CachedNetworkManager.DistributedAuthorityMode)
{
AuthorityMode = AuthorityModes.Owner;
}
CanCommitToTransform = IsServerAuthoritative() ? IsServer : IsOwner;
var currentPosition = GetSpaceRelativePosition();
var currentRotation = GetSpaceRelativeRotation();
@@ -3011,22 +3042,24 @@ namespace Unity.Netcode.Components
#else
var forUpdate = true;
#endif
m_LocalAuthoritativeNetworkState.SynchronizeBaseHalfFloat = false;
if (CanCommitToTransform)
{
// Make sure authority doesn't get added to updates (no need to do this on the authority side)
m_CachedNetworkManager.NetworkTransformRegistration(this, forUpdate, false);
if (UseHalfFloatPrecision)
{
m_HalfPositionState = new NetworkDeltaPosition(currentPosition, m_CachedNetworkManager.ServerTime.Tick, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ));
m_LocalAuthoritativeNetworkState.SynchronizeBaseHalfFloat = isOwnershipChange;
SetState(teleportDisabled: false);
}
m_CurrentPosition = currentPosition;
m_TargetPosition = currentPosition;
RegisterForTickUpdate(this);
m_LocalAuthoritativeNetworkState.SynchronizeBaseHalfFloat = false;
if (UseHalfFloatPrecision && isOwnershipChange && !IsServerAuthoritative() && Interpolate)
{
m_HalfFloatTargetTickOwnership = m_CachedNetworkManager.ServerTime.Tick;
@@ -3038,16 +3071,13 @@ namespace Unity.Netcode.Components
m_CachedNetworkManager.NetworkTransformRegistration(this, forUpdate, true);
// Remove this instance from the tick update
DeregisterForTickUpdate(this);
ResetInterpolatedStateToCurrentAuthoritativeState();
m_LocalAuthoritativeNetworkState.SynchronizeBaseHalfFloat = false;
m_CurrentPosition = currentPosition;
m_TargetPosition = currentPosition;
m_CurrentScale = transform.localScale;
m_TargetScale = transform.localScale;
m_CurrentRotation = currentRotation;
m_TargetRotation = currentRotation.eulerAngles;
}
OnInitialize(ref m_LocalAuthoritativeNetworkState);
}
@@ -3296,7 +3326,7 @@ namespace Unity.Netcode.Components
{
var serverTime = m_CachedNetworkManager.ServerTime;
var cachedServerTime = serverTime.Time;
var offset = (float)serverTime.TickOffset;
//var offset = (float)serverTime.TickOffset;
#if COM_UNITY_MODULES_PHYSICS
var cachedDeltaTime = m_UseRigidbodyForMotion ? m_CachedNetworkManager.RealTimeProvider.FixedDeltaTime : m_CachedNetworkManager.RealTimeProvider.DeltaTime;
#else
@@ -3314,7 +3344,7 @@ namespace Unity.Netcode.Components
//offset = m_NetworkTransformTickRegistration.Offset;
//}
var cachedRenderTime = serverTime.TimeTicksAgo(ticksAgo, offset).Time;
var cachedRenderTime = serverTime.TimeTicksAgo(ticksAgo).Time;
// Now only update the interpolators for the portions of the transform being synchronized
if (SynchronizePosition)
@@ -3388,28 +3418,32 @@ namespace Unity.Netcode.Components
#endif
/// <summary>
/// Override this method and return false to switch to owner authoritative mode
/// Determines whether the <see cref="NetworkTransform"/> is <see cref="AuthorityModes.Server"/> or <see cref="AuthorityModes.Owner"/> based on the <see cref="AuthorityMode"/> property.
/// You can override this method to control this logic.
/// </summary>
/// <returns>(<see cref="true"/> or <see cref="false"/>) where when false it runs as owner-client authoritative</returns>
/// <returns><see cref="true"/> or <see cref="false"/></returns>
protected virtual bool OnIsServerAuthoritative()
{
if (m_CachedNetworkManager)
{
return !m_CachedNetworkManager.DistributedAuthorityMode;
}
return true;
return AuthorityMode == AuthorityModes.Server;
}
/// <summary>
/// Method to determine if this <see cref="NetworkTransform"/> instance is owner or server authoritative.
/// </summary>
/// <remarks>
/// Used by <see cref="NetworkRigidbody"/> to determines if this is server or owner authoritative.
/// When using a <see cref="NetworkTopologyTypes.DistributedAuthority"/> <see cref="NetworkConfig.NetworkTopology"/>, this will always be viewed as a <see cref="AuthorityModes.Owner"/> authoritative motion model.
/// </remarks>
/// <returns><see cref="true"/> or <see cref="false"/></returns>
public bool IsServerAuthoritative()
{
return OnIsServerAuthoritative();
if (m_CachedNetworkManager && m_CachedNetworkManager.DistributedAuthorityMode)
{
return false;
}
else
{
return OnIsServerAuthoritative();
}
}
#endregion
@@ -3451,9 +3485,10 @@ namespace Unity.Netcode.Components
private NetworkTransformMessage m_OutboundMessage = new NetworkTransformMessage();
internal void SerializeMessage(FastBufferWriter writer, int targetVersion)
internal int SerializeMessage(FastBufferWriter writer, int targetVersion)
{
var networkObject = NetworkObject;
var position = writer.Position;
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
BytePacker.WriteValueBitPacked(writer, (int)NetworkBehaviourId);
writer.WriteNetworkSerializable(m_LocalAuthoritativeNetworkState);
@@ -3470,6 +3505,7 @@ namespace Unity.Netcode.Components
BytePacker.WriteValuePacked(writer, targetId);
}
}
return writer.Position - position;
}
/// <summary>
@@ -3482,7 +3518,7 @@ namespace Unity.Netcode.Components
return;
}
bool isServerAuthoritative = OnIsServerAuthoritative();
bool isServerAuthoritative = IsServerAuthoritative();
if (isServerAuthoritative && !IsServer)
{
Debug.LogError($"Server authoritative {nameof(NetworkTransform)} can only be updated by the server!");
@@ -3526,6 +3562,7 @@ namespace Unity.Netcode.Components
// Clients (owner authoritative) send messages to the server-host
NetworkManager.MessageManager.SendMessage(ref m_OutboundMessage, networkDelivery, NetworkManager.ServerClientId);
}
m_LocalAuthoritativeNetworkState.LastSerializedSize = m_OutboundMessage.BytesWritten;
}
#endregion