A little bit of help with networked physics

Hello all,

I’m somewhat new to Unity, and what I’m trying to do right now is instead of make a whole bunch of random little projects to try stuff out, I’m making a very feature-packed game with really simple mechanics.

The game is basically a sumo-wrestling type game, except each character controls a box. By pressing keys, they apply a force and rotation to their box, and they try to push the other box off of a platform.

So what I’m trying to do now is make the game work over the internet, but I’m running into trouble when the boxes start colliding.

What I did initially is have both the client and the server directly control their own box, and send the position and rotation to the other. The problem is that the boxes often clip into each other, and the collisions get screwed up. I also tried using an authoritative set up, but it was too laggy for the client to actually be competitive. What I think I need to do is figure out some way that I can do prediction with the client, and I’m struggling a little bit as to how I’m going to do that.

One of the ideas that I came up with is to have a system where both the client and the server directly control their own boxes, as well as sending RPCs with each player’s inputs to the other player. Then, every once in a while, e.g. every 0.1 seconds, the server and the client will compare their numbers for each player’s position, and interpolate between them, and continue simulating. So would this work? How would I do it?

If it helps at all, here’s the code that I have for the authoritative set up (a lot of it is from M2H’s tutorial)

#pragma strict
#pragma implicit
#pragma downcast

public var owner : NetworkPlayer;

//Last input value, we're saving this to save network messages/bandwidth.
private var lastClientHInput : float=0;
private var lastClientVInput : float=0;
private var lastClientJumpInput : float=0;

//The input values the server will execute on this object
private var serverCurrentHInput : float = 0;
private var serverCurrentVInput : float = 0;
private var serverCurrentJumpInput : float = 0;

private var clientCurrentHInput : float = 0;
private var clientCurrentVInput : float = 0;

var blueColor : Material;
var redColor : Material;

var moveForce = 5;
var moveTorque = 5;
var jumpForce = 20;
var jumpInterval = 0.5;
var useSlider = true;
private var goVector = Vector2(0,0);
private var jumpTime = 0.0;
private var jumps = 1;
/*private var keysPressed = 0;*/
private var forceMod = 0.0;

function Awake(){
	// We are probably not the owner of this object: disable this script.
	// RPC's and OnSerializeNetworkView will STILL get trough!
	// The server ALWAYS run this script though
	if(Network.isClient){
		enabled=false;	 // disable this script (this enables Update());	
	}	
}

@RPC
function SetPlayer(player : NetworkPlayer){
	owner = player;
	if(player==Network.player){
		//Hey thats us! We can control this player: enable this script (this enables Update());
		enabled=true;
	}
}

function Update () {
	if (useSlider == true) {
		moveForce = SliderGUI.speedSlider;
		moveTorque = SliderGUI.speedSlider;
	}
	/*if (Input.GetKeyDown(KeyCode.UpArrow) || Input.GetKeyDown(KeyCode.DownArrow) || Input.GetKeyDown(KeyCode.LeftArrow) || Input.GetKeyDown(KeyCode.RightArrow)) {
		keysPressed ++;
	}
	if (Input.GetKeyUp(KeyCode.UpArrow) || Input.GetKeyUp(KeyCode.DownArrow) || Input.GetKeyUp(KeyCode.LeftArrow) || Input.GetKeyUp(KeyCode.RightArrow)) {
		keysPressed --;
	}		*/	
	/*
	if ((Input.GetKey(KeyCode.UpArrow)  Input.GetKey(KeyCode.LeftArrow)) || (Input.GetKey(KeyCode.UpArrow)  Input.GetKey(KeyCode.RightArrow)) || (Input.GetKey(KeyCode.DownArrow)  Input.GetKey(KeyCode.LeftArrow)) || (Input.GetKey(KeyCode.DownArrow)  Input.GetKey(KeyCode.RightArrow))) {forceMod = Mathf.Sqrt(2)/2;} else {forceMod = 1;}
	if (Network.peerType == NetworkPeerType.Server) {
	if (Input.GetKey(KeyCode.UpArrow)) { constantForce.force.z = moveForce * forceMod ; constantForce.torque.x = moveTorque * forceMod;}
	if (Input.GetKey(KeyCode.DownArrow)) { constantForce.force.z = -moveForce * forceMod; constantForce.torque.x = -moveTorque * forceMod;}
	if (!Input.GetKey(KeyCode.UpArrow)  !Input.GetKey(KeyCode.DownArrow)) { constantForce.force.z = 0; constantForce.torque.x = 0;}
	if (Input.GetKey(KeyCode.LeftArrow)) { constantForce.force.x = -moveForce * forceMod; constantForce.torque.z = moveTorque * forceMod;}
	if (Input.GetKey(KeyCode.RightArrow)) { constantForce.force.x = moveForce * forceMod; constantForce.torque.z = -moveTorque * forceMod;}
	if (!Input.GetKey(KeyCode.LeftArrow)  !Input.GetKey(KeyCode.RightArrow)) { constantForce.force.x = 0; constantForce.torque.z = 0;}
	*/
	//Client code
	if(owner!=null  Network.player==owner){
		renderer.material = blueColor;
		//Only the client that owns this object executes this code
		var HInput : float = Input.GetAxis("Horizontal");
		var VInput : float = Input.GetAxis("Vertical");
		var JumpInput : float = Input.GetAxis("Jump");
		
		//Is our input different? Do we need to update the server?
		if(lastClientHInput!=HInput || lastClientVInput!=VInput || lastClientJumpInput!=JumpInput){
			lastClientHInput = HInput;
			lastClientVInput = VInput;			
			lastClientJumpInput = JumpInput;
			
			if(Network.isServer){
				//Too bad a server can't send an rpc to itself using "RPCMode.Server"!...bugged :[
				SendMovementInput(HInput, VInput, JumpInput);
			}else if(Network.isClient){
				SendMovementInput(HInput, VInput, JumpInput); //Use this (and line 64) for simple "prediction"
				networkView.RPC("SendMovementInput", RPCMode.Server, HInput, VInput, JumpInput);
			}
			
		}
	}
	if (Network.isClient) {
		renderer.material = redColor;
	}
	
	//Server movement code
	if(Network.isServer || Network.player==owner){//Also enable this on the client itself: "|| Network.player==owner){|"
		//Actually move the player using his/her input
		var moveDirection : Vector3 = new /*Vector3.Lerp( */Vector3(serverCurrentHInput, 0, serverCurrentVInput)/*, Vector3(clientCurrentHInput, 0, clientCurrentVInput), Time.time)*/;
		constantForce.force.z = moveDirection.z * moveForce;
		constantForce.force.x = moveDirection.x * moveForce;
		constantForce.torque.z = moveDirection.x * -moveTorque;
		constantForce.torque.x = moveDirection.z * moveTorque;
		if (serverCurrentJumpInput > 0) {
			if (jumps > 0  jumpTime < Time.time) {
				rigidbody.velocity = (Vector3(rigidbody.velocity.x,jumpForce,rigidbody.velocity.z));
				audio.Play();
				jumps --;
				jumpTime = Time.time + jumpInterval;
			}
		}	
	}
}
function OnTriggerEnter (other : Collider) {
   jumps = 2;
   jumpTime = 0.0;
}

@RPC
function SendMovementInput(HInput : float, VInput : float, JumpInput : float){	
	//Called on the server
	serverCurrentHInput = HInput;
	serverCurrentVInput = VInput;
	serverCurrentJumpInput = JumpInput;
}

function GuessMovementInput(HInput : float, VInput : float){	
	//Called on the server
	clientCurrentHInput = HInput;
	clientCurrentVInput = VInput;
}

Did you try using the interpolation scripts from the networking example in the unity3d.com resources? They are pretty fantastic. Download the project and snag “NetworkRigidbody” and “NetworkInterpolatedTransform”.

Yeah, I’ve been using NetworkRigidbody for a while and it’s been working great…

I’m a little confused as to how NetworkInterpolatedTransform works, so basically it stores a bunch of states of the transform of the other player, and then when the latest state is newer than whatever came before it, it interpolates the last two… or something like that.

That will probably come in handy… I’ll see what I can do. So in that script, I suppose I can put some stuff where it’s extrapolating.

I might run into the problem that I had before, because this is directly modifying the object’s transform and it’s quite possible that the rigidbody won’t work properly, but since it only updates every 1/10th of a second it might work perfectly. Let me try it and get back.

So wait, in your game, both player’s objects that they control are rigidbodies, and you tried using a network view observing NetworkRigidbody and letting each player calculate their own movement, and you had collision issues?

That’s strange… that’s exactly how my game is set up and I have no issues over the 'net with my friend in England with 200ms.

I’ve had problems getting shared physics simulation to work at all.

it might be worth setting up and authoritative server situation, where the client simply submits his controls to the server and the server updates the client with the result. that way all physics sim is done on one computer, and there’s no possibility for screw-ups.

Are you making sure you disable NetworkRigidbody on the client in control of the rigidbody? Not sure if that’ll help, but its worth a shot, and its how they do it in the cars demo in the networking example package.

In fact, you should look at the cars example in general, because this is exactly what it does (spawns a car for each player with a control script and a rigidbody component, enables and syncs NetworkRigidbody on the remote instances for other players).

As for other rigidbodies (things like crates or barrels), if it makes sense in your game, the best solution is to let each client calculate their own physics locally (eg: does it really matter if the way this ragdoll falls looks the same for every player?). But if it is absolutely necessary that they be the same for everyone (like if you have enemy AI objects that are rigidbody driven), just do all their movement calculations on the server and sync NetworkRigidbody to the clients.