Photon PUN 2 - Bone Rotations Not Syncing
This should work from what I have seen but isn't. I want to sync the rotations of target bones across the network. Here is how I am doing it:
private Transform local_head, local_neck, local_spine, local_chest = null;
private Quaternion server_head, server_neck, server_spine, server_chest = Quaternion.identity;
...
...
public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) //this function called by Photon View component
{
if (_syncBones == true)
{
if (stream.IsWriting) //Authoritative player sending data to server
{
//Bone Rotations
stream.SendNext(local_head.localRotation);
stream.SendNext(local_neck.localRotation);
stream.SendNext(local_spine.localRotation);
stream.SendNext(local_chest.localRotation);
}
else //Network player copies receiving data from server
{
//Bone Rotations
server_head = (Quaternion)stream.ReceiveNext();
server_neck = (Quaternion)stream.ReceiveNext();
server_spine = (Quaternion)stream.ReceiveNext();
server_chest = (Quaternion)stream.ReceiveNext();
lag = Mathf.Abs((float)(PhotonNetwork.Time - info.timestamp));
}
}
}
void LateUpdate()
{
if (GetComponent<PhotonView>().IsMine == false)
{
SyncBoneRotation();
}
}
void SyncBoneRotation()
{
local_head.localRotation = Quaternion.Lerp(local_head.localRotation, server_head, Time.deltaTime * _boneLerpRate);
local_neck.localRotation = Quaternion.Lerp(local_neck.localRotation, server_neck, Time.deltaTime * _boneLerpRate);
local_spine.localRotation = Quaternion.Lerp(local_spine.localRotation, server_spine, Time.deltaTime * _boneLerpRate);
local_chest.localRotation = Quaternion.Lerp(local_chest.localRotation, server_chest, Time.deltaTime * _boneLerpRate);
}
I think what is happening is that the line stream.ReceiveNext()
is setting the rotation to Quaternion.identity
every other frame. Honestly I am just not sure if I am missing something obvious here....
Note: All of the local_* are getting set like the following:
local_head = animator.GetBoneTransform(HumanBodyBones.Head).transform;
Did you set bone next frame after receive it?
In Editor, yes the editor. Did you see public property server_head/ server_neck
change when receive new data or not?
I do see it change for one frame right when it receives it and reset back to the Quaternion.identity value every other frame. I viewed the change via a debug.log value since these are not public/Serialized variables.
The linked documentation is for an I$$anonymous$$ pass. This isn't ik, at least in terms of using OnAnimatorI$$anonymous$$() function.
ok now you have to check is it really sending quaternion.Identity during stream.IsWriting
.
If it is, then 100% due to several animator bugs 2017.3+ related which I know they still can't fix it at 2018.3. All you have to do is not rotate when server_head = quaternion.Identity
If not, it can happen if you lost package or data (which not suppose to happen because PUN I know of not always check if full package have arrive in full and intact)
$$anonymous$$y //HAC$$anonymous$$ for animator bugs for my project is caching "local_head.localRotation" stuff during Update() and LateUpdate() so it can't be reset during whatever happen after LateUpdate() and FixedUpdate(). Which also the time Photon serialized got called.
FYI you are right in that it is setting it back to quaternion.identity. However, this is failing... not sure why...
tmp_headRot = (Quaternion)stream.ReceiveNext();
...
...
Debug.Log(tmp_headRot.GetType());
Debug.Log(tmp_headRot+ " == "+Quaternion.identity);
if (tmp_headRot != Quaternion.identity && tmp_headRot != this.correctBoneHeadRot)
{
Debug.Log("SETTING WITH VALUE: " + tmp_headRot);
headTime = 0.0f;
curHead = local_head.localRotation;
this.correctBoneHeadRot = tmp_headRot;
}
However, I see in the log SETTING WITH VALUE: (0.0f, 0.0f, 0.0f, 1.0f)
.... uuuuhhh what?
Answer by wesleywh · Jan 03, 2019 at 01:44 AM
I did the same thing I did for UNet: https://answers.unity.com/questions/1548530/unet-sync-bone-rotations-jittering.html
I didn't use Photons Method to send my updates across the network because it's insanely buggy. I did it manually:
[SerializeField] private bool _syncBones = true;
[SerializeField] private int _syncBonesRate = 5;
[SerializeField] private float _boneLerpRate = 90.0f;
private int currentBoneRate = 0;
..
..
void FixedUpdate()
{
if (_syncBones == true && GetComponent<PhotonView>().IsMine == true)
{
if (currentBoneRate == _syncBonesRate)
{
currentBoneRate = 0;
photonView.RPC("SyncRotations", RpcTarget.Others, local_head.localRotation, local_neck.localRotation, local_spine.localRotation, local_chest.localRotation);
}
else
{
currentBoneRate += 1;
}
}
}
void SyncBoneRotation()
{
if (_syncBones == true && GetComponent<PhotonView>().IsMine == false)
{
local_head.localRotation = Quaternion.Lerp(local_head.localRotation, correctBoneHeadRot, Time.deltaTime * _boneLerpRate);
local_neck.localRotation = Quaternion.Lerp(local_neck.localRotation, correctBoneNeckRot, Time.deltaTime * _boneLerpRate);
local_spine.localRotation = Quaternion.Lerp(local_spine.localRotation, correctBoneSpineRot, Time.deltaTime * _boneLerpRate);
local_chest.localRotation = Quaternion.Lerp(local_chest.localRotation, correctBoneChestRot, Time.deltaTime * _boneLerpRate);
}
}
Answer by Ooggaboogaloid · Feb 24, 2019 at 06:41 PM
mind posting your full script, here is what I have done
void OnAnimatorIK() { if(PV.IsMine) { anim.SetLookAtWeight(0.7f, 0.5f); anim.SetLookAtPosition(targetFocus.position); } }
void Update()
{
if(PV.IsMine == true && _syncBones == true)
{
if (currentBoneRate == _syncBonesRate)
{
currentBoneRate = 0;
PV.RPC("RPC_UpdateBoneRotations", RpcTarget.AllBuffered, local_head.localRotation, local_neck.localRotation, local_spine.localRotation, local_chest.localRotation);
}
else
{
currentBoneRate += 1;
}
}
if (_syncBones == true && PV.IsMine == false)
{
Debug.Log("Attempting to sync bones for other clients");
local_head.localRotation = Quaternion.Lerp(local_head.localRotation, correctBoneHeadRot, Time.deltaTime * _boneLerpRate);
local_neck.localRotation = Quaternion.Lerp(local_neck.localRotation, correctBoneNeckRot, Time.deltaTime * _boneLerpRate);
local_spine.localRotation = Quaternion.Lerp(local_spine.localRotation, correctBoneSpineRot, Time.deltaTime * _boneLerpRate);
local_chest.localRotation = Quaternion.Lerp(local_chest.localRotation, correctBoneChestRot, Time.deltaTime * _boneLerpRate);
}
}
[PunRPC]
void RPC_UpdateBoneRotations (Quaternion h, Quaternion n, Quaternion s, Quaternion c)
{
Debug.Log("SyncingBones");
correctBoneHeadRot = h;
correctBoneNeckRot = n;
correctBoneSpineRot = s;
correctBoneChestRot = c;
}
and it aint syncing
Take it out of the Update() function it wont work there. I used to know why but I can't remember. That's why I have this in a fixed update function with the lerp functionality to smooth out the motion. Without the lerp it will be very jerky.
Hey man, I think it's pretty late to ask, but did u make it work?
Yes I did get this working. I was using the onvector package for this and they do some custom logic that required me to do this in the fixed update. I have since found a much better way to do this. Just sync all player inputs and the camera position. Then the bone rotation logic will be the same across all clients. You don't need to explicitly sync the individual bones. Saves on bandwidth too
What do you mean by syncing camera pos? Add a photon view and photon transform on the camera?
Yes I mean the physical position and rotation of the camera between clients needs to be exact. So you will need to make a dummy camera(just a empty gameobject not a real camera) for the none owned players. Then the owned player will send their real cameras position and rotation across the network to all the none owned versions dummy camera. Just make sure the none owned players are all looking at their own dummy cameras. That way its like everyone has their own camera but in reality are just following what the real player is sending.
For the above reason you CANNOT use the photon provided component to sync this. Its too stupid to figure it out. You have to have another custom component that is watching the camera (or receiving) position and rotation and sending it across the network. Then, across the network, that same component will set the dummy cameras position and rotation. No need to interpolate, it can be choppy because no one will see it.
Its complicated but this is a way that will allow you to have perfectly smooth syncing, always.
Answer by unity_x6Zl-zvtB8Mp_g · Aug 31, 2021 at 11:14 PM
Thank you for your help man! I used this method because it works better for me. This is my script if anyone is struggling with it.
private Quaternion server_head;
[SerializeField] private Transform Head;
private void LateUpdate()
{
if (photonView.IsMine == false)
{
Head.localRotation = server_head;
}
}
private void FixedUpdate()
{
if (photonView.IsMine == true)
{
Move();
switch (stance)
{
case CharacterStance.Standing:
if (inputs.Crouch.PressedDown()) { RequestStanceChange(CharacterStance.Crouching); }
break;
case CharacterStance.Crouching:
if (inputs.Crouch.PressedDown()) { RequestStanceChange(CharacterStance.Standing); }
break;
}
photonView.RPC("RPC_UpdateBoneRotations", RpcTarget.AllBuffered, Head.localRotation);
}
}
[PunRPC]
private void RPC_UpdateBoneRotations(Quaternion h)
{
server_head = h;
}