using UnityEngine;
using System.Collections;
/**
* @Author : www.xuanyusong.com
*/
[RequireComponent(typeof(CharacterController))]
publicclass ThirdPersonController : MonoBehaviour {
public AnimationClip idleAnimation ;
public AnimationClip walkAnimation ;
public AnimationClip runAnimation ;
public AnimationClip jumpPoseAnimation;
publicfloat walkMaxAnimationSpeed = 0.75f;
publicfloat trotMaxAnimationSpeed = 1.0f;
publicfloat runMaxAnimationSpeed = 1.0f;
publicfloat jumpAnimationSpeed = 1.15f;
publicfloat landAnimationSpeed = 1.0f;
private Animation _animation;
enum CharacterState
{
Idle = 0,
Walking = 1,
Trotting = 2,
Running = 3,
Jumping = 4,
}
private CharacterState _characterState;
// The speed when walkingfloat walkSpeed = 2.0f;
// after trotAfterSeconds of walking we trot with trotSpeedfloat trotSpeed = 4.0f;
// when pressing "Fire3" button (cmd) we start runningfloat runSpeed = 6.0f;
float inAirControlAcceleration = 3.0f;
// How high do we jump when pressing jump and letting go immediatelyfloat jumpHeight = 0.5f;
// The gravity for the characterfloat gravity = 20.0f;
// The gravity in controlled descent modefloat speedSmoothing = 10.0f;
float rotateSpeed = 500.0f;
float trotAfterSeconds = 3.0f;
bool canJump = true;
privatefloat jumpRepeatTime = 0.05f;
privatefloat jumpTimeout = 0.15f;
privatefloat groundedTimeout = 0.25f;
// The camera doesnt start following the target immediately but waits for a split second to avoid too much waving around.privatefloat lockCameraTimer = 0.0f;
// The current move direction in x-zprivate Vector3 moveDirection = Vector3.zero;
// The current vertical speedprivatefloat verticalSpeed = 0.0f;
// The current x-z move speedprivatefloat moveSpeed = 0.0f;
// The last collision flags returned from controller.Moveprivate CollisionFlags collisionFlags;
// Are we jumping? (Initiated with jump button and not grounded yet)privatebool jumping = false;
privatebool jumpingReachedApex = false;
// Are we moving backwards (This locks the camera to not do a 180 degree spin)privatebool movingBack = false;
// Is the user pressing any keys?privatebool isMoving = false;
// When did the user start walking (Used for going into trot after a while)privatefloat walkTimeStart = 0.0f;
// Last time the jump button was clicked downprivatefloat lastJumpButtonTime = -10.0f;
// Last time we performed a jumpprivatefloat lastJumpTime = -1.0f;
// the height we jumped from (Used to determine for how long to apply extra jump power after jumping.)privatefloat lastJumpStartHeight = 0.0f;
private Vector3 inAirVelocity = Vector3.zero;
privatefloat lastGroundedTime = 0.0f;
privatebool isControllable = true;
void Awake ()
{
moveDirection = transform.TransformDirection(Vector3.forward);
_animation = GetComponent<Animation>();
if(!_animation)
Debug.Log("The character you would like to control doesn't have animations. Moving her might look weird.");
/*
public var idleAnimation : AnimationClip;
public var walkAnimation : AnimationClip;
public var runAnimation : AnimationClip;
public var jumpPoseAnimation : AnimationClip;
*/if(!idleAnimation) {
_animation = null;
Debug.Log("No idle animation found. Turning off animations.");
}
if(!walkAnimation) {
_animation = null;
Debug.Log("No walk animation found. Turning off animations.");
}
if(!runAnimation) {
_animation = null;
Debug.Log("No run animation found. Turning off animations.");
}
if(!jumpPoseAnimation && canJump) {
_animation = null;
Debug.Log("No jump animation found and the character has canJump enabled. Turning off animations.");
}
}
void UpdateSmoothedMovementDirection ()
{
Transform cameraTransform = Camera.main.transform;
bool grounded = IsGrounded();
// Forward vector relative to the camera along the x-z plane
Vector3 forward = cameraTransform.TransformDirection(Vector3.forward);
forward.y = 0;
forward = forward.normalized;
// Right vector relative to the camera// Always orthogonal to the forward vector
Vector3 right = new Vector3(forward.z, 0, -forward.x);
float v = Input.GetAxisRaw("Vertical");
float h = Input.GetAxisRaw("Horizontal");
// Are we moving backwards or looking backwardsif (v < -0.2f)
movingBack = true;
else
movingBack = false;
bool wasMoving = isMoving;
isMoving = Mathf.Abs (h) > 0.1f || Mathf.Abs (v) > 0.1f;
// Target direction relative to the camera
Vector3 targetDirection = h * right + v * forward;
// Grounded controlsif (grounded)
{
// Lock camera for short period when transitioning moving & standing still
lockCameraTimer += Time.deltaTime;
if (isMoving != wasMoving)
lockCameraTimer = 0.0f;
// 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 directionif (moveSpeed < walkSpeed * 0.9f && grounded)
{
moveDirection = targetDirection.normalized;
}
// Otherwise smoothly turn towards itelse
{
moveDirection = Vector3.RotateTowards(moveDirection, targetDirection, rotateSpeed * Mathf.Deg2Rad * Time.deltaTime, 1000);
moveDirection = moveDirection.normalized;
}
}
// Smooth the speed based on the current target directionfloat 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 sidewaysfloat targetSpeed = Mathf.Min(targetDirection.magnitude, 1.0f);
_characterState = CharacterState.Idle;
// Pick speed modifierif (Input.GetKey (KeyCode.LeftShift) | Input.GetKey (KeyCode.RightShift) | isMoving)
{
targetSpeed *= runSpeed;
_characterState = CharacterState.Running;
}
elseif (Time.time - trotAfterSeconds > walkTimeStart)
{
targetSpeed *= trotSpeed;
_characterState = CharacterState.Trotting;
}
else
{
targetSpeed *= walkSpeed;
_characterState = CharacterState.Walking;
}
moveSpeed = Mathf.Lerp(moveSpeed, targetSpeed, curSmooth);
// Reset walk time start when we slow downif (moveSpeed < walkSpeed * 0.3f)
walkTimeStart = Time.time;
}
// In air controlselse
{
// Lock camera while in airif (jumping)
lockCameraTimer = 0.0f;
if (isMoving)
inAirVelocity += targetDirection.normalized * Time.deltaTime * inAirControlAcceleration;
}
}
void ApplyJumping ()
{
// Prevent jumping too fast after each otherif (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);
}
}
}
void ApplyGravity ()
{
if (isControllable) // don't move player at all if not controllable.
{
// Apply gravitybool jumpButton = Input.GetButton("Jump");
// When we reach the apex of the jump we send out a messageif (jumping && !jumpingReachedApex && verticalSpeed <= 0.0f)
{
jumpingReachedApex = true;
SendMessage("DidJumpReachApex", SendMessageOptions.DontRequireReceiver);
}
if (IsGrounded ())
verticalSpeed = 0.0f;
else
verticalSpeed -= gravity * Time.deltaTime;
}
}
float CalculateJumpVerticalSpeed (float targetJumpHeight)
{
// 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);
}
void DidJump ()
{
jumping = true;
jumpingReachedApex = false;
lastJumpTime = Time.time;
lastJumpStartHeight = transform.position.y;
lastJumpButtonTime = -10;
_characterState = CharacterState.Jumping;
}
void 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 ();
// Apply jumping logic
ApplyJumping ();
if(IsMoving())
{
var newPos = transform.position + (transform.rotation * Vector3.forward * moveSpeed);
newPos.y = Terrain.activeTerrain.SampleHeight(newPos);
var heropos = transform.position;
heropos.y = Terrain.activeTerrain.SampleHeight(transform.position);
Debug.DrawLine(heropos,newPos,Color.red);
var c = moveSpeed;
var b = newPos.y - heropos.y;
if(b > 0)
{
var a = Mathf.Sqrt(Mathf.Pow(c,2) - Mathf.Pow(b,2));
moveSpeed = a;
}
}
// Calculate actual motion
Vector3 movement = moveDirection * moveSpeed + new Vector3 (0, verticalSpeed, 0) + inAirVelocity;
movement *= Time.deltaTime;
// Move the controller
CharacterController controller = GetComponent<CharacterController>();
collisionFlags = controller.Move(movement);
// ANIMATION sectorif(_animation) {
if(_characterState == CharacterState.Jumping)
{
if(!jumpingReachedApex) {
_animation[jumpPoseAnimation.name].speed = jumpAnimationSpeed;
_animation[jumpPoseAnimation.name].wrapMode = WrapMode.ClampForever;
_animation.CrossFade(jumpPoseAnimation.name);
} else {
_animation[jumpPoseAnimation.name].speed = -landAnimationSpeed;
_animation[jumpPoseAnimation.name].wrapMode = WrapMode.ClampForever;
_animation.CrossFade(jumpPoseAnimation.name);
}
}
else
{
if(controller.velocity.sqrMagnitude < 0.1f) {
_animation.CrossFade(idleAnimation.name);
}
else
{
if(_characterState == CharacterState.Running) {
_animation[runAnimation.name].speed = Mathf.Clamp(controller.velocity.magnitude, 0.0f, runMaxAnimationSpeed);
_animation.CrossFade(runAnimation.name);
}
elseif(_characterState == CharacterState.Trotting) {
_animation[walkAnimation.name].speed = Mathf.Clamp(controller.velocity.magnitude, 0.0f, trotMaxAnimationSpeed);
_animation.CrossFade(walkAnimation.name);
}
elseif(_characterState == CharacterState.Walking) {
_animation[walkAnimation.name].speed = Mathf.Clamp(controller.velocity.magnitude, 0.0f, walkMaxAnimationSpeed);
_animation.CrossFade(walkAnimation.name);
}
}
}
}
// ANIMATION sector// Set rotation to the move directionif (IsGrounded())
{
transform.rotation = Quaternion.LookRotation(moveDirection);
}
else
{
Vector3 xzMove = movement;
xzMove.y = 0;
if (xzMove.sqrMagnitude > 0.001f)
{
transform.rotation = Quaternion.LookRotation(xzMove);
}
}
// We are in jump mode but just became groundedif (IsGrounded())
{
lastGroundedTime = Time.time;
inAirVelocity = Vector3.zero;
if (jumping)
{
jumping = false;
SendMessage("DidLand", SendMessageOptions.DontRequireReceiver);
}
}
}
void OnControllerColliderHit (ControllerColliderHit hit )
{
// Debug.DrawRay(hit.point, hit.normal);if (hit.moveDirection.y > 0.01f)
return;
}
float GetSpeed () {
return moveSpeed;
}
publicboolIsJumping () {
return jumping;
}
bool IsGrounded () {
return (collisionFlags & CollisionFlags.CollidedBelow) != 0;
}
Vector3 GetDirection () {
return moveDirection;
}
publicboolIsMovingBackwards () {
return movingBack;
}
publicfloatGetLockCameraTimer ()
{
return lockCameraTimer;
}
bool IsMoving ()
{
return Mathf.Abs(Input.GetAxisRaw("Vertical")) + Mathf.Abs(Input.GetAxisRaw("Horizontal")) > 0.5f;
}
bool HasJumpReachedApex ()
{
return jumpingReachedApex;
}
bool IsGroundedWithTimeout ()
{
return lastGroundedTime + groundedTimeout > Time.time;
}
void Reset ()
{
gameObject.tag = "Player";
}
}