WOW Camera Movement

Hello all!

I’m new to the Unity and to a Mac but I can script with no problem.

I am looking for information on how to mimic the camera movement such as in World Of Warcraft. Can someone please either provide me with suggestions OR with links where I can read up about the subject and try and tackle it myself.

Thanks in advance for your help and support!

Ok, I have mimic’d the movements of WOW and I will be posting the scripts shortly. I am in needed of one last detail.

I need a function to call that will put the camera behind the player in the direction that they are facing at the same distance that the camera is already from. I was looking at using the “RotateAround” function but not sure if this is the best course of action.

Thanks for any help! Will post current code tonight.

[edit]

Here is the code I promised to post.

This is named “moveCharacter”. Add it to your Character

var turnSpeed = 10.0;
var moveSpeed = 10.0;
var mouseTurnMultiplier = 1;

private var x : float;
private var z : float;
function Update () 
{
	// x is used for the x axis.  set it to zero so it doesn't automatically rotate
	x = 0;
	
	// check to see if the W or S key is being pressed.  
	z = Input.GetAxis("Vertical") * Time.deltaTime * moveSpeed;
	
	// Move the character forwards or backwards
	transform.Translate(0, 0, z);
			
	// Check to see if the A or S key are being pressed
	if (Input.GetAxis("Horizontal"))
	{
		// Get the A or S key (-1 or 1)
		x = Input.GetAxis("Horizontal");
	}
	
	// Check to see if the right mouse button is pressed
	if (Input.GetMouseButton(1))
	{
		// Get the difference in horizontal mouse movement
		x = Input.GetAxis("Mouse X") * turnSpeed * mouseTurnMultiplier;
	}

	// rotate the character based on the x value
	transform.Rotate(0, x, 0);
}

This is named orbitCharacter. Add it to your camera.

var target : Transform;
var distance = 10.0;

var xSpeed = 250.0;
var ySpeed = 120.0;

var yMinLimit = -20;
var yMaxLimit = 80;

var zoomRate = 20;

private var x = 0.0;
private var y = 0.0;

@script AddComponentMenu("Camera-Control/Mouse Orbit")

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

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

function LateUpdate () {

    if (target) {
    	if (Input.GetMouseButton(0))
    	{
        x += Input.GetAxis("Mouse X") * xSpeed * 0.02;
        y -= Input.GetAxis("Mouse Y") * ySpeed * 0.02;
        var test = 0;
        test = y;
    	}
        distance += -(Input.GetAxis("Mouse ScrollWheel") * Time.deltaTime) * zoomRate * Mathf.Abs(distance);
 		if (distance < 2.5)
 		{
 			distance = 2.5;
 		}
 		if (distance > 20)
 		{
 			distance = 20;
 		}
    	
 		
 		y = ClampAngle(y, yMinLimit, yMaxLimit);
 		       
 		//Debug.Log("y: "+y+" test: "+test);
 		
 		if( y == yMinLimit  test == yMinLimit)
 		{
 			// This is to allow the camera to slide across the bottom if the player is too low in the y 
 			distance += -(Input.GetAxis("Mouse Y") * Time.deltaTime) * 10 * Mathf.Abs(distance);
 		}
 		
        var rotation = Quaternion.Euler(y, x, 0);
        var position = rotation * Vector3(0.0, 2.0, -distance) + target.position;
        
        //Debug.Log("Distance: "+distance);
        transform.rotation = rotation;
        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);
}

Parent the camera to the player, move it to your ‘default’ position and rotation. Store those values upon startup. Then after you’re done with your wow style mouselooks or what have you, simply use Lerp to move back to your default settings.

1 Like

Thanks! I follow what you are saying but I’m a little new to the Lerp concept. I see many Lerps (Unity - Scripting API:) but I’m not sure which one to use.

Any other thoughts?

thank you matrix for posting your code! I’d been working on this same issue, building a WoW style controller, but got stuck and put it aside. Now I’m back on the task and am curious if you figured out the camera snapping issue. I’d rewritten some of the code that comes packaged with Unity but yours works much better, except for that same final problem I had, getting the camera to default back to behind the player. Were you able to solve it?
Thanks!

Yes and no. I have it fixed so it does snap behind the player when using the Right Mouse button and when using the left it will Rotate Around the player.

But I am having issues with the collision detection on the camera. Currently if there is a break in the ray (I shoot the ray FROM the player to the origin) and I try to move the camera to that point. What sometimes happens is that it gets stuck. Trying to find help on that.

Let me post my code.

// This camera is similar to the one used in Jak  Dexter

var target : Transform;
var distance = 4.0;
var height = 1.0;
var smoothLag = 0.2;
var maxSpeed = 10.0;
var snapLag = 0.3;
var clampHeadPositionScreenSpace = 0.75;
var lineOfSightMask : LayerMask = 0;
var zoomSpeed = 50;

private var isSnapping = false;
private var headOffset = Vector3.zero;
private var centerOffset = Vector3.zero;
private var controller : ThirdPersonController;
private var velocity = Vector3.zero;
private var targetHeight = 100000.0;
private var theta;
private var theta2;
private var isColliding = false;

var targetOffset = Vector3.zero;

var closerRadius : float = 0.2;
var closerSnapLag : float = 0.2;

var xSpeed = 200.0;
var ySpeed = 80.0;

var yMinLimit = -20;
var yMaxLimit = 80;

private var currentDistance = 10.0;
private var x = 0.0;
private var y = 0.0;
private var distanceVelocity = 0.0;

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

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

function Awake ()
{
      theta = 0;
      theta2 = 0;
      var characterController : CharacterController = target.collider;
      if (characterController)
      {
              centerOffset = characterController.bounds.center -
target.position;
              headOffset = centerOffset;
              headOffset.y = characterController.bounds.max.y -
target.position.y;
      }

      if (target)
      {
              controller = target.GetComponent(ThirdPersonController);
      }

      if (!controller)
              Debug.Log("Please assign a target to the camera that has a Third Person Controller script component.");
}

function LateUpdate () {

      var targetCenter = target.position + centerOffset;
      var targetHead = target.position + headOffset;

      // When jumping don't move camera upwards but only down!
      if (controller.IsJumping ())
      {
              // We'd be moving the camera upwards, do that only if it's really high
              var newTargetHeight = targetCenter.y + height;
              if (newTargetHeight < targetHeight || newTargetHeight - targetHeight > 5)
                      targetHeight = targetCenter.y + height;
      }
      // When walking always update the target height
      else
      {
              targetHeight = targetCenter.y + height;
      }

      // We start snapping when user pressed Fire2!
      if (Input.GetButton("Fire2")  !isSnapping)
      {
              //velocity = Vector3.zero;
              isSnapping = true;
      }

      if (isSnapping)
      {
              //ApplySnapping (targetCenter);
              OrbitPlayerFromBehind (targetCenter);
      }
      else
      {
              //ApplyPositionDamping (Vector3(targetCenter.x, targetHeight, targetCenter.z));
      }

      SetUpRotation(targetCenter, targetHead);

          if (Input.GetMouseButton(1))
          {
            OrbitPlayerFromBehind (targetCenter);
          }


      if (Input.GetMouseButton(0))
      {
              OrbitPlayer (targetCenter);
              isSnapping = false;
      }
      else
      {
              //ApplyPositionDamping (Vector3(targetCenter.x, targetHeight, targetCenter.z));
              isSnapping = true;
      }

   distance += -(Input.GetAxis("Mouse ScrollWheel") * Time.deltaTime) * zoomSpeed * Mathf.Abs(distance);
       if (distance < 5)
       {
               distance = 5;
       }
       if (distance > 50)
       {
               distance = 50;
       }
}

function ApplySnapping (targetCenter : Vector3)
{
}

function OrbitPlayer (targetPlayer : Vector3)
{
       y = transform.position.y;
       x += Input.GetAxis("Mouse X") * xSpeed * 0.02;
       y -= Input.GetAxis("Mouse Y") * ySpeed * 0.02;

       var rotation = Quaternion.Euler(0, x, 0);

       var targetPos = target.position;
       var direction = rotation * -Vector3.forward;

       var targetDistance = AdjustLineOfSight(targetPos, direction);
       currentDistance = distance;
       transform.rotation = rotation;
       transform.position = targetPos + direction * currentDistance;
       if (y - target.position.y > 50)
       {
               y = target.position.y + 50;
       }

       transform.position.y = y;
       camera.transform.LookAt(targetPlayer);
}

function OrbitPlayerFromBehind (targetPlayer : Vector3)
{
      var newPosition = Vector3();
      var fwd = new Vector3();
      var rightVector = new Vector3();
      var upVector = new Vector3();
      var movingVector = new Vector3();
      var collisionVector = new Vector3();
      var zeroVector = new Vector3();

      if (Input.GetMouseButton(1))
      {
               var mouseY = Input.GetAxis("Mouse Y");
      }

      theta = jangle(transform.position - targetPlayer) * Time.deltaTime;
      theta2 = theta2 + mouseY * Time.deltaTime;

      if (1.40 <= theta2)
              theta2 = 1.40;

      fwd = target.TransformDirection(Vector3.forward);
      fwd = fwd.normalized;

      newPosition = target.position - (distance * fwd);
      newPosition.y = target.position.y + distance * Mathf.Sin(theta2);

      y = target.transform.eulerAngles.z;
      x = target.transform.eulerAngles.y;
      rightVector = Vector3(fwd.z, 0, -fwd.x);
      rightVector = rightVector.normalized;

      upVector = Vector3.Cross(fwd, rightVector);
      upVector = upVector.normalized;

      movingVector = Vector3.Slerp(transform.position, newPosition, Time.deltaTime * 50);

      collisionVector = AdjustLineOfSight(transform.position, targetPlayer);

       if (collisionVector != Vector3.zero)
       {
               Debug.Log("I am trying to get new position");
               movingVector = collisionVector - (fwd * -1);
               distance = movingVector.z - collisionVector.z;
       }

      transform.position = movingVector;
      camera.transform.LookAt(targetPlayer, upVector);



}


function AdjustLineOfSight (newPosition : Vector3, target : Vector3)
{
      var hit : RaycastHit;
      if (Physics.Linecast (target, newPosition, hit, lineOfSightMask.value))
      {
                  Debug.Log("I hit someting at: "+hit.point);
              velocity = Vector3.zero;
              isColliding = true;
              return hit.point;
      }
      //return newPosition;
}


function ApplyPositionDamping (targetCenter : Vector3)
{
}

function SetUpRotation (centerPos : Vector3, headPos : Vector3)
{
}

function AngleDistance (a : float, b : float)
{
      a = Mathf.Repeat(a, 360);
      b = Mathf.Repeat(b, 360);

      return Mathf.Abs(b - a);
}

