// 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"; }