Rigidbody synchronize rotation

Options
Hi,
In a game im currently making a rigidbody platform is moved by a player. The position gets syncrhonized right over the clients, but the rotation does not get synchronized and I got no idea why.
This is the network code for the platform:
using UnityEngine;
using System.Collections;

public class ShipNetwork : Photon.MonoBehaviour {
	ShipMovement ShipMovementScript;
	private bool test = false;
	
    void Awake()
    {
		ShipMovementScript = GetComponent<ShipMovement>();
        gameObject.name = gameObject.name + photonView.viewID.ID;
		correctPlayerPos = transform.position; //We lerp towards this
	    correctPlayerRot = transform.rotation; //We lerp towards this
		
		ShipMovementScript.SetIsLocalPlayer(photonView.isMine);
    }

    void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {       
		
		if (stream.isWriting) {
            //We own this player: send the others our data
            stream.SendNext(transform.position);
            stream.SendNext(transform.rotation); 
			stream.SendNext (rigidbody.velocity);
        } else { //Network player, receive data
            correctPlayerPos = (Vector3)stream.ReceiveNext();
            correctPlayerRot = (Quaternion)stream.ReceiveNext();
            rigidbody.velocity = (Vector3)stream.ReceiveNext();
	    }
    }
	
	private Vector3 correctPlayerPos = Vector3.zero; //We lerp towards this
    private Quaternion correctPlayerRot = Quaternion.identity; //We lerp towards this

    void Update()
    {      
	    if (!photonView.isMine){
            //Update remote player (smooth this, this looks good, at the cost of some accuracy)
            transform.position = Vector3.Lerp(transform.position, correctPlayerPos, Time.deltaTime * 5);
            transform.rotation = Quaternion.Lerp(transform.rotation, correctPlayerRot, Time.deltaTime * 5);
		}
    }

}

I rotate the platform with rigidbody.AddRelativeTorque(0, -100, 0);
When I change that to transform.rotate the rotation gets synchronized right, so I think it does something to do with adding torques. Someone knows a solution for this?

