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.
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...
Another note.
I am using the "Lan Host" and "Lan Client" options while I figure this out.
Also... I have tried this with OnAnimatorI$$anonymous$$() using "animator.SetBoneLocalRotation" but it has the same effect as the code above.
I am not sure how Unet work with [Command]. But the problem here is t_head.localRotation = head;
Over Network, Command can be lost and it might fail to send "c_head" value and on client, they will see as Quaternion.Identity (Or it might be the script called before and after receive network value). So your character head back and forth between default position + network position.
I would try to smooth head rotation to prevent this as HAC$$anonymous$$. Since I am not sure how Unet work.
And show us OnAnimatorIk() code. Also might be something wrong with execute Animation code.
Tried adding in
t_head.localRotation = Quaternion.Lerp(t_head.localRotation, head, Time.time * 0.1f);
with the same effect. I added a gif to help visualize what is happening for me. The animation for the head look is being taken care of by another script (this wasn't written by me). I am just referencing rotation values in this script without modifying anything else. The head look script is completely disabled on the Non - Network Identity players. Only this script is enabled on both Network ID players and Non Network ID players.
Show us. The player gameobject. All script attach to it. You can try delete script 1 by 1. To see what wrong/script affect player.
I have a similar problem when all player on client side share same camera look. I kinda forgot to seperate main player and another player. Your problem might be the same
Answer by wesleywh · Sep 01, 2018 at 07:08 PM
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: