Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Additional documentation and release notes are available at [Multiplayer Documen

### Fixed

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

Expand Down
41 changes: 36 additions & 5 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,9 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa
// 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
Expand All @@ -841,8 +844,11 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa
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;
Expand Down Expand Up @@ -1235,6 +1241,13 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId)
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.Header.IsSceneObject)
{
obj.Header.HasParent = true;
obj.WorldPositionStays = m_CachedWorldPositionStays;
}
}

if (parentNetworkObject != null)
Expand All @@ -1254,19 +1267,37 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId)
if (IncludeTransformWhenSpawning == null || IncludeTransformWhenSpawning(OwnerClientId))
{
obj.Header.HasTransform = true;

// We start with the default AutoObjectParentSync values to determine which transform space we will
// be synchronizing clients with.
var syncRotationPositionLocalSpaceRelative = obj.Header.HasParent && !m_CachedWorldPositionStays;
var syncScaleLocalSpaceRelative = obj.Header.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.Header.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,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO

if (networkObject != null)
{
// SPECIAL CASE:
// 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.
Expand All @@ -423,7 +423,9 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO
// but it is up to the user to set those values
if (sceneObject.Header.HasTransform && !isSpawnedByPrefabHandler)
{
if (worldPositionStays)
// 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)
{
networkObject.transform.position = position;
networkObject.transform.rotation = rotation;
Expand All @@ -443,6 +445,9 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneO
// that is the default value of Vector3.
if (!sceneObject.Header.IsPlayerObject)
{
// Since scale is always applied to local space scale, we do the transform
// space logic during serialization such that it works out whether AutoObjectParentSync
// is enabled or not (see NetworkObject.SceneObject)
networkObject.transform.localScale = scale;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System.Collections.Generic;
using UnityEngine;
using Unity.Netcode;


namespace TestProject.RuntimeTests
{
/// <summary>
/// Helper class that builds 4 transform lists based on parent-child hierarchy
/// for the <see cref="ParentingInSceneObjectsTests.InSceneNestedAutoSyncObjectTest"/>
/// </summary>
public class ParentingAutoSyncManager : NetworkBehaviour
{
public static ParentingAutoSyncManager ServerInstance;
public static Dictionary<ulong, ParentingAutoSyncManager> ClientInstances = new Dictionary<ulong, ParentingAutoSyncManager>();

public GameObject WithNetworkObjectAutoSyncOn;
public GameObject WithNetworkObjectAutoSyncOff;
public GameObject GameObjectAutoSyncOn;
public GameObject GameObjectAutoSyncOff;

public List<Transform> NetworkObjectAutoSyncOnTransforms = new List<Transform>();
public List<Transform> NetworkObjectAutoSyncOffTransforms = new List<Transform>();
public List<Transform> GameObjectAutoSyncOnTransforms = new List<Transform>();
public List<Transform> GameObjectAutoSyncOffTransforms = new List<Transform>();

public static void Reset()
{
ServerInstance = null;
ClientInstances.Clear();
}

public override void OnNetworkSpawn()
{
if (IsServer)
{
ServerInstance = this;
}
else
{
ClientInstances.Add(NetworkManager.LocalClientId, this);
}
var currentRoot = WithNetworkObjectAutoSyncOn.transform;
NetworkObjectAutoSyncOnTransforms.Add(currentRoot);
NetworkObjectAutoSyncOnTransforms.Add(currentRoot.GetChild(0));
NetworkObjectAutoSyncOnTransforms.Add(currentRoot.GetChild(0).GetChild(0));

currentRoot = WithNetworkObjectAutoSyncOff.transform;
NetworkObjectAutoSyncOffTransforms.Add(currentRoot);
NetworkObjectAutoSyncOffTransforms.Add(currentRoot.GetChild(0));
NetworkObjectAutoSyncOffTransforms.Add(currentRoot.GetChild(0).GetChild(0));

currentRoot = GameObjectAutoSyncOn.transform;
GameObjectAutoSyncOnTransforms.Add(currentRoot);
GameObjectAutoSyncOnTransforms.Add(currentRoot.GetChild(0));
GameObjectAutoSyncOnTransforms.Add(currentRoot.GetChild(0).GetChild(0));

currentRoot = GameObjectAutoSyncOff.transform;
GameObjectAutoSyncOffTransforms.Add(currentRoot);
GameObjectAutoSyncOffTransforms.Add(currentRoot.GetChild(0));
GameObjectAutoSyncOffTransforms.Add(currentRoot.GetChild(0).GetChild(0));
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading