UNet Sync Bone Rotations - Jittering

I’m so close but there is something I am still not doing right here.

All I need to do is sync the bone rotations across the network without having a crazy jitter effect. I’m using the basic components while I figure this out.


Here is the player gameobject. Everything that is disabled doesn’t become enabled unless “isLocalPlayer == true”. Everything that is disabled is not a network behavior either. Please note: I cannot show you any “V” scripts because they are not written by myself but are from the Invector Third Person Shooter package.

123759-playergameobject.png


Setup Local Player Script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using Invector.vCharacterController;                    //to access "vThirdPersonController","vThirdPersonInput"
using Invector.vShooter;                                //to access "vShooterMeleeInput"
using Invector.vItemManager;                            //to access "vAmmoManager"
using Invector.vCharacterController.vActions;           //to access "vGenericAction"

[RequireComponent(typeof(NetworkIdentity))]             //needed by this script
[RequireComponent(typeof(NetworkTransform))]            //not needed by this script, used to sync movements
[RequireComponent(typeof(NetworkAnimator))]             //not needed by this script, used to sync animations
public class SetupLocalPlayer : NetworkBehaviour {

    private Animator animator = null;
    private Quaternion head, neck, spine, chest;
    private Transform t_head, t_neck, t_spine, t_chest = null;

    void Start()
    {
        animator = GetComponent<Animator>();
        if (isLocalPlayer == true)
        {
            GetComponent<vThirdPersonController>().enabled = true;
            if (GetComponent<vThirdPersonInput>())
            {
                GetComponent<vThirdPersonInput>().enabled = true;
            }
            if (GetComponent<vShooterMeleeInput>())
            {
                GetComponent<vShooterMeleeInput>().enabled = true;
                GetComponent<vShooterManager>().enabled = true;
                GetComponent<vAmmoManager>().enabled = true;
                GetComponent<vGenericAction>().enabled = true;
            }
            GetComponent<vHeadTrack>().enabled = true;
        }
        VerifyBones();
    }

    public override void PreStartClient()
    {
        GetComponent<NetworkAnimator>().SetParameterAutoSend(0, true);
        GetComponent<NetworkAnimator>().SetParameterAutoSend(1, true);
        GetComponent<NetworkAnimator>().SetParameterAutoSend(2, true);
        GetComponent<NetworkAnimator>().SetParameterAutoSend(3, true);
    }

    void VerifyBones()
    {
        if (t_head == null)
        {
            try
            {
                t_head = animator.GetBoneTransform(HumanBodyBones.Head).transform;
            }
            catch (System.Exception e)
            {
                Debug.LogError(e);
            }
        }
        if (t_neck == null)
        {
            try
            {
                t_neck = animator.GetBoneTransform(HumanBodyBones.Neck).transform;
            }
            catch (System.Exception e)
            {
                Debug.LogError(e);
            }
        }
        if (t_spine == null)
        {
            try
            {
                t_spine = animator.GetBoneTransform(HumanBodyBones.Spine).transform;
            }
            catch (System.Exception e)
            {
                Debug.LogError(e);
            }
        }
        if (t_chest == null)
        {
            try
            {
                t_chest = animator.GetBoneTransform(HumanBodyBones.Chest).transform;
            }
            catch (System.Exception e)
            {
                Debug.LogError(e);
            }
        }
    }

    void LateUpdate()
    {
        if (isLocalPlayer == true)
        {
            Cmd_RecieveRotations(t_head.localRotation, t_neck.localRotation, t_spine.localRotation, t_chest.localRotation);
        }
        else
        {
            SetLocalRotation();
        }
    }

    void SetLocalRotation()
    {
        t_head.localRotation = head;
        t_neck.localRotation = neck;
        t_spine.localRotation = spine;
        t_chest.localRotation = chest;
    }

    //Local Client -> Server
    [Command]
    void Cmd_RecieveRotations(Quaternion s_head, Quaternion s_neck, Quaternion s_spine, Quaternion s_chest)
    {
        RpcSetRotations(s_head, s_neck, s_spine, s_chest);
    }

    //Server -> All Connected Clients
    [ClientRpc]
    void RpcSetRotations(Quaternion s_head, Quaternion s_neck, Quaternion s_spine, Quaternion s_chest)
    {
        if (isLocalPlayer == false) //To prevent from calling on yourself in Lan Host Mode
        {
            head = s_head;
            neck = s_neck;
            spine = s_spine;
            chest = s_chest;
        }
    }
    
    //private void OnAnimatorIK()
    //{
    //    if (isLocalPlayer == false)
    //    {
    //        animator.SetBoneLocalRotation(HumanBodyBones.Head, head);
    //        animator.SetBoneLocalRotation(HumanBodyBones.Head, neck);
    //        animator.SetBoneLocalRotation(HumanBodyBones.Head, spine);
    //        animator.SetBoneLocalRotation(HumanBodyBones.Head, chest);
    //    }
    //}

}

So this script works. I can see them look around but they snap back to their original rotations on what seems like every update and on every LateUpdate snap back to the synced rotation. So I get the effect like they are listening to some heavy death metal all day (jittering).

What am I doing wrong here?


Here you can see that the rotations are clearly coming through the network but is getting reset client side somehow. Not sure what…

123719-jitter-opt.gif

Got it working. I switched everything around and made it so the server wasn’t actually calling to the clients. The clients would always check the server for updates on rotations. I did this via “SyncVar”.

So the [Client] function will tell the server to update its global variables ([Command] function) and wouldn’t do anything else. Since I put [SyncVar] on my variables I don’t need to explicitly call functions on clients anymore since the clients are always constantly checking for updates with the server. So it will notice the change in the variable and will update itself.

Now I smooth out the movements by running Lerp like @NoDumbQuestion suggested. Bam! It works like a charm. Here is my final code for anyone else trying to do what I just went through.

Nice thing about this is it will work with animations from mecanim quite nicely.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using Invector.vCharacterController;                    //to access "vThirdPersonController","vThirdPersonInput"
using Invector.vShooter;                                //to access "vShooterMeleeInput"
using Invector.vItemManager;                            //to access "vAmmoManager"
using Invector.vCharacterController.vActions;           //to access "vGenericAction"

[RequireComponent(typeof(NetworkIdentity))]             //needed by this script
[RequireComponent(typeof(NetworkTransform))]            //not needed by this script, used to sync movements
[RequireComponent(typeof(NetworkAnimator))]             //not needed by this script, used to sync animations
public class SetupLocalPlayer : NetworkBehaviour {

    private Animator animator = null;
    [SyncVar] private Quaternion head;
    [SyncVar] private Quaternion neck;
    [SyncVar] private Quaternion spine;
    [SyncVar] private Quaternion chest;
    private Transform t_head, t_neck, t_spine, t_chest = null;
    [SerializeField] private float lerpRate = 90.0f;

    void Start()
    {
        animator = GetComponent<Animator>();
        if (isLocalPlayer == true)
        {
            GetComponent<vThirdPersonController>().enabled = true;
            if (GetComponent<vThirdPersonInput>())
            {
                GetComponent<vThirdPersonInput>().enabled = true;
            }
            if (GetComponent<vShooterMeleeInput>())
            {
                GetComponent<vShooterMeleeInput>().enabled = true;
                GetComponent<vShooterManager>().enabled = true;
                GetComponent<vAmmoManager>().enabled = true;
                GetComponent<vGenericAction>().enabled = true;
            }
            GetComponent<vHeadTrack>().enabled = true;
        }
        VerifyBones();
    }

    public override void PreStartClient()
    {
        GetComponent<NetworkAnimator>().SetParameterAutoSend(0, true);
        GetComponent<NetworkAnimator>().SetParameterAutoSend(1, true);
        GetComponent<NetworkAnimator>().SetParameterAutoSend(2, true);
        GetComponent<NetworkAnimator>().SetParameterAutoSend(3, true);
    }

    void VerifyBones()
    {
        if (t_head == null)
        {
            try
            {
                t_head = animator.GetBoneTransform(HumanBodyBones.Head).transform;
            }
            catch (System.Exception e)
            {
                Debug.LogError(e);
            }
        }
        if (t_neck == null)
        {
            try
            {
                t_neck = animator.GetBoneTransform(HumanBodyBones.Neck).transform;
            }
            catch (System.Exception e)
            {
                Debug.LogError(e);
            }
        }
        if (t_spine == null)
        {
            try
            {
                t_spine = animator.GetBoneTransform(HumanBodyBones.Spine).transform;
            }
            catch (System.Exception e)
            {
                Debug.LogError(e);
            }
        }
        if (t_chest == null)
        {
            try
            {
                t_chest = animator.GetBoneTransform(HumanBodyBones.Chest).transform;
            }
            catch (System.Exception e)
            {
                Debug.LogError(e);
            }
        }
    }

    void LateUpdate()
    {
        if (isLocalPlayer == false)
        {
            t_head.localRotation = Quaternion.Lerp(t_head.localRotation, head, Time.deltaTime * lerpRate);
            t_neck.localRotation = Quaternion.Lerp(t_neck.localRotation, neck, Time.deltaTime * lerpRate);
            t_spine.localRotation = Quaternion.Lerp(t_spine.localRotation, spine, Time.deltaTime * lerpRate);
            t_chest.localRotation = Quaternion.Lerp(t_chest.localRotation, chest, Time.deltaTime * lerpRate);
        }
    }

    private void FixedUpdate()
    {
        if (isLocalPlayer == true)
        {
            TransmitRotations();
        }
    }

    [Client]
    void TransmitRotations()
    {
        if (isLocalPlayer == true)
        {
            Cmd_RecieveRotations(t_head.localRotation, t_neck.localRotation, t_spine.localRotation, t_chest.localRotation);
        }
    }

    [Command]
    void Cmd_RecieveRotations(Quaternion s_head, Quaternion s_neck, Quaternion s_spine, Quaternion s_chest)
    {
        head = s_head;
        neck = s_neck;
        spine = s_spine;
        chest = s_chest;
    }
}

Here is what I looks like now:

123766-fixed.gif