Authoritative server help

I managed to get an authoratative server going fr om a tutorial I found here: http://www.playblack.net/zee-code/unity-3d-authoritative-server-networking-pt1

I ran into a road block though and I can’t quite wrap my head around it. The client’s that connect request a “spawn” from the server, the server then instantiates a “player” for the client and assigns it to the player. The tutorial then goes onto the movement logic for the client side, and each frame (from what I can tell) an RPC is called to the server to “updateClientMotion”.

So the client does the movement logic:

Client Side Movement Handling:

void Update ()
	{
	
		if (Network.isServer) {
			return; //get lost, this is the client side!
		}

		float motionH, motionY;
		
		if ((owner != null)  (Network.player == owner)) {
			
			
			grounded = IsGrounded ();
			
			if (grounded) {
				
				// Calculate how fast we should be moving
				jumping = false;
				targetVelocity = new Vector3 (Input.GetAxis ("Horizontal"), 0, Input.GetAxis ("Vertical"));
			
				motionH = Input.GetAxis ("Horizontal");
				motionY = Input.GetAxis ("Vertical");
				networkView.RPC ("updateClientMotion",  RPCMode.Server, motionH, motionY);
				
					
				targetVelocity = transform.TransformDirection (targetVelocity);

				targetVelocity *= speed;
		
		                Vector3 velocity = rigidbody.velocity;
		
				Vector3 velocityChange = (targetVelocity - velocity) + groundVelocity;
		
				velocityChange.x = Mathf.Clamp (velocityChange.x, -maxVelocityChange, maxVelocityChange);
		
				velocityChange.z = Mathf.Clamp (velocityChange.z, -maxVelocityChange, maxVelocityChange);
		
				velocityChange.y = 0;
		
				rigidbody.AddForce (velocityChange, ForceMode.VelocityChange);
					
							
					// Jump

					if (canJump  Input.GetButton ("Jump")) {
						//no idea how to handle this, do I call another RPC?
						grounded = false;
						canJump = false;
						
					}
				
			}else{

				targetVelocity = new Vector3 (Input.GetAxis ("Horizontal"), 0, Input.GetAxis ("Vertical"));
				
				motionH = Input.GetAxis ("Horizontal");
				motionY = Input.GetAxis ("Vertical");
				
				networkView.RPC ("updateClientMotion",  RPCMode.Server, motionH, motionY);
				
				targetVelocity = transform.TransformDirection (targetVelocity) * inAirControl;

				rigidbody.AddForce (targetVelocity, ForceMode.VelocityChange);
			}	
			
			
			if (Input.GetButtonUp ("Jump")) {
				
				canJump = true;	
			}
			
			
			rigidbody.AddForce (new Vector3 (0, -gravity * 1, 0));
			
			networkView.RPC ("updateClientMotion",  RPCMode.Server, motionH, motionY, transform.eulerAngles, 0.0f);
			
		}
		
		

	}

First question is, am I supposed to be calling the RPC function each frame?

On the server, this is my “updateClientMotion” RPC function:

RPC that the client is supposed to call when the player moves:

[RPC]
	void updateClientMotion (float client_motionH, float client_motionY)
	{
		motionH = client_motionH;
		motionY = client_motionY;
	}

Where I get lost now is, how am I supposed to do the physics correctly on the server, what I have now it makes my “players” go haywire. I know it’s supposed to handle the physics the same way as the client side, but it doesn’t seem to work… then I get lost on how to calculate a “jump”. Here is the server way of handling movement:

The server side code for handling player movement:

public void Update ()
	{
		if (Network.isClient) {
			return; //Get lost, this is the server-side!
		}
		
		
		
		transform.eulerAngles = rotation;
		
		grounded = IsGrounded ();
		
		if (grounded) {
		
			//Debug.Log("Processing clients movement commands on server");
			targetVelocity = new Vector3 (motionH, 0, motionY);
				
			targetVelocity = transform.TransformDirection (targetVelocity);
	
			targetVelocity *= speed;
	
	        
			
	
			// Apply a force that attempts to reach our target velocity
	
			Vector3 velocity = rigidbody.velocity;
	
			Vector3 velocityChange = (targetVelocity - velocity) + groundVelocity;
	
			velocityChange.x = Mathf.Clamp (velocityChange.x, -maxVelocityChange, maxVelocityChange);
	
			velocityChange.z = Mathf.Clamp (velocityChange.z, -maxVelocityChange, maxVelocityChange);
	
			velocityChange.y = 0;
	
			rigidbody.AddForce (velocityChange, ForceMode.VelocityChange);
			
			if (jumpVerticalSpeed > 0.0f) {
				rigidbody.velocity = new Vector3 (velocity.x, CalculateJumpVerticalSpeed (), velocity.z);
				grounded = false;
			}
		}else{
			// Add in air 

			//targetVelocity = new Vector3 (Input.GetAxis ("Horizontal"), 0, Input.GetAxis ("Vertical"));
			targetVelocity = new Vector3 (motionH, 0, motionY);
			targetVelocity = transform.TransformDirection (targetVelocity) * inAirControl;

        

			rigidbody.AddForce (targetVelocity, ForceMode.VelocityChange);
		}	
		
		
		rigidbody.AddForce (new Vector3 (0, -gravity * 1, 0));
		
        
	}