function jangle(pTemp : Vector3)
{
      var tTheta:Number;

      pTemp.Normalize();

      // quadrant I  II
      tTheta = Mathf.Acos(pTemp.x);

      // quadtrant III  IV
      if (pTemp.z < 0)
              tTheta = 2*Mathf.PI - tTheta;

      return tTheta;
}

function bindAngle(tDegrees:Number)
{
      if (tDegrees < 0)
              tDegrees += Mathf.PI*2;
      else if (Mathf.PI*2 <= tDegrees)
              tDegrees += - Mathf.PI*2;
      return tDegrees;
}

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);
}


@script AddComponentMenu ("Third Person Camera/Spring Follow Camera")

and this:

// The speed when walking
var walkSpeed = 3.0;
// after trotAfterSeconds of walking we trot with trotSpeed
var trotSpeed = 4.0;
// when pressing "Fire3" button (cmd) we start running
var runSpeed = 6.0;

var inAirControlAcceleration = 3.0;

// How high do we jump when pressing jump and letting go immediately
var jumpHeight = 0.5;
// We add extraJumpHeight meters on top when holding the button down longer while jumping
var extraJumpHeight = 2.5;

// The gravity for the character
var gravity = 20.0;
// The gravity in controlled descent mode
var controlledDescentGravity = 2.0;
var speedSmoothing = 10.0;
var rotateSpeed = 500.0;
var trotAfterSeconds = 3.0;

var canJump = true;
var canControlDescent = true;
var canWallJump = false;

private var jumpRepeatTime = 0.05;
private var wallJumpTimeout = 0.15;
private var jumpTimeout = 0.15;
private var groundedTimeout = 0.25;

// The camera doesnt start following the target immediately but waits for a split second to avoid too much waving around.
private var lockCameraTimer = 0.0;

// The current move direction in x-z
private var moveDirection = Vector3.zero;
// The current vertical speed
private var verticalSpeed = 0.0;
// The current x-z move speed
private var moveSpeed = 0.0;

// The last collision flags returned from controller.Move
private var collisionFlags : CollisionFlags; 

// Are we jumping? (Initiated with jump button and not grounded yet)
private var jumping = false;
private var jumpingReachedApex = false;

// Are we moving backwards (This locks the camera to not do a 180 degree spin)
private var movingBack = false;
// Is the user pressing any keys?
private var isMoving = false;
// When did the user start walking (Used for going into trot after a while)
private var walkTimeStart = 0.0;
// Last time the jump button was clicked down
private var lastJumpButtonTime = -10.0;
// Last time we performed a jump
private var lastJumpTime = -1.0;
// Average normal of the last touched geometry
private var wallJumpContactNormal : Vector3;
private var wallJumpContactNormalHeight : float;

// the height we jumped from (Used to determine for how long to apply extra jump power after jumping.)
private var lastJumpStartHeight = 0.0;
// When did we touch the wall the first time during this jump (Used for wall jumping)
private var touchWallJumpTime = -1.0;

private var inAirVelocity = Vector3.zero;

private var lastGroundedTime = 0.0;

private var lean = 0.0;
private var slammed = false;

private var isControllable = true;

private var oldForwardVector : Vector3;

private var strafe : float;

function Awake ()
{
	moveDirection = transform.TransformDirection(Vector3.forward);
}

// This next function responds to the "HidePlayer" message by hiding the player. 
// The message is also 'replied to' by identically-named functions in the collision-handling scripts.
// - Used by the LevelStatus script when the level completed animation is triggered.

function HidePlayer()
{
	GameObject.Find("rootJoint").GetComponent(SkinnedMeshRenderer).enabled = false; // stop rendering the player.
	isControllable = false;	// disable player controls.
}

// This is a complementary function to the above. We don't use it in the tutorial, but it's included for
// the sake of completeness. (I like orthogonal APIs; so sue me!)

function ShowPlayer()
{
	GameObject.Find("rootJoint").GetComponent(SkinnedMeshRenderer).enabled = true; // start rendering the player again.
	isControllable = true;	// allow player to control the character again.
}


function UpdateSmoothedMovementDirection ()
{
	strafe = 0;
	var cameraTransform = this.transform;
	var grounded = IsGrounded();
	
	// Forward vector relative to the camera along the x-z plane	
	var forward = cameraTransform.TransformDirection(Vector3.forward);
	forward = forward.normalized;

	// Right vector relative to the camera
	// Always orthogonal to the forward vector
	var right = Vector3(forward.z, 0, -forward.x);
        right = right.normalized;	
        
	var v = Input.GetAxisRaw("Vertical");
	var h = Input.GetAxisRaw("Horizontal");

	// Are we moving backwards or looking backwards
	if (v < -0.2)
		movingBack = true;
	else
		movingBack = false;
	
	var wasMoving = isMoving;
	isMoving = Mathf.Abs (h) > 0.1 || Mathf.Abs (v) > 0.1;
		
	// Target direction relative to the camera
	var targetDirection = h * right + Mathf.Abs(v) * forward;
	
	// Grounded controls
	if (grounded)
	{
		// Lock camera for short period when transitioning moving  standing still
		//lockCameraTimer += Time.deltaTime;

		// We store speed and direction seperately,
		// so that when the character stands still we still have a valid forward direction
		// moveDirection is always normalized, and we only update it if there is user input.
		if (targetDirection != Vector3.zero)
		{
			// If we are really slow, just snap to the target direction
			if (moveSpeed < walkSpeed * 0.9  grounded)
			{
				moveDirection = moveDirection.normalized;
			}
			// Otherwise smoothly turn towards it
			else
			{
				moveDirection = moveDirection.normalized;
			}
		}
		
		// Smooth the speed based on the current target direction
		var curSmooth = speedSmoothing * Time.deltaTime;
		
		// Choose target speed
		// We want to support analog input but make sure you cant walk faster diagonally than just forward or sideways
		var targetSpeed = Mathf.Min(targetDirection.magnitude, 1.0);
	
		// Pick speed modifier
		if (Input.GetButton ("Fire3"))
		{
			targetSpeed *= runSpeed;
		}
		else if (Time.time - trotAfterSeconds > walkTimeStart)
		{
			//targetSpeed *= trotSpeed;
		}
		else
		{
			targetSpeed *= walkSpeed;
		}
		
		//Debug.Log("moveSpeed: "+moveSpeed);
		
		if (v == 0  h != 0)
		{
			moveSpeed = .2;
		}
		else
		{
			moveSpeed = Mathf.Lerp(moveSpeed, targetSpeed, curSmooth);
		}
		
		// Reset walk time start when we slow down
		if (moveSpeed < walkSpeed * 0.3)
			walkTimeStart = Time.time;
			
	if (Input.GetAxis("Horizontal") != 0)
	{
	    h = Input.GetAxis("Horizontal");
			   
        forward = transform.TransformDirection(Vector3.forward);
        forward = forward.normalized;
       
	    right = Vector3(forward.z, 0, -forward.x); 
        right = right.normalized;	        

	    targetDirection = h * right + 10 * forward;  
	      
	    moveDirection = targetDirection;
	    moveDirection = moveDirection.normalized;
  
	}

	if (Input.GetMouseButton(1))
	{
		// Get the difference in horizontal mouse movement
		h = Input.GetAxis("Mouse X");
		if (h > 4)
		{
			h = 4;
		}
		if (h < -4)
		{
			h = -4;
		}
		
       forward = transform.TransformDirection(Vector3.forward);
       forward = forward.normalized;
       
       if (forward != oldForwardVector  v == 0)
       {
       		//Debug.Log("1 v: "+moveSpeed);
       		moveSpeed = 0.2;
       }
       
	   right = Vector3(forward.z, 0, -forward.x);      

	   targetDirection = h * right + 3 * forward;  
	      
	   moveDirection = targetDirection;
	   moveDirection = moveDirection.normalized;
	   
	   oldForwardVector = forward;
	}	

	// Strafing
	if (Input.GetAxisRaw("Strafe") != 0)
	{
	   strafe = Input.GetAxisRaw("Strafe");
	   Debug.Log("srafe: "+strafe);
	   strafe *= 5;
	}
	
	}
	// In air controls
	else
	{
		// Lock camera while in air
		if (jumping)
			lockCameraTimer = 0.0;

		if (isMoving)
			inAirVelocity += targetDirection.normalized * Time.deltaTime * inAirControlAcceleration;
	}
	

		
}

function ApplyWallJump ()
{
	// We must actually jump against a wall for this to work
	if (!jumping)
		return;

	// Store when we first touched a wall during this jump
	if (collisionFlags == CollisionFlags.CollidedSides)
	{
		touchWallJumpTime = Time.time;
	}

	// The user can trigger a wall jump by hitting the button shortly before or shortly after hitting the wall the first time.
	var mayJump = lastJumpButtonTime > touchWallJumpTime - wallJumpTimeout  lastJumpButtonTime < touchWallJumpTime + wallJumpTimeout;
	if (!mayJump)
		return;
	
	// Prevent jumping too fast after each other
	if (lastJumpTime + jumpRepeatTime > Time.time)
		return;
	
		
	if (Mathf.Abs(wallJumpContactNormal.y) < 0.2)
	{
		wallJumpContactNormal.y = 0;
		moveDirection = wallJumpContactNormal.normalized;
		// Wall jump gives us at least trotspeed
		moveSpeed = Mathf.Clamp(moveSpeed * 1.5, trotSpeed, runSpeed);
	}
	else
	{
		moveSpeed = 0;
	}
	
	verticalSpeed = CalculateJumpVerticalSpeed (jumpHeight);
	DidJump();
	SendMessage("DidWallJump", null, SendMessageOptions.DontRequireReceiver);
}

function ApplyJumping ()
{
	// Prevent jumping too fast after each other
	if (lastJumpTime + jumpRepeatTime > Time.time)
		return;

	if (IsGrounded()) {
		// Jump
		// - Only when pressing the button down
		// - With a timeout so you can press the button slightly before landing		
		if (canJump  Time.time < lastJumpButtonTime + jumpTimeout) {
			verticalSpeed = CalculateJumpVerticalSpeed (jumpHeight);
			SendMessage("DidJump", SendMessageOptions.DontRequireReceiver);
		}
	}
}


function ApplyGravity ()
{
	if (isControllable)	// don't move player at all if not controllable.
	{
		// Apply gravity
		var jumpButton = Input.GetButton("Jump");
		
		// * When falling down we use controlledDescentGravity (only when holding down jump)
		var controlledDescent = canControlDescent  verticalSpeed <= 0.0  jumpButton  jumping;
		
		// When we reach the apex of the jump we send out a message
		if (jumping  !jumpingReachedApex  verticalSpeed <= 0.0)
		{
			jumpingReachedApex = true;
			SendMessage("DidJumpReachApex", SendMessageOptions.DontRequireReceiver);
		}
	
		// * When jumping up we don't apply gravity for some time when the user is holding the jump button
		//   This gives more control over jump height by pressing the button longer
		var extraPowerJump =  IsJumping ()  verticalSpeed > 0.0  jumpButton  transform.position.y < lastJumpStartHeight + extraJumpHeight;
		
		if (controlledDescent)			
			verticalSpeed -= controlledDescentGravity * Time.deltaTime;
		else if (extraPowerJump)
			return;
		else if (IsGrounded ())
			verticalSpeed = 0.0;
		else
			verticalSpeed -= gravity * Time.deltaTime;
	}
}

