Unity-Technologies/com.unity.multiplayer.samples.coop

Spawning an object with ownership leads to a different initial rotation between server and client

rCarleon opened this issue · 6 comments

Description
I am using ClientNetworkTransform component on a gameobject. When spawning the object with a specific rotation (Z Axis) and with ownership (SpawnWithOwnerShip) the object is spawned on the client correctly with the correct rotation. On the server it is also spawned correctly at first, but it changes the rotation within a short period of time (1 second or so) to EulerAngles 0/0/0, resulting in a different Rotation between Server and Client.

When spawning the object first (without Ownership) and changing the ownership directly afterwards, the described behavior does not occur but the ownership of the gameobject is not applied (client can't transform object). Seems to be related to an already reported issue.

Only tested with Z axis rotation.

To Reproduce
Steps to reproduce the behavior:

  1. Add NetworkObject and ClientNetworkTransform to an object which is going to be spawn
  2. Create Clone of the Project and start the project as server and start the clone project as client
  3. After the client has connected, instantiate the object with rotation 180 and spawn the object with ownership
  4. Or use minimum unity Project attached below

Screenshot 2022-01-03 133126

Screenshot 2022-01-03 132914

Environment

  • OS: [e.g. Windows 10]
  • Unity Version: [e.g. 2020.3.13]
  • Netcode Version: [e.g. 1.0.0-pre.3]
  • Netcode Commit: [e.g. https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/commit/c102935df1d7e0928283b48948fe96e5d96dd961]

Additional context

testing.zip

I have the same issue. Please fix

Same here !

I did some digging and the problem seems to come from the base NetworkTransform's m_HasSentLastValue private field being initialized to false.

This mechanism is supposed to duplicate the sending of the last change of a streak of changes in order to clamp the interpolation. However, if upon ownership transfer there is no changes, it being initialized to false will result in the duplication of an "uninitialized" state.

In the case of the ClientNetworkTransform, once the client gains ownership of the object, the OnNetworkSpawn is executed (when spawned with SpawnWithOwnership) and right after, during its first subsequent Update, it will try to commit its truth to the server - the server state is OK up to that point in time. But since it'll find no changes (it was just spawned with the same state as the server), the ClientNetworkTransform won't flag these values as "dirty" and will enter the else if case of the base NetworkTransform's TryCommit method due to the m_HasSentLastValue field being false.

NetworkTransform - TryCommit(bool isDirty):

            if (isDirty)
            {
                Send(m_LocalAuthoritativeNetworkState);
                m_HasSentLastValue = false;
                m_LastSentTick = m_CachedNetworkManager.LocalTime.Tick;
                m_LastSentState = m_LocalAuthoritativeNetworkState;
            }
            else if (!m_HasSentLastValue && m_CachedNetworkManager.LocalTime.Tick >= m_LastSentTick + 1)
            {
                m_LastSentState.SentTime = m_CachedNetworkManager.LocalTime.Time;
                Send(m_LastSentState);
                m_HasSentLastValue = true;
            }

That results in sending the uninitialized m_LastSentState to the server which contains default values (e.g. scaling of x: 0, y: 0 and z: 0). The server will then pick that up in its base NetworkTransform Update -> ApplyInterpolatedNetworkStateToTransform and will "change" the transform values back to the reset state (zero everything).

A sad workaround that you can apply is to edit the ClientTransformNetwork's code and use some reflection upon ownership transfer (in OnNetworkSpawn if spawned with SpawnWithOwnership or in OnGainedOwnership if spawned with Spawn followed by ChangeOwnership) like this:

        public override void OnNetworkSpawn()
        {
            base.OnNetworkSpawn();
            CanCommitToTransform = IsOwner;

            if (CanCommitToTransform)
            {
                GetType().BaseType.GetField("m_HasSentLastValue", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(this, true);
            }
        }

Hope this helps !

Tracked in our backlog MTT-3044

This project, with a few minor updates since the v1.0.0-pre.3 release, works using the PR-2102 branch.

This bug was resolved in v1.0.2.
I am including the adjusted testing.zip project that uses v1.0.2 that I used to confirm this is fixed.
694_Resolved.zip