|
|
// Require a character controller to be attached to the same game object
|
|
@script RequireComponent(CharacterController)
|
|
|
|
public var idleAnimation : AnimationClip;
|
|
public var walkAnimation : AnimationClip;
|
|
public var runAnimation : AnimationClip;
|
|
public var jumpPoseAnimation : AnimationClip;
|
|
|
|
public var walkMaxAnimationSpeed : float = 0.75;
|
|
public var trotMaxAnimationSpeed : float = 1.0;
|
|
public var runMaxAnimationSpeed : float = 1.0;
|
|
public var jumpAnimationSpeed : float = 1.15;
|
|
public var landAnimationSpeed : float = 1.0;
|
|
|
|
private var _animation : Animation;
|
|
|
|
enum CharacterState {
|
|
Idle = 0,
|
|
Walking = 1,
|
|
Trotting = 2,
|
|
Running = 3,
|
|
Jumping = 4,
|
|
}
|
|
|
|
private var _characterState : CharacterState;
|
|
|
|
// The speed when walking
|
|
var walkSpeed = 2.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;
|
|
|
|
// The gravity for the character
|
|
var gravity = 20.0;
|
|
// The gravity in controlled descent mode
|
|
var speedSmoothing = 10.0;
|
|
var rotateSpeed = 500.0;
|
|
var trotAfterSeconds = 3.0;
|
|
|
|
var canJump = true;
|
|
|
|
private var jumpRepeatTime = 0.05;
|
|
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;
|
|
|
|
|
|
// the height we jumped from (Used to determine for how long to apply extra jump power after jumping.)
|
|
private var lastJumpStartHeight = 0.0;
|
|
|
|
|
|
private var inAirVelocity = Vector3.zero;
|
|
|
|
private var lastGroundedTime = 0.0;
|
|
|
|
|
|
private var isControllable = true;
|
|
|
|
function 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.");
|
|
}
|
|
|
|
}
|
|
|
|
|
|
function UpdateSmoothedMovementDirection ()
|
|
{
|
|
var cameraTransform = Camera.main.transform;
|
|
var grounded = IsGrounded();
|
|
|
|
// Forward vector relative to the camera along the x-z plane
|
|
var forward = cameraTransform.TransformDirection(Vector3.forward);
|
|
forward.y = 0;
|
|
forward = forward.normalized;
|
|
|
|
// Right vector relative to the camera
|
|
// Always orthogonal to the forward vector
|
|
var right = Vector3(forward.z, 0, -forward.x);
|
|
|
|
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 + v * forward;
|
|
|
|
// Grounded controls
|
|
if (grounded)
|
|
{
|
|
// Lock camera for short period when transitioning moving & standing still
|
|
lockCameraTimer += Time.deltaTime;
|
|
if (isMoving != wasMoving)
|
|
lockCameraTimer = 0.0;
|
|
|
|
// 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 = targetDirection.normalized;
|
|
}
|
|
// Otherwise smoothly turn towards it
|
|
else
|
|
{
|
|
moveDirection = Vector3.RotateTowards(moveDirection, targetDirection, rotateSpeed * Mathf.Deg2Rad * Time.deltaTime, 1000);
|
|
|
|
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);
|
|
|
|
_characterState = CharacterState.Idle;
|
|
|
|
// Pick speed modifier
|
|
if (Input.GetKey (KeyCode.LeftShift) || Input.GetKey (KeyCode.RightShift))
|
|
{
|
|
targetSpeed *= runSpeed;
|
|
_characterState = CharacterState.Running;
|
|
}
|
|
else if (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 down
|
|
if (moveSpeed < walkSpeed * 0.3)
|
|
walkTimeStart = Time.time;
|
|
}
|
|
// In air controls
|
|
else
|
|
{
|
|
// Lock camera while in air
|
|
if (jumping)
|
|
lockCameraTimer = 0.0;
|
|
|
|
if (isMoving)
|
|
inAirVelocity += targetDirection.normalized * Time.deltaTime * inAirControlAcceleration;
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
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 we reach the apex of the jump we send out a message
|
|
if (jumping && !jumpingReachedApex && verticalSpeed <= 0.0)
|
|
{
|
|
jumpingReachedApex = true;
|
|
SendMessage("DidJumpReachApex", SendMessageOptions.DontRequireReceiver);
|
|
}
|
|
|
|
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;
|
|
lastJumpButtonTime = -10;
|
|
|
|
_characterState = CharacterState.Jumping;
|
|
}
|
|
|
|
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 ();
|
|
|
|
// Apply jumping logic
|
|
ApplyJumping ();
|
|
|
|
// Calculate actual motion
|
|
var movement = moveDirection * moveSpeed + Vector3 (0, verticalSpeed, 0) + inAirVelocity;
|
|
movement *= Time.deltaTime;
|
|
|
|
// Move the controller
|
|
var controller : CharacterController = GetComponent(CharacterController);
|
|
collisionFlags = controller.Move(movement);
|
|
|
|
// ANIMATION sector
|
|
if(_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.1) {
|
|
_animation.CrossFade(idleAnimation.name);
|
|
}
|
|
else
|
|
{
|
|
if(_characterState == CharacterState.Running) {
|
|
_animation[runAnimation.name].speed = Mathf.Clamp(controller.velocity.magnitude, 0.0, runMaxAnimationSpeed);
|
|
_animation.CrossFade(runAnimation.name);
|
|
}
|
|
else if(_characterState == CharacterState.Trotting) {
|
|
_animation[walkAnimation.name].speed = Mathf.Clamp(controller.velocity.magnitude, 0.0, trotMaxAnimationSpeed);
|
|
_animation.CrossFade(walkAnimation.name);
|
|
}
|
|
else if(_characterState == CharacterState.Walking) {
|
|
_animation[walkAnimation.name].speed = Mathf.Clamp(controller.velocity.magnitude, 0.0, walkMaxAnimationSpeed);
|
|
_animation.CrossFade(walkAnimation.name);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
// ANIMATION sector
|
|
|
|
// Set rotation to the move direction
|
|
if (IsGrounded())
|
|
{
|
|
|
|
transform.rotation = Quaternion.LookRotation(moveDirection);
|
|
|
|
}
|
|
else
|
|
{
|
|
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 )
|
|
{
|
|
// Debug.DrawRay(hit.point, hit.normal);
|
|
if (hit.moveDirection.y > 0.01)
|
|
return;
|
|
}
|
|
|
|
function GetSpeed () {
|
|
return moveSpeed;
|
|
}
|
|
|
|
function IsJumping () {
|
|
return jumping;
|
|
}
|
|
|
|
function IsGrounded () {
|
|
return (collisionFlags & CollisionFlags.CollidedBelow) != 0;
|
|
}
|
|
|
|
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 Reset ()
|
|
{
|
|
gameObject.tag = "Player";
|
|
}
|
|
|