function CalculateJumpVerticalSpeed (targetJumpHeight : float)
{
	// From the jump height and gravity we deduce the upwards speed 
	// for the character to reach at the apex.
	return Mathf.Sqrt(2 * targetJumpHeight * gravity);
}

function DidJump ()
{
	jumping = true;
	jumpingReachedApex = false;
	lastJumpTime = Time.time;
	lastJumpStartHeight = transform.position.y;
	touchWallJumpTime = -1;
	lastJumpButtonTime = -10;
}

function Update() {
	
	if (!isControllable)
	{
		// kill all inputs if not controllable.
		Input.ResetInputAxes();
	}

	if (Input.GetButtonDown ("Jump"))
	{
		lastJumpButtonTime = Time.time;
	}

	UpdateSmoothedMovementDirection();
	
	// Apply gravity
	// - extra power jump modifies gravity
	// - controlledDescent mode modifies gravity
	ApplyGravity ();

	// Perform a wall jump logic
	// - Make sure we are jumping against wall etc.
	// - Then apply jump in the right direction)
	if (canWallJump)
		ApplyWallJump();

	// Apply jumping logic
	ApplyJumping ();
	
	// Calculate actual motion
	// If moving forwards
	
		forward = transform.TransformDirection(Vector3.forward);
        forward = forward.normalized;
 
        right = Vector3(forward.z, 0, -forward.x);
        right = right.normalized;
	
	if (strafe != 0  moveSpeed == 0)
	{
		moveSpeed = .2;
	}
	

	if (movingBack == false)
	{
		var movement = moveDirection * moveSpeed + Vector3 (0, verticalSpeed, strafe) + inAirVelocity;
		movement *= Time.deltaTime;
	}
	else
	{	
		movement = (moveDirection  * -1) * moveSpeed + Vector3 (0, verticalSpeed, strafe) + inAirVelocity;
		movement *= Time.deltaTime;
	}
	
	if (!isMoving)
	{

   		//Debug.Log("No movement");
		//movement = right * strafe * 5 + Vector3 (0, verticalSpeed, 0) + inAirVelocity;
		//movement *= Time.deltaTime;		
	}
	else
	{
		//Debug.Log("Movement");
	}
		//Debug.Log("movement: "+movement+ " moveSpeed: "+moveSpeed);
	// Move the controller
	var controller : CharacterController = GetComponent(CharacterController);
	wallJumpContactNormal = Vector3.zero;
	collisionFlags = controller.Move(movement);
	
	// Set rotation to the move direction
	if (IsGrounded())
	{
		if(slammed) // we got knocked over by an enemy. We need to reset some stuff
		{
			slammed = false;
			controller.height = 2;
			transform.position.y += 0.75;
		}
		
		transform.rotation = Quaternion.LookRotation(moveDirection);
			
	}	
	else
	{
		if(!slammed)
		{
			var xzMove = movement;
			xzMove.y = 0;
			if (xzMove.sqrMagnitude > 0.001)
			{
				//transform.rotation = Quaternion.LookRotation(xzMove);
			}
		}
	}	
	
	// We are in jump mode but just became grounded
	if (IsGrounded())
	{
		lastGroundedTime = Time.time;
		inAirVelocity = Vector3.zero;
		if (jumping)
		{
			jumping = false;
			SendMessage("DidLand", SendMessageOptions.DontRequireReceiver);
		}
	}
}

function OnControllerColliderHit (hit : ControllerColliderHit )
{
	if (hit.moveDirection.y > 0.01) 
		return;
	wallJumpContactNormal = hit.normal;
}

function GetSpeed () {
	return moveSpeed;
}

function IsJumping () {
	return jumping  !slammed;
}

function IsGrounded () {
	return (collisionFlags  CollisionFlags.CollidedBelow) != 0;
}

function SuperJump (height : float)
{
	verticalSpeed = CalculateJumpVerticalSpeed (height);
	collisionFlags = CollisionFlags.None;
	SendMessage("DidJump", SendMessageOptions.DontRequireReceiver);
}

function SuperJump (height : float, jumpVelocity : Vector3)
{
	verticalSpeed = CalculateJumpVerticalSpeed (height);
	inAirVelocity = jumpVelocity;
	
	collisionFlags = CollisionFlags.None;
	SendMessage("DidJump", SendMessageOptions.DontRequireReceiver);
}

function Slam (direction : Vector3)
{
	verticalSpeed = CalculateJumpVerticalSpeed (1);
	inAirVelocity = direction * 6;
	direction.y = 0.6;
	Quaternion.LookRotation(-direction);
	var controller : CharacterController = GetComponent(CharacterController);
	controller.height = 0.5;
	slammed = true;
	collisionFlags = CollisionFlags.None;
	SendMessage("DidJump", SendMessageOptions.DontRequireReceiver);
}

function GetDirection () {
	return moveDirection;
}

function IsMovingBackwards () {
	return movingBack;
}

function GetLockCameraTimer () 
{
	return lockCameraTimer;
}

function IsMoving ()  : boolean
{
	return Mathf.Abs(Input.GetAxisRaw("Vertical")) + Mathf.Abs(Input.GetAxisRaw("Horizontal")) > 0.5;
}

function HasJumpReachedApex ()
{
	return jumpingReachedApex;
}

function IsGroundedWithTimeout ()
{
	return lastGroundedTime + groundedTimeout > Time.time;
}

function IsControlledDescent ()
{
	// * When falling down we use controlledDescentGravity (only when holding down jump)
	var jumpButton = Input.GetButton("Jump");
	return canControlDescent  verticalSpeed <= 0.0  jumpButton  jumping;
}

function Reset ()
{
	gameObject.tag = "Player";
}
// Require a character controller to be attached to the same game object
@script RequireComponent(CharacterController)
@script AddComponentMenu("Third Person Player/Third Person Controller")

I need help with the collision for breaking the ray and the strafing.

thank you matrix211v1, great script, i tried it and works nicely.

I was wondering did you ever get the collisions of the camera figured out yet?

I was playing with it and yes, i noticed the camera going thru inside the objects and even below the ground, it looks funny.

I also noticed that when to toon is on idle and i try to move it left of right by “a” or “d” on my keyboard it does a violent 360 degree turn, but when running mode use the left or right turn it works accordingly, just setup the rotation speed and it does it correctly, but like stated before, if the toon is on idle and even with the rotation speed of the camera setup low, it does a fast and violent turn, i still cant figure this part yet.

by the way, I was just testing your spring follow camera script and the default third caracter controller from the demo, because could not use your third caracter controller script, everytime i added to the player and tried to test it, unity editor will close without a reason, like the whole unity game editor shuts down with no error message.

anyways thanks again for your nice script and let us know if you got the colission part figured out yet.

regards

Once again, yes and no. I tried to use a Character Collider with the move vector but that caused all sorts of problems. I think the best way to solve it is to put a sphere collider on the camera GameObject itself and test that (since I know the position vector that the camera needs to be at because the script is working now!)

Lately I have been busy building a multiplayer server which I just finished (As the title suggest, I am mimicing WOW) and now trying to get the server to record the player positions in a database (already got most of this working).

Once I finish that (Sunday), I will get back on the camera collision thing and post what I have for everyone.

Till later!

That is great news matrix211v1,
cant wait to see your work.

thank you.

keep up the good work.

regards

Hey everyone!

Matrix, thanks for starting this topic! Like many others, I’ve stumbled on this thread looking for a way to simulate WoW’s camera controls, and you’ve made a good head-start with your first batch of code (I say first batch because the second batch kept crashing Unity for me… I’m not sure why, but I thought it’d be best to start with what you have earlier).

Hope you don’t mind, but I extended on your earlier code to get it close enough to WoW (things like camera rotation on left mouse hold, character rotation on right mouse hold, camera easing behind character when neither mouse buttons are held down while moving, etc.). The only thing I think I haven’t implemented it to make it complete is collision detection, which you seem to be working on right now. I kept it as simple as possible so you guys can tear it apart… in fact I would love feedback, because I’m new to 3D math and perhaps there are some things I do in there that could be done with greater efficiency or with built-in functions I do not know of. Anyway, enough stalling, on to the code!

WowCharacterController.js:

private var jumpSpeed:float = 8.0;
private var gravity:float = 20.0;
private var runSpeed:float = 5.0;
private var walkSpeed:float = 1.0;
private var rotateSpeed:float = 150.0;

private var grounded:boolean = false;
private var moveDirection:Vector3 = Vector3.zero;
private var isWalking:boolean = false;
private var moveStatus:String = "idle";

function Update ()
{
	// Only allow movement and jumps while grounded
	if(grounded) {
		moveDirection = new Vector3((Input.GetMouseButton(1) ? Input.GetAxis("Horizontal") : 0),0,Input.GetAxis("Vertical"));
		
		// if moving forward and to the side at the same time, compensate for distance
		// TODO: may be better way to do this?
		if(Input.GetMouseButton(1)  Input.GetAxis("Horizontal")  Input.GetAxis("Vertical")) {
			moveDirection *= .7;
		}
		
		moveDirection = transform.TransformDirection(moveDirection);
		moveDirection *= isWalking ? walkSpeed : runSpeed;
		
		moveStatus = "idle";
		if(moveDirection != Vector3.zero)
			moveStatus = isWalking ? "walking" : "running";
		
		// Jump!
		if(Input.GetButton("Jump"))
			moveDirection.y = jumpSpeed;
	}
	
	// Allow turning at anytime. Keep the character facing in the same direction as the Camera if the right mouse button is down.
	if(Input.GetMouseButton(1)) {
		transform.rotation = Quaternion.Euler(0,Camera.main.transform.eulerAngles.y,0);
	} else {
		transform.Rotate(0,Input.GetAxis("Horizontal") * rotateSpeed * Time.deltaTime, 0);
	}
	
	if(Input.GetMouseButton(1) || Input.GetMouseButton(0))
		Screen.lockCursor = true;
	else
		Screen.lockCursor = false;
	
	// Toggle walking/running with the T key
	if(Input.GetKeyDown("t"))
		isWalking = !isWalking;
	
	//Apply gravity
	moveDirection.y -= gravity * Time.deltaTime;
	
	//Move controller
	var controller:CharacterController = GetComponent(CharacterController);
	var flags = controller.Move(moveDirection * Time.deltaTime);
	grounded = (flags  CollisionFlags.Below) != 0;
}

@script RequireComponent(CharacterController)

WowCamera.js

var target : Transform;

