Fast Data Sync - Technical Specification

First you define some data. This will be the data that will define how one player differs to another.

class PlayerDetails {
	var TotalHP : float = 100.0;
	var HP : float = 100.0;
	var EXP : int = 0;
	var jumpSpeed:float = 8.0;
	var runSpeed:float = 5.0;
	var attackSpeed: float = 1.0;
	var currentSpell: int = 0;
	var selectedTarget : NetworkViewID = NetworkViewID.unassigned ;
}

Next create a script that we can add to a Network Player Prefab. I called mine NetworkSyncPlayer.js

public var localPlayer : PlayerDetails;

function Start()
{
	//Create the Player Details
	localPlayer = new PlayerDetails();
}

function OnSerializeNetworkView(stream : BitStream, info : NetworkMessageInfo)
{
	if(stream.isWriting)  //Am I server
	{
		stream.Serialize(localPlayer.HP);
		stream.Serialize(localPlayer.TotalHP);
		stream.Serialize(localPlayer.EXP);
		stream.Serialize(localPlayer.jumpSpeed);
		stream.Serialize(localPlayer.runSpeed);
		stream.Serialize(localPlayer.attackSpeed);
		stream.Serialize(localPlayer.currentSpell);
		stream.Serialize(localPlayer.selectedTarget);
	}
	else 	//or client
	{
		localPlayer = new PlayerDetails();
		stream.Serialize(localPlayer.HP);
		stream.Serialize(localPlayer.TotalHP);
		stream.Serialize(localPlayer.EXP);
		stream.Serialize(localPlayer.jumpSpeed);
		stream.Serialize(localPlayer.runSpeed);
		stream.Serialize(localPlayer.attackSpeed);
		stream.Serialize(localPlayer.currentSpell);
		stream.Serialize(localPlayer.selectedTarget);
	}
}

This will keep clients up to date with the server.

If your properties affect other scripts you can pass them or read them in the Late Update function.

function LateUpdate()
{
	//Dont let Hp exceed the total.
	if(localPlayer.HP > localPlayer.TotalHP)
	{
		localPlayer.HP = localPlayer.TotalHP;
	}
	//Check if we dead.
	else if (localPlayer.HP <= 0)
	{
		//Detonate();
	}

	//Out Values
	characterController.jumpSpeed = localPlayer.jumpSpeed;
	characterController.runSpeed = localPlayer.runSpeed;
	characterController.attackSpeed = localPlayer.attackSpeed;

	//In Values
	localPlayer.currentSpell = characterController.currentSpell;
	localPlayer.selectedTarget = characterController.selectedTarget;
}

And last but not least this script is where you declare methods that will modify the data; you can wrap these methods with Network.isServer to ensure the server modifies the data and not the clients.

function ApplyDamage (damage : float) {
	if(Network.isServer)
	{
		// We already have less than 0 HP, maybe we got killed already?
		if (localPlayer.HP <= 0)
			return;
		//Take the damage
		localPlayer.HP -= damage;
		//Animate getting hit
		animation.CrossFade("gothit");
		//Tell the NetworkSyncAnimation
		SendMessage("SyncAnimation", "gothit");
	}
}
function ApplyHeal (heal : float) {
	if(Network.isServer)
	{
		// We already have less than 0 HP, maybe we got killed already?
		if (localPlayer.HP <= 0)
			return;

		if(localPlayer.HP < localPlayer.TotalHP)
		{
			localPlayer.HP += heal;
		}
		//Animate getting healed
		animation.CrossFade("gothealed");
		//Tell the NetworkSyncAnimation
		SendMessage("SyncAnimation", " gothealed ");
	}
}
Conclusion:
Give this script a network view and add it to each local spawned player on the server and the clients. 

Controller and Networking
_____________________________________________________________________

First we declare our base stats and values for movement

public var jumpSpeed:float;
public var runSpeed:float;
public var attackSpeed: float;
public var currentSpell: int;
public var selectedTarget: NetworkViewID;
public var rotationAngle: int;

private var gravity = 20.0;
private var rotateSpeed =150.0;
private var walkSpeed = 1.0;
private var moveSpeed = 0.0;

private var moveDirection:Vector3 = Vector3.zero;
private var isWalking:boolean = false;

Next we declare our input variables.

public var verticalInput : float;
public var horizontalInput : float;
public var mouseLeft : boolean;
public var mouseRight: boolean;
public var jumpButton : boolean;
public var fireButton : boolean;

And a flag to enable/disable them

public var getUserInput : boolean = false;

Now for our update function, this is the section that will consume the variables.

function Update ()
{
	//************CLIENT****************//
	if(getUserInput)
	{
		// Sample user input	
		verticalInput = Input.GetAxisRaw("Vertical");
		horizontalInput = Input.GetAxisRaw("Horizontal");
		jumpButton = Input.GetButton("Jump");
		mouseLeft = Input.GetMouseButton(0);
		mouseRight = Input.GetMouseButton(1);
		fireButton = Input.GetButton("Fire1");
		rotationAngle = Camera.main.transform.eulerAngles.y;
		// Toggle walking/running with the T key
		if(Input.GetKeyDown("t"))
			isWalking = !isWalking;
			
	}
}

The code above will be the responsibility of the client to compute and set these variables so the rest of the controller can move your character around.

We only want player to be able to move when on the ground so add the following variables to our declarations

private var grounded:boolean = false;
private var groundedTimeout = 0.25;
private var lastGroundedTime = 0.0;

Now add the following to the bottom of the update function.

//************CLIENT AND SERVER****************//
// Only allow movement and jumps while grounded
if(grounded) 
{
lastGroundedTime = Time.time;
moveDirection = new Vector3((mouseRight ? horizontalInput : 0),0, ((mouseRight && mouseLeft) ? (verticalInput == 0 ? 1 : verticalInput) : verticalInput));

// if moving forward and to the side at the same time, compensate for distance
if(mouseRight && horizontalInput && verticalInput) {
moveDirection *= .7;
}

moveDirection = transform.TransformDirection(moveDirection);
moveDirection *= isWalking ? walkSpeed : runSpeed;

 	// Jump!
if(jumpButton)
{
moveDirection.y = jumpSpeed;
lastJumpButtonTime = Time.time;
}
}

At this point your character will be able to move backwards/forwards/Left/Right but will not be able to rotate.

So next we add.

// Allow turning at anytime. Keep the character facing in the same direction as the Camera if the right mouse button is down.
if(mouseRight) {
transform.rotation = Quaternion.Euler(0,rotationAngle,0);
}else {
transform.Rotate(0,horizontalInput * rotateSpeed * Time.deltaTime, 0);
}

This will facilitate the rotating with mouse (rotation angle) or with keys(horizontalInput * rotateSpeed)

Next we add gravity.

//Apply gravity
moveDirection.y -= gravity * Time.deltaTime;

Last but not least we update the base unity character controller and set the grounded flag.

//Move controller
var controller:CharacterController = GetComponent(CharacterController);
var flags = controller.Move(moveDirection * Time.deltaTime);
grounded = (flags & CollisionFlags.Below) != 0;

Camera
___________________________________________________________________________________

First we declare our camera variables.

var target : Transform; 
var targetHeight = 2.0; 
var distance = 2.8; 
var maxDistance = 10; 
var minDistance = 0.5; 
var xSpeed = 250.0; 
var ySpeed = 120.0; 
var yMinLimit = -40; 
var yMaxLimit = 80; 
var zoomRate = 20; 
var rotationDampening = 3.0; 
private var x = 0.0; 
private var y = 0.0; 
var isTalking:boolean = false; 

@script AddComponentMenu("Camera-Control/Custom Camera") 

Next we set start values to our variables.

function Start () { 
    var angles = transform.eulerAngles; 
    x = angles.y; 
    y = angles.x; 

   // Make the rigid body not change rotation 
      if (rigidbody) 
      rigidbody.freezeRotation = true; 
} 

Next is where we calculate our camera view.

function LateUpdate () { 
   if(!target) 
      return; 
    
// If either mouse buttons are down, let them govern camera position 
   if (Input.GetMouseButton(0) || (Input.GetMouseButton(1))){ 
   x += Input.GetAxis("Mouse X") * xSpeed * 0.02; 
   y -= Input.GetAxis("Mouse Y") * ySpeed * 0.02; 
    
    
// otherwise, ease behind the target if any of the directional keys are pressed 
   } else if(Input.GetAxis("Vertical") || Input.GetAxis("Horizontal")) { 
      var targetRotationAngle = target.eulerAngles.y; 
      var currentRotationAngle = transform.eulerAngles.y; 
      x = Mathf.LerpAngle(currentRotationAngle, targetRotationAngle, rotationDampening * Time.deltaTime); 
    } 
      

   distance -= (Input.GetAxis("Mouse ScrollWheel") * Time.deltaTime) * zoomRate * Mathf.Abs(distance); 
   distance = Mathf.Clamp(distance, minDistance, maxDistance); 
    
   y = ClampAngle(y, yMinLimit, yMaxLimit); 
   
// ROTATE CAMERA: 
   var rotation:Quaternion = Quaternion.Euler(y, x, 0); 
   transform.rotation = rotation; 
    
// POSITION CAMERA: 
   var position = target.position - (rotation * Vector3.forward * distance + Vector3(0,-targetHeight,0)); 
   transform.position = position; 
    
// IS VIEW BLOCKED? 
    var hit : RaycastHit; 
    var trueTargetPosition : Vector3 = target.transform.position - Vector3(0,-targetHeight,0); 
// Cast the line to check: 
    if (Physics.Linecast (trueTargetPosition, transform.position, hit)) {  
// If so, shorten distance so camera is in front of object: 
      var tempDistance = Vector3.Distance (trueTargetPosition, hit.point) - 0.28; 
// Finally, rePOSITION the CAMERA: 
      position = target.position - (rotation * Vector3.forward * tempDistance + Vector3(0,-targetHeight,0)); 
      transform.position = position; 
   } 
} 

static function ClampAngle (angle : float, min : float, max : float) { 
   if (angle < -360) 
      angle += 360; 
   if (angle > 360) 
      angle -= 360; 
   return Mathf.Clamp (angle, min, max); 
    
} 

Last edited Feb 9, 2011 at 10:43 PM by gigaflare, version 1