Network Transform lags

Does anyone know how to fix the glitchy/laggie movement of the network transform.
I already tried to update to Unity 5.1.1 where they said transform interpolation has been fixed.

I use standard Network Transform settings, tested localhost with a server and 1 client.

Movement looks far from smooth :frowning:

1 Like

@Royall,try “Transform sync more”. I found it more smooth when using CharaterController mode instead of Transform mode.

I only see
Sync none
Sync Transform
Sync Rigidbody 2D
Synv Rigidbody 3D
Sync Character Controller

Unfortunately Sync Transform and Sync character controller both look laggy…

What do you mean with sync more?

Transform Mode is not smooth at all.
It just apply the new position received, so your game object will be teleported.

Rigidbody/Character controller should do the job in certain manners, the last velocity is applied each frame but you can still have the feeling of teleportation.

I did my own NetworkTransform implementation inspired by Quake 3 and unreal.

The idea is:

  • for the local character:

  • process the gameplay

  • send the current state of the character to the server.

  • The server valid the state and send it to the other clients.

  • for the other characters:

  • received new character state and put it in an array.

  • find the closest saved states (received by the server) for the current time stamp (Time.time - interpolationTime)

  • interpolate between the closest states and the previous one and apply the result

If you want a solid net code I don’t think NetworkTransform is enought. If it is for prototyping purpose it can be good but don’t expect too much :slight_smile:

I hope it can help.

1 Like

I highly doubt the Network Transform is just some position and rotation sending. There are parameters for interpolation but they don’t do much… I get the same results with transform mode as rigidbody and charcontroller mode.

They sell the HLAPI as a simple and flexible solution for multiplayer games where you can convert your single player game with 3 mouse clicks. I can understand if they don’t offer state of the art movement interpolation but they can atleast release something decent because right know it looks the Network Transform is just not usable in any way…

It maybe is even better to just syncvar some positions and just lerp between them atm…

1 Like

If you take a look inside the assembly code you will see I’m right.
Interpolation parameters is used for the others types of Sync.

Cheers

Any chance you could share your interpolation code?

I don’t have access to my code where I am actually but It is pretty similar to this code:

I accounted a problem to sync the time server to the clients. I’m still on it.

hey guys its 2016 Oct, any progress to control lag on network i follow unity offical tut
INTRODUCTION TO A SIMPLE MULTIPLAYER EXAMPLE
but the problem its performance is low. There is lag between my VR player instance and normal player instance. how do contorl?

Hey bro did you solve it? i did this example, but i see a little lag the movement player

I didn’t find any solution yet but there are different option in network transform that can be used to control lag (i guess).

I use the following code for position:

using UnityEngine;
using System.Collections;
using UnityEngine.Networking;

public class Object_SyncPosition : NetworkBehaviour {

    private Transform myTransform;
    [SerializeField] float lerpRate = 5;
    [SyncVar] private Vector3 syncPos;
//    private NetworkIdentity theNetID;

    private Vector3 lastPos;
    private float threshold = 0.5f;


    void Start () {
        myTransform = GetComponent<Transform> ();
        syncPos = GetComponent<Transform>().position;
    }


    void FixedUpdate () {
        TransmitPosition ();
        LerpPosition ();
    }

    void LerpPosition () {
        if (!hasAuthority) {
            myTransform.position = Vector3.Lerp (myTransform.position, syncPos, Time.deltaTime * lerpRate);
        }
    }

    [Command]
    void Cmd_ProvidePositionToServer (Vector3 pos) {
        syncPos = pos;
    }

    [ClientCallback]
    void TransmitPosition () {
        if (hasAuthority  && Vector3.Distance(myTransform.position, lastPos) > threshold) {
            Cmd_ProvidePositionToServer (myTransform.position);
            lastPos = myTransform.position;
        }
    }
}
4 Likes

So it is the replacement of Network transform? or i use this script with network transform also

I did this solution because UNET’s network transform… doesn’t interpolate nor predicts, I don’t know why is there an interpolation factor :confused:

using UnityEngine;
using System.Collections;
using UnityEngine.Networking;

public class NetworkMovement : NetworkBehaviour {
    #region Properties
    [SerializeField]
    protected Transform target;

    #region Setup
    [Header("Setup")]
    [Range(0,10)] public int SendRate = 2;
    [Range(0,2)] public float movementThreshold = 0.2f;
    [Range(0,30)] public float angleThreshold = 5;
    [Range(0, 10)] public float distanceBeforeSnap = 4;
    [Range(0,90)] public float angleBeforeSnap = 40;
    #endregion

    #region Interpolation
    [Header("Interpolation")]
    [Range(0,1)] public float movementInterpolation = 0.1f;
    [Range(0,1)] public float rotationInterpolation = 0.1f;
    #endregion

    #region Prediction
    public float thresholdMovementPrediction = 0.7f;
    public float thresholdRotationPrediction = 15;
    #endregion

    #region ProtectedProperties
    protected Vector3 lastDirectionPerFrame = Vector3.zero;
    protected Vector3 lastPositionSent = Vector3.zero;
    protected Quaternion lastRotationSent = Quaternion.identity;
    protected Quaternion lastRotationDirectionPerFrame = Quaternion.identity;
    protected bool send = false;
    protected bool sending = false;
    protected int count = 0;
    #endregion

    #endregion

    #region Logic
    void FixedUpdate () {
        if (isLocalPlayer) {
            sendInfo();
        } else {
            recontiliation();
        }
    }

    protected void sendInfo() {
        if (send) {
            if (count == SendRate) {
                count = 0;
                send = false;
                Vector3 v = target.position;
                Quaternion q = target.rotation;
                CmdSendPosition(v,q);
            } else {
                count++;
            }
        } else {
            checkIfSend();
        }
    }
    protected void checkIfSend() {
        if (sending) {
            send = true;
            sending = false;
            return;
        }
        Vector3 v = target.position;
        Quaternion q = target.rotation;
        float distance = Vector3.Distance(lastPositionSent, v);
        float angle = Quaternion.Angle(lastRotationSent, q); ;
        if (distance > movementThreshold || angle > angleThreshold) {
            send = true;
            sending = true;
        }
    }
    protected void recontiliation() {
        Vector3 v = target.position;
        Quaternion q = target.rotation;
        float distance = Vector3.Distance(lastPositionSent, v);
        float angle = Vector3.Angle(lastRotationSent.eulerAngles, q.eulerAngles);
        if (distance > distanceBeforeSnap) {
            target.position = lastPositionSent;
        }
        if (angle > angleBeforeSnap) {
            target.rotation = lastRotationSent;
        }
        //prediction
        v += lastDirectionPerFrame;
        q *= lastRotationDirectionPerFrame;
        //interpolation
        Vector3 vLerp = Vector3.Lerp(v, lastPositionSent, movementInterpolation);
        Quaternion qLerp = Quaternion.Lerp(q, lastRotationSent, rotationInterpolation);
            target.position = vLerp;
            target.rotation = qLerp;
    }
    #endregion

    #region OverNetwork
    [Command (channel = 1)]
    protected void CmdSendPosition(Vector3 newPos,Quaternion newRot) {
        RpcReceivePosition(newPos, newRot);
    }

    [ClientRpc(channel = 1)]
    protected void RpcReceivePosition(Vector3 newPos,Quaternion newRot) {
        int frames = (SendRate + 1);
        lastDirectionPerFrame = newPos - lastPositionSent;
        //right now prediction is made with the new direction and amount of frames
        lastDirectionPerFrame /= frames;
        if (lastDirectionPerFrame.magnitude > thresholdMovementPrediction) {
            lastDirectionPerFrame = Vector3.zero;
        }
        Vector3 lastEuler = lastRotationSent.eulerAngles;
        Vector3 newEuler = newRot.eulerAngles;
        if (Quaternion.Angle(lastRotationDirectionPerFrame, newRot) < thresholdRotationPrediction) {
            lastRotationDirectionPerFrame = Quaternion.Euler(( newEuler - lastEuler ) / frames);
        }else {
            lastRotationDirectionPerFrame = Quaternion.identity;
        }
        lastPositionSent = newPos;
        lastRotationSent = newRot;
    }
    #endregion
}
1 Like

Replace

1 Like

Thanks upstairs , look lot of topic finally fixed that issue , I made a sync transform (position and rotation)

   class Play_syncRotation : NetworkBehaviour
   {
      [SyncVar]
      private Quaternion playerQuaternion;

      [SyncVar]
      private Vector3 newPosition;

      private float speed;
      private float syncThreshold;
      private float rotationSpeed;


      void Start()
      {
         speed = GetComponent<TPlayerController>().moveSpeed;
         syncThreshold = GetComponent<TPlayerController>().syncPositionThreshold;
         rotationSpeed = GetComponent<TPlayerController>().rotationSpeed;
      }

      void FixedUpdate()
      {
         if (!isLocalPlayer)
         {
            if(transform.localRotation != playerQuaternion)
            {
               transform.localRotation = Quaternion.Lerp(transform.localRotation, playerQuaternion, Time.deltaTime * rotationSpeed);
            }
            Move_Torwards(transform.transform.position, newPosition, speed);

         }
       
      }
    

      [Command]
      void CmdProviderNewPositionToServer(Vector3 playerPos, Quaternion playerQuaternion)
      {
         this.newPosition = playerPos;
         this.playerQuaternion = playerQuaternion;
      }

      [Client]
      public void Transmit(Vector3 playerPos, Quaternion playerQuaternion)
      {
         if (isLocalPlayer)
         {
            CmdProviderNewPositionToServer(playerPos, playerQuaternion);
         }
      }


      private bool Move_Torwards(Vector3 startPos, Vector3 endPos, float moveMax)
      {
         if (Vector3.Distance(startPos, endPos) <= syncThreshold)
         {
            return true;
         }
         else
         {
            Vector3 moveForward = endPos - startPos;
            moveForward.y = 0;
            moveForward.Normalize();
            float maxDistanceDelta = Time.deltaTime * moveMax; 
            Vector3 targetPos = Vector3.MoveTowards(transform.position, endPos, maxDistanceDelta);
            transform.position = targetPos;
            return false;
         }

      }
   }

AND the character controller

   class TPlayerController : NetworkBehaviour
   {

      [SerializeField]
      private float m_moveSpeed = 5;
      public float moveSpeed { get { return m_moveSpeed; } }

      [SerializeField]
      private float m_syncPositionThreshold = 0.25f;
      public float syncPositionThreshold { get { return m_syncPositionThreshold; } }

      [SerializeField]
      private int m_rotationSpeed = 15;
      public int rotationSpeed { get { return m_rotationSpeed; } }

      void Awake()
      {

      }

      void Start()
      {
         if (isLocalPlayer)
         {
            GetComponent<Play_syncRotation>().Transmit(transform.transform.position, transform.rotation);
         }

      }

      void Update()
      {
         if (!isLocalPlayer)
         {
            return;
         }
         // CrossPlatformInputManager.GetButtonDown("w");

         float h = CrossPlatformInputManager.GetAxis("Horizontal");
         float v = CrossPlatformInputManager.GetAxis("Vertical");

         if (h != 0 || v != 0)
         {
            GetComponent<Animator>().SetBool("Run", true);
         }
         else if (h == 0 && v == 0)
         {
            GetComponent<Animator>().SetBool("Run", false);
         }

         // transFormValue = (v * Vector3.forward + h * Vector3.right) * Time.deltaTime;
         Vector3 m_CamForward = Vector3.Scale(Camera.main.transform.forward, new Vector3(1, 0, 1)).normalized;
         Vector3 transFormValue = v * m_CamForward + h * Camera.main.transform.right;
         float distance = 0;
         if (transFormValue.x != 0 || transFormValue.z != 0)
         {
            distance = Vector3.Distance(transform.position, transFormValue);
            //this.rotation = Quaternion.LookRotation(transFormValue).eulerAngles;
            //CmdChangeRot(this.rotation);
            //transform.rotation = Quaternion.Euler(rotation);
            //transform.localRotation = Quaternion.LookRotation(transFormValue);
            Quaternion lerpRota = Quaternion.Lerp(transform.localRotation, Quaternion.LookRotation(transFormValue), Time.deltaTime * 15);
            if (transform.localRotation != lerpRota)
            {
               transform.localRotation = lerpRota;
               //Debug.Log("sent " + transform.localRotation);
               //GetComponent<Play_syncRotation>().TransmitRotation(transform.localRotation);
            }

            //Debug.Log("update : " + GetComponent<NetworkIdentity>().netId + ":" + transform.rotation);
            //Debug.Log(h + "-" + v + transFormValue + "--" + m_TurnAmount);

            // transform.Rotate(0, 90, 0);
            transFormValue *= Time.deltaTime;

            // Debug.Log("transFormValue" + );

            transform.Translate(transFormValue * m_moveSpeed, Space.World);
            if (distance >= m_syncPositionThreshold)
            {
               GetComponent<Play_syncRotation>().Transmit(transform.transform.position, transform.localRotation);
            }

         }
         Camera.main.transform.LookAt(this.transform);
         Camera.main.transform.position = new Vector3(this.transform.position.x, this.transform.position.y + 8f, this.transform.position.z + 8f);
      }

   }

And you can add a Queue in the Play_syncRotation to make move smoothly like this

 if (moveQueue.Count > 0)
         {

            target.GetComponent<Animator>().SetBool("Run", true);
            MoveAction action = moveQueue.Peek();
            if (Move_Torwards(target, target.transform.position, action.to, action.speed)) {
               moveQueue.Dequeue();
            }
            Debug.Log("play move : " + target.transform.position + ":" + action.to + " rest action " + moveQueue.Count); 
            //StartCoroutine();
         }

I’m currently learning Unity’s Networking through Merry Fragmas video tutorial and the documentation. I don’t know if I fall in the same category as the people in this topic but I make a 2D top down arena shm’up and I’m facing the following issue:
I see my player move smoothly and the others not, and my friends can see their players run smoothly and the others not.
I tried all sorts of player movement technics in the Player script, I tried all kind of settings in Transform and Transform Child Network (even removing them) and the problem stays the same. They don’t sync.

Am I having the same problem as the posters in this thread?

PS: My Player doesn’t have a character controller and it doesn’t move by forces.

i did an update to get rotation too:

using UnityEngine;
using UnityEngine.Networking;

public class TransformMotion : NetworkBehaviour
{

    [SyncVar]
    private Vector3 syncPos;

    [SyncVar]
    private float syncYRot;

    private Vector3 lastPos;
    private Quaternion lastRot;
    private Transform myTransform;
    [SerializeField]
    private float lerpRate = 10;
    [SerializeField]
    private float posThreshold = 0.5f;
    [SerializeField]
    private float rotThreshold = 5;

    // Use this for initialization
    void Start()
    {
        myTransform = transform;
    }

    // Update is called once per frame
    void FixedUpdate()
    {
        TransmitMotion();
        LerpMotion();
    }

    [Command]
    void Cmd_ProvidePositionToServer(Vector3 pos, float rot)
    {
        syncPos = pos;
        syncYRot = rot;
    }

    [ClientCallback]
    void TransmitMotion()
    {
        if(hasAuthority)
        {
            if (Vector3.Distance(myTransform.position, lastPos) > posThreshold || Quaternion.Angle(myTransform.rotation, lastRot) > rotThreshold)
            {
                Cmd_ProvidePositionToServer(myTransform.position, myTransform.localEulerAngles.y);

                lastPos = myTransform.position;
                lastRot = myTransform.rotation;
            }
        }
    }

    void LerpMotion()
    {
        if (!hasAuthority)
        {
            myTransform.position = Vector3.Lerp(myTransform.transform.position, syncPos, Time.deltaTime * lerpRate);

            Vector3 newRot = new Vector3(0, syncYRot, 0);
            myTransform.rotation = Quaternion.Lerp(myTransform.rotation, Quaternion.Euler(newRot), Time.deltaTime * lerpRate);
        }
    }
}

[/code]

2 Likes

Great, i have a separate script for that.

I say Fork lerping and just made the update tick 128 times a sec and im sending the server “raw” vectors I’ve had 0 problems and the movement is smooth enough for a fast paste fps. Everything in real time. Perfect.