var targetHeight = 2.0;
var distance = 5.0;

var maxDistance = 20;
var minDistance = 2.5;

var xSpeed = 250.0;
var ySpeed = 120.0;

var yMinLimit = -20;
var yMaxLimit = 80;

var zoomRate = 20;

var rotationDampening = 3.0;

private var x = 0.0;
private var y = 0.0;

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

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

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

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);
   
	var rotation:Quaternion = Quaternion.Euler(y, x, 0);
	var position = target.position - (rotation * Vector3.forward * distance + Vector3(0,-targetHeight,0));
	
	transform.rotation = rotation;
	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);
}

Paintbrush:

Actually yes, I got the camera collision detection working. I will try and clean it up and post this weekend.

beautiful :slight_smile: can’t wait to see it then!

Hi,

I just wanted to say that in a forum with incredibly helpful people, this set of postings stands out as one of the most useful I’ve read. I’ve been struggling to get something like the WoW control system working without luck. The code you’ve posted here works wonderfully and I can’t wait to try out the version with collision detection.

Thank you!!

Brian

Just curious, did you ever clean up the version with collision detection?

@Paintbrush:

Sorry for the delay.

Ok, here is an almost complete version of Wow with camera collision. There are 2 issues.

  1. When the camera hits something, it “instantly” gets behind the player.
  2. The “AdjustLineOfSight” doesn’t seem to fire (so if there is a object between the player and camera, it is not moving closer)

WowCamera.js

var target : Transform; 

var targetHeight = 12.0; 
var distance = 5.0; 

var maxDistance = 20; 
var minDistance = 2.5; 

var xSpeed = 250.0; 
var ySpeed = 120.0; 

var yMinLimit = -20; 
var yMaxLimit = 80; 

var zoomRate = 20; 

var rotationDampening = 3.0; 

var theta2 : float = 0.5;

private var x = 0.0; 
private var y = 0.0; 

private var fwd = new Vector3();
private var rightVector = new Vector3();
private var upVector = new Vector3();
private var movingVector = new Vector3();
private var collisionVector = new Vector3();
private var isColliding : boolean = false;
	
private var a1 = new Vector3();
private var b1 = new Vector3();
private var c1 = new Vector3();
private var d1 = new Vector3();
private var e1 = new Vector3();
private var f1 = new Vector3();
private var h1 = new Vector3();
private var i1 = new Vector3();

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

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

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

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); 
    
   var rotation:Quaternion = Quaternion.Euler(y, x, 0); 
   var position = target.position - (rotation * Vector3.forward * distance + Vector3(0,-targetHeight,0)); 
   
	// Check to see if we have a collision 
	collisionVector = AdjustLineOfSight(transform.position, position);
    
	// Check Line Of Sight
	if (collisionVector != Vector3.zero)
	{
		Debug.Log("Check Line Of Sight");
 		a1 = transform.position;
		b1 = position;
		c1 = AdjustLineOfSight(transform.position, position);
		d1 = c1 - a1;
		e1 = d1.normalized * -1;
		f1 = d1 + e1 * 1;
		g1 = f1 + a1;
		position = g1;

		// check distance player to camera
		h1 = position - a1;
		if (h1.magnitude < 10) 
		{
			position = a1 - fwd * 4;
			//position.y = targetPlayer.y;
			theta2 = theta2 + .25;
		}
		
		// set new camera distance
		h1 = position - a1;
		distance = h1.magnitude;
	}

	// check collision
	if (Physics.CheckSphere (position, .5) )
	{
 		a1 = transform.position;
		
		newPosition = a1 - fwd * 4;
		//newPosition.y = targetPlayer.y;
		theta2 = theta2 + .25;
		
		// set new camera distance
		h1 = position - a1;
		distance = h1.magnitude;
	}	
	
	//position = Vector3.Slerp(transform.position, position, Time.deltaTime * 100);	
	
   transform.rotation = rotation; 
   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); 
} 

function AdjustLineOfSight (vecA: Vector3, vecB: Vector3)
{
      var hit: RaycastHit;
	  
      if (Physics.Linecast (vecA, vecB, hit))
      {
			Debug.Log("I hit something");
			return hit.point;
      }
      
	  return Vector3.zero;
}

WowCharacterController

private var jumpSpeed:float = 8.0; 
private var gravity:float = 20.0; 
private var runSpeed:float = 50.0; 
private var walkSpeed:float = 15.0; 
private var rotateSpeed:float = 150.0; 

private var grounded:boolean = false; 
private var moveDirection:Vector3 = Vector3.zero; 
private var isWalking:boolean = false; 
private var moveStatus:String = "idle"; 
private var jumping:boolean = false;
private var moveSpeed:float = 0.0;

function Update () 
{ 
   // Only allow movement and jumps while grounded 
   if(grounded) { 
      moveDirection = new Vector3((Input.GetMouseButton(1) ? Input.GetAxis("Horizontal") : 0),0,Input.GetAxis("Vertical")); 
       
      // if moving forward and to the side at the same time, compensate for distance 
      // TODO: may be better way to do this? 
      if(Input.GetMouseButton(1)  Input.GetAxis("Horizontal")  Input.GetAxis("Vertical")) { 
         moveDirection *= .7; 
      } 
       
      moveDirection = transform.TransformDirection(moveDirection); 
      moveDirection *= isWalking ? walkSpeed : runSpeed; 
       
      moveStatus = "idle"; 
      if(moveDirection != Vector3.zero) 
         moveStatus = isWalking ? "walking" : "running"; 
       
      // Jump! 
      if(Input.GetButton("Jump")) 
         moveDirection.y = jumpSpeed; 
   } 
    
   // Allow turning at anytime. Keep the character facing in the same direction as the Camera if the right mouse button is down. 
   if(Input.GetMouseButton(1)) { 
      transform.rotation = Quaternion.Euler(0,Camera.main.transform.eulerAngles.y,0); 
   } else { 
      transform.Rotate(0,Input.GetAxis("Horizontal") * rotateSpeed * Time.deltaTime, 0); 
   } 
    
   if(Input.GetMouseButton(1) || Input.GetMouseButton(0)) 
      Screen.lockCursor = true; 
   else 
      Screen.lockCursor = false; 
    
   // Toggle walking/running with the T key 
   if(Input.GetAxis("Run") == 1) 
      isWalking = !isWalking; 
    
   //Apply gravity 
   moveDirection.y -= gravity * Time.deltaTime; 
    
   //Move controller 
   var controller:CharacterController = GetComponent(CharacterController); 
   var flags = controller.Move(moveDirection * Time.deltaTime); 
   grounded = (flags  CollisionFlags.Below) != 0; 
} 

function GetSpeed () {
	if (moveStatus == "idle")
		moveSpeed = 0;
	if (moveStatus == "walking")
		moveSpeed = walkSpeed;
	if (moveStatus == "running")
		moveSpeed = runSpeed;
	return moveSpeed;
}

function IsJumping () {
	return jumping;
}

function GetWalkSpeed () {
	return walkSpeed;
}
@script RequireComponent(CharacterController)

Thanks for the response matrix :slight_smile:

I’ve never actually used raycasting or linecasting before, so your example got me looking into it.

I think I nailed the WoW camera behaviour this time with the following script… right down to the way the camera lerps based on collision!

Since the last time I posted, I’ve switched to using C# to script for Unity. I tried to add as many comments as I could to explain my thought process. As always, I’m welcome to any feedback to improve it, be it in coding style, algorithm, or efficiency:

WowCamera.cs:

using UnityEngine;
using System.Collections;

public class WowCamera : MonoBehaviour
{

    public Transform target;
   
    public float targetHeight = 1.7f;
    public float distance = 5.0f;

    public float maxDistance = 20;
    public float minDistance = .6f;

    public float xSpeed = 250.0f;
    public float ySpeed = 120.0f;

    public int yMinLimit = -80;
    public int yMaxLimit = 80;

    public int zoomRate = 40;

    public float rotationDampening = 3.0f;
    public float zoomDampening = 5.0f;

    private float x = 0.0f;
    private float y = 0.0f;
    private float currentDistance;
    private float desiredDistance;
    private float correctedDistance;

	void Start ()
    {
        Vector3 angles = transform.eulerAngles;
        x = angles.x;
        y = angles.y;

        currentDistance = distance;
        desiredDistance = distance;
        correctedDistance = distance;

        // Make the rigid body not change rotation
        if (rigidbody)
            rigidbody.freezeRotation = true;
	}
	
    /**
     * Camera logic on LateUpdate to only update after all character movement logic has been handled.
     */
	void LateUpdate ()
    {
	    // Don't do anything if target is not defined
        if (!target)
            return;

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

        y = ClampAngle(y, yMinLimit, yMaxLimit);

        // set camera rotation
        Quaternion rotation = Quaternion.Euler(y, x, 0);

        // calculate the desired distance
        desiredDistance -= Input.GetAxis("Mouse ScrollWheel") * Time.deltaTime * zoomRate * Mathf.Abs(desiredDistance);
        desiredDistance = Mathf.Clamp(desiredDistance, minDistance, maxDistance);
        correctedDistance = desiredDistance;

        // calculate desired camera position
        Vector3 position = target.position - (rotation * Vector3.forward * desiredDistance + new Vector3(0, -targetHeight, 0));

        // check for collision using the true target's desired registration point as set by user using height
        RaycastHit collisionHit;
        Vector3 trueTargetPosition = new Vector3(target.position.x, target.position.y + targetHeight, target.position.z);

        // if there was a collision, correct the camera position and calculate the corrected distance
        bool isCorrected = false;
        if (Physics.Linecast(trueTargetPosition, position, out collisionHit))
        {
            position = collisionHit.point;
            correctedDistance = Vector3.Distance(trueTargetPosition, position);
            isCorrected = true;
        }
        
        // For smoothing, lerp distance only if either distance wasn't corrected, or correctedDistance is more than currentDistance
        currentDistance = !isCorrected || correctedDistance > currentDistance ? Mathf.Lerp(currentDistance, correctedDistance, Time.deltaTime * zoomDampening) : correctedDistance;

        // recalculate position based on the new currentDistance
        position = target.position - (rotation * Vector3.forward * currentDistance + new Vector3(0, -targetHeight, 0));

        transform.rotation = rotation;
        transform.position = position;
	}

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

My only problem with this is that it cuts thru the ground (The camera is at the same Y as the terrain, and therefore you see under the ground)

Which is why I used a

Physics.CheckSphere (position, .5)

Which gives it a radius of .5, and therefore should “skoot” along the ground and not clip.

Otherwise you are doing the “exact point” of the collision, which we do not want because the camera frame is taking half the screen depending on where the character is located.

well, you’ve got a ‘correctedDistance’ variable, all you’d have to do is reduce it a tad and you have that compensation :slight_smile:

Ok. You got an example? Brain is not firing at the moment (had a Army of Blue Ducks that I had to fix)