com.unity.netcode.gameobjects@2.0.0-exp.4
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.4] - 2024-05-31 ### Added - Added `NetworkRigidbodyBase.AttachToFixedJoint` and `NetworkRigidbodyBase.DetachFromFixedJoint` to replace parenting for rigid bodies that have `NetworkRigidbodyBase.UseRigidBodyForMotion` enabled. (#2933) - Added `NetworkBehaviour.OnNetworkPreSpawn` and `NetworkBehaviour.OnNetworkPostSpawn` methods that provide the ability to handle pre and post spawning actions during the `NetworkObject` spawn sequence. (#2912) - Added a client-side only `NetworkBehaviour.OnNetworkSessionSynchronized` convenience method that is invoked on all `NetworkBehaviour`s after a newly joined client has finished synchronizing with the network session in progress. (#2912) - Added `NetworkBehaviour.OnInSceneObjectsSpawned` convenience method that is invoked when all in-scene `NetworkObject`s have been spawned after a scene has been loaded or upon a host or server starting. (#2912) ### Fixed - Fixed issue where non-authoritative rigid bodies with `NetworkRigidbodyBase.UseRigidBodyForMotion` enabled would constantly log errors about the renderTime being before `StartTimeConsumed`. (#2933) - Fixed issue where in-scene placed NetworkObjects could be destroyed if a client disconnects early and/or before approval. (#2924) - Fixed issue where a `NetworkObject` component's associated `NetworkBehaviour` components would not be detected if scene loading is disabled in the editor and the currently loaded scene has in-scene placed `NetworkObject`s. (#2912) - Fixed issue where an in-scene placed `NetworkObject` with `NetworkTransform` that is also parented under a `GameObject` would not properly synchronize when the parent `GameObject` had a world space position other than 0,0,0. (#2898) ### Changed - Change all the access modifiers of test class from Public to Internal (#2930) - Changed messages are now sorted by enum values as opposed to ordinally sorting the messages by their type name. (#2929) - Changed `NetworkClient.SessionModeTypes` to `NetworkClient.NetworkTopologyTypes`. (#2875) - Changed `NetworkClient.SessionModeType` to `NetworkClient.NetworkTopologyType`. (#2875) - Changed `NetworkConfig.SessionMode` to `NeworkConfig.NetworkTopology`. (#2875)
This commit is contained in:
8
Runtime/Components.meta
Normal file
8
Runtime/Components.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e42215d00468b549bbc69ebf8a74a1e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
170
Runtime/Components/HalfVector3.cs
Normal file
170
Runtime/Components/HalfVector3.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Half float precision <see cref="Vector3"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The Vector3T<ushort> values are half float values returned by <see cref="Mathf.FloatToHalf(float)"/> for each
|
||||
/// individual axis and the 16 bits of the half float are stored as <see cref="ushort"/> values since C# does not have
|
||||
/// a half float type.
|
||||
/// </remarks>
|
||||
public struct HalfVector3 : INetworkSerializable
|
||||
{
|
||||
internal const int Length = 3;
|
||||
|
||||
/// <summary>
|
||||
/// The half float precision value of the x-axis as a <see cref="half"/>.
|
||||
/// </summary>
|
||||
public half X => Axis.x;
|
||||
/// <summary>
|
||||
/// The half float precision value of the y-axis as a <see cref="half"/>.
|
||||
/// </summary>
|
||||
public half Y => Axis.y;
|
||||
/// <summary>
|
||||
/// The half float precision value of the z-axis as a <see cref="half"/>.
|
||||
/// </summary>
|
||||
public half Z => Axis.z;
|
||||
|
||||
/// <summary>
|
||||
/// Used to store the half float precision values as a <see cref="half3"/>
|
||||
/// </summary>
|
||||
public half3 Axis;
|
||||
|
||||
/// <summary>
|
||||
/// Determine which axis will be synchronized during serialization
|
||||
/// </summary>
|
||||
public bool3 AxisToSynchronize;
|
||||
|
||||
/// <summary>
|
||||
/// Directly sets each axial value to the passed in full precision values
|
||||
/// that are converted to half precision
|
||||
/// </summary>
|
||||
internal void Set(float x, float y, float z)
|
||||
{
|
||||
Axis.x = math.half(x);
|
||||
Axis.y = math.half(y);
|
||||
Axis.z = math.half(z);
|
||||
}
|
||||
|
||||
private void SerializeWrite(FastBufferWriter writer)
|
||||
{
|
||||
for (int i = 0; i < Length; i++)
|
||||
{
|
||||
if (AxisToSynchronize[i])
|
||||
{
|
||||
writer.WriteUnmanagedSafe(Axis[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SerializeRead(FastBufferReader reader)
|
||||
{
|
||||
for (int i = 0; i < Length; i++)
|
||||
{
|
||||
if (AxisToSynchronize[i])
|
||||
{
|
||||
var axisValue = Axis[i];
|
||||
reader.ReadUnmanagedSafe(out axisValue);
|
||||
Axis[i] = axisValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The serialization implementation of <see cref="INetworkSerializable"/>.
|
||||
/// </summary>
|
||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
|
||||
{
|
||||
if (serializer.IsReader)
|
||||
{
|
||||
SerializeRead(serializer.GetFastBufferReader());
|
||||
}
|
||||
else
|
||||
{
|
||||
SerializeWrite(serializer.GetFastBufferWriter());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the full precision value as a <see cref="Vector3"/>.
|
||||
/// </summary>
|
||||
/// <returns>a <see cref="Vector3"/> as the full precision value.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vector3 ToVector3()
|
||||
{
|
||||
Vector3 fullPrecision = Vector3.zero;
|
||||
Vector3 fullConversion = math.float3(Axis);
|
||||
for (int i = 0; i < Length; i++)
|
||||
{
|
||||
if (AxisToSynchronize[i])
|
||||
{
|
||||
fullPrecision[i] = fullConversion[i];
|
||||
}
|
||||
}
|
||||
return fullPrecision;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a full precision <see cref="Vector3"/> to half precision and updates the current instance.
|
||||
/// </summary>
|
||||
/// <param name="vector3">The <see cref="Vector3"/> to convert.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void UpdateFrom(ref Vector3 vector3)
|
||||
{
|
||||
var half3Full = math.half3(vector3);
|
||||
for (int i = 0; i < Length; i++)
|
||||
{
|
||||
if (AxisToSynchronize[i])
|
||||
{
|
||||
Axis[i] = half3Full[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="vector3">The initial axial values (converted to half floats) when instantiated.</param>
|
||||
/// <param name="vector3AxisToSynchronize">The axis to synchronize.</param>
|
||||
public HalfVector3(Vector3 vector3, bool3 axisToSynchronize)
|
||||
{
|
||||
Axis = half3.zero;
|
||||
AxisToSynchronize = axisToSynchronize;
|
||||
UpdateFrom(ref vector3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that defaults to all axis being synchronized.
|
||||
/// </summary>
|
||||
/// <param name="vector3">The initial axial values (converted to half floats) when instantiated.</param>
|
||||
public HalfVector3(Vector3 vector3) : this(vector3, math.bool3(true))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="x">The initial x axis (converted to half float) value when instantiated.</param>
|
||||
/// <param name="y">The initial y axis (converted to half float) value when instantiated.</param>
|
||||
/// <param name="z">The initial z axis (converted to half float) value when instantiated.</param>
|
||||
/// <param name="axisToSynchronize">The axis to synchronize.</param>
|
||||
public HalfVector3(float x, float y, float z, bool3 axisToSynchronize) : this(new Vector3(x, y, z), axisToSynchronize)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that defaults to all axis being synchronized.
|
||||
/// </summary>
|
||||
/// <param name="x">The initial x axis (converted to half float) value when instantiated.</param>
|
||||
/// <param name="y">The initial y axis (converted to half float) value when instantiated.</param>
|
||||
/// <param name="z">The initial z axis (converted to half float) value when instantiated.</param>
|
||||
public HalfVector3(float x, float y, float z) : this(new Vector3(x, y, z), math.bool3(true))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Runtime/Components/HalfVector3.cs.meta
Normal file
11
Runtime/Components/HalfVector3.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0e371533eaeac446b16b10886f64f84
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
137
Runtime/Components/HalfVector4.cs
Normal file
137
Runtime/Components/HalfVector4.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Half Precision <see cref="Vector4"/> that can also be used to convert a <see cref="Quaternion"/> to half precision.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The Vector4T<ushort> values are half float values returned by <see cref="Mathf.FloatToHalf(float)"/> for each
|
||||
/// individual axis and the 16 bits of the half float are stored as <see cref="ushort"/> values since C# does not have
|
||||
/// a half float type.
|
||||
/// </remarks>
|
||||
public struct HalfVector4 : INetworkSerializable
|
||||
{
|
||||
internal const int Length = 4;
|
||||
/// <summary>
|
||||
/// The half float precision value of the x-axis as a <see cref="half"/>.
|
||||
/// </summary>
|
||||
public half X => Axis.x;
|
||||
|
||||
/// <summary>
|
||||
/// The half float precision value of the y-axis as a <see cref="half"/>.
|
||||
/// </summary>
|
||||
public half Y => Axis.y;
|
||||
|
||||
/// <summary>
|
||||
/// The half float precision value of the z-axis as a <see cref="half"/>.
|
||||
/// </summary>
|
||||
public half Z => Axis.z;
|
||||
|
||||
/// <summary>
|
||||
/// The half float precision value of the w-axis as a <see cref="half"/>.
|
||||
/// </summary>
|
||||
public half W => Axis.w;
|
||||
|
||||
/// <summary>
|
||||
/// Used to store the half float precision values as a <see cref="half4"/>
|
||||
/// </summary>
|
||||
public half4 Axis;
|
||||
|
||||
private void SerializeWrite(FastBufferWriter writer)
|
||||
{
|
||||
for (int i = 0; i < Length; i++)
|
||||
{
|
||||
writer.WriteUnmanagedSafe(Axis[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void SerializeRead(FastBufferReader reader)
|
||||
{
|
||||
for (int i = 0; i < Length; i++)
|
||||
{
|
||||
var axisValue = Axis[i];
|
||||
reader.ReadUnmanagedSafe(out axisValue);
|
||||
Axis[i] = axisValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The serialization implementation of <see cref="INetworkSerializable"/>.
|
||||
/// </summary>
|
||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
|
||||
{
|
||||
if (serializer.IsReader)
|
||||
{
|
||||
SerializeRead(serializer.GetFastBufferReader());
|
||||
}
|
||||
else
|
||||
{
|
||||
SerializeWrite(serializer.GetFastBufferWriter());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts this instance to a full precision <see cref="Vector4"/>.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Vector4"/> as the full precision value.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vector4 ToVector4()
|
||||
{
|
||||
return math.float4(Axis);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts this instance to a full precision <see cref="Quaternion"/>.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Quaternion"/> as the full precision value.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Quaternion ToQuaternion()
|
||||
{
|
||||
return math.quaternion(Axis);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a full precision <see cref="Vector4"/> to half precision and updates the current instance.
|
||||
/// </summary>
|
||||
/// <param name="vector4">The <see cref="Vector4"/> to convert and update this instance with.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void UpdateFrom(ref Vector4 vector4)
|
||||
{
|
||||
Axis = math.half4(vector4);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a full precision <see cref="Vector4"/> to half precision and updates the current instance.
|
||||
/// </summary>
|
||||
/// <param name="quaternion">The <see cref="Quaternion"/> to convert and update this instance with.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void UpdateFrom(ref Quaternion quaternion)
|
||||
{
|
||||
Axis = math.half4(math.half(quaternion.x), math.half(quaternion.y), math.half(quaternion.z), math.half(quaternion.w));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="vector4">The initial axial values (converted to half floats) when instantiated.</param>
|
||||
public HalfVector4(Vector4 vector4)
|
||||
{
|
||||
Axis = default;
|
||||
UpdateFrom(ref vector4);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="x">The initial x axis (converted to half float) value when instantiated.</param>
|
||||
/// <param name="y">The initial y axis (converted to half float) value when instantiated.</param>
|
||||
/// <param name="z">The initial z axis (converted to half float) value when instantiated.</param>
|
||||
/// <param name="w">The initial w axis (converted to half float) value when instantiated.</param>
|
||||
public HalfVector4(float x, float y, float z, float w) : this(new Vector4(x, y, z, w))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Runtime/Components/HalfVector4.cs.meta
Normal file
11
Runtime/Components/HalfVector4.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 03c78136f41ff84499e2a6ac4a7dd7a5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Runtime/Components/Interpolator.meta
Normal file
8
Runtime/Components/Interpolator.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8eb56856ab05d41fa9e422a92acbc109
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
392
Runtime/Components/Interpolator/BufferedLinearInterpolator.cs
Normal file
392
Runtime/Components/Interpolator/BufferedLinearInterpolator.cs
Normal file
@@ -0,0 +1,392 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Solves for incoming values that are jittered
|
||||
/// Partially solves for message loss. Unclamped lerping helps hide this, but not completely
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of interpolated value</typeparam>
|
||||
public abstract class BufferedLinearInterpolator<T> where T : struct
|
||||
{
|
||||
internal float MaxInterpolationBound = 3.0f;
|
||||
private struct BufferedItem
|
||||
{
|
||||
public T Item;
|
||||
public double TimeSent;
|
||||
|
||||
public BufferedItem(T item, double timeSent)
|
||||
{
|
||||
Item = item;
|
||||
TimeSent = timeSent;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// There's two factors affecting interpolation: buffering (set in NetworkManager's NetworkTimeSystem) and interpolation time, which is the amount of time it'll take to reach the target. This is to affect the second one.
|
||||
/// </summary>
|
||||
public float MaximumInterpolationTime = 0.1f;
|
||||
|
||||
private const double k_SmallValue = 9.999999439624929E-11; // copied from Vector3's equal operator
|
||||
|
||||
private T m_InterpStartValue;
|
||||
private T m_CurrentInterpValue;
|
||||
private T m_InterpEndValue;
|
||||
|
||||
private double m_EndTimeConsumed;
|
||||
private double m_StartTimeConsumed;
|
||||
|
||||
private readonly List<BufferedItem> m_Buffer = new List<BufferedItem>(k_BufferCountLimit);
|
||||
|
||||
// Buffer consumption scenarios
|
||||
// Perfect case consumption
|
||||
// | 1 | 2 | 3 |
|
||||
// | 2 | 3 | 4 | consume 1
|
||||
// | 3 | 4 | 5 | consume 2
|
||||
// | 4 | 5 | 6 | consume 3
|
||||
// | 5 | 6 | 7 | consume 4
|
||||
// jittered case
|
||||
// | 1 | 2 | 3 |
|
||||
// | 2 | 3 | | consume 1
|
||||
// | 3 | | | consume 2
|
||||
// | 4 | 5 | 6 | consume 3
|
||||
// | 5 | 6 | 7 | consume 4
|
||||
// bursted case (assuming max count is 5)
|
||||
// | 1 | 2 | 3 |
|
||||
// | 2 | 3 | | consume 1
|
||||
// | 3 | | | consume 2
|
||||
// | | | | consume 3
|
||||
// | | | |
|
||||
// | 4 | 5 | 6 | 7 | 8 | --> consume all and teleport to last value <8> --> this is the nuclear option, ideally this example would consume 4 and 5
|
||||
// instead of jumping to 8, but since in OnValueChange we don't yet have an updated server time (updated in pre-update) to know which value
|
||||
// we should keep and which we should drop, we don't have enough information to do this. Another thing would be to not have the burst in the first place.
|
||||
|
||||
// Constant absolute value for max buffer count instead of dynamic time based value. This is in case we have very low tick rates, so
|
||||
// that we don't have a very small buffer because of this.
|
||||
private const int k_BufferCountLimit = 100;
|
||||
private BufferedItem m_LastBufferedItemReceived;
|
||||
private int m_NbItemsReceivedThisFrame;
|
||||
|
||||
private int m_LifetimeConsumedCount;
|
||||
|
||||
private bool InvalidState => m_Buffer.Count == 0 && m_LifetimeConsumedCount == 0;
|
||||
|
||||
/// <summary>
|
||||
/// Resets interpolator to initial state
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
m_Buffer.Clear();
|
||||
m_EndTimeConsumed = 0.0d;
|
||||
m_StartTimeConsumed = 0.0d;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Teleports current interpolation value to targetValue.
|
||||
/// </summary>
|
||||
/// <param name="targetValue">The target value to teleport instantly</param>
|
||||
/// <param name="serverTime">The current server time</param>
|
||||
public void ResetTo(T targetValue, double serverTime)
|
||||
{
|
||||
m_LifetimeConsumedCount = 1;
|
||||
m_InterpStartValue = targetValue;
|
||||
m_InterpEndValue = targetValue;
|
||||
m_CurrentInterpValue = targetValue;
|
||||
m_Buffer.Clear();
|
||||
m_EndTimeConsumed = 0.0d;
|
||||
m_StartTimeConsumed = 0.0d;
|
||||
|
||||
Update(0, serverTime, serverTime);
|
||||
}
|
||||
|
||||
// todo if I have value 1, 2, 3 and I'm treating 1 to 3, I shouldn't interpolate between 1 and 3, I should interpolate from 1 to 2, then from 2 to 3 to get the best path
|
||||
private void TryConsumeFromBuffer(double renderTime, double serverTime)
|
||||
{
|
||||
int consumedCount = 0;
|
||||
// only consume if we're ready
|
||||
|
||||
// this operation was measured as one of our most expensive, and we should put some thought into this.
|
||||
// NetworkTransform has (currently) 7 buffered linear interpolators (3 position, 3 scale, 1 rot), and
|
||||
// each has its own independent buffer and 'm_endTimeConsume'. That means every frame I have to do 7x
|
||||
// these checks vs. if we tracked these values in a unified way
|
||||
if (renderTime >= m_EndTimeConsumed)
|
||||
{
|
||||
BufferedItem? itemToInterpolateTo = null;
|
||||
// assumes we're using sequenced messages for netvar syncing
|
||||
// buffer contains oldest values first, iterating from end to start to remove elements from list while iterating
|
||||
|
||||
// calling m_Buffer.Count shows up hot in the profiler.
|
||||
for (int i = m_Buffer.Count - 1; i >= 0; i--) // todo stretch: consume ahead if we see we're missing values due to packet loss
|
||||
{
|
||||
var bufferedValue = m_Buffer[i];
|
||||
// Consume when ready and interpolate to last value we can consume. This can consume multiple values from the buffer
|
||||
if (bufferedValue.TimeSent <= serverTime)
|
||||
{
|
||||
if (!itemToInterpolateTo.HasValue || bufferedValue.TimeSent > itemToInterpolateTo.Value.TimeSent)
|
||||
{
|
||||
if (m_LifetimeConsumedCount == 0)
|
||||
{
|
||||
// if interpolator not initialized, teleport to first value when available
|
||||
m_StartTimeConsumed = bufferedValue.TimeSent;
|
||||
m_InterpStartValue = bufferedValue.Item;
|
||||
}
|
||||
else if (consumedCount == 0)
|
||||
{
|
||||
// Interpolating to new value, end becomes start. We then look in our buffer for a new end.
|
||||
m_StartTimeConsumed = m_EndTimeConsumed;
|
||||
m_InterpStartValue = m_InterpEndValue;
|
||||
}
|
||||
|
||||
if (bufferedValue.TimeSent > m_EndTimeConsumed)
|
||||
{
|
||||
itemToInterpolateTo = bufferedValue;
|
||||
m_EndTimeConsumed = bufferedValue.TimeSent;
|
||||
m_InterpEndValue = bufferedValue.Item;
|
||||
}
|
||||
}
|
||||
|
||||
m_Buffer.RemoveAt(i);
|
||||
consumedCount++;
|
||||
m_LifetimeConsumedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience version of 'Update' mainly for testing
|
||||
/// the reason we don't want to always call this version is so that on the calling side we can compute
|
||||
/// the renderTime once for the many things being interpolated (and the many interpolators per object)
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">time since call</param>
|
||||
/// <param name="serverTime">current server time</param>
|
||||
/// <returns>The newly interpolated value of type 'T'</returns>
|
||||
public T Update(float deltaTime, NetworkTime serverTime)
|
||||
{
|
||||
return Update(deltaTime, serverTime.TimeTicksAgo(1).Time, serverTime.Time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call to update the state of the interpolators before reading out
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">time since last call</param>
|
||||
/// <param name="renderTime">our current time</param>
|
||||
/// <param name="serverTime">current server time</param>
|
||||
/// <returns>The newly interpolated value of type 'T'</returns>
|
||||
public T Update(float deltaTime, double renderTime, double serverTime)
|
||||
{
|
||||
TryConsumeFromBuffer(renderTime, serverTime);
|
||||
|
||||
if (InvalidState)
|
||||
{
|
||||
throw new InvalidOperationException("trying to update interpolator when no data has been added to it yet");
|
||||
}
|
||||
|
||||
// Interpolation example to understand the math below
|
||||
// 4 4.5 6 6.5
|
||||
// | | | |
|
||||
// A render B Server
|
||||
|
||||
if (m_LifetimeConsumedCount >= 1) // shouldn't interpolate between default values, let's wait to receive data first, should only interpolate between real measurements
|
||||
{
|
||||
float t = 1.0f;
|
||||
double range = m_EndTimeConsumed - m_StartTimeConsumed;
|
||||
if (range > k_SmallValue)
|
||||
{
|
||||
var rangeFactor = 1.0f / (float)range;
|
||||
|
||||
t = ((float)renderTime - (float)m_StartTimeConsumed) * rangeFactor;
|
||||
|
||||
if (t < 0.0f)
|
||||
{
|
||||
// There is no mechanism to guarantee renderTime to not be before m_StartTimeConsumed
|
||||
// This clamps t to a minimum of 0 and fixes issues with longer frames and pauses
|
||||
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogError($"renderTime was before m_StartTimeConsumed. This should never happen. {nameof(renderTime)} is {renderTime}, {nameof(m_StartTimeConsumed)} is {m_StartTimeConsumed}");
|
||||
}
|
||||
t = 0.0f;
|
||||
}
|
||||
|
||||
if (t > MaxInterpolationBound) // max extrapolation
|
||||
{
|
||||
// TODO this causes issues with teleport, investigate
|
||||
t = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
var target = InterpolateUnclamped(m_InterpStartValue, m_InterpEndValue, t);
|
||||
m_CurrentInterpValue = Interpolate(m_CurrentInterpValue, target, deltaTime / MaximumInterpolationTime); // second interpolate to smooth out extrapolation jumps
|
||||
}
|
||||
|
||||
m_NbItemsReceivedThisFrame = 0;
|
||||
return m_CurrentInterpValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add measurements to be used during interpolation. These will be buffered before being made available to be displayed as "latest value".
|
||||
/// </summary>
|
||||
/// <param name="newMeasurement">The new measurement value to use</param>
|
||||
/// <param name="sentTime">The time to record for measurement</param>
|
||||
public void AddMeasurement(T newMeasurement, double sentTime)
|
||||
{
|
||||
m_NbItemsReceivedThisFrame++;
|
||||
|
||||
// This situation can happen after a game is paused. When starting to receive again, the server will have sent a bunch of messages in the meantime
|
||||
// instead of going through thousands of value updates just to get a big teleport, we're giving up on interpolation and teleporting to the latest value
|
||||
if (m_NbItemsReceivedThisFrame > k_BufferCountLimit)
|
||||
{
|
||||
if (m_LastBufferedItemReceived.TimeSent < sentTime)
|
||||
{
|
||||
m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime);
|
||||
ResetTo(newMeasurement, sentTime);
|
||||
// Next line keeps renderTime above m_StartTimeConsumed. Fixes pause/unpause issues
|
||||
m_Buffer.Add(m_LastBufferedItemReceived);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Part the of reason for disabling extrapolation is how we add and use measurements over time.
|
||||
// TODO: Add detailed description of this area in Jira ticket
|
||||
if (sentTime > m_EndTimeConsumed || m_LifetimeConsumedCount == 0) // treat only if value is newer than the one being interpolated to right now
|
||||
{
|
||||
m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime);
|
||||
m_Buffer.Add(m_LastBufferedItemReceived);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets latest value from the interpolator. This is updated every update as time goes by.
|
||||
/// </summary>
|
||||
/// <returns>The current interpolated value of type 'T'</returns>
|
||||
public T GetInterpolatedValue()
|
||||
{
|
||||
return m_CurrentInterpValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method to override and adapted to the generic type. This assumes interpolation for that value will be clamped.
|
||||
/// </summary>
|
||||
/// <param name="start">The start value (min)</param>
|
||||
/// <param name="end">The end value (max)</param>
|
||||
/// <param name="time">The time value used to interpolate between start and end values (pos)</param>
|
||||
/// <returns>The interpolated value</returns>
|
||||
protected abstract T Interpolate(T start, T end, float time);
|
||||
|
||||
/// <summary>
|
||||
/// Method to override and adapted to the generic type. This assumes interpolation for that value will not be clamped.
|
||||
/// </summary>
|
||||
/// <param name="start">The start value (min)</param>
|
||||
/// <param name="end">The end value (max)</param>
|
||||
/// <param name="time">The time value used to interpolate between start and end values (pos)</param>
|
||||
/// <returns>The interpolated value</returns>
|
||||
protected abstract T InterpolateUnclamped(T start, T end, float time);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// This is a buffered linear interpolator for a <see cref="float"/> type value
|
||||
/// </remarks>
|
||||
public class BufferedLinearInterpolatorFloat : BufferedLinearInterpolator<float>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override float InterpolateUnclamped(float start, float end, float time)
|
||||
{
|
||||
// Disabling Extrapolation:
|
||||
// TODO: Add Jira Ticket
|
||||
return Mathf.Lerp(start, end, time);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override float Interpolate(float start, float end, float time)
|
||||
{
|
||||
return Mathf.Lerp(start, end, time);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// This is a buffered linear interpolator for a <see cref="Quaternion"/> type value
|
||||
/// </remarks>
|
||||
public class BufferedLinearInterpolatorQuaternion : BufferedLinearInterpolator<Quaternion>
|
||||
{
|
||||
/// <summary>
|
||||
/// Use <see cref="Quaternion.Slerp"/> when <see cref="true"/>.
|
||||
/// Use <see cref="Quaternion.Lerp"/> when <see cref="false"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When using half precision (due to the imprecision) using <see cref="Quaternion.Lerp"/> is
|
||||
/// less processor intensive (i.e. precision is already "imprecise").
|
||||
/// When using full precision (to maintain precision) using <see cref="Quaternion.Slerp"/> is
|
||||
/// more processor intensive yet yields more precise results.
|
||||
/// </remarks>
|
||||
public bool IsSlerp;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion end, float time)
|
||||
{
|
||||
if (IsSlerp)
|
||||
{
|
||||
return Quaternion.Slerp(start, end, time);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Quaternion.Lerp(start, end, time);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Quaternion Interpolate(Quaternion start, Quaternion end, float time)
|
||||
{
|
||||
if (IsSlerp)
|
||||
{
|
||||
return Quaternion.Slerp(start, end, time);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Quaternion.Lerp(start, end, time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="BufferedLinearInterpolator<T>"/> <see cref="Vector3"/> implementation.
|
||||
/// </summary>
|
||||
public class BufferedLinearInterpolatorVector3 : BufferedLinearInterpolator<Vector3>
|
||||
{
|
||||
/// <summary>
|
||||
/// Use <see cref="Vector3.Slerp"/> when <see cref="true"/>.
|
||||
/// Use <see cref="Vector3.Lerp"/> when <see cref="false"/>
|
||||
/// </summary>
|
||||
public bool IsSlerp;
|
||||
/// <inheritdoc />
|
||||
protected override Vector3 InterpolateUnclamped(Vector3 start, Vector3 end, float time)
|
||||
{
|
||||
if (IsSlerp)
|
||||
{
|
||||
return Vector3.Slerp(start, end, time);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Vector3.Lerp(start, end, time);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Vector3 Interpolate(Vector3 start, Vector3 end, float time)
|
||||
{
|
||||
if (IsSlerp)
|
||||
{
|
||||
return Vector3.Slerp(start, end, time);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Vector3.Lerp(start, end, time);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a12ebf95bdb4445d9a16e4b6adadb6aa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Runtime/Components/Messages.meta
Normal file
8
Runtime/Components/Messages.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9db1d18fa0117f4da5e8e65386b894a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1498
Runtime/Components/NetworkAnimator.cs
Normal file
1498
Runtime/Components/NetworkAnimator.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Runtime/Components/NetworkAnimator.cs.meta
Normal file
11
Runtime/Components/NetworkAnimator.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e8d0727d5ae3244e3b569694d3912374
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
221
Runtime/Components/NetworkDeltaPosition.cs
Normal file
221
Runtime/Components/NetworkDeltaPosition.cs
Normal file
@@ -0,0 +1,221 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to synchromnize delta position when half float precision is enabled
|
||||
/// </summary>
|
||||
public struct NetworkDeltaPosition : INetworkSerializable
|
||||
{
|
||||
internal const float MaxDeltaBeforeAdjustment = 64f;
|
||||
|
||||
/// <summary>
|
||||
/// The HalfVector3 used to synchronize the delta in position
|
||||
/// </summary>
|
||||
public HalfVector3 HalfVector3;
|
||||
|
||||
internal Vector3 CurrentBasePosition;
|
||||
internal Vector3 PrecisionLossDelta;
|
||||
internal Vector3 HalfDeltaConvertedBack;
|
||||
internal Vector3 PreviousPosition;
|
||||
internal Vector3 DeltaPosition;
|
||||
internal int NetworkTick;
|
||||
|
||||
internal bool SynchronizeBase;
|
||||
|
||||
internal bool CollapsedDeltaIntoBase;
|
||||
|
||||
/// <summary>
|
||||
/// The serialization implementation of <see cref="INetworkSerializable"/>
|
||||
/// </summary>
|
||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
|
||||
{
|
||||
if (!SynchronizeBase)
|
||||
{
|
||||
HalfVector3.NetworkSerialize(serializer);
|
||||
}
|
||||
else
|
||||
{
|
||||
serializer.SerializeValue(ref DeltaPosition);
|
||||
serializer.SerializeValue(ref CurrentBasePosition);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the full precision value of Vector3 position while also potentially updating the current base position.
|
||||
/// </summary>
|
||||
/// <param name="networkTick">Use the current network tick value.</param>
|
||||
/// <returns>The full position as a <see cref="Vector3"/>.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vector3 ToVector3(int networkTick)
|
||||
{
|
||||
// When synchronizing, it is possible to have a state update arrive
|
||||
// for the same synchronization network tick. Under this scenario,
|
||||
// we only want to return the existing CurrentBasePosition + DeltaPosition
|
||||
// values and not process the X, Y, or Z values.
|
||||
// (See the constructors below)
|
||||
if (networkTick == NetworkTick)
|
||||
{
|
||||
return CurrentBasePosition + DeltaPosition;
|
||||
}
|
||||
for (int i = 0; i < HalfVector3.Length; i++)
|
||||
{
|
||||
if (HalfVector3.AxisToSynchronize[i])
|
||||
{
|
||||
DeltaPosition[i] = Mathf.HalfToFloat(HalfVector3.Axis[i].value);
|
||||
// If we exceed or are equal to the maximum delta value then we need to
|
||||
// apply the delta to the CurrentBasePosition value and reset the delta
|
||||
// position for the axis.
|
||||
if (Mathf.Abs(DeltaPosition[i]) >= MaxDeltaBeforeAdjustment)
|
||||
{
|
||||
CurrentBasePosition[i] += DeltaPosition[i];
|
||||
DeltaPosition[i] = 0.0f;
|
||||
HalfVector3.Axis[i] = half.zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
return CurrentBasePosition + DeltaPosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current base position (excluding the delta position offset).
|
||||
/// </summary>
|
||||
/// <returns>The current base position as a <see cref="Vector3"/></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vector3 GetCurrentBasePosition()
|
||||
{
|
||||
return CurrentBasePosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the full position which includes the delta offset position.
|
||||
/// </summary>
|
||||
/// <returns>The full position as a <see cref="Vector3"/>.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vector3 GetFullPosition()
|
||||
{
|
||||
return CurrentBasePosition + DeltaPosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The half float vector3 version of the current delta position.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only applies to the authoritative side for <see cref="NetworkTransform"/> instances.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vector3 GetConvertedDelta()
|
||||
{
|
||||
return HalfDeltaConvertedBack;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The full precision current delta position.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Authoritative: Will have no precision loss
|
||||
/// Non-Authoritative: Has the current network tick's loss of precision.
|
||||
/// Precision loss adjustments are one network tick behind on the
|
||||
/// non-authoritative side.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vector3 GetDeltaPosition()
|
||||
{
|
||||
return DeltaPosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the position delta based off of the current base position.
|
||||
/// </summary>
|
||||
/// <param name="vector3">The full precision <see cref="Vector3"/> value to (converted to half floats) used to determine the delta offset positon.</param>
|
||||
/// <param name="networkTick">Set the current network tick value when updating.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void UpdateFrom(ref Vector3 vector3, int networkTick)
|
||||
{
|
||||
CollapsedDeltaIntoBase = false;
|
||||
NetworkTick = networkTick;
|
||||
DeltaPosition = (vector3 + PrecisionLossDelta) - CurrentBasePosition;
|
||||
for (int i = 0; i < HalfVector3.Length; i++)
|
||||
{
|
||||
if (HalfVector3.AxisToSynchronize[i])
|
||||
{
|
||||
HalfVector3.Axis[i] = math.half(DeltaPosition[i]);
|
||||
HalfDeltaConvertedBack[i] = Mathf.HalfToFloat(HalfVector3.Axis[i].value);
|
||||
PrecisionLossDelta[i] = DeltaPosition[i] - HalfDeltaConvertedBack[i];
|
||||
if (Mathf.Abs(HalfDeltaConvertedBack[i]) >= MaxDeltaBeforeAdjustment)
|
||||
{
|
||||
CurrentBasePosition[i] += HalfDeltaConvertedBack[i];
|
||||
HalfDeltaConvertedBack[i] = 0.0f;
|
||||
DeltaPosition[i] = 0.0f;
|
||||
CollapsedDeltaIntoBase = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < HalfVector3.Length; i++)
|
||||
{
|
||||
if (HalfVector3.AxisToSynchronize[i])
|
||||
{
|
||||
PreviousPosition[i] = vector3[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="vector3">The initial axial values (converted to half floats) when instantiated.</param>
|
||||
/// <param name="networkTick">Set the network tick value to the current network tick when instantiating.</param>
|
||||
/// <param name="axisToSynchronize">The axis to be synchronized.</param>
|
||||
public NetworkDeltaPosition(Vector3 vector3, int networkTick, bool3 axisToSynchronize)
|
||||
{
|
||||
NetworkTick = networkTick;
|
||||
CurrentBasePosition = vector3;
|
||||
PreviousPosition = vector3;
|
||||
PrecisionLossDelta = Vector3.zero;
|
||||
DeltaPosition = Vector3.zero;
|
||||
HalfDeltaConvertedBack = Vector3.zero;
|
||||
HalfVector3 = new HalfVector3(vector3, axisToSynchronize);
|
||||
SynchronizeBase = false;
|
||||
CollapsedDeltaIntoBase = false;
|
||||
UpdateFrom(ref vector3, networkTick);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that defaults to all axis being synchronized.
|
||||
/// </summary>
|
||||
/// <param name="vector3">The initial axial values (converted to half floats) when instantiated.</param>
|
||||
/// <param name="networkTick">Set the network tick value to the current network tick when instantiating.</param>
|
||||
public NetworkDeltaPosition(Vector3 vector3, int networkTick) : this(vector3, networkTick, math.bool3(true))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="x">The initial x axis (converted to half float) value when instantiated.</param>
|
||||
/// <param name="y">The initial y axis (converted to half float) value when instantiated.</param>
|
||||
/// <param name="z">The initial z axis (converted to half float) value when instantiated.</param>
|
||||
/// <param name="networkTick">Set the network tick value to the current network tick when instantiating.</param>
|
||||
/// <param name="axisToSynchronize">The axis to be synchronized.</param>
|
||||
public NetworkDeltaPosition(float x, float y, float z, int networkTick, bool3 axisToSynchronize) :
|
||||
this(new Vector3(x, y, z), networkTick, axisToSynchronize)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="x">The initial x axis (converted to half float) value when instantiated.</param>
|
||||
/// <param name="y">The initial y axis (converted to half float) value when instantiated.</param>
|
||||
/// <param name="z">The initial z axis (converted to half float) value when instantiated.</param>
|
||||
/// <param name="networkTick">Set the network tick value to the current network tick when instantiating.</param>
|
||||
public NetworkDeltaPosition(float x, float y, float z, int networkTick) :
|
||||
this(new Vector3(x, y, z), networkTick, math.bool3(true))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Runtime/Components/NetworkDeltaPosition.cs.meta
Normal file
11
Runtime/Components/NetworkDeltaPosition.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e45e6886578116f4c92fa0fe0d77fb85
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
722
Runtime/Components/NetworkRigidBodyBase.cs
Normal file
722
Runtime/Components/NetworkRigidBodyBase.cs
Normal file
@@ -0,0 +1,722 @@
|
||||
#if COM_UNITY_MODULES_PHYSICS
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// NetworkRigidbodyBase is a unified <see cref="Rigidbody"/> and <see cref="Rigidbody2D"/> integration that helps to synchronize physics motion, collision, and interpolation
|
||||
/// when used with a <see cref="NetworkTransform"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For a customizable netcode Rigidbody, create your own component from this class and use <see cref="Initialize(RigidbodyTypes, NetworkTransform, Rigidbody2D, Rigidbody)"/>
|
||||
/// during instantiation (i.e. invoked from within the Awake method). You can re-initialize after having initialized but only when the <see cref="NetworkObject"/> is not spawned.
|
||||
/// </remarks>
|
||||
public abstract class NetworkRigidbodyBase : NetworkBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// When enabled, the associated <see cref="NetworkTransform"/> will use the Rigidbody/Rigidbody2D to apply and synchronize changes in position, rotation, and
|
||||
/// allows for the use of Rigidbody interpolation/extrapolation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If <see cref="NetworkTransform.Interpolate"/> is enabled, non-authoritative instances can only use Rigidbody interpolation. If a network prefab is set to
|
||||
/// extrapolation and <see cref="NetworkTransform.Interpolate"/> is enabled, then non-authoritative instances will automatically be adjusted to use Rigidbody
|
||||
/// interpolation while the authoritative instance will still use extrapolation.
|
||||
/// </remarks>
|
||||
[Tooltip("When enabled and a NetworkTransform component is attached, the NetworkTransform will use the rigid body for motion and detecting changes in state.")]
|
||||
public bool UseRigidBodyForMotion;
|
||||
|
||||
/// <summary>
|
||||
/// When enabled (default), automatically set the Kinematic state of the Rigidbody based on ownership.
|
||||
/// When disabled, Kinematic state needs to be set by external script(s).
|
||||
/// </summary>
|
||||
public bool AutoUpdateKinematicState = true;
|
||||
|
||||
/// <summary>
|
||||
/// Primarily applies to the <see cref="AutoUpdateKinematicState"/> property when disabled but you still want
|
||||
/// the Rigidbody to be automatically set to Kinematic when despawned.
|
||||
/// </summary>
|
||||
public bool AutoSetKinematicOnDespawn = true;
|
||||
|
||||
// Determines if this is a Rigidbody or Rigidbody2D implementation
|
||||
private bool m_IsRigidbody2D => RigidbodyType == RigidbodyTypes.Rigidbody2D;
|
||||
// Used to cache the authority state of this Rigidbody during the last frame
|
||||
private bool m_IsAuthority;
|
||||
private Rigidbody m_Rigidbody;
|
||||
private Rigidbody2D m_Rigidbody2D;
|
||||
internal NetworkTransform NetworkTransform;
|
||||
private enum InterpolationTypes
|
||||
{
|
||||
None,
|
||||
Interpolate,
|
||||
Extrapolate
|
||||
}
|
||||
private InterpolationTypes m_OriginalInterpolation;
|
||||
|
||||
/// <summary>
|
||||
/// Used to define the type of Rigidbody implemented.
|
||||
/// <see cref=""/>
|
||||
/// </summary>
|
||||
public enum RigidbodyTypes
|
||||
{
|
||||
Rigidbody,
|
||||
Rigidbody2D,
|
||||
}
|
||||
|
||||
public RigidbodyTypes RigidbodyType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the networked Rigidbody based on the <see cref="RigidbodyTypes"/>
|
||||
/// passed in as a parameter.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Cannot be initialized while the associated <see cref="NetworkObject"/> is spawned.
|
||||
/// </remarks>
|
||||
/// <param name="rigidbodyType">type of rigid body being initialized</param>
|
||||
/// <param name="rigidbody2D">(optional) The <see cref="Rigidbody2D"/> to be used</param>
|
||||
/// <param name="rigidbody">(optional) The <see cref="Rigidbody"/> to be used</param>
|
||||
protected void Initialize(RigidbodyTypes rigidbodyType, NetworkTransform networkTransform = null, Rigidbody2D rigidbody2D = null, Rigidbody rigidbody = null)
|
||||
{
|
||||
// Don't initialize if already spawned
|
||||
if (IsSpawned)
|
||||
{
|
||||
Debug.LogError($"[{name}] Attempting to initialize while spawned is not allowed.");
|
||||
return;
|
||||
}
|
||||
RigidbodyType = rigidbodyType;
|
||||
m_Rigidbody2D = rigidbody2D;
|
||||
m_Rigidbody = rigidbody;
|
||||
NetworkTransform = networkTransform;
|
||||
|
||||
if (m_IsRigidbody2D && m_Rigidbody2D == null)
|
||||
{
|
||||
m_Rigidbody2D = GetComponent<Rigidbody2D>();
|
||||
|
||||
}
|
||||
else if (m_Rigidbody == null)
|
||||
{
|
||||
m_Rigidbody = GetComponent<Rigidbody>();
|
||||
}
|
||||
|
||||
SetOriginalInterpolation();
|
||||
|
||||
if (NetworkTransform == null)
|
||||
{
|
||||
NetworkTransform = GetComponent<NetworkTransform>();
|
||||
}
|
||||
|
||||
if (NetworkTransform != null)
|
||||
{
|
||||
NetworkTransform.RegisterRigidbody(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new System.Exception($"[Missing {nameof(NetworkTransform)}] No {nameof(NetworkTransform)} is assigned or can be found during initialization!");
|
||||
}
|
||||
|
||||
if (AutoUpdateKinematicState)
|
||||
{
|
||||
SetIsKinematic(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position of the Rigidbody
|
||||
/// </summary>
|
||||
/// <returns><see cref="Vector3"/></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vector3 GetPosition()
|
||||
{
|
||||
if (m_IsRigidbody2D)
|
||||
{
|
||||
return m_Rigidbody2D.position;
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_Rigidbody.position;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rotation of the Rigidbody
|
||||
/// </summary>
|
||||
/// <returns><see cref="Quaternion"/></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Quaternion GetRotation()
|
||||
{
|
||||
if (m_IsRigidbody2D)
|
||||
{
|
||||
var quaternion = Quaternion.identity;
|
||||
var angles = quaternion.eulerAngles;
|
||||
angles.z = m_Rigidbody2D.rotation;
|
||||
quaternion.eulerAngles = angles;
|
||||
return quaternion;
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_Rigidbody.rotation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the rigid body
|
||||
/// </summary>
|
||||
/// <param name="position">The <see cref="Vector3"/> position to move towards</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void MovePosition(Vector3 position)
|
||||
{
|
||||
if (m_IsRigidbody2D)
|
||||
{
|
||||
m_Rigidbody2D.MovePosition(position);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Rigidbody.MovePosition(position);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Directly applies a position (like teleporting)
|
||||
/// </summary>
|
||||
/// <param name="position"><see cref="Vector3"/> position to apply to the Rigidbody</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetPosition(Vector3 position)
|
||||
{
|
||||
if (m_IsRigidbody2D)
|
||||
{
|
||||
m_Rigidbody2D.position = position;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Rigidbody.position = position;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the rotation and position of the <see cref="GameObject"/>'s <see cref="Transform"/>
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ApplyCurrentTransform()
|
||||
{
|
||||
if (m_IsRigidbody2D)
|
||||
{
|
||||
m_Rigidbody2D.position = transform.position;
|
||||
m_Rigidbody2D.rotation = transform.eulerAngles.z;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Rigidbody.position = transform.position;
|
||||
m_Rigidbody.rotation = transform.rotation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotatates the Rigidbody towards a specified rotation
|
||||
/// </summary>
|
||||
/// <param name="rotation">The rotation expressed as a <see cref="Quaternion"/></param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void MoveRotation(Quaternion rotation)
|
||||
{
|
||||
if (m_IsRigidbody2D)
|
||||
{
|
||||
var quaternion = Quaternion.identity;
|
||||
var angles = quaternion.eulerAngles;
|
||||
angles.z = m_Rigidbody2D.rotation;
|
||||
quaternion.eulerAngles = angles;
|
||||
m_Rigidbody2D.MoveRotation(quaternion);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Rigidbody.MoveRotation(rotation);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a rotation to the Rigidbody
|
||||
/// </summary>
|
||||
/// <param name="rotation">The rotation to apply expressed as a <see cref="Quaternion"/></param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetRotation(Quaternion rotation)
|
||||
{
|
||||
if (m_IsRigidbody2D)
|
||||
{
|
||||
m_Rigidbody2D.rotation = rotation.eulerAngles.z;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Rigidbody.rotation = rotation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the original interpolation of the Rigidbody while taking the Rigidbody type into consideration
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SetOriginalInterpolation()
|
||||
{
|
||||
if (m_IsRigidbody2D)
|
||||
{
|
||||
switch (m_Rigidbody2D.interpolation)
|
||||
{
|
||||
case RigidbodyInterpolation2D.None:
|
||||
{
|
||||
m_OriginalInterpolation = InterpolationTypes.None;
|
||||
break;
|
||||
}
|
||||
case RigidbodyInterpolation2D.Interpolate:
|
||||
{
|
||||
m_OriginalInterpolation = InterpolationTypes.Interpolate;
|
||||
break;
|
||||
}
|
||||
case RigidbodyInterpolation2D.Extrapolate:
|
||||
{
|
||||
m_OriginalInterpolation = InterpolationTypes.Extrapolate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (m_Rigidbody.interpolation)
|
||||
{
|
||||
case RigidbodyInterpolation.None:
|
||||
{
|
||||
m_OriginalInterpolation = InterpolationTypes.None;
|
||||
break;
|
||||
}
|
||||
case RigidbodyInterpolation.Interpolate:
|
||||
{
|
||||
m_OriginalInterpolation = InterpolationTypes.Interpolate;
|
||||
break;
|
||||
}
|
||||
case RigidbodyInterpolation.Extrapolate:
|
||||
{
|
||||
m_OriginalInterpolation = InterpolationTypes.Extrapolate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wakes the Rigidbody if it is sleeping
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WakeIfSleeping()
|
||||
{
|
||||
if (m_IsRigidbody2D)
|
||||
{
|
||||
if (m_Rigidbody2D.IsSleeping())
|
||||
{
|
||||
m_Rigidbody2D.WakeUp();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_Rigidbody.IsSleeping())
|
||||
{
|
||||
m_Rigidbody.WakeUp();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Puts the Rigidbody to sleep
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SleepRigidbody()
|
||||
{
|
||||
if (m_IsRigidbody2D)
|
||||
{
|
||||
m_Rigidbody2D.Sleep();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Rigidbody.Sleep();
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsKinematic()
|
||||
{
|
||||
if (m_IsRigidbody2D)
|
||||
{
|
||||
return m_Rigidbody2D.isKinematic;
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_Rigidbody.isKinematic;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the kinematic state of the Rigidbody and handles updating the Rigidbody's
|
||||
/// interpolation setting based on the Kinematic state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When using the Rigidbody for <see cref="NetworkTransform"/> motion, this automatically
|
||||
/// adjusts from extrapolation to interpolation if:
|
||||
/// - The Rigidbody was originally set to extrapolation
|
||||
/// - The NetworkTransform is set to interpolate
|
||||
/// When the two above conditions are true:
|
||||
/// - When switching from non-kinematic to kinematic this will automatically
|
||||
/// switch the Rigidbody from extrapolation to interpolate.
|
||||
/// - When switching from kinematic to non-kinematic this will automatically
|
||||
/// switch the Rigidbody from interpolation back to extrapolation.
|
||||
/// </remarks>
|
||||
/// <param name="isKinematic"></param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetIsKinematic(bool isKinematic)
|
||||
{
|
||||
if (m_IsRigidbody2D)
|
||||
{
|
||||
m_Rigidbody2D.isKinematic = isKinematic;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Rigidbody.isKinematic = isKinematic;
|
||||
}
|
||||
|
||||
// If we are not spawned, then exit early
|
||||
if (!IsSpawned)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (UseRigidBodyForMotion)
|
||||
{
|
||||
// Only if the NetworkTransform is set to interpolate do we need to check for extrapolation
|
||||
if (NetworkTransform.Interpolate && m_OriginalInterpolation == InterpolationTypes.Extrapolate)
|
||||
{
|
||||
if (IsKinematic())
|
||||
{
|
||||
// If not already set to interpolate then set the Rigidbody to interpolate
|
||||
if (m_Rigidbody.interpolation == RigidbodyInterpolation.Extrapolate)
|
||||
{
|
||||
// Sleep until the next fixed update when switching from extrapolation to interpolation
|
||||
SleepRigidbody();
|
||||
SetInterpolation(InterpolationTypes.Interpolate);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Switch it back to the original interpolation if non-kinematic (doesn't require sleep).
|
||||
SetInterpolation(m_OriginalInterpolation);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetInterpolation(m_IsAuthority ? m_OriginalInterpolation : (NetworkTransform.Interpolate ? InterpolationTypes.None : m_OriginalInterpolation));
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SetInterpolation(InterpolationTypes interpolationType)
|
||||
{
|
||||
switch (interpolationType)
|
||||
{
|
||||
case InterpolationTypes.None:
|
||||
{
|
||||
if (m_IsRigidbody2D)
|
||||
{
|
||||
m_Rigidbody2D.interpolation = RigidbodyInterpolation2D.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Rigidbody.interpolation = RigidbodyInterpolation.None;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case InterpolationTypes.Interpolate:
|
||||
{
|
||||
if (m_IsRigidbody2D)
|
||||
{
|
||||
m_Rigidbody2D.interpolation = RigidbodyInterpolation2D.Interpolate;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Rigidbody.interpolation = RigidbodyInterpolation.Interpolate;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case InterpolationTypes.Extrapolate:
|
||||
{
|
||||
if (m_IsRigidbody2D)
|
||||
{
|
||||
m_Rigidbody2D.interpolation = RigidbodyInterpolation2D.Extrapolate;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Rigidbody.interpolation = RigidbodyInterpolation.Extrapolate;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ResetInterpolation()
|
||||
{
|
||||
SetInterpolation(m_OriginalInterpolation);
|
||||
}
|
||||
|
||||
protected override void OnOwnershipChanged(ulong previous, ulong current)
|
||||
{
|
||||
UpdateOwnershipAuthority();
|
||||
base.OnOwnershipChanged(previous, current);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the authority based on whether it is server or owner authoritative
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Distributed authority sessions will always be owner authoritative.
|
||||
/// </remarks>
|
||||
internal void UpdateOwnershipAuthority()
|
||||
{
|
||||
if (NetworkManager.DistributedAuthorityMode)
|
||||
{
|
||||
// When in distributed authority mode, always use HasAuthority
|
||||
m_IsAuthority = HasAuthority;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (NetworkTransform.IsServerAuthoritative())
|
||||
{
|
||||
m_IsAuthority = NetworkManager.IsServer;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_IsAuthority = IsOwner;
|
||||
}
|
||||
}
|
||||
|
||||
if (AutoUpdateKinematicState)
|
||||
{
|
||||
SetIsKinematic(!m_IsAuthority);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
UpdateOwnershipAuthority();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
if (UseRigidBodyForMotion && HasAuthority)
|
||||
{
|
||||
DetachFromFixedJoint();
|
||||
NetworkRigidbodyConnections.Clear();
|
||||
}
|
||||
|
||||
// If we are automatically handling the kinematic state...
|
||||
if (AutoUpdateKinematicState || AutoSetKinematicOnDespawn)
|
||||
{
|
||||
// Turn off physics for the rigid body until spawned, otherwise
|
||||
// non-owners can run fixed updates before the first full
|
||||
// NetworkTransform update and physics will be applied (i.e. gravity, etc)
|
||||
SetIsKinematic(true);
|
||||
}
|
||||
SetInterpolation(m_OriginalInterpolation);
|
||||
}
|
||||
|
||||
// TODO: Possibly provide a NetworkJoint that allows for more options than fixed.
|
||||
// Rigidbodies do not have the concept of "local space", and as such using a fixed joint will hold the object
|
||||
// in place relative to the parent so jitter/stutter does not occur.
|
||||
// Alternately, users can affix the fixed joint to a child GameObject (without a rigid body) of the parent NetworkObject
|
||||
// and then add a NetworkTransform to that in order to get the parented child NetworkObject to move around in "local space"
|
||||
public FixedJoint FixedJoint { get; private set; }
|
||||
public FixedJoint2D FixedJoint2D { get; private set; }
|
||||
|
||||
internal System.Collections.Generic.List<NetworkRigidbodyBase> NetworkRigidbodyConnections = new System.Collections.Generic.List<NetworkRigidbodyBase>();
|
||||
internal NetworkRigidbodyBase ParentBody;
|
||||
|
||||
private bool m_FixedJoint2DUsingGravity;
|
||||
private bool m_OriginalGravitySetting;
|
||||
private float m_OriginalGravityScale;
|
||||
|
||||
/// <summary>
|
||||
/// When using a custom <see cref="NetworkRigidbodyBase"/>, this virtual method is invoked when the
|
||||
/// <see cref="FixedJoint"/> is created in the event any additional adjustments are needed.
|
||||
/// </summary>
|
||||
protected virtual void OnFixedJointCreated()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When using a custom <see cref="NetworkRigidbodyBase"/>, this virtual method is invoked when the
|
||||
/// <see cref="FixedJoint2D"/> is created in the event any additional adjustments are needed.
|
||||
/// </summary>
|
||||
protected virtual void OnFixedJoint2DCreated()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ApplyFixedJoint2D(NetworkRigidbodyBase bodyToConnect, Vector3 position, float connectedMassScale = 0.0f, float massScale = 1.0f, bool useGravity = false, bool zeroVelocity = true)
|
||||
{
|
||||
transform.position = position;
|
||||
m_Rigidbody2D.position = position;
|
||||
m_OriginalGravitySetting = bodyToConnect.m_Rigidbody.useGravity;
|
||||
m_FixedJoint2DUsingGravity = useGravity;
|
||||
|
||||
if (!useGravity)
|
||||
{
|
||||
m_OriginalGravityScale = m_Rigidbody2D.gravityScale;
|
||||
m_Rigidbody2D.gravityScale = 0.0f;
|
||||
}
|
||||
|
||||
if (zeroVelocity)
|
||||
{
|
||||
m_Rigidbody2D.velocity = Vector2.zero;
|
||||
m_Rigidbody2D.angularVelocity = 0.0f;
|
||||
}
|
||||
|
||||
FixedJoint2D = gameObject.AddComponent<FixedJoint2D>();
|
||||
FixedJoint2D.connectedBody = bodyToConnect.m_Rigidbody2D;
|
||||
OnFixedJoint2DCreated();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ApplyFixedJoint(NetworkRigidbodyBase bodyToConnectTo, Vector3 position, float connectedMassScale = 0.0f, float massScale = 1.0f, bool useGravity = false, bool zeroVelocity = true)
|
||||
{
|
||||
transform.position = position;
|
||||
m_Rigidbody.position = position;
|
||||
if (zeroVelocity)
|
||||
{
|
||||
m_Rigidbody.linearVelocity = Vector3.zero;
|
||||
m_Rigidbody.angularVelocity = Vector3.zero;
|
||||
}
|
||||
m_OriginalGravitySetting = m_Rigidbody.useGravity;
|
||||
m_Rigidbody.useGravity = useGravity;
|
||||
FixedJoint = gameObject.AddComponent<FixedJoint>();
|
||||
FixedJoint.connectedBody = bodyToConnectTo.m_Rigidbody;
|
||||
FixedJoint.connectedMassScale = connectedMassScale;
|
||||
FixedJoint.massScale = massScale;
|
||||
OnFixedJointCreated();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Authority Only:
|
||||
/// When invoked and not already attached to a fixed joint, this will connect two rigid bodies with <see cref="UseRigidBodyForMotion"/> enabled.
|
||||
/// Invoke this method on the rigid body you wish to attach to another (i.e. weapon to player, sticky bomb to player/object, etc).
|
||||
/// <seealso cref="FixedJoint"/>
|
||||
/// <seealso cref="FixedJoint2D"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Parenting relative:
|
||||
/// - This instance can be viewed as the child.
|
||||
/// - The <param name="objectToConnectTo"/> can be viewed as the parent.
|
||||
/// <br/>
|
||||
/// This is the recommended way, as opposed to parenting, to attached/detatch two rigid bodies to one another when <see cref="UseRigidBodyForMotion"/> is enabled.
|
||||
/// For more details on using <see cref="UnityEngine.FixedJoint"/> and <see cref="UnityEngine.FixedJoint2D"/>.
|
||||
/// <br/>
|
||||
/// This provides a simple joint solution between two rigid bodies and serves as an example. You can add different joint types by creating a customized/derived
|
||||
/// version of <see cref="NetworkRigidbodyBase"/>.
|
||||
/// </remarks>
|
||||
/// <param name="objectToConnectTo">The target object to attach to.</param>
|
||||
/// <param name="positionOfConnection">The position of the connection (i.e. where you want the object to be affixed).</param>
|
||||
/// <param name="connectedMassScale">The target object's mass scale relative to this object being attached.</param>
|
||||
/// <param name="massScale">This object's mass scale relative to the target object's.</param>
|
||||
/// <param name="useGravity">Determines if this object will have gravity applied to it along with the object you are connecting this one to (the default is to not use gravity for this object)</param>
|
||||
/// <param name="zeroVelocity">When true (the default), both linear and angular velocities of this object are set to zero.</param>
|
||||
/// <param name="teleportObject">When true (the default), this object will teleport itself to the position of connection.</param>
|
||||
/// <returns>true (success) false (failed)</returns>
|
||||
public bool AttachToFixedJoint(NetworkRigidbodyBase objectToConnectTo, Vector3 positionOfConnection, float connectedMassScale = 0.0f, float massScale = 1.0f, bool useGravity = false, bool zeroVelocity = true, bool teleportObject = true)
|
||||
{
|
||||
if (!UseRigidBodyForMotion)
|
||||
{
|
||||
Debug.LogError($"[{GetType().Name}] {name} does not have {nameof(UseRigidBodyForMotion)} set! Either enable {nameof(UseRigidBodyForMotion)} on this component or do not use a {nameof(FixedJoint)} when parenting under a {nameof(NetworkObject)}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsKinematic())
|
||||
{
|
||||
Debug.LogError($"[{GetType().Name}] {name} is currently kinematic! You cannot use a {nameof(FixedJoint)} with Kinematic bodies!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (objectToConnectTo != null)
|
||||
{
|
||||
if (m_IsRigidbody2D)
|
||||
{
|
||||
ApplyFixedJoint2D(objectToConnectTo, positionOfConnection, connectedMassScale, massScale, useGravity, zeroVelocity);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyFixedJoint(objectToConnectTo, positionOfConnection, connectedMassScale, massScale, useGravity, zeroVelocity);
|
||||
}
|
||||
|
||||
ParentBody = objectToConnectTo;
|
||||
ParentBody.NetworkRigidbodyConnections.Add(this);
|
||||
if (teleportObject)
|
||||
{
|
||||
NetworkTransform.SetState(teleportDisabled: false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void RemoveFromParentBody()
|
||||
{
|
||||
ParentBody.NetworkRigidbodyConnections.Remove(this);
|
||||
ParentBody = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authority Only:
|
||||
/// When invoked and already connected to an object via <see cref="FixedJoint"/> or <see cref="FixedJoint2D"/> (depending upon the type of rigid body),
|
||||
/// this will detach from the fixed joint and destroy the fixed joint component.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is the recommended way, as opposed to parenting, to attached/detatch two rigid bodies to one another when <see cref="UseRigidBodyForMotion"/> is enabled.
|
||||
/// </remarks>
|
||||
public void DetachFromFixedJoint()
|
||||
{
|
||||
if (!HasAuthority)
|
||||
{
|
||||
Debug.LogError($"[{name}] Only authority can invoke {nameof(DetachFromFixedJoint)}!");
|
||||
}
|
||||
if (UseRigidBodyForMotion)
|
||||
{
|
||||
if (m_IsRigidbody2D)
|
||||
{
|
||||
if (FixedJoint2D != null)
|
||||
{
|
||||
if (!m_FixedJoint2DUsingGravity)
|
||||
{
|
||||
FixedJoint2D.connectedBody.gravityScale = m_OriginalGravityScale;
|
||||
}
|
||||
FixedJoint2D.connectedBody = null;
|
||||
Destroy(FixedJoint2D);
|
||||
FixedJoint2D = null;
|
||||
ResetInterpolation();
|
||||
RemoveFromParentBody();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FixedJoint != null)
|
||||
{
|
||||
FixedJoint.connectedBody = null;
|
||||
m_Rigidbody.useGravity = m_OriginalGravitySetting;
|
||||
Destroy(FixedJoint);
|
||||
FixedJoint = null;
|
||||
ResetInterpolation();
|
||||
RemoveFromParentBody();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // COM_UNITY_MODULES_PHYSICS
|
||||
|
||||
11
Runtime/Components/NetworkRigidBodyBase.cs.meta
Normal file
11
Runtime/Components/NetworkRigidBodyBase.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8c4434f0563fb7f42b3b2993c97ae81a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
21
Runtime/Components/NetworkRigidbody.cs
Normal file
21
Runtime/Components/NetworkRigidbody.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
#if COM_UNITY_MODULES_PHYSICS
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// NetworkRigidbody allows for the use of <see cref="Rigidbody"/> on network objects. By controlling the kinematic
|
||||
/// mode of the <see cref="Rigidbody"/> and disabling it on all peers but the authoritative one.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(NetworkTransform))]
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
[AddComponentMenu("Netcode/Network Rigidbody")]
|
||||
public class NetworkRigidbody : NetworkRigidbodyBase
|
||||
{
|
||||
protected virtual void Awake()
|
||||
{
|
||||
Initialize(RigidbodyTypes.Rigidbody);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // COM_UNITY_MODULES_PHYSICS
|
||||
11
Runtime/Components/NetworkRigidbody.cs.meta
Normal file
11
Runtime/Components/NetworkRigidbody.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f6c0be61502bb534f922ebb746851216
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
21
Runtime/Components/NetworkRigidbody2D.cs
Normal file
21
Runtime/Components/NetworkRigidbody2D.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
#if COM_UNITY_MODULES_PHYSICS2D
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// NetworkRigidbody allows for the use of <see cref="Rigidbody2D"/> on network objects. By controlling the kinematic
|
||||
/// mode of the rigidbody and disabling it on all peers but the authoritative one.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(NetworkTransform))]
|
||||
[RequireComponent(typeof(Rigidbody2D))]
|
||||
[AddComponentMenu("Netcode/Network Rigidbody 2D")]
|
||||
public class NetworkRigidbody2D : NetworkRigidbodyBase
|
||||
{
|
||||
protected virtual void Awake()
|
||||
{
|
||||
Initialize(RigidbodyTypes.Rigidbody2D);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // COM_UNITY_MODULES_PHYSICS2D
|
||||
11
Runtime/Components/NetworkRigidbody2D.cs.meta
Normal file
11
Runtime/Components/NetworkRigidbody2D.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 80d7c879794dfda4687da0e400131852
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
3754
Runtime/Components/NetworkTransform.cs
Normal file
3754
Runtime/Components/NetworkTransform.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Runtime/Components/NetworkTransform.cs.meta
Normal file
11
Runtime/Components/NetworkTransform.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e96cb6065543e43c4a752faaa1468eb1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
123
Runtime/Components/QuaternionCompressor.cs
Normal file
123
Runtime/Components/QuaternionCompressor.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// A Smallest Three Quaternion Compressor Implementation
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Explanation of why "The smallest three":
|
||||
/// Since a normalized Quaternion's unit value is 1.0f:
|
||||
/// x*x + y*y + z*z + w*w = M*M (where M is the magnitude of the vector)
|
||||
/// If w was the largest value and the quaternion is normalized:
|
||||
/// M = 1.0f (which M * M would still yield 1.0f)
|
||||
/// w*w = M*M - (x*x + y*y + z*z) or Mathf.Sqrt(1.0f - (x*x + y*y + z*z))
|
||||
/// w = Math.Sqrt(1.0f - (x*x + y*y + z*z))
|
||||
/// Using the largest number avoids potential loss of precision in the smallest three values.
|
||||
/// </remarks>
|
||||
public static class QuaternionCompressor
|
||||
{
|
||||
private const ushort k_PrecisionMask = (1 << 9) - 1;
|
||||
|
||||
// Square root of 2 over 2 (Mathf.Sqrt(2.0f) / 2.0f == 1.0f / Mathf.Sqrt(2.0f))
|
||||
// This provides encoding the smallest three components into a (+/-) Mathf.Sqrt(2.0f) / 2.0f range
|
||||
private const float k_SqrtTwoOverTwoEncoding = 0.70710678118654752440084436210485f;
|
||||
|
||||
// We can further improve the encoding compression by dividing k_SqrtTwoOverTwo into 1.0f and multiplying that
|
||||
// by the precision mask (minor reduction of runtime calculations)
|
||||
private const float k_CompressionEcodingMask = (1.0f / k_SqrtTwoOverTwoEncoding) * k_PrecisionMask;
|
||||
|
||||
// Used to shift the negative bit to the 10th bit position when compressing and encoding
|
||||
private const ushort k_ShiftNegativeBit = 9;
|
||||
|
||||
// We can do the same for our decoding and decompression by dividing k_PrecisionMask into 1.0 and multiplying
|
||||
// that by k_SqrtTwoOverTwo (minor reduction of runtime calculations)
|
||||
private const float k_DcompressionDecodingMask = (1.0f / k_PrecisionMask) * k_SqrtTwoOverTwoEncoding;
|
||||
|
||||
// The sign bit position (10th bit) used when decompressing and decoding
|
||||
private const ushort k_NegShortBit = 0x200;
|
||||
|
||||
// Negative bit set values
|
||||
private const ushort k_True = 1;
|
||||
private const ushort k_False = 0;
|
||||
|
||||
// Used to store the absolute value of the 4 quaternion elements
|
||||
private static Quaternion s_QuatAbsValues = Quaternion.identity;
|
||||
|
||||
/// <summary>
|
||||
/// Compresses a Quaternion into an unsigned integer
|
||||
/// </summary>
|
||||
/// <param name="quaternion">the <see cref="Quaternion"/> to be compressed</param>
|
||||
/// <returns>the <see cref="Quaternion"/> compressed as an unsigned integer</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint CompressQuaternion(ref Quaternion quaternion)
|
||||
{
|
||||
// Store off the absolute value for each Quaternion element
|
||||
s_QuatAbsValues[0] = Mathf.Abs(quaternion[0]);
|
||||
s_QuatAbsValues[1] = Mathf.Abs(quaternion[1]);
|
||||
s_QuatAbsValues[2] = Mathf.Abs(quaternion[2]);
|
||||
s_QuatAbsValues[3] = Mathf.Abs(quaternion[3]);
|
||||
|
||||
// Get the largest element value of the quaternion to know what the remaining "Smallest Three" values are
|
||||
var quatMax = Mathf.Max(s_QuatAbsValues[0], s_QuatAbsValues[1], s_QuatAbsValues[2], s_QuatAbsValues[3]);
|
||||
|
||||
// Find the index of the largest element so we can skip that element while compressing and decompressing
|
||||
var indexToSkip = (ushort)(s_QuatAbsValues[0] == quatMax ? 0 : s_QuatAbsValues[1] == quatMax ? 1 : s_QuatAbsValues[2] == quatMax ? 2 : 3);
|
||||
|
||||
// Get the sign of the largest element which is all that is needed when calculating the sum of squares of a normalized quaternion.
|
||||
|
||||
var quatMaxSign = (quaternion[indexToSkip] < 0 ? k_True : k_False);
|
||||
|
||||
// Start with the index to skip which will be shifted to the highest two bits
|
||||
var compressed = (uint)indexToSkip;
|
||||
|
||||
// Step 1: Start with the first element
|
||||
var currentIndex = 0;
|
||||
|
||||
// Step 2: If we are on the index to skip preserve the current compressed value, otherwise proceed to step 3 and 4
|
||||
// Step 3: Get the sign of the element we are processing. If it is the not the same as the largest value's sign bit then we set the bit
|
||||
// Step 4: Get the compressed and encoded value by multiplying the absolute value of the current element by k_CompressionEcodingMask and round that result up
|
||||
compressed = currentIndex != indexToSkip ? (compressed << 10) | (uint)((quaternion[currentIndex] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEcodingMask * s_QuatAbsValues[currentIndex]) : compressed;
|
||||
currentIndex++;
|
||||
// Repeat the last 3 steps for the remaining elements
|
||||
compressed = currentIndex != indexToSkip ? (compressed << 10) | (uint)((quaternion[currentIndex] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEcodingMask * s_QuatAbsValues[currentIndex]) : compressed;
|
||||
currentIndex++;
|
||||
compressed = currentIndex != indexToSkip ? (compressed << 10) | (uint)((quaternion[currentIndex] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEcodingMask * s_QuatAbsValues[currentIndex]) : compressed;
|
||||
currentIndex++;
|
||||
compressed = currentIndex != indexToSkip ? (compressed << 10) | (uint)((quaternion[currentIndex] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEcodingMask * s_QuatAbsValues[currentIndex]) : compressed;
|
||||
|
||||
// Return the compress quaternion
|
||||
return compressed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompress a compressed quaternion
|
||||
/// </summary>
|
||||
/// <param name="quaternion">quaternion to store the decompressed values within</param>
|
||||
/// <param name="compressed">the compressed quaternion</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void DecompressQuaternion(ref Quaternion quaternion, uint compressed)
|
||||
{
|
||||
// Get the last two bits for the index to skip (0-3)
|
||||
var indexToSkip = (int)(compressed >> 30);
|
||||
|
||||
// Reverse out the values while skipping over the largest value index
|
||||
var sumOfSquaredMagnitudes = 0.0f;
|
||||
for (int i = 3; i >= 0; --i)
|
||||
{
|
||||
if (i == indexToSkip)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Check the negative bit and multiply that result with the decompressed and decoded value
|
||||
quaternion[i] = ((compressed & k_NegShortBit) > 0 ? -1.0f : 1.0f) * ((compressed & k_PrecisionMask) * k_DcompressionDecodingMask);
|
||||
sumOfSquaredMagnitudes += quaternion[i] * quaternion[i];
|
||||
compressed = compressed >> 10;
|
||||
}
|
||||
// Since a normalized quaternion's magnitude is 1.0f, we subtract the sum of the squared smallest three from the unit value and take
|
||||
// the square root of the difference to find the final largest value
|
||||
quaternion[indexToSkip] = Mathf.Sqrt(1.0f - sumOfSquaredMagnitudes);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Runtime/Components/QuaternionCompressor.cs.meta
Normal file
11
Runtime/Components/QuaternionCompressor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb9d8b98d3c8bca469c8ee152353336f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
230
Runtime/Components/RigidbodyContactEventManager.cs
Normal file
230
Runtime/Components/RigidbodyContactEventManager.cs
Normal file
@@ -0,0 +1,230 @@
|
||||
#if COM_UNITY_MODULES_PHYSICS
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Components
|
||||
{
|
||||
public interface IContactEventHandler
|
||||
{
|
||||
Rigidbody GetRigidbody();
|
||||
|
||||
void ContactEvent(ulong eventId, Vector3 averagedCollisionNormal, Rigidbody collidingBody, Vector3 contactPoint, bool hasCollisionStay = false, Vector3 averagedCollisionStayNormal = default);
|
||||
}
|
||||
|
||||
[AddComponentMenu("Netcode/Rigidbody Contact Event Manager")]
|
||||
public class RigidbodyContactEventManager : MonoBehaviour
|
||||
{
|
||||
public static RigidbodyContactEventManager Instance { get; private set; }
|
||||
|
||||
private struct JobResultStruct
|
||||
{
|
||||
public bool HasCollisionStay;
|
||||
public int ThisInstanceID;
|
||||
public int OtherInstanceID;
|
||||
public Vector3 AverageNormal;
|
||||
public Vector3 AverageCollisionStayNormal;
|
||||
public Vector3 ContactPoint;
|
||||
}
|
||||
|
||||
private NativeArray<JobResultStruct> m_ResultsArray;
|
||||
private int m_Count = 0;
|
||||
private JobHandle m_JobHandle;
|
||||
|
||||
private readonly Dictionary<int, Rigidbody> m_RigidbodyMapping = new Dictionary<int, Rigidbody>();
|
||||
private readonly Dictionary<int, IContactEventHandler> m_HandlerMapping = new Dictionary<int, IContactEventHandler>();
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
m_ResultsArray = new NativeArray<JobResultStruct>(16, Allocator.Persistent);
|
||||
Physics.ContactEvent += Physics_ContactEvent;
|
||||
if (Instance != null)
|
||||
{
|
||||
NetworkLog.LogError($"[Invalid][Multiple Instances] Found more than one instance of {nameof(RigidbodyContactEventManager)}: {name} and {Instance.name}");
|
||||
NetworkLog.LogError($"[Disable][Additional Instance] Disabling {name} instance!");
|
||||
gameObject.SetActive(false);
|
||||
return;
|
||||
}
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
public void RegisterHandler(IContactEventHandler contactEventHandler, bool register = true)
|
||||
{
|
||||
var rigidbody = contactEventHandler.GetRigidbody();
|
||||
var instanceId = rigidbody.GetInstanceID();
|
||||
if (register)
|
||||
{
|
||||
if (!m_RigidbodyMapping.ContainsKey(instanceId))
|
||||
{
|
||||
m_RigidbodyMapping.Add(instanceId, rigidbody);
|
||||
}
|
||||
|
||||
if (!m_HandlerMapping.ContainsKey(instanceId))
|
||||
{
|
||||
m_HandlerMapping.Add(instanceId, contactEventHandler);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_RigidbodyMapping.Remove(instanceId);
|
||||
m_HandlerMapping.Remove(instanceId);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
m_JobHandle.Complete();
|
||||
m_ResultsArray.Dispose();
|
||||
|
||||
Physics.ContactEvent -= Physics_ContactEvent;
|
||||
|
||||
m_RigidbodyMapping.Clear();
|
||||
Instance = null;
|
||||
}
|
||||
|
||||
private bool m_HasCollisions;
|
||||
private int m_CurrentCount = 0;
|
||||
|
||||
private void ProcessCollisions()
|
||||
{
|
||||
// Process all collisions
|
||||
for (int i = 0; i < m_Count; i++)
|
||||
{
|
||||
var thisInstanceID = m_ResultsArray[i].ThisInstanceID;
|
||||
var otherInstanceID = m_ResultsArray[i].OtherInstanceID;
|
||||
var rb0Valid = thisInstanceID != 0 && m_RigidbodyMapping.ContainsKey(thisInstanceID);
|
||||
var rb1Valid = otherInstanceID != 0 && m_RigidbodyMapping.ContainsKey(otherInstanceID);
|
||||
// Only notify registered rigid bodies.
|
||||
if (!rb0Valid || !rb1Valid || !m_HandlerMapping.ContainsKey(thisInstanceID))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (m_ResultsArray[i].HasCollisionStay)
|
||||
{
|
||||
m_HandlerMapping[thisInstanceID].ContactEvent(m_EventId, m_ResultsArray[i].AverageNormal, m_RigidbodyMapping[otherInstanceID], m_ResultsArray[i].ContactPoint, m_ResultsArray[i].HasCollisionStay, m_ResultsArray[i].AverageCollisionStayNormal);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_HandlerMapping[thisInstanceID].ContactEvent(m_EventId, m_ResultsArray[i].AverageNormal, m_RigidbodyMapping[otherInstanceID], m_ResultsArray[i].ContactPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
// Only process new collisions
|
||||
if (!m_HasCollisions && m_CurrentCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// This assures we won't process the same collision
|
||||
// set after it has been processed.
|
||||
if (m_HasCollisions)
|
||||
{
|
||||
m_CurrentCount = m_Count;
|
||||
m_HasCollisions = false;
|
||||
m_JobHandle.Complete();
|
||||
}
|
||||
ProcessCollisions();
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
m_CurrentCount = 0;
|
||||
}
|
||||
|
||||
private ulong m_EventId;
|
||||
private void Physics_ContactEvent(PhysicsScene scene, NativeArray<ContactPairHeader>.ReadOnly pairHeaders)
|
||||
{
|
||||
m_EventId++;
|
||||
m_HasCollisions = true;
|
||||
int n = pairHeaders.Length;
|
||||
if (m_ResultsArray.Length < n)
|
||||
{
|
||||
m_ResultsArray.Dispose();
|
||||
m_ResultsArray = new NativeArray<JobResultStruct>(Mathf.NextPowerOfTwo(n), Allocator.Persistent);
|
||||
}
|
||||
m_Count = n;
|
||||
var job = new GetCollisionsJob()
|
||||
{
|
||||
PairedHeaders = pairHeaders,
|
||||
ResultsArray = m_ResultsArray
|
||||
};
|
||||
m_JobHandle = job.Schedule(n, 256);
|
||||
}
|
||||
|
||||
private struct GetCollisionsJob : IJobParallelFor
|
||||
{
|
||||
[ReadOnly]
|
||||
public NativeArray<ContactPairHeader>.ReadOnly PairedHeaders;
|
||||
|
||||
public NativeArray<JobResultStruct> ResultsArray;
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
Vector3 averageNormal = Vector3.zero;
|
||||
Vector3 averagePoint = Vector3.zero;
|
||||
Vector3 averageCollisionStay = Vector3.zero;
|
||||
int count = 0;
|
||||
int collisionStaycount = 0;
|
||||
int positionCount = 0;
|
||||
for (int j = 0; j < PairedHeaders[index].pairCount; j++)
|
||||
{
|
||||
ref readonly var pair = ref PairedHeaders[index].GetContactPair(j);
|
||||
|
||||
if (pair.isCollisionExit)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int k = 0; k < pair.contactCount; k++)
|
||||
{
|
||||
ref readonly var contact = ref pair.GetContactPoint(k);
|
||||
averagePoint += contact.position;
|
||||
positionCount++;
|
||||
if (!pair.isCollisionStay)
|
||||
{
|
||||
averageNormal += contact.normal;
|
||||
count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
averageCollisionStay += contact.normal;
|
||||
collisionStaycount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count != 0)
|
||||
{
|
||||
averageNormal /= count;
|
||||
}
|
||||
|
||||
if (collisionStaycount != 0)
|
||||
{
|
||||
averageCollisionStay /= collisionStaycount;
|
||||
}
|
||||
|
||||
if (positionCount != 0)
|
||||
{
|
||||
averagePoint /= positionCount;
|
||||
}
|
||||
|
||||
var result = new JobResultStruct()
|
||||
{
|
||||
ThisInstanceID = PairedHeaders[index].bodyInstanceID,
|
||||
OtherInstanceID = PairedHeaders[index].otherBodyInstanceID,
|
||||
AverageNormal = averageNormal,
|
||||
HasCollisionStay = collisionStaycount != 0,
|
||||
AverageCollisionStayNormal = averageCollisionStay,
|
||||
ContactPoint = averagePoint
|
||||
};
|
||||
|
||||
ResultsArray[index] = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Runtime/Components/RigidbodyContactEventManager.cs.meta
Normal file
11
Runtime/Components/RigidbodyContactEventManager.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 739e5cee846b6384988f9a47e4691836
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -149,8 +149,8 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public const int RttWindowSize = 64; // number of slots to use for RTT computations (max number of in-flight packets)
|
||||
|
||||
[Tooltip("Determines if the network session will run in client-server or distributed authority mode.")]
|
||||
public SessionModeTypes SessionMode;
|
||||
[Tooltip("Determines whether to use the client-server or distributed authority network topology.")]
|
||||
public NetworkTopologyTypes NetworkTopology;
|
||||
|
||||
[HideInInspector]
|
||||
public bool UseCMBService;
|
||||
@@ -158,6 +158,12 @@ namespace Unity.Netcode
|
||||
[Tooltip("When enabled (default), the player prefab will automatically be spawned (client-side) upon the client being approved and synchronized.")]
|
||||
public bool AutoSpawnPlayerPrefabClientSide = true;
|
||||
|
||||
#if MULTIPLAYER_TOOLS
|
||||
public bool NetworkMessageMetrics = true;
|
||||
#endif
|
||||
|
||||
public bool NetworkProfilingMetrics = true;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a base64 encoded version of the configuration
|
||||
/// </summary>
|
||||
|
||||
@@ -4,7 +4,7 @@ using UnityEngine;
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
|
||||
public enum SessionModeTypes
|
||||
public enum NetworkTopologyTypes
|
||||
{
|
||||
ClientServer,
|
||||
DistributedAuthority
|
||||
@@ -41,7 +41,7 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
internal bool IsApproved { get; set; }
|
||||
|
||||
public SessionModeTypes SessionModeType { get; internal set; }
|
||||
public NetworkTopologyTypes NetworkTopologyType { get; internal set; }
|
||||
|
||||
public bool DAHost { get; internal set; }
|
||||
|
||||
@@ -77,9 +77,9 @@ namespace Unity.Netcode
|
||||
if (networkManager != null)
|
||||
{
|
||||
SpawnManager = networkManager.SpawnManager;
|
||||
SessionModeType = networkManager.NetworkConfig.SessionMode;
|
||||
NetworkTopologyType = networkManager.NetworkConfig.NetworkTopology;
|
||||
|
||||
if (SessionModeType == SessionModeTypes.DistributedAuthority)
|
||||
if (NetworkTopologyType == NetworkTopologyTypes.DistributedAuthority)
|
||||
{
|
||||
DAHost = IsClient && IsServer;
|
||||
|
||||
|
||||
@@ -660,16 +660,69 @@ namespace Unity.Netcode
|
||||
/// <param name="despawnTick">the future network tick that the <see cref="NetworkObject"/> will be despawned on non-authoritative instances</param>
|
||||
public virtual void OnDeferringDespawn(int despawnTick) { }
|
||||
|
||||
/// Gets called after the <see cref="NetworkObject"/> is spawned. No NetworkBehaviours associated with the NetworkObject will have had <see cref="OnNetworkSpawn"/> invoked yet.
|
||||
/// A reference to <see cref="NetworkManager"/> is passed in as a parameter to determine the context of execution (IsServer/IsClient)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <param name="networkManager">a ref to the <see cref="NetworkManager"/> since this is not yet set on the <see cref="NetworkBehaviour"/></param>
|
||||
/// The <see cref="NetworkBehaviour"/> will not have anything assigned to it at this point in time.
|
||||
/// Settings like ownership, NetworkBehaviourId, NetworkManager, and most other spawn related properties will not be set.
|
||||
/// This can be used to handle things like initializing/instantiating a NetworkVariable or the like.
|
||||
/// </remarks>
|
||||
protected virtual void OnNetworkPreSpawn(ref NetworkManager networkManager) { }
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when the <see cref="NetworkObject"/> gets spawned, message handlers are ready to be registered and the network is setup.
|
||||
/// </summary>
|
||||
public virtual void OnNetworkSpawn() { }
|
||||
|
||||
/// <summary>
|
||||
/// Gets called after the <see cref="NetworkObject"/> is spawned. All NetworkBehaviours associated with the NetworkObject will have had <see cref="OnNetworkSpawn"/> invoked.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Will be invoked on each <see cref="NetworkBehaviour"/> associated with the <see cref="NetworkObject"/> being spawned.
|
||||
/// All associated <see cref="NetworkBehaviour"/> components will have had <see cref="OnNetworkSpawn"/> invoked on the spawned <see cref="NetworkObject"/>.
|
||||
/// </remarks>
|
||||
protected virtual void OnNetworkPostSpawn() { }
|
||||
|
||||
/// <summary>
|
||||
/// [Client-Side Only]
|
||||
/// When a new client joins it is synchronized with all spawned NetworkObjects and scenes loaded for the session joined. At the end of the synchronization process, when all
|
||||
/// <see cref="NetworkObject"/>s and scenes (if scene management is enabled) have finished synchronizing, all NetworkBehaviour components associated with spawned <see cref="NetworkObject"/>s
|
||||
/// will have this method invoked.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This can be used to handle post synchronization actions where you might need to access a different NetworkObject and/or NetworkBehaviour not local to the current NetworkObject context.
|
||||
/// This is only invoked on clients during a client-server network topology session.
|
||||
/// </remarks>
|
||||
protected virtual void OnNetworkSessionSynchronized() { }
|
||||
|
||||
/// <summary>
|
||||
/// [Client & Server Side]
|
||||
/// When a scene is loaded an in-scene placed NetworkObjects are all spawned, this method is invoked on all of the newly spawned in-scene placed NetworkObjects.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This can be used to handle post scene loaded actions for in-scene placed NetworkObjcts where you might need to access a different NetworkObject and/or NetworkBehaviour not local to the current NetworkObject context.
|
||||
/// </remarks>
|
||||
protected virtual void OnInSceneObjectsSpawned() { }
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when the <see cref="NetworkObject"/> gets despawned. Is called both on the server and clients.
|
||||
/// </summary>
|
||||
public virtual void OnNetworkDespawn() { }
|
||||
|
||||
internal void NetworkPreSpawn(ref NetworkManager networkManager)
|
||||
{
|
||||
try
|
||||
{
|
||||
OnNetworkPreSpawn(ref networkManager);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
|
||||
internal void InternalOnNetworkSpawn()
|
||||
{
|
||||
IsSpawned = true;
|
||||
@@ -699,6 +752,42 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
internal void NetworkPostSpawn()
|
||||
{
|
||||
try
|
||||
{
|
||||
OnNetworkPostSpawn();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
|
||||
internal void NetworkSessionSynchronized()
|
||||
{
|
||||
try
|
||||
{
|
||||
OnNetworkSessionSynchronized();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
|
||||
internal void InSceneNetworkObjectsSpawned()
|
||||
{
|
||||
try
|
||||
{
|
||||
OnInSceneObjectsSpawned();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
|
||||
internal void InternalOnNetworkDespawn()
|
||||
{
|
||||
IsSpawned = false;
|
||||
|
||||
@@ -8,6 +8,7 @@ using UnityEditor;
|
||||
#endif
|
||||
using UnityEngine.SceneManagement;
|
||||
using Debug = UnityEngine.Debug;
|
||||
using Unity.Netcode.Components;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
@@ -43,7 +44,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
get
|
||||
{
|
||||
return NetworkConfig.SessionMode == SessionModeTypes.DistributedAuthority;
|
||||
return NetworkConfig.NetworkTopology == NetworkTopologyTypes.DistributedAuthority;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,6 +178,45 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
internal Dictionary<ulong, NetworkTransform> NetworkTransformUpdate = new Dictionary<ulong, NetworkTransform>();
|
||||
#if COM_UNITY_MODULES_PHYSICS
|
||||
internal Dictionary<ulong, NetworkTransform> NetworkTransformFixedUpdate = new Dictionary<ulong, NetworkTransform>();
|
||||
#endif
|
||||
|
||||
internal void NetworkTransformRegistration(NetworkTransform networkTransform, bool forUpdate = true, bool register = true)
|
||||
{
|
||||
if (forUpdate)
|
||||
{
|
||||
if (register)
|
||||
{
|
||||
if (!NetworkTransformUpdate.ContainsKey(networkTransform.NetworkObjectId))
|
||||
{
|
||||
NetworkTransformUpdate.Add(networkTransform.NetworkObjectId, networkTransform);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkTransformUpdate.Remove(networkTransform.NetworkObjectId);
|
||||
}
|
||||
}
|
||||
#if COM_UNITY_MODULES_PHYSICS
|
||||
else
|
||||
{
|
||||
if (register)
|
||||
{
|
||||
if (!NetworkTransformFixedUpdate.ContainsKey(networkTransform.NetworkObjectId))
|
||||
{
|
||||
NetworkTransformFixedUpdate.Add(networkTransform.NetworkObjectId, networkTransform);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkTransformFixedUpdate.Remove(networkTransform.NetworkObjectId);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public void NetworkUpdate(NetworkUpdateStage updateStage)
|
||||
{
|
||||
switch (updateStage)
|
||||
@@ -192,11 +232,36 @@ namespace Unity.Netcode
|
||||
MessageManager.CleanupDisconnectedClients();
|
||||
}
|
||||
break;
|
||||
#if COM_UNITY_MODULES_PHYSICS
|
||||
case NetworkUpdateStage.FixedUpdate:
|
||||
{
|
||||
foreach (var networkTransformEntry in NetworkTransformFixedUpdate)
|
||||
{
|
||||
if (networkTransformEntry.Value.gameObject.activeInHierarchy && networkTransformEntry.Value.IsSpawned)
|
||||
{
|
||||
networkTransformEntry.Value.OnFixedUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case NetworkUpdateStage.PreUpdate:
|
||||
{
|
||||
NetworkTimeSystem.UpdateTime();
|
||||
}
|
||||
break;
|
||||
case NetworkUpdateStage.PreLateUpdate:
|
||||
{
|
||||
// Non-physics based non-authority NetworkTransforms update their states after all other components
|
||||
foreach (var networkTransformEntry in NetworkTransformUpdate)
|
||||
{
|
||||
if (networkTransformEntry.Value.gameObject.activeInHierarchy && networkTransformEntry.Value.IsSpawned)
|
||||
{
|
||||
networkTransformEntry.Value.OnUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NetworkUpdateStage.PostLateUpdate:
|
||||
{
|
||||
// Handle deferred despawning
|
||||
@@ -942,6 +1007,10 @@ namespace Unity.Netcode
|
||||
|
||||
internal void Initialize(bool server)
|
||||
{
|
||||
#if COM_UNITY_MODULES_PHYSICS
|
||||
NetworkTransformFixedUpdate.Clear();
|
||||
#endif
|
||||
NetworkTransformUpdate.Clear();
|
||||
|
||||
//DANGOEXP TODO: Remove this before finalizing the experimental release
|
||||
NetworkConfig.AutoSpawnPlayerPrefabClientSide = DistributedAuthorityMode;
|
||||
@@ -978,7 +1047,11 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
this.RegisterNetworkUpdate(NetworkUpdateStage.EarlyUpdate);
|
||||
#if COM_UNITY_MODULES_PHYSICS
|
||||
this.RegisterNetworkUpdate(NetworkUpdateStage.FixedUpdate);
|
||||
#endif
|
||||
this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate);
|
||||
this.RegisterNetworkUpdate(NetworkUpdateStage.PreLateUpdate);
|
||||
this.RegisterNetworkUpdate(NetworkUpdateStage.PostLateUpdate);
|
||||
|
||||
// ComponentFactory needs to set its defaults next
|
||||
@@ -994,11 +1067,17 @@ namespace Unity.Netcode
|
||||
MessageManager.Hook(new NetworkManagerHooks(this));
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
MessageManager.Hook(new ProfilingHooks());
|
||||
if (NetworkConfig.NetworkProfilingMetrics)
|
||||
{
|
||||
MessageManager.Hook(new ProfilingHooks());
|
||||
}
|
||||
#endif
|
||||
|
||||
#if MULTIPLAYER_TOOLS
|
||||
MessageManager.Hook(new MetricHooks(this));
|
||||
if (NetworkConfig.NetworkMessageMetrics)
|
||||
{
|
||||
MessageManager.Hook(new MetricHooks(this));
|
||||
}
|
||||
#endif
|
||||
|
||||
// Assures there is a server message queue available
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Unity.Netcode.Components;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
@@ -14,6 +15,7 @@ using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
@@ -55,6 +57,34 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// All <see cref="NetworkTransform"></see> component instances associated with a <see cref="NetworkObject"/> component instance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When parented, all child <see cref="NetworkTransform"/> component instances under a <see cref="NetworkObject"/> component instance that do not have
|
||||
/// another <see cref="NetworkObject"/> component instance will be associated with the initial component instance. This list does not contain any parented
|
||||
/// children <see cref="NetworkObject"/> instances with one or more <see cref="NetworkTransform"/> component instance(s).
|
||||
/// </remarks>
|
||||
public List<NetworkTransform> NetworkTransforms { get; private set; }
|
||||
|
||||
#if COM_UNITY_MODULES_PHYSICS
|
||||
/// <summary>
|
||||
/// All <see cref="NetworkRigidbodyBase"></see> component instances associated with a <see cref="NetworkObject"/> component instance.
|
||||
/// NOTE: This is only available if a physics package is included. If not, then this will not be available!
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When parented, all child <see cref="NetworkRigidbodyBase"/> component instances under a <see cref="NetworkObject"/> component instance that do not have
|
||||
/// another <see cref="NetworkObject"/> component instance will be associated with the initial component instance. This list does not contain any parented
|
||||
/// child <see cref="NetworkObject"/> instances with one or more <see cref="NetworkTransform"/> component instance(s).
|
||||
/// </remarks>
|
||||
public List<NetworkRigidbodyBase> NetworkRigidbodies { get; private set; }
|
||||
#endif
|
||||
/// <summary>
|
||||
/// The current parent <see cref="NetworkObject"/> component instance to this <see cref="NetworkObject"/> component instance. When there is no parent then
|
||||
/// this will be <see cref="null"/>.
|
||||
/// </summary>
|
||||
public NetworkObject CurrentParent { get; private set; }
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private const string k_GlobalIdTemplate = "GlobalObjectId_V1-{0}-{1}-{2}-{3}";
|
||||
|
||||
@@ -398,8 +428,11 @@ namespace Unity.Netcode
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a NetworkObject can be distributed to other clients during
|
||||
/// a <see cref="SessionModeTypes.DistributedAuthority"/> session.
|
||||
/// a <see cref="NetworkTopologyTypes.DistributedAuthority"/> session.
|
||||
/// </summary>
|
||||
#if !MULTIPLAYER_SDK_INSTALLED
|
||||
[HideInInspector]
|
||||
#endif
|
||||
[SerializeField]
|
||||
internal OwnershipStatus Ownership = OwnershipStatus.Distributable;
|
||||
|
||||
@@ -1894,7 +1927,6 @@ namespace Unity.Netcode
|
||||
|
||||
internal bool InternalTrySetParent(NetworkObject parent, bool worldPositionStays = true)
|
||||
{
|
||||
|
||||
if (parent != null && (IsSpawned ^ parent.IsSpawned))
|
||||
{
|
||||
if (NetworkManager != null && !NetworkManager.ShutdownInProgress)
|
||||
@@ -1907,10 +1939,12 @@ namespace Unity.Netcode
|
||||
|
||||
if (parent == null)
|
||||
{
|
||||
CurrentParent = null;
|
||||
transform.SetParent(null, worldPositionStays);
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentParent = parent;
|
||||
transform.SetParent(parent.transform, worldPositionStays);
|
||||
}
|
||||
|
||||
@@ -2209,6 +2243,18 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
internal void InvokeBehaviourNetworkPreSpawn()
|
||||
{
|
||||
var networkManager = NetworkManager;
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
|
||||
{
|
||||
ChildNetworkBehaviours[i].NetworkPreSpawn(ref networkManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void InvokeBehaviourNetworkSpawn()
|
||||
{
|
||||
NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId);
|
||||
@@ -2238,6 +2284,42 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
internal void InvokeBehaviourNetworkPostSpawn()
|
||||
{
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
|
||||
{
|
||||
ChildNetworkBehaviours[i].NetworkPostSpawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal void InternalNetworkSessionSynchronized()
|
||||
{
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
|
||||
{
|
||||
ChildNetworkBehaviours[i].NetworkSessionSynchronized();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void InternalInSceneNetworkObjectsSpawned()
|
||||
{
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
|
||||
{
|
||||
ChildNetworkBehaviours[i].InSceneNetworkObjectsSpawned();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal void InvokeBehaviourNetworkDespawn()
|
||||
{
|
||||
NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId, true);
|
||||
@@ -2271,6 +2353,25 @@ namespace Unity.Netcode
|
||||
if (networkBehaviours[i].NetworkObject == this)
|
||||
{
|
||||
m_ChildNetworkBehaviours.Add(networkBehaviours[i]);
|
||||
var type = networkBehaviours[i].GetType();
|
||||
if (type.IsInstanceOfType(typeof(NetworkTransform)) || type.IsSubclassOf(typeof(NetworkTransform)))
|
||||
{
|
||||
if (NetworkTransforms == null)
|
||||
{
|
||||
NetworkTransforms = new List<NetworkTransform>();
|
||||
}
|
||||
NetworkTransforms.Add(networkBehaviours[i] as NetworkTransform);
|
||||
}
|
||||
#if COM_UNITY_MODULES_PHYSICS
|
||||
else if (type.IsSubclassOf(typeof(NetworkRigidbodyBase)))
|
||||
{
|
||||
if (NetworkRigidbodies == null)
|
||||
{
|
||||
NetworkRigidbodies = new List<NetworkRigidbodyBase>();
|
||||
}
|
||||
NetworkRigidbodies.Add(networkBehaviours[i] as NetworkRigidbodyBase);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2862,6 +2963,9 @@ namespace Unity.Netcode
|
||||
// in order to be able to determine which NetworkVariables the client will be allowed to read.
|
||||
networkObject.OwnerClientId = sceneObject.OwnerClientId;
|
||||
|
||||
// Special Case: Invoke NetworkBehaviour.OnPreSpawn methods here before SynchronizeNetworkBehaviours
|
||||
networkObject.InvokeBehaviourNetworkPreSpawn();
|
||||
|
||||
// Synchronize NetworkBehaviours
|
||||
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
|
||||
networkObject.SynchronizeNetworkBehaviours(ref bufferSerializer, networkManager.LocalClientId);
|
||||
@@ -3051,6 +3155,11 @@ namespace Unity.Netcode
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
m_ChildNetworkBehaviours = null;
|
||||
NetworkTransforms?.Clear();
|
||||
#if COM_UNITY_MODULES_PHYSICS
|
||||
NetworkRigidbodies?.Clear();
|
||||
#endif
|
||||
SetCachedParent(transform.parent);
|
||||
SceneOrigin = gameObject.scene;
|
||||
}
|
||||
|
||||
@@ -63,16 +63,39 @@ namespace Unity.Netcode
|
||||
/// <param name="networkDelivery">The delivery type (QoS) to send data with</param>
|
||||
public void SendUnnamedMessage(IReadOnlyList<ulong> clientIds, FastBufferWriter messageBuffer, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced)
|
||||
{
|
||||
if (!m_NetworkManager.IsServer)
|
||||
{
|
||||
throw new InvalidOperationException("Can not send unnamed messages to multiple users as a client");
|
||||
}
|
||||
|
||||
if (clientIds == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(clientIds), "You must pass in a valid clientId List");
|
||||
throw new ArgumentNullException(nameof(clientIds), "You must pass in a valid clientId List!");
|
||||
}
|
||||
|
||||
if (!m_NetworkManager.DistributedAuthorityMode && !m_NetworkManager.IsServer)
|
||||
{
|
||||
if (clientIds.Count > 1 || (clientIds.Count == 1 && clientIds[0] != NetworkManager.ServerClientId))
|
||||
{
|
||||
Debug.LogError("Clients cannot send unnamed messages to other clients!");
|
||||
return;
|
||||
}
|
||||
else if (clientIds.Count == 1)
|
||||
{
|
||||
SendUnnamedMessage(clientIds[0], messageBuffer, networkDelivery);
|
||||
}
|
||||
}
|
||||
else if (m_NetworkManager.DistributedAuthorityMode && !m_NetworkManager.DAHost)
|
||||
{
|
||||
if (clientIds.Count > 1)
|
||||
{
|
||||
Debug.LogError("Sending an unnamed message to multiple clients is not yet supported in distributed authority.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (clientIds.Count == 0)
|
||||
{
|
||||
Debug.LogError($"{nameof(clientIds)} is empty! No clients to send to.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (m_NetworkManager.IsHost)
|
||||
{
|
||||
for (var i = 0; i < clientIds.Count; ++i)
|
||||
@@ -203,6 +226,14 @@ namespace Unity.Netcode
|
||||
var hash32 = XXHash.Hash32(name);
|
||||
var hash64 = XXHash.Hash64(name);
|
||||
|
||||
if (m_NetworkManager.LogLevel <= LogLevel.Developer)
|
||||
{
|
||||
if (m_MessageHandlerNameLookup32.ContainsKey(hash32) || m_MessageHandlerNameLookup64.ContainsKey(hash64))
|
||||
{
|
||||
Debug.LogWarning($"Registering {name} named message over existing registration! Your previous registration's callback is being overwritten!");
|
||||
}
|
||||
}
|
||||
|
||||
m_NamedMessageHandlers32[hash32] = callback;
|
||||
m_NamedMessageHandlers64[hash64] = callback;
|
||||
|
||||
@@ -303,14 +334,37 @@ namespace Unity.Netcode
|
||||
/// <param name="networkDelivery">The delivery type (QoS) to send data with</param>
|
||||
public void SendNamedMessage(string messageName, IReadOnlyList<ulong> clientIds, FastBufferWriter messageStream, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced)
|
||||
{
|
||||
if (!m_NetworkManager.IsServer)
|
||||
{
|
||||
throw new InvalidOperationException("Can not send unnamed messages to multiple users as a client");
|
||||
}
|
||||
|
||||
if (clientIds == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(clientIds), "You must pass in a valid clientId List");
|
||||
throw new ArgumentNullException(nameof(clientIds), "Client list is null! You must pass in a valid clientId list to send a named message.");
|
||||
}
|
||||
|
||||
if (!m_NetworkManager.DistributedAuthorityMode && !m_NetworkManager.IsServer)
|
||||
{
|
||||
if (clientIds.Count > 1 || (clientIds.Count == 1 && clientIds[0] != NetworkManager.ServerClientId))
|
||||
{
|
||||
Debug.LogError("Clients cannot send named messages to other clients!");
|
||||
return;
|
||||
}
|
||||
else if (clientIds.Count == 1)
|
||||
{
|
||||
SendNamedMessage(messageName, clientIds[0], messageStream, networkDelivery);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (m_NetworkManager.DistributedAuthorityMode && !m_NetworkManager.DAHost)
|
||||
{
|
||||
if (clientIds.Count > 1)
|
||||
{
|
||||
Debug.LogError("Sending a named message to multiple clients is not yet supported in distributed authority.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (clientIds.Count == 0)
|
||||
{
|
||||
Debug.LogError($"{nameof(clientIds)} is empty! No clients to send the named message {messageName} to!");
|
||||
return;
|
||||
}
|
||||
|
||||
ulong hash = 0;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
@@ -12,9 +13,117 @@ namespace Unity.Netcode
|
||||
internal static readonly List<NetworkMessageManager.MessageWithHandler> __network_message_types = new List<NetworkMessageManager.MessageWithHandler>();
|
||||
#pragma warning restore IDE1006 // restore naming rule violation check
|
||||
|
||||
/// <summary>
|
||||
/// Enum representing the different types of messages that can be sent over the network.
|
||||
/// The values cannot be changed, as they are used to serialize and deserialize messages.
|
||||
/// Adding new messages should be done by adding new values to the end of the enum
|
||||
/// using the next free value.
|
||||
/// </summary>
|
||||
/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
/// Add any new Message types to this table at the END with incremented index value
|
||||
/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
internal enum NetworkMessageTypes : uint
|
||||
{
|
||||
ConnectionApproved = 0,
|
||||
ConnectionRequest = 1,
|
||||
ChangeOwnership = 2,
|
||||
ClientConnected = 3,
|
||||
ClientDisconnected = 4,
|
||||
ClientRpc = 5,
|
||||
CreateObject = 6,
|
||||
DestroyObject = 7,
|
||||
DisconnectReason = 8,
|
||||
ForwardClientRpc = 9,
|
||||
ForwardServerRpc = 10,
|
||||
NamedMessage = 11,
|
||||
NetworkTransformMessage = 12,
|
||||
NetworkVariableDelta = 13,
|
||||
ParentSync = 14,
|
||||
Proxy = 15,
|
||||
Rpc = 16,
|
||||
SceneEvent = 17,
|
||||
ServerLog = 18,
|
||||
ServerRpc = 19,
|
||||
TimeSync = 20,
|
||||
Unnamed = 21,
|
||||
SessionOwner = 22
|
||||
}
|
||||
|
||||
|
||||
// Enable this for integration tests that need no message types defined
|
||||
internal static bool IntegrationTestNoMessages;
|
||||
|
||||
public List<NetworkMessageManager.MessageWithHandler> GetMessages()
|
||||
{
|
||||
return __network_message_types;
|
||||
// return no message types when defined for integration tests
|
||||
if (IntegrationTestNoMessages)
|
||||
{
|
||||
return new List<NetworkMessageManager.MessageWithHandler>();
|
||||
}
|
||||
var messageTypeCount = Enum.GetValues(typeof(NetworkMessageTypes)).Length;
|
||||
// Assure the allowed types count is the same as our NetworkMessageType enum count
|
||||
if (__network_message_types.Count != messageTypeCount)
|
||||
{
|
||||
throw new Exception($"Allowed types is not equal to the number of message type indices! Allowed Count: {__network_message_types.Count} | Index Count: {messageTypeCount}");
|
||||
}
|
||||
|
||||
// Populate with blanks to be replaced later
|
||||
var adjustedMessageTypes = new List<NetworkMessageManager.MessageWithHandler>();
|
||||
var blank = new NetworkMessageManager.MessageWithHandler();
|
||||
for (int i = 0; i < messageTypeCount; i++)
|
||||
{
|
||||
adjustedMessageTypes.Add(blank);
|
||||
}
|
||||
|
||||
// Create a type to enum index lookup table
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
// Add new Message types to this table paired with its new NetworkMessageTypes enum
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
var messageTypes = new Dictionary<Type, NetworkMessageTypes>
|
||||
{
|
||||
{ typeof(ConnectionApprovedMessage), NetworkMessageTypes.ConnectionApproved }, // This MUST be first
|
||||
{ typeof(ConnectionRequestMessage), NetworkMessageTypes.ConnectionRequest }, // This MUST be second
|
||||
{ typeof(ChangeOwnershipMessage), NetworkMessageTypes.ChangeOwnership },
|
||||
{ typeof(ClientConnectedMessage), NetworkMessageTypes.ClientConnected },
|
||||
{ typeof(ClientDisconnectedMessage), NetworkMessageTypes.ClientDisconnected },
|
||||
{ typeof(ClientRpcMessage), NetworkMessageTypes.ClientRpc },
|
||||
{ typeof(CreateObjectMessage), NetworkMessageTypes.CreateObject },
|
||||
{ typeof(DestroyObjectMessage), NetworkMessageTypes.DestroyObject },
|
||||
{ typeof(DisconnectReasonMessage), NetworkMessageTypes.DisconnectReason },
|
||||
{ typeof(ForwardClientRpcMessage), NetworkMessageTypes.ForwardClientRpc },
|
||||
{ typeof(ForwardServerRpcMessage), NetworkMessageTypes.ForwardServerRpc },
|
||||
{ typeof(NamedMessage), NetworkMessageTypes.NamedMessage },
|
||||
{ typeof(NetworkTransformMessage), NetworkMessageTypes.NetworkTransformMessage },
|
||||
{ typeof(NetworkVariableDeltaMessage), NetworkMessageTypes.NetworkVariableDelta },
|
||||
{ typeof(ParentSyncMessage), NetworkMessageTypes.ParentSync },
|
||||
{ typeof(ProxyMessage), NetworkMessageTypes.Proxy },
|
||||
{ typeof(RpcMessage), NetworkMessageTypes.Rpc },
|
||||
{ typeof(SceneEventMessage), NetworkMessageTypes.SceneEvent },
|
||||
{ typeof(ServerLogMessage), NetworkMessageTypes.ServerLog },
|
||||
{ typeof(ServerRpcMessage), NetworkMessageTypes.ServerRpc },
|
||||
{ typeof(TimeSyncMessage), NetworkMessageTypes.TimeSync },
|
||||
{ typeof(UnnamedMessage), NetworkMessageTypes.Unnamed },
|
||||
{ typeof(SessionOwnerMessage), NetworkMessageTypes.SessionOwner }
|
||||
};
|
||||
|
||||
// Assure the type to lookup table count and NetworkMessageType enum count matches (i.e. to catch human error when adding new messages)
|
||||
if (messageTypes.Count != messageTypeCount)
|
||||
{
|
||||
throw new Exception($"Message type to Message type index count mistmatch! Table Count: {messageTypes.Count} | Index Count: {messageTypeCount}");
|
||||
}
|
||||
|
||||
// Now order the allowed types list based on the order of the NetworkMessageType enum
|
||||
foreach (var messageHandler in __network_message_types)
|
||||
{
|
||||
if (!messageTypes.ContainsKey(messageHandler.MessageType))
|
||||
{
|
||||
throw new Exception($"Missing message type from lookup table: {messageHandler.MessageType}");
|
||||
}
|
||||
adjustedMessageTypes[(int)messageTypes[messageHandler.MessageType]] = messageHandler;
|
||||
}
|
||||
|
||||
// return the NetworkMessageType enum ordered list
|
||||
return adjustedMessageTypes;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace Unity.Netcode
|
||||
{
|
||||
public int Version => 0;
|
||||
|
||||
private const string k_Name = "ChangeOwnershipMessage";
|
||||
|
||||
public ulong NetworkObjectId;
|
||||
public ulong OwnerClientId;
|
||||
// DANGOEXP TODO: Remove these notes or change their format
|
||||
@@ -199,7 +201,7 @@ namespace Unity.Netcode
|
||||
// authority of the NetworkObject in question.
|
||||
if (!networkManager.DAHost && !networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, GetType().Name);
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, k_Name);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -222,6 +222,12 @@ namespace Unity.Netcode
|
||||
}
|
||||
// When scene management is disabled we notify after everything is synchronized
|
||||
networkManager.ConnectionManager.InvokeOnClientConnectedCallback(context.SenderId);
|
||||
|
||||
// For convenience, notify all NetworkBehaviours that synchronization is complete.
|
||||
foreach (var networkObject in networkManager.SpawnManager.SpawnedObjectsList)
|
||||
{
|
||||
networkObject.InternalNetworkSessionSynchronized();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -230,16 +236,22 @@ namespace Unity.Netcode
|
||||
// Mark the client being connected
|
||||
networkManager.IsConnectedClient = true;
|
||||
|
||||
// Spawn any in-scene placed NetworkObjects
|
||||
networkManager.SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
|
||||
networkManager.SceneManager.IsRestoringSession = IsRestoredSession;
|
||||
|
||||
// Spawn the local player of the session owner
|
||||
if (networkManager.AutoSpawnPlayerPrefabClientSide)
|
||||
if (!IsRestoredSession)
|
||||
{
|
||||
networkManager.ConnectionManager.CreateAndSpawnPlayer(OwnerClientId);
|
||||
// Spawn any in-scene placed NetworkObjects
|
||||
networkManager.SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
|
||||
|
||||
// Spawn the local player of the session owner
|
||||
if (networkManager.AutoSpawnPlayerPrefabClientSide)
|
||||
{
|
||||
networkManager.ConnectionManager.CreateAndSpawnPlayer(OwnerClientId);
|
||||
}
|
||||
|
||||
// Synchronize the service with the initial session owner's loaded scenes and spawned objects
|
||||
networkManager.SceneManager.SynchronizeNetworkObjects(NetworkManager.ServerClientId);
|
||||
}
|
||||
// Synchronize the service with the initial session owner's loaded scenes and spawned objects
|
||||
networkManager.SceneManager.SynchronizeNetworkObjects(NetworkManager.ServerClientId);
|
||||
}
|
||||
}
|
||||
ConnectedClientIds.Dispose();
|
||||
|
||||
@@ -7,6 +7,8 @@ namespace Unity.Netcode
|
||||
{
|
||||
public int Version => 0;
|
||||
|
||||
private const string k_Name = "CreateObjectMessage";
|
||||
|
||||
public NetworkObject.SceneObject ObjectInfo;
|
||||
private FastBufferReader m_ReceivedNetworkVariableData;
|
||||
|
||||
@@ -161,7 +163,7 @@ namespace Unity.Netcode
|
||||
|
||||
if (!networkManager.NetworkConfig.ForceSamePrefabs && !networkManager.SpawnManager.HasPrefab(ObjectInfo))
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Hash, reader, ref context, GetType().Name);
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Hash, reader, ref context, k_Name);
|
||||
return false;
|
||||
}
|
||||
m_ReceivedNetworkVariableData = reader;
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace Unity.Netcode
|
||||
{
|
||||
public int Version => 0;
|
||||
|
||||
private const string k_Name = "DestroyObjectMessage";
|
||||
|
||||
public ulong NetworkObjectId;
|
||||
public bool DestroyGameObject;
|
||||
private byte m_DestroyFlags;
|
||||
@@ -84,7 +86,7 @@ namespace Unity.Netcode
|
||||
// Client-Server mode we always defer where in distributed authority mode we only defer if it is not a targeted destroy
|
||||
if (!networkManager.DistributedAuthorityMode || (networkManager.DistributedAuthorityMode && !IsTargetedDestroy))
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, GetType().Name);
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, k_Name);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
190
Runtime/Messaging/Messages/NetworkTransformMessage.cs
Normal file
190
Runtime/Messaging/Messages/NetworkTransformMessage.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
using Unity.Netcode.Components;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// NetworkTransform State Update Message
|
||||
/// </summary>
|
||||
internal struct NetworkTransformMessage : INetworkMessage
|
||||
{
|
||||
public int Version => 0;
|
||||
private const string k_Name = "NetworkTransformMessage";
|
||||
|
||||
internal NetworkTransform NetworkTransform;
|
||||
|
||||
// Only used for DAHost
|
||||
internal NetworkTransform.NetworkTransformState State;
|
||||
private FastBufferReader m_CurrentReader;
|
||||
|
||||
private unsafe void CopyPayload(ref FastBufferWriter writer)
|
||||
{
|
||||
writer.WriteBytesSafe(m_CurrentReader.GetUnsafePtrAtCurrentPosition(), m_CurrentReader.Length - m_CurrentReader.Position);
|
||||
}
|
||||
|
||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
if (m_CurrentReader.IsInitialized)
|
||||
{
|
||||
CopyPayload(ref writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkTransform.SerializeMessage(writer, targetVersion);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
{
|
||||
var networkManager = context.SystemOwner as NetworkManager;
|
||||
if (networkManager == null)
|
||||
{
|
||||
Debug.LogError($"[{nameof(NetworkTransformMessage)}] System owner context was not of type {nameof(NetworkManager)}!");
|
||||
return false;
|
||||
}
|
||||
var currentPosition = reader.Position;
|
||||
var networkObjectId = (ulong)0;
|
||||
var networkBehaviourId = 0;
|
||||
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out networkObjectId);
|
||||
var isSpawnedLocally = networkManager.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId);
|
||||
|
||||
// Only defer if the NetworkObject is not spawned yet and the local NetworkManager is not running as a DAHost.
|
||||
if (!isSpawnedLocally && !networkManager.DAHost)
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, networkObjectId, reader, ref context, k_Name);
|
||||
return false;
|
||||
}
|
||||
|
||||
// While the below check and assignment might seem out of place, this is specific to running in DAHost mode when a NetworkObject is
|
||||
// hidden from the DAHost but is visible to other clients. Since the DAHost needs to forward updates to the clients, we ignore processing
|
||||
// this message locally
|
||||
var networkObject = (NetworkObject)null;
|
||||
var isServerAuthoritative = false;
|
||||
var ownerAuthoritativeServerSide = false;
|
||||
|
||||
// Get the behaviour index
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out networkBehaviourId);
|
||||
|
||||
if (isSpawnedLocally)
|
||||
{
|
||||
networkObject = networkManager.SpawnManager.SpawnedObjects[networkObjectId];
|
||||
// Get the target NetworkTransform
|
||||
NetworkTransform = networkObject.ChildNetworkBehaviours[networkBehaviourId] as NetworkTransform;
|
||||
isServerAuthoritative = NetworkTransform.IsServerAuthoritative();
|
||||
ownerAuthoritativeServerSide = !isServerAuthoritative && networkManager.IsServer;
|
||||
|
||||
reader.ReadNetworkSerializableInPlace(ref NetworkTransform.InboundState);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Deserialize the state
|
||||
reader.ReadNetworkSerializableInPlace(ref State);
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
if (ownerAuthoritativeServerSide)
|
||||
{
|
||||
var targetCount = 1;
|
||||
|
||||
if (networkManager.DistributedAuthorityMode && networkManager.DAHost)
|
||||
{
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out targetCount);
|
||||
}
|
||||
|
||||
var targetIds = stackalloc ulong[targetCount];
|
||||
|
||||
if (networkManager.DistributedAuthorityMode && networkManager.DAHost)
|
||||
{
|
||||
var targetId = (ulong)0;
|
||||
for (int i = 0; i < targetCount; i++)
|
||||
{
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out targetId);
|
||||
targetIds[i] = targetId;
|
||||
}
|
||||
|
||||
if (!isSpawnedLocally)
|
||||
{
|
||||
// If we are the DAHost and the NetworkObject is hidden from the host we still need to forward this message
|
||||
ownerAuthoritativeServerSide = networkManager.DAHost && !isSpawnedLocally;
|
||||
}
|
||||
}
|
||||
|
||||
var ownerClientId = (ulong)0;
|
||||
|
||||
if (networkObject != null)
|
||||
{
|
||||
ownerClientId = networkObject.OwnerClientId;
|
||||
if (ownerClientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
// Ownership must have changed, ignore any additional pending messages that might have
|
||||
// come from a previous owner client.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (networkManager.DAHost)
|
||||
{
|
||||
// Specific to distributed authority mode, the only sender of state updates will be the owner
|
||||
ownerClientId = context.SenderId;
|
||||
}
|
||||
|
||||
var networkDelivery = State.IsReliableStateUpdate() ? NetworkDelivery.ReliableSequenced : NetworkDelivery.UnreliableSequenced;
|
||||
|
||||
// Forward the state update if there are any remote clients to foward it to
|
||||
if (networkManager.ConnectionManager.ConnectedClientsList.Count > (networkManager.IsHost ? 2 : 1))
|
||||
{
|
||||
var clientCount = networkManager.DistributedAuthorityMode ? targetCount : networkManager.ConnectionManager.ConnectedClientsList.Count;
|
||||
if (clientCount == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// This is only to copy the existing and already serialized struct for forwarding purposes only.
|
||||
// This will not include any changes made to this struct at this particular stage of processing the message.
|
||||
var currentMessage = this;
|
||||
// Create a new reader that replicates this message
|
||||
currentMessage.m_CurrentReader = new FastBufferReader(reader, Collections.Allocator.None);
|
||||
// Rewind the new reader to the beginning of the message's payload
|
||||
currentMessage.m_CurrentReader.Seek(currentPosition);
|
||||
// Forward the message to all connected clients that are observers of the associated NetworkObject
|
||||
|
||||
for (int i = 0; i < clientCount; i++)
|
||||
{
|
||||
var clientId = networkManager.DistributedAuthorityMode ? targetIds[i] : networkManager.ConnectionManager.ConnectedClientsList[i].ClientId;
|
||||
if (NetworkManager.ServerClientId == clientId || (!isServerAuthoritative && clientId == ownerClientId) ||
|
||||
(!networkManager.DistributedAuthorityMode && !networkObject.Observers.Contains(clientId)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
networkManager.MessageManager.SendMessage(ref currentMessage, networkDelivery, clientId);
|
||||
}
|
||||
// Dispose of the reader used for forwarding
|
||||
currentMessage.m_CurrentReader.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
var networkManager = context.SystemOwner as NetworkManager;
|
||||
// Only if the local NetworkManager instance is running as the DAHost we just exit if there is no local
|
||||
// NetworkTransform component to apply the state update to (i.e. it is hidden from the DAHost and it
|
||||
// just forwarded the state update to any other connected client)
|
||||
if (networkManager.DAHost && NetworkTransform == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (NetworkTransform == null)
|
||||
{
|
||||
Debug.LogError($"[{nameof(NetworkTransformMessage)}][Dropped] Reciever {nameof(NetworkTransform)} was not set!");
|
||||
return;
|
||||
}
|
||||
NetworkTransform.TransformStateUpdate(context.SenderId);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Runtime/Messaging/Messages/NetworkTransformMessage.cs.meta
Normal file
11
Runtime/Messaging/Messages/NetworkTransformMessage.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dcfc8ac43fef97e42adb19b998d70c37
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -23,6 +23,8 @@ namespace Unity.Netcode
|
||||
|
||||
private FastBufferReader m_ReceivedNetworkVariableData;
|
||||
|
||||
private const string k_Name = "NetworkVariableDeltaMessage";
|
||||
|
||||
// DANGO-TODO: Made some modifications here that overlap/won't play nice with EnsureNetworkVariableLenghtSafety.
|
||||
// Worth either merging or more cleanly separating these codepaths.
|
||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
@@ -296,7 +298,7 @@ namespace Unity.Netcode
|
||||
// DANGO-TODO: Fix me!
|
||||
// When a client-spawned NetworkObject is despawned by the owner client, the owner client will still get messages for deltas and cause this to
|
||||
// log a warning. The issue is primarily how NetworkVariables handle updating and will require some additional re-factoring.
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, m_ReceivedNetworkVariableData, ref context, GetType().Name);
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, m_ReceivedNetworkVariableData, ref context, k_Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace Unity.Netcode
|
||||
{
|
||||
public int Version => 0;
|
||||
|
||||
private const string k_Name = "DestroyObjectMessage";
|
||||
|
||||
public ulong NetworkObjectId;
|
||||
|
||||
private byte m_BitField;
|
||||
@@ -92,7 +94,7 @@ namespace Unity.Netcode
|
||||
// If the target NetworkObject does not exist =or= the target latest parent does not exist then defer the message
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId) || (LatestParent.HasValue && !networkManager.SpawnManager.SpawnedObjects.ContainsKey(LatestParent.Value)))
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, GetType().Name);
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, k_Name);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -99,6 +99,8 @@ namespace Unity.Netcode
|
||||
public FastBufferWriter WriteBuffer;
|
||||
public FastBufferReader ReadBuffer;
|
||||
|
||||
private const string k_Name = "ServerRpcMessage";
|
||||
|
||||
public unsafe void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
RpcMessageHelpers.Serialize(ref writer, ref Metadata, ref WriteBuffer);
|
||||
@@ -106,7 +108,7 @@ namespace Unity.Netcode
|
||||
|
||||
public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
{
|
||||
return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer, GetType().Name);
|
||||
return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer, k_Name);
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
@@ -134,6 +136,8 @@ namespace Unity.Netcode
|
||||
public FastBufferWriter WriteBuffer;
|
||||
public FastBufferReader ReadBuffer;
|
||||
|
||||
private const string k_Name = "ClientRpcMessage";
|
||||
|
||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
RpcMessageHelpers.Serialize(ref writer, ref Metadata, ref WriteBuffer);
|
||||
@@ -141,7 +145,7 @@ namespace Unity.Netcode
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
{
|
||||
return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer, GetType().Name);
|
||||
return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer, k_Name);
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
@@ -169,6 +173,8 @@ namespace Unity.Netcode
|
||||
public FastBufferWriter WriteBuffer;
|
||||
public FastBufferReader ReadBuffer;
|
||||
|
||||
private const string k_Name = "RpcMessage";
|
||||
|
||||
public unsafe void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
BytePacker.WriteValuePacked(writer, SenderClientId);
|
||||
@@ -179,7 +185,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
ByteUnpacker.ReadValuePacked(reader, out SenderClientId);
|
||||
|
||||
return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer, GetType().Name);
|
||||
return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer, k_Name);
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
|
||||
@@ -120,49 +120,20 @@ namespace Unity.Netcode
|
||||
public VersionGetter GetVersion;
|
||||
}
|
||||
|
||||
internal List<MessageWithHandler> PrioritizeMessageOrder(List<MessageWithHandler> allowedTypes)
|
||||
{
|
||||
var prioritizedTypes = new List<MessageWithHandler>();
|
||||
|
||||
// First pass puts the priority message in the first indices
|
||||
// Those are the messages that must be delivered in order to allow re-ordering the others later
|
||||
foreach (var t in allowedTypes)
|
||||
{
|
||||
if (t.MessageType.FullName == typeof(ConnectionRequestMessage).FullName ||
|
||||
t.MessageType.FullName == typeof(ConnectionApprovedMessage).FullName)
|
||||
{
|
||||
prioritizedTypes.Add(t);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var t in allowedTypes)
|
||||
{
|
||||
if (t.MessageType.FullName != typeof(ConnectionRequestMessage).FullName &&
|
||||
t.MessageType.FullName != typeof(ConnectionApprovedMessage).FullName)
|
||||
{
|
||||
prioritizedTypes.Add(t);
|
||||
}
|
||||
}
|
||||
|
||||
return prioritizedTypes;
|
||||
}
|
||||
|
||||
public NetworkMessageManager(INetworkMessageSender sender, object owner, INetworkMessageProvider provider = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_Sender = sender;
|
||||
m_Owner = owner;
|
||||
|
||||
if (provider == null)
|
||||
{
|
||||
provider = new ILPPMessageProvider();
|
||||
}
|
||||
|
||||
// Get the presorted message types returned by the provider
|
||||
var allowedTypes = provider.GetMessages();
|
||||
|
||||
allowedTypes.Sort((a, b) => string.CompareOrdinal(a.MessageType.FullName, b.MessageType.FullName));
|
||||
allowedTypes = PrioritizeMessageOrder(allowedTypes);
|
||||
foreach (var type in allowedTypes)
|
||||
{
|
||||
RegisterMessageType(type);
|
||||
|
||||
@@ -425,7 +425,7 @@ namespace Unity.Netcode
|
||||
|
||||
/// <summary>
|
||||
/// Returns the currently loaded scenes that are synchronized with the session owner or server depending upon the selected
|
||||
/// NetworkManager session mode.
|
||||
/// network topology.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The <see cref="SceneManager"/> scenes loaded returns all scenes loaded where this returns only the scenes that have been
|
||||
@@ -444,6 +444,7 @@ namespace Unity.Netcode
|
||||
internal Dictionary<int, int> ServerSceneHandleToClientSceneHandle = new Dictionary<int, int>();
|
||||
internal Dictionary<int, int> ClientSceneHandleToServerSceneHandle = new Dictionary<int, int>();
|
||||
|
||||
internal bool IsRestoringSession;
|
||||
/// <summary>
|
||||
/// Add the client to server (and vice versa) scene handle lookup.
|
||||
/// Add the client-side handle to scene entry in the HandleToScene table.
|
||||
@@ -455,7 +456,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
ServerSceneHandleToClientSceneHandle.Add(serverHandle, clientHandle);
|
||||
}
|
||||
else
|
||||
else if (!IsRestoringSession)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -464,7 +465,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
ClientSceneHandleToServerSceneHandle.Add(clientHandle, serverHandle);
|
||||
}
|
||||
else
|
||||
else if (!IsRestoringSession)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1045,7 +1046,7 @@ namespace Unity.Netcode
|
||||
/// <param name="targetClientIds">array of client identifiers to receive the scene event message</param>
|
||||
private void SendSceneEventData(uint sceneEventId, ulong[] targetClientIds)
|
||||
{
|
||||
if (targetClientIds.Length == 0)
|
||||
if (targetClientIds.Length == 0 && !NetworkManager.DistributedAuthorityMode)
|
||||
{
|
||||
// This would be the Host/Server with no clients connected
|
||||
// Silently return as there is nothing to be done
|
||||
@@ -1056,6 +1057,16 @@ namespace Unity.Netcode
|
||||
|
||||
if (NetworkManager.DistributedAuthorityMode && !NetworkManager.DAHost)
|
||||
{
|
||||
if (NetworkManager.DistributedAuthorityMode && HasSceneAuthority())
|
||||
{
|
||||
sceneEvent.TargetClientId = NetworkManager.ServerClientId;
|
||||
var message = new SceneEventMessage
|
||||
{
|
||||
EventData = sceneEvent,
|
||||
};
|
||||
var size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, NetworkManager.ServerClientId);
|
||||
NetworkManager.NetworkMetrics.TrackSceneEventSent(NetworkManager.ServerClientId, (uint)sceneEvent.SceneEventType, SceneNameFromHash(sceneEvent.SceneHash), size);
|
||||
}
|
||||
foreach (var clientId in targetClientIds)
|
||||
{
|
||||
sceneEvent.TargetClientId = clientId;
|
||||
@@ -1859,6 +1870,17 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var keyValuePairByGlobalObjectIdHash in ScenePlacedObjects)
|
||||
{
|
||||
foreach (var keyValuePairBySceneHandle in keyValuePairByGlobalObjectIdHash.Value)
|
||||
{
|
||||
if (!keyValuePairBySceneHandle.Value.IsPlayerObject)
|
||||
{
|
||||
keyValuePairBySceneHandle.Value.InternalInSceneNetworkObjectsSpawned();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add any despawned when spawned in-scene placed NetworkObjects to the scene event data
|
||||
sceneEventData.AddDespawnedInSceneNetworkObjects();
|
||||
|
||||
@@ -2413,6 +2435,18 @@ namespace Unity.Netcode
|
||||
{
|
||||
NetworkLog.LogInfo($"[Client-{NetworkManager.LocalClientId}][Scene Management Enabled] Synchronization complete!");
|
||||
}
|
||||
// For convenience, notify all NetworkBehaviours that synchronization is complete.
|
||||
foreach (var networkObject in NetworkManager.SpawnManager.SpawnedObjectsList)
|
||||
{
|
||||
networkObject.InternalNetworkSessionSynchronized();
|
||||
}
|
||||
|
||||
if (NetworkManager.DistributedAuthorityMode && HasSceneAuthority() && IsRestoringSession)
|
||||
{
|
||||
IsRestoringSession = false;
|
||||
PostSynchronizationSceneUnloading = m_OriginalPostSynchronizationSceneUnloading;
|
||||
}
|
||||
|
||||
EndSceneEvent(sceneEventId);
|
||||
}
|
||||
break;
|
||||
@@ -2599,6 +2633,8 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
internal bool SkipSceneHandling;
|
||||
|
||||
private bool m_OriginalPostSynchronizationSceneUnloading;
|
||||
|
||||
/// <summary>
|
||||
/// Both Client and Server: Incoming scene event entry point
|
||||
/// </summary>
|
||||
@@ -2672,6 +2708,12 @@ namespace Unity.Netcode
|
||||
// Only if ClientSynchronizationMode is Additive and the client receives a synchronize scene event
|
||||
if (ClientSynchronizationMode == LoadSceneMode.Additive)
|
||||
{
|
||||
if (NetworkManager.DistributedAuthorityMode && HasSceneAuthority() && IsRestoringSession && clientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
m_OriginalPostSynchronizationSceneUnloading = PostSynchronizationSceneUnloading;
|
||||
PostSynchronizationSceneUnloading = true;
|
||||
}
|
||||
|
||||
// Check for scenes already loaded and create a table of scenes already loaded (SceneEntries) that will be
|
||||
// used if the server is synchronizing the same scenes (i.e. if a matching scene is already loaded on the
|
||||
// client side, then that scene will be used as opposed to loading another scene). This allows for clients
|
||||
|
||||
@@ -839,7 +839,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
// is not packed!
|
||||
InternalBuffer.ReadValueSafe(out ushort newObjectsCount);
|
||||
|
||||
var sceneObjects = new List<NetworkObject>();
|
||||
for (ushort i = 0; i < newObjectsCount; i++)
|
||||
{
|
||||
var sceneObject = new NetworkObject.SceneObject();
|
||||
@@ -851,10 +851,22 @@ namespace Unity.Netcode
|
||||
m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(sceneObject.NetworkSceneHandle);
|
||||
}
|
||||
|
||||
NetworkObject.AddSceneObject(sceneObject, InternalBuffer, m_NetworkManager);
|
||||
var networkObject = NetworkObject.AddSceneObject(sceneObject, InternalBuffer, m_NetworkManager);
|
||||
|
||||
if (sceneObject.IsSceneObject)
|
||||
{
|
||||
sceneObjects.Add(networkObject);
|
||||
}
|
||||
}
|
||||
// Now deserialize the despawned in-scene placed NetworkObjects list (if any)
|
||||
DeserializeDespawnedInScenePlacedNetworkObjects();
|
||||
|
||||
// Notify all newly spawned in-scene placed NetworkObjects that all in-scene placed
|
||||
// NetworkObjects have been spawned.
|
||||
foreach (var networkObject in sceneObjects)
|
||||
{
|
||||
networkObject.InternalInSceneNetworkObjectsSpawned();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -1122,6 +1134,15 @@ namespace Unity.Netcode
|
||||
UnityEngine.Debug.Log(builder.ToString());
|
||||
}
|
||||
|
||||
// Notify that all in-scene placed NetworkObjects have been spawned
|
||||
foreach (var networkObject in m_NetworkObjectsSync)
|
||||
{
|
||||
if (networkObject.IsSceneObject.HasValue && networkObject.IsSceneObject.Value)
|
||||
{
|
||||
networkObject.InternalInSceneNetworkObjectsSpawned();
|
||||
}
|
||||
}
|
||||
|
||||
// Now deserialize the despawned in-scene placed NetworkObjects list (if any)
|
||||
DeserializeDespawnedInScenePlacedNetworkObjects();
|
||||
|
||||
|
||||
@@ -810,20 +810,27 @@ namespace Unity.Netcode
|
||||
networkObject.DontDestroyWithOwner = sceneObject.DontDestroyWithOwner;
|
||||
networkObject.Ownership = (NetworkObject.OwnershipStatus)sceneObject.OwnershipFlags;
|
||||
|
||||
|
||||
var nonNetworkObjectParent = false;
|
||||
// SPECIAL CASE FOR IN-SCENE PLACED: (only when the parent has a NetworkObject)
|
||||
// This is a special case scenario where a late joining client has joined and loaded one or
|
||||
// more scenes that contain nested in-scene placed NetworkObject children yet the server's
|
||||
// synchronization information does not indicate the NetworkObject in question has a parent.
|
||||
// Under this scenario, we want to remove the parent before spawning and setting the transform values.
|
||||
if (sceneObject.IsSceneObject && !sceneObject.HasParent && networkObject.transform.parent != null)
|
||||
if (sceneObject.IsSceneObject && networkObject.transform.parent != null)
|
||||
{
|
||||
var parentNetworkObject = networkObject.transform.parent.GetComponent<NetworkObject>();
|
||||
// if the in-scene placed NetworkObject has a parent NetworkObject but the synchronization information does not
|
||||
// include parenting, then we need to force the removal of that parent
|
||||
if (networkObject.transform.parent.GetComponent<NetworkObject>() != null)
|
||||
if (!sceneObject.HasParent && parentNetworkObject)
|
||||
{
|
||||
// remove the parent
|
||||
networkObject.ApplyNetworkParenting(true, true);
|
||||
}
|
||||
else if (sceneObject.HasParent && !parentNetworkObject)
|
||||
{
|
||||
nonNetworkObjectParent = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the transform unless we were spawned by a prefab handler
|
||||
@@ -833,7 +840,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
// If world position stays is true or we have auto object parent synchronization disabled
|
||||
// then we want to apply the position and rotation values world space relative
|
||||
if (worldPositionStays || !networkObject.AutoObjectParentSync)
|
||||
if ((worldPositionStays && !nonNetworkObjectParent) || !networkObject.AutoObjectParentSync)
|
||||
{
|
||||
networkObject.transform.position = position;
|
||||
networkObject.transform.rotation = rotation;
|
||||
@@ -917,6 +924,8 @@ namespace Unity.Netcode
|
||||
Debug.LogError("Spawning NetworkObjects with nested NetworkObjects is only supported for scene objects. Child NetworkObjects will not be spawned over the network!");
|
||||
}
|
||||
}
|
||||
// Invoke NetworkBehaviour.OnPreSpawn methods
|
||||
networkObject.InvokeBehaviourNetworkPreSpawn();
|
||||
|
||||
// DANGO-TODO: It would be nice to allow users to specify which clients are observers prior to spawning
|
||||
// For now, this is the best place I could find to add all connected clients as observers for newly
|
||||
@@ -957,12 +966,18 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
SpawnNetworkObjectLocallyCommon(networkObject, networkId, sceneObject, playerObject, ownerClientId, destroyWithScene);
|
||||
|
||||
// Invoke NetworkBehaviour.OnPostSpawn methods
|
||||
networkObject.InvokeBehaviourNetworkPostSpawn();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is only invoked to instantiate a serialized NetworkObject via
|
||||
/// <see cref="NetworkObject.AddSceneObject(in NetworkObject.SceneObject, FastBufferReader, NetworkManager, bool)"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// IMPORTANT: Pre spawn methods need to be invoked from within <see cref="NetworkObject.AddSceneObject"/>.
|
||||
/// </remarks>
|
||||
internal void SpawnNetworkObjectLocally(NetworkObject networkObject, in NetworkObject.SceneObject sceneObject, bool destroyWithScene)
|
||||
{
|
||||
if (networkObject == null)
|
||||
@@ -975,7 +990,11 @@ namespace Unity.Netcode
|
||||
throw new SpawnStateException($"[{networkObject.name}] Object-{networkObject.NetworkObjectId} is already spawned!");
|
||||
}
|
||||
|
||||
// Do not invoke Pre spawn here (SynchronizeNetworkBehaviours needs to be invoked prior to this)
|
||||
SpawnNetworkObjectLocallyCommon(networkObject, sceneObject.NetworkObjectId, sceneObject.IsSceneObject, sceneObject.IsPlayerObject, sceneObject.OwnerClientId, destroyWithScene);
|
||||
|
||||
// It is ok to invoke NetworkBehaviour.OnPostSpawn methods
|
||||
networkObject.InvokeBehaviourNetworkPostSpawn();
|
||||
}
|
||||
|
||||
private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong ownerClientId, bool destroyWithScene)
|
||||
@@ -1226,7 +1245,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
// If it is an in-scene placed NetworkObject then just despawn and let it be destroyed when the scene
|
||||
// is unloaded. Otherwise, despawn and destroy it.
|
||||
var shouldDestroy = !(networkObjects[i].IsSceneObject != null && networkObjects[i].IsSceneObject.Value);
|
||||
var shouldDestroy = !(networkObjects[i].IsSceneObject == null || (networkObjects[i].IsSceneObject != null && networkObjects[i].IsSceneObject.Value));
|
||||
|
||||
// If we are going to destroy this NetworkObject, check for any in-scene placed children that need to be removed
|
||||
if (shouldDestroy)
|
||||
@@ -1307,6 +1326,7 @@ namespace Unity.Netcode
|
||||
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
|
||||
#endif
|
||||
var isConnectedCMBService = NetworkManager.CMBServiceConnection;
|
||||
var networkObjectsToSpawn = new List<NetworkObject>();
|
||||
for (int i = 0; i < networkObjects.Length; i++)
|
||||
{
|
||||
if (networkObjects[i].NetworkManager == NetworkManager)
|
||||
@@ -1323,9 +1343,17 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
SpawnNetworkObjectLocally(networkObjects[i], GetNetworkObjectId(), true, false, ownerId, true);
|
||||
networkObjectsToSpawn.Add(networkObjects[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify all in-scene placed NetworkObjects have been spawned
|
||||
foreach (var networkObject in networkObjectsToSpawn)
|
||||
{
|
||||
networkObject.InternalInSceneNetworkObjectsSpawned();
|
||||
}
|
||||
networkObjectsToSpawn.Clear();
|
||||
}
|
||||
|
||||
internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObject, bool modeDestroy = false)
|
||||
|
||||
@@ -426,10 +426,11 @@ namespace Unity.Netcode.Transports.UTP
|
||||
internal static event Action<int> TransportDisposed;
|
||||
internal NetworkDriver NetworkDriver => m_Driver;
|
||||
|
||||
protected NetworkDriver m_Driver;
|
||||
|
||||
private PacketLossCache m_PacketLossCache = new PacketLossCache();
|
||||
|
||||
private State m_State = State.Disconnected;
|
||||
private NetworkDriver m_Driver;
|
||||
private NetworkSettings m_NetworkSettings;
|
||||
private ulong m_ServerClientId;
|
||||
|
||||
@@ -554,12 +555,17 @@ namespace Unity.Netcode.Transports.UTP
|
||||
return false;
|
||||
}
|
||||
|
||||
var serverConnection = m_Driver.Connect(serverEndpoint);
|
||||
var serverConnection = Connect(serverEndpoint);
|
||||
m_ServerClientId = ParseClientId(serverConnection);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual NetworkConnection Connect(NetworkEndpoint serverEndpoint)
|
||||
{
|
||||
return m_Driver.Connect(serverEndpoint);
|
||||
}
|
||||
|
||||
private bool ServerBindAndListen(NetworkEndpoint endPoint)
|
||||
{
|
||||
// Verify the endpoint is valid before proceeding
|
||||
|
||||
@@ -46,6 +46,21 @@
|
||||
"name": "Unity",
|
||||
"expression": "2023",
|
||||
"define": "UNITY_DEDICATED_SERVER_ARGUMENTS_PRESENT"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.modules.animation",
|
||||
"expression": "",
|
||||
"define": "COM_UNITY_MODULES_ANIMATION"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.modules.physics",
|
||||
"expression": "",
|
||||
"define": "COM_UNITY_MODULES_PHYSICS"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.modules.physics2d",
|
||||
"expression": "",
|
||||
"define": "COM_UNITY_MODULES_PHYSICS2D"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user