Child gameobject lagging behind parent network object

Hi, I have a network object that calls a client rpc on spawn so clients can instanciate the corresponding model (gameobject) for that network object.

This works fine but when I move the parent (the network object) the child is lagging behind the parent position and stutters

This is the hierarchy:

Base object (network object that moves with physics)

Model game object (just a game object with a 3d model)

Does anybody know why the child lags behind the parent and how to solve this?
Thanks

What NGO components are you using other than a NetworkObject?
Do you use a NetworkTransform and NetworkRigidbody?
If so, is the motion model server authoritative or owner/client authoritative?

Hi and thanks for answering
the parent has NetworkTransform and NetworkRigidbody, yes
and the motion is server authoritative. i add some forces and torques to the parent for motion

Do you have interpolation enabled?
Is there a way you could share your script that handles applying the forces/torque to the rigid body?

No, im not using interpolation, ill try that tho

in our model, server updates rigidbodys information, while client shows particle effects based on ShipCommandData, which is a network variable of a class with the user input

using Unity.Collections;
using Unity.Netcode;
using UnityEngine;

[RequireComponent(typeof(Ship))]
public class ShipEngineController : NetworkBehaviour
{
    private Ship Ship;

    [SerializeField]
    ParticleSystem m_Thrust;
    ParticleSystem.MainModule m_ThrustMain;
    private NetworkVariable<float> m_Thrusting = new NetworkVariable<float>();
    Rigidbody m_Rigidbody;

    void Awake()
    {
        Ship = GetComponent<Ship>();
        m_Rigidbody = GetComponent<Rigidbody>();

        m_ThrustMain = m_Thrust.main;
    }

    void Update()
    {
        if (IsServer) UpdateServer();
        if (IsClient) UpdateClient();
    }

    void UpdateServer()
    {
        // update rotation
        float rotate = Ship.ShipCommandData.Value.ShipHorizontalThruster * Ship.data.RotateSpeed;

        m_Rigidbody.angularVelocity = new Vector3(0, -rotate, 0);
        //m_Rigidbody.AddTorque(new Vector3(0, -rotate, 0));

        // update thrust
        if (Ship.ShipCommandData.Value.ShipVerticalThruster != 0)
        {
            float accel = Ship.data.Acceleration;

            Vector3 thrustVec = transform.forward * (Ship.ShipCommandData.Value.ShipVerticalThruster * accel);
            m_Rigidbody.AddForce(thrustVec);

            // restrict max speed
            float top = Ship.data.TopSpeed;

            if (m_Rigidbody.velocity.magnitude > top)
            {
                m_Rigidbody.velocity = m_Rigidbody.velocity.normalized * top;
            }
        }
    }

    void UpdateClient()
    {

        if (!IsLocalPlayer) return;

        // control thrust particles
        if (Ship.ShipCommandData.Value.ShipVerticalThruster == 0.0f)
        {
            m_ThrustMain.startLifetime = 0.1f;
            m_ThrustMain.startSize = 1f;
            GetComponent<AudioSource>().Pause();
        }
        else
        {
            m_ThrustMain.startLifetime = 0.4f;
            m_ThrustMain.startSize = 1.2f;
            GetComponent<AudioSource>().Play();
        }
    }
}

this is whats happening, the GUI and camera follow the network object correctly, but the model (a child of the network object) does not

ok, solved, it seems it was a camera issue using a network variable to set position instead of the network object reference

1 Like

Glad to hear you resolved your issue!
Here is an alternate version of your script in the event you are interested:

#if UNITY_EDITOR
using UnityEditor;
// This bypases the default custom editor for NetworkTransform
// and lets you modify your custom NetworkTransform's properties
// within the inspector view
[CustomEditor(typeof(ShipEngineController), true)]
public class ShipEngineControllerEditor : Editor
{
}
#endif
[RequireComponent(typeof(Ship))]
    public class ShipEngineController : NetworkTransform
    {
        public enum AuthorityModes
        {
            Server,
            Owner
        }

        [SerializeField]
        private AuthorityModes m_AuthorityMode = AuthorityModes.Server;
        [SerializeField]
        private ParticleSystem m_Thrust;

        private Ship Ship;
        private ParticleSystem.MainModule m_ThrustMain;
        private NetworkVariable<float> m_Thrusting = new NetworkVariable<float>();
        private Rigidbody m_Rigidbody;

        /// <summary>
        /// Determines the authority mode.
        /// </summary>
        protected override bool OnIsServerAuthoritative()
        {
            return m_AuthorityMode == AuthorityModes.Server;
        }

        protected override void Awake()
        {
            // Always invoke the base Awake method when deriving from NetworkTransform
            base.Awake();

            Ship = GetComponent<Ship>();
            m_Rigidbody = GetComponent<Rigidbody>();
            m_ThrustMain = m_Thrust.main;
        }

        protected override void Update()
        {
            // Don't do anything if we are not spawned
            if (!IsSpawned)
            {
                return;
            }

            if (CanCommitToTransform)
            {
                AuthorityUpdate();
            }

            // I think you want this to be invoked on all instances to reflect the thruster FX for all instances
            // (Used to be UpdateClient)
            UpdateFx();

            // Invokong the base Update applies the authority's state
            // If you want to take control over the object locally (only locally) you can
            // add a condition arond this to skip updating the authority state updates and interpolation
            base.Update();
        }

        private void AuthorityUpdate()
        {
            // update rotation
            float rotate = Ship.ShipCommandData.Value.ShipHorizontalThruster * Ship.data.RotateSpeed;

            m_Rigidbody.angularVelocity = new Vector3(0, -rotate, 0);
            //m_Rigidbody.AddTorque(new Vector3(0, -rotate, 0));

            // update thrust
            if (Ship.ShipCommandData.Value.ShipVerticalThruster != 0)
            {
                float accel = Ship.data.Acceleration;

                Vector3 thrustVec = transform.forward * (Ship.ShipCommandData.Value.ShipVerticalThruster * accel);
                m_Rigidbody.AddForce(thrustVec);

                // restrict max speed
                float top = Ship.data.TopSpeed;

                if (m_Rigidbody.velocity.magnitude > top)
                {
                    m_Rigidbody.velocity = m_Rigidbody.velocity.normalized * top;
                }
            }
        }

        /// <summary>
        /// Update the FX for all ships
        /// Formerly UpdateClient
        /// </summary>
        private void UpdateFx()
        {
            // control thrust particles on all non-
            if (Ship.ShipCommandData.Value.ShipVerticalThruster == 0.0f)
            {
                m_ThrustMain.startLifetime = 0.1f;
                m_ThrustMain.startSize = 1f;
                GetComponent<AudioSource>().Pause();
            }
            else
            {
                m_ThrustMain.startLifetime = 0.4f;
                m_ThrustMain.startSize = 1.2f;
                GetComponent<AudioSource>().Play();
            }
        }

        /// <summary>
        /// Just making you aware of this, right after the authority pushes a state update this is invoked.
        /// It can be useful to know what was most recently updated on the transform.
        /// </summary>
        protected override void OnAuthorityPushTransformState(ref NetworkTransformState networkTransformState)
        {
            base.OnAuthorityPushTransformState(ref networkTransformState);
        }

        /// <summary>
        /// Just making you aware of this, right after the non-authority receives an authority state update this is invoked.
        /// It can be useful to know what was the most recent authoritative state update is and what the previous one was.
        /// </summary>
        protected override void OnNetworkTransformStateUpdated(ref NetworkTransformState oldState, ref NetworkTransformState newState)
        {
            base.OnNetworkTransformStateUpdated(ref oldState, ref newState);
        }
    }

You would replace your NetworkTransform and ShipEngineController with this… not sure if it makes sense but as an alternative.

Oh that’s a very interesting approach! Thanks for the feedback!!

Very useful! Thanks again!