Comments

  • legend411
    Options
    If you want to continue using torque, you probably want to also send rigidbody.angularVelocity and/or rigidbody.rotation, and then lerp to them in FixedUpdate(), not Update().
  • Azaldur
    Options
    Thanks for your answer, I edited my code but it still doesn't work. The platform just doesn't rotate.
    This is the new code:
    using UnityEngine;
    using System.Collections;
    
    public class ShipNetwork : Photon.MonoBehaviour {
    	ShipMovement ShipMovementScript;
    	
        void Awake()
        {
    		ShipMovementScript = GetComponent<ShipMovement>();
            gameObject.name = gameObject.name + photonView.viewID.ID;
    		correctPlayerPos = transform.position; //We lerp towards this
    	    correctPlayerRot = transform.rotation; //We lerp towards this
    		correctPlayerRigRot = rigidbody.rotation; //We lerp towards this
    		
    		ShipMovementScript.SetIsLocalPlayer(photonView.isMine);
        }
    
        void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
        {       
    		
    		if (stream.isWriting) {
                //We own this player: send the others our data
                stream.SendNext(transform.position);
                stream.SendNext(transform.rotation); 
    			stream.SendNext(rigidbody.velocity);
    			stream.SendNext(rigidbody.angularVelocity);
    			stream.SendNext (rigidbody.rotation);
            } else { //Network player, receive data
                correctPlayerPos = (Vector3)stream.ReceiveNext();
                correctPlayerRot = (Quaternion)stream.ReceiveNext();
                rigidbody.velocity = (Vector3)stream.ReceiveNext();
    			rigidbody.angularVelocity = (Vector3)stream.ReceiveNext();;
    			correctPlayerRigRot = (Quaternion)stream.ReceiveNext();
    			
    	    }
        }
    	
    	private Vector3 correctPlayerPos = Vector3.zero; //We lerp towards this
        private Quaternion correctPlayerRot = Quaternion.identity; //We lerp towards this
    	private Quaternion correctPlayerRigRot = Quaternion.identity;
    
        void FixedUpdate()
        {      
    	    if (!photonView.isMine){
                //Update remote player (smooth this, this looks good, at the cost of some accuracy)
                transform.position = Vector3.Lerp(transform.position, correctPlayerPos, Time.deltaTime * 5);
                transform.rotation = Quaternion.Lerp(transform.rotation, correctPlayerRot, Time.deltaTime * 5);
    			rigidbody.rotation = Quaternion.Lerp (rigidbody.rotation, correctPlayerRigRot, Time.deltaTime * 5);
    		}
        }
    
    }
    

    The only thing I really need physics for is the correct collision with other objects. I read that using transform.translate and transform.rotate on a rigidbody object is not recommended. But i also fear that using physics with a network game can cause big problems and I don't know how to get a correct collison but no physic movement for the platform.
    You got any idea why the rotation does not get synchronized or a solution for collision without the need to use rigidbody.addforce and rigidbody.addtorque?
  • Azaldur
    Options
    Hi,
    I got it to work with the code above, the problem was somewhere else in my code(not network related). But the movement of the rigidbody is not very smooth on the clients, often the rigidbody pause and then makes a "huge" step forward(this mostly happens when i rotate the object, not when i move it in one direction). But I also noticed that behaviour on my players, so maybe its not rigidbody related(they do not have a rigidbody attached).
    Here is my player code:
    using UnityEngine;
    using System.Collections;
    
    public class FirstPersonNetwork : Photon.MonoBehaviour {
    	MouseLook cameraScript;
    	FirstPersonControl controllerScript;
    
        void Awake()
        {
            cameraScript = GetComponent<MouseLook>();
            controllerScript = GetComponent<FirstPersonControl>();
    
             if (photonView.isMine)
            {
                //MINE: local player, simply enable the local scripts
                cameraScript.enabled = true;
                controllerScript.enabled = true;
    			
    			GameObject cam = GameObject.FindWithTag("MainCamera");
    			Transform camTrans = Camera.main.transform;
    			
    			cam.GetComponent<MouseLook>().enabled = true;
    			
    			camTrans.parent = transform;
    			camTrans.localPosition = new Vector3(0, 1.7f, 0);
    			camTrans.localEulerAngles = new Vector3(10, 0, 0);
    			controllerScript.SetIsLocalPlayer(true);
            }
            else
            {           
                cameraScript.enabled = false;
                controllerScript.enabled = false;
                controllerScript.SetIsLocalPlayer(false);
            }
    
            gameObject.name = gameObject.name + photonView.viewID.ID;
        }
    
        void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
        {
            if (stream.isWriting)
            {
                //We own this player: send the others our data
                stream.SendNext(transform.position);
                stream.SendNext(transform.rotation); 
            }
            else
            {
                //Network player, receive data
                correctPlayerPos = (Vector3)stream.ReceiveNext();
                correctPlayerRot = (Quaternion)stream.ReceiveNext();
            }
        }
    
        private Vector3 correctPlayerPos = Vector3.zero; //We lerp towards this
        private Quaternion correctPlayerRot = Quaternion.identity; //We lerp towards this
    
        void Update()
        {
            if (!photonView.isMine)
            {
                //Update remote player (smooth this, this looks good, at the cost of some accuracy)
                transform.position = Vector3.Lerp(transform.position, correctPlayerPos, Time.deltaTime * 5);
                transform.rotation = Quaternion.Lerp(transform.rotation, correctPlayerRot, Time.deltaTime * 5);
            }
        }
    
    }
    

    To synchronize the rigidbody movement(platform) I also tried to use the code from unity network example(I only modified it to run with photon) but then the movement is even worse:
    using UnityEngine;
    using System.Collections;
    
    public class NetworkRigidbody : Photon.MonoBehaviour {
    	private ShipMovement ShipMovementScript;
    	public double m_InterpolationBackTime = 0.1;
    	public double m_ExtrapolationLimit = 0.5;
    	
    	internal struct  State
    	{
    		internal double timestamp;
    		internal Vector3 pos;
    		internal Vector3 velocity;
    		internal Quaternion rot;
    		internal Vector3 angularVelocity;
    	}
    	
    	 void Awake() {
    		ShipMovementScript = GetComponent<ShipMovement>();
            gameObject.name = gameObject.name + photonView.viewID.ID;
    
    		if(photonView.isMine){
    			ShipMovementScript.enabled = true;
    		} else {
    			ShipMovementScript.enabled = false;
    			//Destroy (rigidbody);
    		}
    		
    		ShipMovementScript.SetIsLocalPlayer(photonView.isMine);
        }
    	
    	// We store twenty states with "playback" information
    	State[] m_BufferedState = new State[20];
    	// Keep track of what slots are used
    	int m_TimestampCount;
    	
    	void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    	{
    		// Send data to server
    		if (stream.isWriting)
    		{
    			Vector3 pos = rigidbody.position;
    			Vector3 velocity = rigidbody.velocity;
    			Quaternion rot = rigidbody.rotation;
    			Vector3 angularVelocity = rigidbody.angularVelocity;
    
    			stream.SendNext(pos);
    			stream.SendNext(velocity);
    			stream.SendNext(rot);
    			stream.SendNext(angularVelocity);
    		}else {// Read data from remote client
    			Vector3 pos = Vector3.zero;
    			Vector3 velocity = Vector3.zero;
    			Quaternion rot = Quaternion.identity;
    			Vector3 angularVelocity = Vector3.zero;
    			
    			
    			pos = (Vector3)stream.ReceiveNext();
    			velocity = (Vector3)stream.ReceiveNext();
    			rot = (Quaternion)stream.ReceiveNext();
    			angularVelocity = (Vector3)stream.ReceiveNext();
    			
    //			stream.SendNext(pos);
    //			stream.SendNext(velocity);
    //			stream.SendNext(rot);
    //			stream.SendNext(angularVelocity);
    			
    			// Shift the buffer sideways, deleting state 20
    			for (int i=m_BufferedState.Length-1;i>=1;i--)
    			{
    				m_BufferedState[i] = m_BufferedState[i-1];
    			}
    			
    			// Record current state in slot 0
    			State state;
    			state.timestamp = info.timestamp;
    			state.pos = pos;
    			state.velocity = velocity;
    			state.rot = rot;
    			state.angularVelocity = angularVelocity;
    			m_BufferedState[0] = state;
    			
    			// Update used slot count, however never exceed the buffer size
    			// Slots aren't actually freed so this just makes sure the buffer is
    			// filled up and that uninitalized slots aren't used.
    			m_TimestampCount = Mathf.Min(m_TimestampCount + 1, m_BufferedState.Length);
    
    			// Check if states are in order, if it is inconsistent you could reshuffel or 
    			// drop the out-of-order state. Nothing is done here
    			for (int i=0;i<m_TimestampCount-1;i++)
    			{
    				if (m_BufferedState[i].timestamp < m_BufferedState[i+1].timestamp)
    					Debug.Log("State inconsistent");
    			}	
    		}
    	}
    	
    	// We have a window of interpolationBackTime where we basically play 
    	// By having interpolationBackTime the average ping, you will usually use interpolation.
    	// And only if no more data arrives we will use extra polation
    	void Update () {
    		// This is the target playback time of the rigid body
    		double interpolationTime = PhotonNetwork.time - m_InterpolationBackTime;
    		
    		// Use interpolation if the target playback time is present in the buffer
    		if (m_BufferedState[0].timestamp > interpolationTime)
    		{
    			// Go through buffer and find correct state to play back
    			for (int i=0;i<m_TimestampCount;i++)
    			{
    				if (m_BufferedState[i].timestamp <= interpolationTime || i == m_TimestampCount-1)
    				{
    					// The state one slot newer (<100ms) than the best playback state
    					State rhs = m_BufferedState[Mathf.Max(i-1, 0)];
    					// The best playback state (closest to 100 ms old (default time))
    					State lhs = m_BufferedState[i];
    					
    					// Use the time between the two slots to determine if interpolation is necessary
    					double length = rhs.timestamp - lhs.timestamp;
    					float t = 0.0F;
    					// As the time difference gets closer to 100 ms t gets closer to 1 in 
    					// which case rhs is only used
    					// Example:
    					// Time is 10.000, so sampleTime is 9.900 
    					// lhs.time is 9.910 rhs.time is 9.980 length is 0.070
    					// t is 9.900 - 9.910 / 0.070 = 0.14. So it uses 14% of rhs, 86% of lhs
    					if (length > 0.0001)
    						t = (float)((interpolationTime - lhs.timestamp) / length);
    					
    					// if t=0 => lhs is used directly
    					transform.localPosition = Vector3.Lerp(lhs.pos, rhs.pos, t);
    					transform.localRotation = Quaternion.Slerp(lhs.rot, rhs.rot, t);
    					return;
    				}
    			}
    		} else {// Use extrapolation
    			State latest = m_BufferedState[0];
    			
    			float extrapolationLength = (float)(interpolationTime - latest.timestamp);
    			// Don't extrapolation for more than 500 ms, you would need to do that carefully
    			if (extrapolationLength < m_ExtrapolationLimit)
    			{
    				float axisLength = extrapolationLength * latest.angularVelocity.magnitude * Mathf.Rad2Deg;
    				Quaternion angularRotation = Quaternion.AngleAxis(axisLength, latest.angularVelocity);
    				
    				rigidbody.position = latest.pos + latest.velocity * extrapolationLength;
    				rigidbody.rotation = angularRotation * latest.rot;
    				rigidbody.velocity = latest.velocity;
    				rigidbody.angularVelocity = latest.angularVelocity;
    			}
    		}
    	}
    }
    

    Someone know what the problem could cause? I use Photon Cloud, if that is important.

    Edit: In general, even if I don't use physics and just rotate the platform with transform.rotate the client often gets the rotation only sended after a few seconds(especially if I hit the rotate button for a short time again and again, the clients does not receive any rotation info). This does not happen with transform.translate.

    Second Edit :): I just changed the observe option to unreliable, the movement and especially rotation is a lot smoother, now. Can someone explain what the difference between reliable delta compressed and unreliable is and if it it is okay to change it to unreliable or if I just did something wrong ;)? I also noticed that(with reliable observe) at the start of sending a rotation(when someone press a button) it takes a while to rotate the platform on the clients(those who do not own the platform) and the platform stutters, but when you hold the rotation button longer, the synchronization gets smoother.
  • Tobias
    Options
    PUN 1.17 is now in the Asset Store. I think it contains a few fixes that might be helpful for Rigidbody synchronization.
    Let us know.
  • Azaldur
    Options
    Hi, the problem still exists, but it doesn't matter if the object is a rigidbody or not. If I set Observe option to unreliable, it seems fine. But with Reliable Delta Compressed, every time I rotate the object, it just "jumps" from one positon to the next. Sometimes it holds on for a second, and then instantly rotates to the correct received rotation. I think this problem also occurs with just moving the object in one direction, but its not that noticeable. Do you know why this is happening?

    Another question:
    With GetPing() I get a average ping of 95. But when I do PhotonNetwork.time - info.timestamp, which I thought should be the time since the position/rotation was send, i get values between 0.1 and 0.3 that changes everytime. Why is it always changing and shouldnt it be the same as ping? And why can't I see any data on the Application Graphs(just n.a.) of Photon Cloud?
  • Tobias
    Options
    Azaldur wrote:
    it just "jumps" from one positon to the next.
    Most likely, there is no smoothing done. I don't know if the rotation is applied with forces, so that the physics can go on rotate the rigidbody between network updates. This would smooth out the rotation.
    Mike is back next week (currently on vacation). So I think we can take another look.
    Azaldur wrote:
    With GetPing() I get a average ping of 95. But when I do PhotonNetwork.time - info.timestamp, which I thought should be the time since the position/rotation was send, i get values between 0.1 and 0.3 that changes everytime. Why is it always changing and shouldnt it be the same as ping?
    The ping value is smoothed, so it won't fit each and every roundtrip.
    Especially not so, if the data route is from any other client to yours. In that case, the other client's roundtrip also plays a role in the equation.
    Each client's synced timestamp is a bit off and inexact. We try to get a pretty exact one but that's next to impossible.
    So roundtrips you measure by the info.timestamp are not exactly the ones you get locally.

    Azaldur wrote:
    And why can't I see any data on the Application Graphs(just n.a.) of Photon Cloud?
    Because they are not yet available. We added region support and are now moving the graph generating server. This should be finished by the end of the week.