And finally, here is my “Predictor” which is not entirely the same as the tutorials because that one seemed to jitter around alot and it wasn’t even predicting my movement correctly, so I found a script called “NetworkRigidBody” which I had tested much earlier and it seems to be pretty much the same as the one shown in the tutorial, but I sort of combined the two:

The “Prediction” script for movement (interpolation/extrapolation):

using UnityEngine;
using System.Collections;



internal struct  State
{
	internal double timestamp;
	internal Vector3 pos;
	internal Vector3 velocity;
	internal Quaternion rot;
	internal Vector3 angularVelocity;
}

public class Predictor : MonoBehaviour {
	
	public Transform observedTransform;
    public C_PlayerManager receiver; //Guy who is receiving data
    public float pingMargin = 0.5f; //ping top-margin
	
	public double m_InterpolationBackTime = 0.1;
	public double m_ExtrapolationLimit = 0.5;
	float extrapolationLength;
	
	 // We store twenty states with "playback" information
	 State[] m_BufferedState = new State[20];
	 // Keep track of what slots are used
	 int m_TimestampCount;
 
    private float clientPing;
	
	// Use this for initialization
	void Start () {
	
	}
	
	// Update is called once per frame
	void Update () {
	
		if ((Network.player == receiver.getOwner()) || Network.isServer) {
        	return; //This is only for remote peers, get off
    	}
		
		double interpolationTime = Network.time - m_InterpolationBackTime;
		
    	//client side has !!only the server connected!!
    	//clientPing = (Network.GetAveragePing(Network.connections[0]) / 100) + pingMargin;
    	
		
	    //Try interpolation if possible. 
	    //If the latest serverStateBuffer timestamp is smaller than the latency
	    //we're not slow enough to really lag out and just extrapolate.
	    if (m_BufferedState[0].timestamp > interpolationTime){
	        for (int i=0;i<m_TimestampCount;i++){
	            if (m_BufferedState[i].timestamp <= interpolationTime || i == m_TimestampCount-1){
		            // Find the state which matches the interp. time or use last state
		            State rhs = m_BufferedState[Mathf.Max(i-1, 0)];
					State lhs = m_BufferedState[i];
				
					double length = rhs.timestamp - lhs.timestamp;
					float t = 0.0F;
					
					if (length > 0.0001)
						t = (float)((interpolationTime - lhs.timestamp) / length);
		                
		                
		                transform.position = Vector3.Lerp(lhs.pos, rhs.pos, t);
		
		                transform.rotation = Quaternion.Slerp(lhs.rot, rhs.rot, t);
		                //Okay found our way through to lerp the positions, lets return here
		                return;
	            }
	        }
	    }
	    //so it appears there is no lag through latency.
	    else {
	        State latest = m_BufferedState[0];
	        extrapolationLength = (float)(interpolationTime - latest.timestamp);
	        if (extrapolationLength < m_ExtrapolationLimit)
		   	{
		    	float axisLength = extrapolationLength * latest.angularVelocity.magnitude * Mathf.Rad2Deg;
		    	Quaternion angularRotation = Quaternion.AngleAxis(axisLength, latest.angularVelocity);
		    
		    	observedTransform.rigidbody.position = latest.pos + latest.velocity * extrapolationLength;
				observedTransform.rigidbody.rotation = angularRotation * latest.rot;
		    	observedTransform.rigidbody.velocity = latest.velocity;
		    	observedTransform.rigidbody.angularVelocity = latest.angularVelocity;
		   }
	    }
	}
	
	public void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info) {
		if (stream.isWriting)
	  	{
			
	   		Vector3 pos = observedTransform.rigidbody.position;
	   		Quaternion rot = observedTransform.rigidbody.rotation;
	   		Vector3 velocity = observedTransform.rigidbody.velocity;
	   		Vector3 angularVelocity = observedTransform.rigidbody.angularVelocity;
	   		stream.Serialize(ref pos);
	   		stream.Serialize(ref velocity);
	   		stream.Serialize(ref rot);
	   		stream.Serialize(ref angularVelocity);
	  	}
	  	// Read data from remote client
	  	else
	  	{
	   		Vector3 pos = Vector3.zero;
	   		Vector3 velocity = Vector3.zero;
	   		Quaternion rot = Quaternion.identity;
			Vector3 angularVelocity = Vector3.zero;
	   		stream.Serialize(ref pos);
	   		stream.Serialize(ref velocity);
	   		stream.Serialize(ref rot);
	   		stream.Serialize(ref 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);
		}
	}

}

As of now, my server side “prediction” code is commented out. The “isWriting” portion on the “OnSerializeNetworkView” function never seems to get called.

I can host a game, players can join, but their positions are not being sent to other players. I’m hoping someone can help me that has any idea of what I’m supposed to do and how to do it correctly.

            var motionH : float = Input.GetAxis("Horizontal");
            var motionV : float = Input.GetAxis("Vertical");
            if ((motionH != lastMotionH) || (motionV != lastMotionV)) {
                networkView.RPC("updateClientMotion", 
                                RPCMode.Server, 
                                Input.GetAxis("Horizontal"), 
                                Input.GetAxis("Vertical"));
                lastMotionH = motionH;
                lastMotionV = motionV;

The rpc is only sent if the inputs change.
To which script did you set the networkview’s observed property ?

I have my networkviews observed property set to the “Predictor” script.

As for sending only when input changes, what about my camera rotation, would that be sent in a different RPC call? (same for the “jump” action?)

You’ll also notice in my client-side movement script, i have an “else” case:

if (grounded){
    ...
}else{
    targetVelocity = new Vector3 (Input.GetAxis ("Horizontal"), 0, Input.GetAxis ("Vertical"));
    targetVelocity = transform.TransformDirection (targetVelocity) * inAirControl;
    rigidbody.AddForce (targetVelocity, ForceMode.VelocityChange);
}

how is the server supposed to distinguish which physics calculation to apply when I send an RPC call from the “else” block?

I don’t see why you would need to send your camera rotation to the server, this should only be handled client side.
The else block doensn’t really matter, it’s the rpc that matters.
The server distinguishes the incoming rpc calls and matches those with the networkviews.

How are the other players supposed to know which way I’m looking though?

Sorry, I’m not sure how to apply that to my problem.


Here’s an example of what I have now then, for my client-side movement control:

float maxVelocityChange = 10.0f;
private Vector3 groundVelocity ;
float gravity = 10.0f;
float speed = 8.0f;

bool IsGrounded ()
	{
		return Physics.Raycast (transform.position, -Vector3.up, distToGround + 0.1f);
	}

grounded = IsGrounded ();
			
			if (grounded) {
				
				// Calculate how fast we should be moving
				
				targetVelocity = new Vector3 (Input.GetAxis ("Horizontal"), 0, Input.GetAxis ("Vertical"));
			
				motionH = Input.GetAxis ("Horizontal");
				motionY = Input.GetAxis ("Vertical");
				networkView.RPC ("updateClientMotion",  RPCMode.Server, motionH, motionY, transform.eulerAngles);
				
					
				targetVelocity = transform.TransformDirection (targetVelocity);

				targetVelocity *= speed;
		
		        
		
				// Apply a force that attempts to reach our target velocity
		                Vector3 velocity = rigidbody.velocity;
		                Vector3 velocityChange = (targetVelocity - velocity) + groundVelocity;
		                velocityChange.x = Mathf.Clamp (velocityChange.x, -maxVelocityChange, maxVelocityChange);
		                velocityChange.z = Mathf.Clamp (velocityChange.z, -maxVelocityChange, maxVelocityChange);
		                velocityChange.y = 0;
		                rigidbody.AddForce (velocityChange, ForceMode.VelocityChange);
				
				
			}else{
				// Add in air 
	                        targetVelocity = new Vector3 (Input.GetAxis ("Horizontal"), 0, Input.GetAxis ("Vertical"));
				
				motionH = Input.GetAxis ("Horizontal");
				motionY = Input.GetAxis ("Vertical");
				networkView.RPC ("updateClientMotion",  RPCMode.Server, motionH, motionY, transform.eulerAngles);
				
				targetVelocity = transform.TransformDirection (targetVelocity) * inAirControl;
	
	        
	
				rigidbody.AddForce (targetVelocity, ForceMode.VelocityChange);
			}	
			
			rigidbody.AddForce (new Vector3 (0, -gravity * 1, 0));

On the server side of handling movement:

bool IsGrounded ()
	{
		return Physics.Raycast (transform.position, -Vector3.up, distToGround + 0.1f);
	}
    
	public void Update ()
	{
		if (Network.isClient) {
			return; //Get lost, this is the server-side!
		}
		
		
		grounded = IsGrounded ();
		transform.eulerAngles = rotation;

		if (grounded) {
		
			//Debug.Log("Processing clients movement commands on server");
			targetVelocity = new Vector3 (motionH, 0, motionY);
				
			targetVelocity = transform.TransformDirection (targetVelocity);
	
			targetVelocity *= speed;
	
	        
			
	
			// Apply a force that attempts to reach our target velocity
	
			Vector3 velocity = rigidbody.velocity;
	
			Vector3 velocityChange = (targetVelocity - velocity) + groundVelocity;
	
			velocityChange.x = Mathf.Clamp (velocityChange.x, -maxVelocityChange, maxVelocityChange);
	
			velocityChange.z = Mathf.Clamp (velocityChange.z, -maxVelocityChange, maxVelocityChange);
	
			velocityChange.y = 0;
	
			rigidbody.AddForce (velocityChange, ForceMode.VelocityChange);
			
		}else{
			// Add in air 

			//targetVelocity = new Vector3 (Input.GetAxis ("Horizontal"), 0, Input.GetAxis ("Vertical"));
			targetVelocity = new Vector3 (motionH, 0, motionY);
			targetVelocity = transform.TransformDirection (targetVelocity) * inAirControl;

        

			rigidbody.AddForce (targetVelocity, ForceMode.VelocityChange);
		}	
		
		
		rigidbody.AddForce (new Vector3 (0, -gravity * 1, 0));
		
        
	}

[RPC]
	void updateClientMotion (float client_motionH, float client_motionY, Vector3 client_rotation)
	{
		motionH = client_motionH;
		motionY = client_motionY;
		rotation = client_rotation;
	}

Now you may be asking, what is the result?

The result is, when I connect initially with my first client and move around, it’s all jittery and the player keeps moving in the direction even when I let go of the keys. I had to send over the “capsules” transform.eularAngles because otherwise they end up rolling around on their side on remote-clients view. Also, if I connect as another player and I move around and then stop moving, on the opposite client, that player also keeps moving in the direction and velocity.

So, what’s the proper way of doing this? Like, how does the server know when the player stopped moving?

Here I have attached the dev files for this project i’m working on… there isnt much too it besides a terrain and instantiating a character prefab. The “player” prefab with all the scripts attached to it is located in: “Assets/Prefabs/Player/player”

If anyone can take a look if they so please, maybe they can see what the problem is much more clearer and hopefully will get a little more assistance.

Download the zip at: http://www.4shared.com/zip/A1GNcC2t/FPS.html

So apparently, motionH and motionY variables were not being populated on the server side… apparently you can’t go:

motionH = Input.GetAxis("Horizontal");
motionY = Input.GetAxis("Vertical") ;

networkView.RPC ("updateClientMotion",  RPCMode.Server, motionH ,motionY , transform.eulerAngles, transform.position);

I had to send it directly:

networkView.RPC ("updateClientMotion",  RPCMode.Server, Input.GetAxis ("Horizontal"), Input.GetAxis ("Vertical"), transform.eulerAngles, transform.position);

So now it kind of works… except the capsule on the remote server just tips over when force is applied -.-

Unless I’m supposed to be sending the rotation from the client to the server, then setting the transform rotation to it… but that doesn’t seem right…

anyone?

Hrm that doesn’t seem right… what does the updatClientMotion declaration look like? I send Input floats in RPC’s all the time without sending them directly; it might be because you’re not statically typing those floats.

If your capsules are rigidbodies, you have to set rigidbody.freezeRotation if you don’t want them to tip over.

Sorry, this is an old problem I have scrapped and realised I’ve been doing it wrong. Thanks for your help regardless :slight_smile: