com.unity.netcode.gameobjects@1.2.0
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). ## [1.2.0] - 2022-11-21 ### Added - Added protected method `NetworkBehaviour.OnSynchronize` which is invoked during the initial `NetworkObject` synchronization process. This provides users the ability to include custom serialization information that will be applied to the `NetworkBehaviour` prior to the `NetworkObject` being spawned. (#2298) - Added support for different versions of the SDK to talk to each other in circumstances where changes permit it. Starting with this version and into future versions, patch versions should be compatible as long as the minor version is the same. (#2290) - Added `NetworkObject` auto-add helper and Multiplayer Tools install reminder settings to Project Settings. (#2285) - Added `public string DisconnectReason` getter to `NetworkManager` and `string Reason` to `ConnectionApprovalResponse`. Allows connection approval to communicate back a reason. Also added `public void DisconnectClient(ulong clientId, string reason)` allowing setting a disconnection reason, when explicitly disconnecting a client. (#2280) ### Changed - Changed 3rd-party `XXHash` (32 & 64) implementation with an in-house reimplementation (#2310) - When `NetworkConfig.EnsureNetworkVariableLengthSafety` is disabled `NetworkVariable` fields do not write the additional `ushort` size value (_which helps to reduce the total synchronization message size_), but when enabled it still writes the additional `ushort` value. (#2298) - Optimized bandwidth usage by encoding most integer fields using variable-length encoding. (#2276) ### Fixed - Fixed issue where `NetworkTransform` components nested under a parent with a `NetworkObject` component (i.e. network prefab) would not have their associated `GameObject`'s transform synchronized. (#2298) - Fixed issue where `NetworkObject`s that failed to instantiate could cause the entire synchronization pipeline to be disrupted/halted for a connecting client. (#2298) - Fixed issue where in-scene placed `NetworkObject`s nested under a `GameObject` would be added to the orphaned children list causing continual console warning log messages. (#2298) - Custom messages are now properly received by the local client when they're sent while running in host mode. (#2296) - Fixed issue where the host would receive more than one event completed notification when loading or unloading a scene only when no clients were connected. (#2292) - Fixed an issue in `UnityTransport` where an error would be logged if the 'Use Encryption' flag was enabled with a Relay configuration that used a secure protocol. (#2289) - Fixed issue where in-scene placed `NetworkObjects` were not honoring the `AutoObjectParentSync` property. (#2281) - Fixed the issue where `NetworkManager.OnClientConnectedCallback` was being invoked before in-scene placed `NetworkObject`s had been spawned when starting `NetworkManager` as a host. (#2277) - Creating a `FastBufferReader` with `Allocator.None` will not result in extra memory being allocated for the buffer (since it's owned externally in that scenario). (#2265) ### Removed - Removed the `NetworkObject` auto-add and Multiplayer Tools install reminder settings from the Menu interface. (#2285)
This commit is contained in:
@@ -21,6 +21,7 @@ namespace Unity.Netcode
|
||||
Client = 2
|
||||
}
|
||||
|
||||
|
||||
// NetworkBehaviourILPP will override this in derived classes to return the name of the concrete type
|
||||
internal virtual string __getTypeName() => nameof(NetworkBehaviour);
|
||||
|
||||
@@ -286,7 +287,18 @@ namespace Unity.Netcode
|
||||
/// Gets the NetworkManager that owns this NetworkBehaviour instance
|
||||
/// See note around `NetworkObject` for how there is a chicken / egg problem when we are not initialized
|
||||
/// </summary>
|
||||
public NetworkManager NetworkManager => NetworkObject.NetworkManager;
|
||||
public NetworkManager NetworkManager
|
||||
{
|
||||
get
|
||||
{
|
||||
if (NetworkObject?.NetworkManager != null)
|
||||
{
|
||||
return NetworkObject?.NetworkManager;
|
||||
}
|
||||
|
||||
return NetworkManager.Singleton;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If a NetworkObject is assigned, it will return whether or not this NetworkObject
|
||||
@@ -335,23 +347,29 @@ namespace Unity.Netcode
|
||||
m_NetworkObject.NetworkManager.IsServer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the NetworkObject that owns this NetworkBehaviour instance
|
||||
/// TODO: this needs an overhaul. It's expensive, it's ja little naive in how it looks for networkObject in
|
||||
/// its parent and worst, it creates a puzzle if you are a NetworkBehaviour wanting to see if you're live or not
|
||||
/// (e.g. editor code). All you want to do is find out if NetworkManager is null, but to do that you
|
||||
/// need NetworkObject, but if you try and grab NetworkObject and NetworkManager isn't up you'll get
|
||||
/// the warning below. This is why IsBehaviourEditable had to be created. Matt was going to re-do
|
||||
/// how NetworkObject works but it was close to the release and too risky to change
|
||||
///
|
||||
/// <summary>
|
||||
/// Gets the NetworkObject that owns this NetworkBehaviour instance
|
||||
/// </summary>
|
||||
public NetworkObject NetworkObject
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_NetworkObject == null)
|
||||
try
|
||||
{
|
||||
m_NetworkObject = GetComponentInParent<NetworkObject>();
|
||||
if (m_NetworkObject == null)
|
||||
{
|
||||
m_NetworkObject = GetComponentInParent<NetworkObject>();
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// ShutdownInProgress check:
|
||||
@@ -712,7 +730,7 @@ namespace Unity.Netcode
|
||||
var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, MessagingSystem.FRAGMENTED_MESSAGE_MAX_SIZE);
|
||||
using (tmpWriter)
|
||||
{
|
||||
message.Serialize(tmpWriter);
|
||||
message.Serialize(tmpWriter, message.Version);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -745,6 +763,14 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronizes by setting only the NetworkVariable field values that the client has permission to read.
|
||||
/// Note: This is only invoked when first synchronizing a NetworkBehaviour (i.e. late join or spawned NetworkObject)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When NetworkConfig.EnsureNetworkVariableLengthSafety is enabled each NetworkVariable field will be preceded
|
||||
/// by the number of bytes written for that specific field.
|
||||
/// </remarks>
|
||||
internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClientId)
|
||||
{
|
||||
if (NetworkVariableFields.Count == 0)
|
||||
@@ -754,27 +780,47 @@ namespace Unity.Netcode
|
||||
|
||||
for (int j = 0; j < NetworkVariableFields.Count; j++)
|
||||
{
|
||||
bool canClientRead = NetworkVariableFields[j].CanClientRead(targetClientId);
|
||||
|
||||
if (canClientRead)
|
||||
if (NetworkVariableFields[j].CanClientRead(targetClientId))
|
||||
{
|
||||
var writePos = writer.Position;
|
||||
writer.WriteValueSafe((ushort)0);
|
||||
var startPos = writer.Position;
|
||||
NetworkVariableFields[j].WriteField(writer);
|
||||
var size = writer.Position - startPos;
|
||||
writer.Seek(writePos);
|
||||
writer.WriteValueSafe((ushort)size);
|
||||
writer.Seek(startPos + size);
|
||||
if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
var writePos = writer.Position;
|
||||
// Note: This value can't be packed because we don't know how large it will be in advance
|
||||
// we reserve space for it, then write the data, then come back and fill in the space
|
||||
// to pack here, we'd have to write data to a temporary buffer and copy it in - which
|
||||
// isn't worth possibly saving one byte if and only if the data is less than 63 bytes long...
|
||||
// The way we do packing, any value > 63 in a ushort will use the full 2 bytes to represent.
|
||||
writer.WriteValueSafe((ushort)0);
|
||||
var startPos = writer.Position;
|
||||
NetworkVariableFields[j].WriteField(writer);
|
||||
var size = writer.Position - startPos;
|
||||
writer.Seek(writePos);
|
||||
writer.WriteValueSafe((ushort)size);
|
||||
writer.Seek(startPos + size);
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkVariableFields[j].WriteField(writer);
|
||||
}
|
||||
}
|
||||
else
|
||||
else // Only if EnsureNetworkVariableLengthSafety, otherwise just skip
|
||||
if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
writer.WriteValueSafe((ushort)0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void SetNetworkVariableData(FastBufferReader reader)
|
||||
/// <summary>
|
||||
/// Synchronizes by setting only the NetworkVariable field values that the client has permission to read.
|
||||
/// Note: This is only invoked when first synchronizing a NetworkBehaviour (i.e. late join or spawned NetworkObject)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When NetworkConfig.EnsureNetworkVariableLengthSafety is enabled each NetworkVariable field will be preceded
|
||||
/// by the number of bytes written for that specific field.
|
||||
/// </remarks>
|
||||
internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId)
|
||||
{
|
||||
if (NetworkVariableFields.Count == 0)
|
||||
{
|
||||
@@ -783,13 +829,23 @@ namespace Unity.Netcode
|
||||
|
||||
for (int j = 0; j < NetworkVariableFields.Count; j++)
|
||||
{
|
||||
reader.ReadValueSafe(out ushort varSize);
|
||||
if (varSize == 0)
|
||||
var varSize = (ushort)0;
|
||||
var readStartPos = 0;
|
||||
if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
reader.ReadValueSafe(out varSize);
|
||||
if (varSize == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
readStartPos = reader.Position;
|
||||
}
|
||||
else // If the client cannot read this field, then skip it
|
||||
if (!NetworkVariableFields[j].CanClientRead(clientId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var readStartPos = reader.Position;
|
||||
NetworkVariableFields[j].ReadField(reader);
|
||||
|
||||
if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
@@ -826,6 +882,138 @@ namespace Unity.Netcode
|
||||
return NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(networkId, out NetworkObject networkObject) ? networkObject : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override this method if your derived NetworkBehaviour requires custom synchronization data.
|
||||
/// Note: Use of this method is only for the initial client synchronization of NetworkBehaviours
|
||||
/// and will increase the payload size for client synchronization and dynamically spawned
|
||||
/// <see cref="NetworkObject"/>s.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When serializing (writing) this will be invoked during the client synchronization period and
|
||||
/// when spawning new NetworkObjects.
|
||||
/// When deserializing (reading), this will be invoked prior to the NetworkBehaviour's associated
|
||||
/// NetworkObject being spawned.
|
||||
/// </remarks>
|
||||
/// <param name="serializer">The serializer to use to read and write the data.</param>
|
||||
/// <typeparam name="T">
|
||||
/// Either BufferSerializerReader or BufferSerializerWriter, depending whether the serializer
|
||||
/// is in read mode or write mode.
|
||||
/// </typeparam>
|
||||
protected virtual void OnSynchronize<T>(ref BufferSerializer<T> serializer) where T : IReaderWriter
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal method that determines if a NetworkBehaviour has additional synchronization data to
|
||||
/// be synchronized when first instantiated prior to its associated NetworkObject being spawned.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This includes try-catch blocks to recover from exceptions that might occur and continue to
|
||||
/// synchronize any remaining NetworkBehaviours.
|
||||
/// </remarks>
|
||||
/// <returns>true if it wrote synchronization data and false if it did not</returns>
|
||||
internal bool Synchronize<T>(ref BufferSerializer<T> serializer) where T : IReaderWriter
|
||||
{
|
||||
if (serializer.IsWriter)
|
||||
{
|
||||
// Get the writer to handle seeking and determining how many bytes were written
|
||||
var writer = serializer.GetFastBufferWriter();
|
||||
// Save our position before we attempt to write anything so we can seek back to it (i.e. error occurs)
|
||||
var positionBeforeWrite = writer.Position;
|
||||
writer.WriteValueSafe(NetworkBehaviourId);
|
||||
|
||||
// Save our position where we will write the final size being written so we can skip over it in the
|
||||
// event an exception occurs when deserializing.
|
||||
var sizePosition = writer.Position;
|
||||
writer.WriteValueSafe((ushort)0);
|
||||
|
||||
// Save our position before synchronizing to determine how much was written
|
||||
var positionBeforeSynchronize = writer.Position;
|
||||
var threwException = false;
|
||||
try
|
||||
{
|
||||
OnSynchronize(ref serializer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
threwException = true;
|
||||
if (NetworkManager.LogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"{name} threw an exception during synchronization serialization, this {nameof(NetworkBehaviour)} is being skipped and will not be synchronized!");
|
||||
if (NetworkManager.LogLevel == LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogError($"{ex.Message}\n {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
}
|
||||
var finalPosition = writer.Position;
|
||||
|
||||
// If we wrote nothing then skip writing anything for this NetworkBehaviour
|
||||
if (finalPosition == positionBeforeSynchronize || threwException)
|
||||
{
|
||||
writer.Seek(positionBeforeWrite);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Write the number of bytes serialized to handle exceptions on the deserialization side
|
||||
var bytesWritten = finalPosition - positionBeforeSynchronize;
|
||||
writer.Seek(sizePosition);
|
||||
writer.WriteValueSafe((ushort)bytesWritten);
|
||||
writer.Seek(finalPosition);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var reader = serializer.GetFastBufferReader();
|
||||
// We will always read the expected byte count
|
||||
reader.ReadValueSafe(out ushort expectedBytesToRead);
|
||||
|
||||
// Save our position before we begin synchronization deserialization
|
||||
var positionBeforeSynchronize = reader.Position;
|
||||
var synchronizationError = false;
|
||||
try
|
||||
{
|
||||
// Invoke synchronization
|
||||
OnSynchronize(ref serializer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (NetworkManager.LogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"{name} threw an exception during synchronization deserialization, this {nameof(NetworkBehaviour)} is being skipped and will not be synchronized!");
|
||||
if (NetworkManager.LogLevel == LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogError($"{ex.Message}\n {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
synchronizationError = true;
|
||||
}
|
||||
|
||||
var totalBytesRead = reader.Position - positionBeforeSynchronize;
|
||||
if (totalBytesRead != expectedBytesToRead)
|
||||
{
|
||||
if (NetworkManager.LogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"{name} read {totalBytesRead} bytes but was expected to read {expectedBytesToRead} bytes during synchronization deserialization! This {nameof(NetworkBehaviour)} is being skipped and will not be synchronized!");
|
||||
}
|
||||
synchronizationError = true;
|
||||
}
|
||||
|
||||
// Skip over the entry if deserialization fails
|
||||
if (synchronizationError)
|
||||
{
|
||||
var skipToPosition = positionBeforeSynchronize + expectedBytesToRead;
|
||||
reader.Seek(skipToPosition);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the <see cref="GameObject"/> the <see cref="NetworkBehaviour"/> is attached to.
|
||||
/// NOTE: If you override this, you will want to always invoke this base class version of this
|
||||
|
||||
@@ -86,6 +86,12 @@ namespace Unity.Netcode
|
||||
private bool m_ShuttingDown;
|
||||
private bool m_StopProcessingMessages;
|
||||
|
||||
/// <summary>
|
||||
/// When disconnected from the server, the server may send a reason. If a reason was sent, this property will
|
||||
/// tell client code what the reason was. It should be queried after the OnClientDisconnectCallback is called
|
||||
/// </summary>
|
||||
public string DisconnectReason { get; internal set; }
|
||||
|
||||
private class NetworkManagerHooks : INetworkHooks
|
||||
{
|
||||
private NetworkManager m_NetworkManager;
|
||||
@@ -443,6 +449,12 @@ namespace Unity.Netcode
|
||||
/// If the Approval decision cannot be made immediately, the client code can set Pending to true, keep a reference to the ConnectionApprovalResponse object and write to it later. Client code must exercise care to setting all the members to the value it wants before marking Pending to false, to indicate completion. If the field is set as Pending = true, we'll monitor the object until it gets set to not pending anymore and use the parameters then.
|
||||
/// </summary>
|
||||
public bool Pending;
|
||||
|
||||
/// <summary>
|
||||
/// Optional reason. If Approved is false, this reason will be sent to the client so they know why they
|
||||
/// were not approved.
|
||||
/// </summary>
|
||||
public string Reason;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -889,6 +901,7 @@ namespace Unity.Netcode
|
||||
return;
|
||||
}
|
||||
|
||||
DisconnectReason = string.Empty;
|
||||
IsApproved = false;
|
||||
|
||||
ComponentFactory.SetDefaults();
|
||||
@@ -1181,6 +1194,11 @@ namespace Unity.Netcode
|
||||
|
||||
SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
|
||||
|
||||
// This assures that any in-scene placed NetworkObject is spawned and
|
||||
// any associated NetworkBehaviours' netcode related properties are
|
||||
// set prior to invoking OnClientConnected.
|
||||
InvokeOnClientConnectedCallback(LocalClientId);
|
||||
|
||||
OnServerStarted?.Invoke();
|
||||
|
||||
return true;
|
||||
@@ -1341,6 +1359,7 @@ namespace Unity.Netcode
|
||||
private void DisconnectRemoteClient(ulong clientId)
|
||||
{
|
||||
var transportId = ClientIdToTransportId(clientId);
|
||||
MessagingSystem.ProcessSendQueues();
|
||||
NetworkConfig.NetworkTransport.DisconnectRemoteClient(transportId);
|
||||
}
|
||||
|
||||
@@ -1421,7 +1440,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
if (IsClient && IsConnectedClient)
|
||||
if (IsClient && IsListening)
|
||||
{
|
||||
// Client only, send disconnect to server
|
||||
NetworkConfig.NetworkTransport.DisconnectLocalClient();
|
||||
@@ -1579,6 +1598,7 @@ namespace Unity.Netcode
|
||||
} while (IsListening && networkEvent != NetworkEvent.Nothing);
|
||||
|
||||
MessagingSystem.ProcessIncomingMessageQueue();
|
||||
MessagingSystem.CleanupDisconnectedClients();
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_TransportPoll.End();
|
||||
@@ -1660,7 +1680,23 @@ namespace Unity.Netcode
|
||||
ShouldSendConnectionData = NetworkConfig.ConnectionApproval,
|
||||
ConnectionData = NetworkConfig.ConnectionData
|
||||
};
|
||||
|
||||
message.MessageVersions = new NativeArray<MessageVersionData>(MessagingSystem.MessageHandlers.Length, Allocator.Temp);
|
||||
for (int index = 0; index < MessagingSystem.MessageHandlers.Length; index++)
|
||||
{
|
||||
if (MessagingSystem.MessageTypes[index] != null)
|
||||
{
|
||||
var type = MessagingSystem.MessageTypes[index];
|
||||
message.MessageVersions[index] = new MessageVersionData
|
||||
{
|
||||
Hash = XXHash.Hash32(type.FullName),
|
||||
Version = MessagingSystem.GetLocalVersion(type)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
SendMessage(ref message, NetworkDelivery.ReliableSequenced, ServerClientId);
|
||||
message.MessageVersions.Dispose();
|
||||
}
|
||||
|
||||
private IEnumerator ApprovalTimeout(ulong clientId)
|
||||
@@ -1821,7 +1857,18 @@ namespace Unity.Netcode
|
||||
NetworkLog.LogInfo($"Disconnect Event From {clientId}");
|
||||
}
|
||||
|
||||
OnClientDisconnectCallback?.Invoke(clientId);
|
||||
// Process the incoming message queue so that we get everything from the server disconnecting us
|
||||
// or, if we are the server, so we got everything from that client.
|
||||
MessagingSystem.ProcessIncomingMessageQueue();
|
||||
|
||||
try
|
||||
{
|
||||
OnClientDisconnectCallback?.Invoke(clientId);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogException(exception);
|
||||
}
|
||||
|
||||
if (IsServer)
|
||||
{
|
||||
@@ -1987,12 +2034,31 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
/// <param name="clientId">The ClientId to disconnect</param>
|
||||
public void DisconnectClient(ulong clientId)
|
||||
{
|
||||
DisconnectClient(clientId, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects the remote client.
|
||||
/// </summary>
|
||||
/// <param name="clientId">The ClientId to disconnect</param>
|
||||
/// <param name="reason">Disconnection reason. If set, client will receive a DisconnectReasonMessage and have the
|
||||
/// reason available in the NetworkManager.DisconnectReason property</param>
|
||||
public void DisconnectClient(ulong clientId, string reason)
|
||||
{
|
||||
if (!IsServer)
|
||||
{
|
||||
throw new NotServerException($"Only server can disconnect remote clients. Please use `{nameof(Shutdown)}()` instead.");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(reason))
|
||||
{
|
||||
var disconnectReason = new DisconnectReasonMessage();
|
||||
disconnectReason.Reason = reason;
|
||||
SendMessage(ref disconnectReason, NetworkDelivery.Reliable, clientId);
|
||||
}
|
||||
MessagingSystem.ProcessSendQueues();
|
||||
|
||||
OnClientDisconnectFromServer(clientId);
|
||||
DisconnectRemoteClient(clientId);
|
||||
}
|
||||
@@ -2137,14 +2203,11 @@ namespace Unity.Netcode
|
||||
// Generate a SceneObject for the player object to spawn
|
||||
var sceneObject = new NetworkObject.SceneObject
|
||||
{
|
||||
Header = new NetworkObject.SceneObject.HeaderData
|
||||
{
|
||||
IsPlayerObject = true,
|
||||
OwnerClientId = ownerClientId,
|
||||
IsSceneObject = false,
|
||||
HasTransform = true,
|
||||
Hash = playerPrefabHash,
|
||||
},
|
||||
OwnerClientId = ownerClientId,
|
||||
IsPlayerObject = true,
|
||||
IsSceneObject = false,
|
||||
HasTransform = true,
|
||||
Hash = playerPrefabHash,
|
||||
TargetClientId = ownerClientId,
|
||||
Transform = new NetworkObject.SceneObject.TransformData
|
||||
{
|
||||
@@ -2184,22 +2247,23 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId);
|
||||
|
||||
message.MessageVersions = new NativeArray<MessageVersionData>(MessagingSystem.MessageHandlers.Length, Allocator.Temp);
|
||||
for (int index = 0; index < MessagingSystem.MessageHandlers.Length; index++)
|
||||
{
|
||||
if (MessagingSystem.MessageTypes[index] != null)
|
||||
{
|
||||
var orderingMessage = new OrderingMessage
|
||||
var type = MessagingSystem.MessageTypes[index];
|
||||
message.MessageVersions[index] = new MessageVersionData
|
||||
{
|
||||
Order = index,
|
||||
Hash = XXHash.Hash32(MessagingSystem.MessageTypes[index].FullName)
|
||||
Hash = XXHash.Hash32(type.FullName),
|
||||
Version = MessagingSystem.GetLocalVersion(type)
|
||||
};
|
||||
|
||||
SendMessage(ref orderingMessage, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId);
|
||||
}
|
||||
}
|
||||
|
||||
SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId);
|
||||
message.MessageVersions.Dispose();
|
||||
|
||||
// If scene management is enabled, then let NetworkSceneManager handle the initial scene and NetworkObject synchronization
|
||||
if (!NetworkConfig.EnableSceneManagement)
|
||||
{
|
||||
@@ -2214,7 +2278,6 @@ namespace Unity.Netcode
|
||||
{
|
||||
LocalClient = client;
|
||||
SpawnManager.UpdateObservedNetworkObjects(ownerClientId);
|
||||
InvokeOnClientConnectedCallback(ownerClientId);
|
||||
}
|
||||
|
||||
if (!response.CreatePlayerObject || (response.PlayerPrefabHash == null && NetworkConfig.PlayerPrefab == null))
|
||||
@@ -2227,6 +2290,15 @@ namespace Unity.Netcode
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(response.Reason))
|
||||
{
|
||||
var disconnectReason = new DisconnectReasonMessage();
|
||||
disconnectReason.Reason = response.Reason;
|
||||
SendMessage(ref disconnectReason, NetworkDelivery.Reliable, ownerClientId);
|
||||
|
||||
MessagingSystem.ProcessSendQueues();
|
||||
}
|
||||
|
||||
PendingClients.Remove(ownerClientId);
|
||||
DisconnectRemoteClient(ownerClientId);
|
||||
}
|
||||
@@ -2253,11 +2325,11 @@ namespace Unity.Netcode
|
||||
{
|
||||
ObjectInfo = ConnectedClients[clientId].PlayerObject.GetMessageSceneObject(clientPair.Key)
|
||||
};
|
||||
message.ObjectInfo.Header.Hash = playerPrefabHash;
|
||||
message.ObjectInfo.Header.IsSceneObject = false;
|
||||
message.ObjectInfo.Header.HasParent = false;
|
||||
message.ObjectInfo.Header.IsPlayerObject = true;
|
||||
message.ObjectInfo.Header.OwnerClientId = clientId;
|
||||
message.ObjectInfo.Hash = playerPrefabHash;
|
||||
message.ObjectInfo.IsSceneObject = false;
|
||||
message.ObjectInfo.HasParent = false;
|
||||
message.ObjectInfo.IsPlayerObject = true;
|
||||
message.ObjectInfo.OwnerClientId = clientId;
|
||||
var size = SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientPair.Key);
|
||||
NetworkMetrics.TrackObjectSpawnSent(clientPair.Key, ConnectedClients[clientId].PlayerObject, size);
|
||||
}
|
||||
|
||||
@@ -830,6 +830,9 @@ namespace Unity.Netcode
|
||||
// parent and then re-parents the child under a GameObject with a NetworkObject component attached.
|
||||
if (parentNetworkObject == null)
|
||||
{
|
||||
// If we are parented under a GameObject, go ahead and mark the world position stays as false
|
||||
// so clients synchronize their transform in local space. (only for in-scene placed NetworkObjects)
|
||||
m_CachedWorldPositionStays = false;
|
||||
return true;
|
||||
}
|
||||
else // If the parent still isn't spawned add this to the orphaned children and return false
|
||||
@@ -841,8 +844,11 @@ namespace Unity.Netcode
|
||||
else
|
||||
{
|
||||
// If we made it this far, go ahead and set the network parenting values
|
||||
// with the default WorldPoisitonSays value
|
||||
SetNetworkParenting(parentNetworkObject.NetworkObjectId, true);
|
||||
// with the WorldPoisitonSays value set to false
|
||||
// Note: Since in-scene placed NetworkObjects are parented in the scene
|
||||
// the default "assumption" is that children are parenting local space
|
||||
// relative.
|
||||
SetNetworkParenting(parentNetworkObject.NetworkObjectId, false);
|
||||
|
||||
// Set the cached parent
|
||||
m_CachedParent = parentNetworkObject.transform;
|
||||
@@ -1002,13 +1008,17 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
}
|
||||
internal void SetNetworkVariableData(FastBufferReader reader)
|
||||
|
||||
/// <summary>
|
||||
/// Only invoked during first synchronization of a NetworkObject (late join or newly spawned)
|
||||
/// </summary>
|
||||
internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId)
|
||||
{
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
var behaviour = ChildNetworkBehaviours[i];
|
||||
behaviour.InitializeVariables();
|
||||
behaviour.SetNetworkVariableData(reader);
|
||||
behaviour.SetNetworkVariableData(reader, clientId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1045,7 +1055,20 @@ namespace Unity.Netcode
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
NetworkLog.LogError($"Behaviour index was out of bounds. Did you mess up the order of your {nameof(NetworkBehaviour)}s?");
|
||||
NetworkLog.LogError($"{nameof(NetworkBehaviour)} index {index} was out of bounds for {name}. NetworkBehaviours must be the same, and in the same order, between server and client.");
|
||||
}
|
||||
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
{
|
||||
var currentKnownChildren = new System.Text.StringBuilder();
|
||||
currentKnownChildren.Append($"Known child {nameof(NetworkBehaviour)}s:");
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
var childNetworkBehaviour = ChildNetworkBehaviours[i];
|
||||
currentKnownChildren.Append($" [{i}] {childNetworkBehaviour.__getTypeName()}");
|
||||
currentKnownChildren.Append(i < ChildNetworkBehaviours.Count - 1 ? "," : ".");
|
||||
}
|
||||
NetworkLog.LogInfo(currentKnownChildren.ToString());
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -1056,19 +1079,43 @@ namespace Unity.Netcode
|
||||
|
||||
internal struct SceneObject
|
||||
{
|
||||
public struct HeaderData : INetworkSerializeByMemcpy
|
||||
{
|
||||
public ulong NetworkObjectId;
|
||||
public ulong OwnerClientId;
|
||||
public uint Hash;
|
||||
private byte m_BitField;
|
||||
public uint Hash;
|
||||
public ulong NetworkObjectId;
|
||||
public ulong OwnerClientId;
|
||||
|
||||
public bool IsPlayerObject;
|
||||
public bool HasParent;
|
||||
public bool IsSceneObject;
|
||||
public bool HasTransform;
|
||||
public bool IsPlayerObject
|
||||
{
|
||||
get => ByteUtility.GetBit(m_BitField, 0);
|
||||
set => ByteUtility.SetBit(ref m_BitField, 0, value);
|
||||
}
|
||||
public bool HasParent
|
||||
{
|
||||
get => ByteUtility.GetBit(m_BitField, 1);
|
||||
set => ByteUtility.SetBit(ref m_BitField, 1, value);
|
||||
}
|
||||
public bool IsSceneObject
|
||||
{
|
||||
get => ByteUtility.GetBit(m_BitField, 2);
|
||||
set => ByteUtility.SetBit(ref m_BitField, 2, value);
|
||||
}
|
||||
public bool HasTransform
|
||||
{
|
||||
get => ByteUtility.GetBit(m_BitField, 3);
|
||||
set => ByteUtility.SetBit(ref m_BitField, 3, value);
|
||||
}
|
||||
|
||||
public HeaderData Header;
|
||||
public bool IsLatestParentSet
|
||||
{
|
||||
get => ByteUtility.GetBit(m_BitField, 4);
|
||||
set => ByteUtility.SetBit(ref m_BitField, 4, value);
|
||||
}
|
||||
|
||||
public bool WorldPositionStays
|
||||
{
|
||||
get => ByteUtility.GetBit(m_BitField, 5);
|
||||
set => ByteUtility.SetBit(ref m_BitField, 5, value);
|
||||
}
|
||||
|
||||
//If(Metadata.HasParent)
|
||||
public ulong ParentObjectId;
|
||||
@@ -1084,7 +1131,6 @@ namespace Unity.Netcode
|
||||
public TransformData Transform;
|
||||
|
||||
//If(Metadata.IsReparented)
|
||||
public bool IsLatestParentSet;
|
||||
|
||||
//If(IsLatestParentSet)
|
||||
public ulong? LatestParent;
|
||||
@@ -1094,114 +1140,90 @@ namespace Unity.Netcode
|
||||
|
||||
public int NetworkSceneHandle;
|
||||
|
||||
public bool WorldPositionStays;
|
||||
|
||||
public unsafe void Serialize(FastBufferWriter writer)
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
var writeSize = sizeof(HeaderData);
|
||||
if (Header.HasParent)
|
||||
writer.WriteValueSafe(m_BitField);
|
||||
writer.WriteValueSafe(Hash);
|
||||
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
|
||||
BytePacker.WriteValueBitPacked(writer, OwnerClientId);
|
||||
|
||||
if (HasParent)
|
||||
{
|
||||
writeSize += FastBufferWriter.GetWriteSize(ParentObjectId);
|
||||
writeSize += FastBufferWriter.GetWriteSize(WorldPositionStays);
|
||||
writeSize += FastBufferWriter.GetWriteSize(IsLatestParentSet);
|
||||
writeSize += IsLatestParentSet ? FastBufferWriter.GetWriteSize<ulong>() : 0;
|
||||
BytePacker.WriteValueBitPacked(writer, ParentObjectId);
|
||||
if (IsLatestParentSet)
|
||||
{
|
||||
BytePacker.WriteValueBitPacked(writer, LatestParent.Value);
|
||||
}
|
||||
}
|
||||
writeSize += Header.HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
|
||||
writeSize += Header.IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
|
||||
|
||||
var writeSize = 0;
|
||||
writeSize += HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
|
||||
writeSize += IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
|
||||
|
||||
if (!writer.TryBeginWrite(writeSize))
|
||||
{
|
||||
throw new OverflowException("Could not serialize SceneObject: Out of buffer space.");
|
||||
}
|
||||
|
||||
writer.WriteValue(Header);
|
||||
|
||||
if (Header.HasParent)
|
||||
{
|
||||
writer.WriteValue(ParentObjectId);
|
||||
writer.WriteValue(WorldPositionStays);
|
||||
writer.WriteValue(IsLatestParentSet);
|
||||
if (IsLatestParentSet)
|
||||
{
|
||||
writer.WriteValue(LatestParent.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (Header.HasTransform)
|
||||
if (HasTransform)
|
||||
{
|
||||
writer.WriteValue(Transform);
|
||||
}
|
||||
|
||||
// In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their
|
||||
// NetworkSceneHandle and GlobalObjectIdHash. Since each loaded scene has a unique
|
||||
// handle, it provides us with a unique and persistent "scene prefab asset" instance.
|
||||
// This is only set on in-scene placed NetworkObjects to reduce the over-all packet
|
||||
// sizes for dynamically spawned NetworkObjects.
|
||||
if (Header.IsSceneObject)
|
||||
// NetworkSceneHandle and GlobalObjectIdHash. Client-side NetworkSceneManagers use
|
||||
// this to locate their local instance of the in-scene placed NetworkObject instance.
|
||||
// Only written for in-scene placed NetworkObjects.
|
||||
if (IsSceneObject)
|
||||
{
|
||||
writer.WriteValue(OwnerObject.GetSceneOriginHandle());
|
||||
}
|
||||
|
||||
OwnerObject.WriteNetworkVariableData(writer, TargetClientId);
|
||||
// Synchronize NetworkVariables and NetworkBehaviours
|
||||
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer));
|
||||
OwnerObject.SynchronizeNetworkBehaviours(ref bufferSerializer, TargetClientId);
|
||||
}
|
||||
|
||||
public unsafe void Deserialize(FastBufferReader reader)
|
||||
public void Deserialize(FastBufferReader reader)
|
||||
{
|
||||
if (!reader.TryBeginRead(sizeof(HeaderData)))
|
||||
reader.ReadValueSafe(out m_BitField);
|
||||
reader.ReadValueSafe(out Hash);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId);
|
||||
|
||||
if (HasParent)
|
||||
{
|
||||
throw new OverflowException("Could not deserialize SceneObject: Out of buffer space.");
|
||||
}
|
||||
reader.ReadValue(out Header);
|
||||
var readSize = 0;
|
||||
if (Header.HasParent)
|
||||
{
|
||||
readSize += FastBufferWriter.GetWriteSize(ParentObjectId);
|
||||
readSize += FastBufferWriter.GetWriteSize(WorldPositionStays);
|
||||
readSize += FastBufferWriter.GetWriteSize(IsLatestParentSet);
|
||||
// We need to read at this point in order to get the IsLatestParentSet value
|
||||
if (!reader.TryBeginRead(readSize))
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out ParentObjectId);
|
||||
if (IsLatestParentSet)
|
||||
{
|
||||
throw new OverflowException("Could not deserialize SceneObject: Out of buffer space.");
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out ulong latestParent);
|
||||
LatestParent = latestParent;
|
||||
}
|
||||
|
||||
// Read the initial parenting related properties
|
||||
reader.ReadValue(out ParentObjectId);
|
||||
reader.ReadValue(out WorldPositionStays);
|
||||
reader.ReadValue(out IsLatestParentSet);
|
||||
|
||||
// Now calculate the remaining bytes to read
|
||||
readSize = 0;
|
||||
readSize += IsLatestParentSet ? FastBufferWriter.GetWriteSize<ulong>() : 0;
|
||||
}
|
||||
|
||||
readSize += Header.HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
|
||||
readSize += Header.IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
|
||||
var readSize = 0;
|
||||
readSize += HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
|
||||
readSize += IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
|
||||
|
||||
// Try to begin reading the remaining bytes
|
||||
if (!reader.TryBeginRead(readSize))
|
||||
{
|
||||
throw new OverflowException("Could not deserialize SceneObject: Out of buffer space.");
|
||||
throw new OverflowException("Could not deserialize SceneObject: Reading past the end of the buffer");
|
||||
}
|
||||
|
||||
if (IsLatestParentSet)
|
||||
{
|
||||
reader.ReadValueSafe(out ulong latestParent);
|
||||
LatestParent = latestParent;
|
||||
}
|
||||
|
||||
if (Header.HasTransform)
|
||||
if (HasTransform)
|
||||
{
|
||||
reader.ReadValue(out Transform);
|
||||
}
|
||||
|
||||
// In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their
|
||||
// NetworkSceneHandle and GlobalObjectIdHash. Since each loaded scene has a unique
|
||||
// handle, it provides us with a unique and persistent "scene prefab asset" instance.
|
||||
// Client-side NetworkSceneManagers use this to locate their local instance of the
|
||||
// NetworkObject instance.
|
||||
if (Header.IsSceneObject)
|
||||
// NetworkSceneHandle and GlobalObjectIdHash. Client-side NetworkSceneManagers use
|
||||
// this to locate their local instance of the in-scene placed NetworkObject instance.
|
||||
// Only read for in-scene placed NetworkObjects
|
||||
if (IsSceneObject)
|
||||
{
|
||||
reader.ReadValueSafe(out NetworkSceneHandle);
|
||||
reader.ReadValue(out NetworkSceneHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1214,18 +1236,87 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles synchronizing NetworkVariables and custom synchronization data for NetworkBehaviours.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is where we determine how much data is written after the associated NetworkObject in order to recover
|
||||
/// from a failed instantiated NetworkObject without completely disrupting client synchronization.
|
||||
/// </remarks>
|
||||
internal void SynchronizeNetworkBehaviours<T>(ref BufferSerializer<T> serializer, ulong targetClientId = 0) where T : IReaderWriter
|
||||
{
|
||||
if (serializer.IsWriter)
|
||||
{
|
||||
var writer = serializer.GetFastBufferWriter();
|
||||
var positionBeforeSynchronizing = writer.Position;
|
||||
writer.WriteValueSafe((ushort)0);
|
||||
var sizeToSkipCalculationPosition = writer.Position;
|
||||
|
||||
// Synchronize NetworkVariables
|
||||
WriteNetworkVariableData(writer, targetClientId);
|
||||
// Reserve the NetworkBehaviour synchronization count position
|
||||
var networkBehaviourCountPosition = writer.Position;
|
||||
writer.WriteValueSafe((byte)0);
|
||||
|
||||
// Parse through all NetworkBehaviours and any that return true
|
||||
// had additional synchronization data written.
|
||||
// (See notes for reading/deserialization below)
|
||||
var synchronizationCount = (byte)0;
|
||||
foreach (var childBehaviour in ChildNetworkBehaviours)
|
||||
{
|
||||
if (childBehaviour.Synchronize(ref serializer))
|
||||
{
|
||||
synchronizationCount++;
|
||||
}
|
||||
}
|
||||
|
||||
var currentPosition = writer.Position;
|
||||
// Write the total number of bytes written for NetworkVariable and NetworkBehaviour
|
||||
// synchronization.
|
||||
writer.Seek(positionBeforeSynchronizing);
|
||||
// We want the size of everything after our size to skip calculation position
|
||||
var size = (ushort)(currentPosition - sizeToSkipCalculationPosition);
|
||||
writer.WriteValueSafe(size);
|
||||
// Write the number of NetworkBehaviours synchronized
|
||||
writer.Seek(networkBehaviourCountPosition);
|
||||
writer.WriteValueSafe(synchronizationCount);
|
||||
// seek back to the position after writing NetworkVariable and NetworkBehaviour
|
||||
// synchronization data.
|
||||
writer.Seek(currentPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
var reader = serializer.GetFastBufferReader();
|
||||
|
||||
reader.ReadValueSafe(out ushort sizeOfSynchronizationData);
|
||||
var seekToEndOfSynchData = reader.Position + sizeOfSynchronizationData;
|
||||
// Apply the network variable synchronization data
|
||||
SetNetworkVariableData(reader, targetClientId);
|
||||
// Read the number of NetworkBehaviours to synchronize
|
||||
reader.ReadValueSafe(out byte numberSynchronized);
|
||||
var networkBehaviourId = (ushort)0;
|
||||
|
||||
// If a NetworkBehaviour writes synchronization data, it will first
|
||||
// write its NetworkBehaviourId so when deserializing the client-side
|
||||
// can find the right NetworkBehaviour to deserialize the synchronization data.
|
||||
for (int i = 0; i < numberSynchronized; i++)
|
||||
{
|
||||
serializer.SerializeValue(ref networkBehaviourId);
|
||||
var networkBehaviour = GetNetworkBehaviourAtOrderIndex(networkBehaviourId);
|
||||
networkBehaviour.Synchronize(ref serializer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal SceneObject GetMessageSceneObject(ulong targetClientId)
|
||||
{
|
||||
var obj = new SceneObject
|
||||
{
|
||||
Header = new SceneObject.HeaderData
|
||||
{
|
||||
IsPlayerObject = IsPlayerObject,
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
OwnerClientId = OwnerClientId,
|
||||
IsSceneObject = IsSceneObject ?? true,
|
||||
Hash = HostCheckForGlobalObjectIdHashOverride(),
|
||||
},
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
OwnerClientId = OwnerClientId,
|
||||
IsPlayerObject = IsPlayerObject,
|
||||
IsSceneObject = IsSceneObject ?? true,
|
||||
Hash = HostCheckForGlobalObjectIdHashOverride(),
|
||||
OwnerObject = this,
|
||||
TargetClientId = targetClientId
|
||||
};
|
||||
@@ -1235,11 +1326,18 @@ namespace Unity.Netcode
|
||||
if (!AlwaysReplicateAsRoot && transform.parent != null)
|
||||
{
|
||||
parentNetworkObject = transform.parent.GetComponent<NetworkObject>();
|
||||
// In-scene placed NetworkObjects parented under GameObjects with no NetworkObject
|
||||
// should set the has parent flag and preserve the world position stays value
|
||||
if (parentNetworkObject == null && obj.IsSceneObject)
|
||||
{
|
||||
obj.HasParent = true;
|
||||
obj.WorldPositionStays = m_CachedWorldPositionStays;
|
||||
}
|
||||
}
|
||||
|
||||
if (parentNetworkObject != null)
|
||||
{
|
||||
obj.Header.HasParent = true;
|
||||
obj.HasParent = true;
|
||||
obj.ParentObjectId = parentNetworkObject.NetworkObjectId;
|
||||
obj.WorldPositionStays = m_CachedWorldPositionStays;
|
||||
var latestParent = GetNetworkParenting();
|
||||
@@ -1253,20 +1351,38 @@ namespace Unity.Netcode
|
||||
|
||||
if (IncludeTransformWhenSpawning == null || IncludeTransformWhenSpawning(OwnerClientId))
|
||||
{
|
||||
obj.Header.HasTransform = true;
|
||||
obj.HasTransform = true;
|
||||
|
||||
// We start with the default AutoObjectParentSync values to determine which transform space we will
|
||||
// be synchronizing clients with.
|
||||
var syncRotationPositionLocalSpaceRelative = obj.HasParent && !m_CachedWorldPositionStays;
|
||||
var syncScaleLocalSpaceRelative = obj.HasParent && !m_CachedWorldPositionStays;
|
||||
|
||||
// If auto object synchronization is turned off
|
||||
if (!AutoObjectParentSync)
|
||||
{
|
||||
// We always synchronize position and rotation world space relative
|
||||
syncRotationPositionLocalSpaceRelative = false;
|
||||
// Scale is special, it synchronizes local space relative if it has a
|
||||
// parent since applying the world space scale under a parent with scale
|
||||
// will result in the improper scale for the child
|
||||
syncScaleLocalSpaceRelative = obj.HasParent;
|
||||
}
|
||||
|
||||
|
||||
obj.Transform = new SceneObject.TransformData
|
||||
{
|
||||
// If we are parented and we have the m_CachedWorldPositionStays disabled, then use local space
|
||||
// values as opposed world space values.
|
||||
Position = parentNetworkObject && !m_CachedWorldPositionStays ? transform.localPosition : transform.position,
|
||||
Rotation = parentNetworkObject && !m_CachedWorldPositionStays ? transform.localRotation : transform.rotation,
|
||||
Position = syncRotationPositionLocalSpaceRelative ? transform.localPosition : transform.position,
|
||||
Rotation = syncRotationPositionLocalSpaceRelative ? transform.localRotation : transform.rotation,
|
||||
|
||||
// We only use the lossyScale if the NetworkObject has a parent. Multi-generation nested children scales can
|
||||
// impact the final scale of the child NetworkObject in question. The solution is to use the lossy scale
|
||||
// which can be thought of as "world space scale".
|
||||
// More information:
|
||||
// https://docs.unity3d.com/ScriptReference/Transform-lossyScale.html
|
||||
Scale = parentNetworkObject && !m_CachedWorldPositionStays ? transform.localScale : transform.lossyScale,
|
||||
Scale = syncScaleLocalSpaceRelative ? transform.localScale : transform.lossyScale,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1278,10 +1394,10 @@ namespace Unity.Netcode
|
||||
/// when the client is approved or during a scene transition
|
||||
/// </summary>
|
||||
/// <param name="sceneObject">Deserialized scene object data</param>
|
||||
/// <param name="variableData">reader for the NetworkVariable data</param>
|
||||
/// <param name="reader">FastBufferReader for the NetworkVariable data</param>
|
||||
/// <param name="networkManager">NetworkManager instance</param>
|
||||
/// <returns>optional to use NetworkObject deserialized</returns>
|
||||
internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBufferReader variableData, NetworkManager networkManager)
|
||||
internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBufferReader reader, NetworkManager networkManager)
|
||||
{
|
||||
//Attempt to create a local NetworkObject
|
||||
var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject(sceneObject);
|
||||
@@ -1289,18 +1405,36 @@ namespace Unity.Netcode
|
||||
if (networkObject == null)
|
||||
{
|
||||
// Log the error that the NetworkObject failed to construct
|
||||
Debug.LogError($"Failed to spawn {nameof(NetworkObject)} for Hash {sceneObject.Header.Hash}.");
|
||||
if (networkManager.LogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogError($"Failed to spawn {nameof(NetworkObject)} for Hash {sceneObject.Hash}.");
|
||||
}
|
||||
|
||||
// If we failed to load this NetworkObject, then skip past the network variable data
|
||||
variableData.ReadValueSafe(out ushort varSize);
|
||||
variableData.Seek(variableData.Position + varSize);
|
||||
try
|
||||
{
|
||||
// If we failed to load this NetworkObject, then skip past the Network Variable and (if any) synchronization data
|
||||
reader.ReadValueSafe(out ushort networkBehaviourSynchronizationDataLength);
|
||||
reader.Seek(reader.Position + networkBehaviourSynchronizationDataLength);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
|
||||
// We have nothing left to do here.
|
||||
return null;
|
||||
}
|
||||
|
||||
// This will get set again when the NetworkObject is spawned locally, but we set it here ahead of spawning
|
||||
// in order to be able to determine which NetworkVariables the client will be allowed to read.
|
||||
networkObject.OwnerClientId = sceneObject.OwnerClientId;
|
||||
|
||||
// Synchronize NetworkBehaviours
|
||||
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
|
||||
networkObject.SynchronizeNetworkBehaviours(ref bufferSerializer, networkManager.LocalClientId);
|
||||
|
||||
// Spawn the NetworkObject
|
||||
networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, sceneObject, variableData, false);
|
||||
networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, sceneObject, false);
|
||||
|
||||
return networkObject;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user