using System; // using System.Collections.Generic; // using Boo.Lang; using UnityEngine; [Serializable] [RequireComponent(typeof(CharacterController))] public class ThirdPersonController : MonoBehaviour { public AnimationClip idleAnimation; public AnimationClip walkAnimation; public AnimationClip runAnimation; public AnimationClip jumpPoseAnimation; public float walkMaxAnimationSpeed = 0.75f; public float trotMaxAnimationSpeed = 1f; public float runMaxAnimationSpeed = 1f; public float jumpAnimationSpeed = 1.15f; public float landAnimationSpeed = 1f; private Animation _animation; private CharacterState _characterState; public float walkSpeed = 2f; public float trotSpeed = 4f; public float runSpeed = 6f; public float inAirControlAcceleration = 3f; public float jumpHeight = 0.5f; public float gravity = 20f; public float speedSmoothing = 10f; public float rotateSpeed = 500f; public float trotAfterSeconds = 3f; public bool canJump = true; private float jumpRepeatTime = 0.05f; private float jumpTimeout = 0.15f; private float groundedTimeout = 0.25f; private float lockCameraTimer = 0f; private Vector3 moveDirection = Vector3.zero; private float verticalSpeed = 0f; private float moveSpeed = 0f; private CollisionFlags collisionFlags; private bool jumping = false; private bool jumpingReachedApex = false; private bool movingBack = false; private bool isMoving = false; private float walkTimeStart = 0f; private float lastJumpButtonTime = -10f; private float lastJumpTime = -1f; private float lastJumpStartHeight = 0f; private Vector3 inAirVelocity = Vector3.zero; private float lastGroundedTime = 0f; private bool isControllable = true; void Awake() { moveDirection = transform.TransformDirection(Vector3.forward); _animation = GetComponent(); if (_animation == null) { Debug.LogWarning("No Animation component found."); return; } if (!idleAnimation) { Debug.LogWarning("No idle animation found."); _animation = null; } if (!walkAnimation) { Debug.LogWarning("No walk animation found."); _animation = null; } if (!runAnimation) { Debug.LogWarning("No run animation found."); _animation = null; } if (!jumpPoseAnimation && canJump) { Debug.LogWarning("No jump animation found."); _animation = null; } } void UpdateSmoothedMovementDirection() { Transform cameraTransform = Camera.main.transform; bool grounded = IsGrounded(); Vector3 forward = cameraTransform.TransformDirection(Vector3.forward); forward.y = 0; forward.Normalize(); Vector3 right = new Vector3(forward.z, 0, -forward.x); float v = Input.GetAxisRaw("Vertical"); float h = Input.GetAxisRaw("Horizontal"); movingBack = v < -0.2f; bool wasMoving = isMoving; isMoving = Mathf.Abs(h) > 0.1f || Mathf.Abs(v) > 0.1f; Vector3 targetDirection = h * right + v * forward; if (grounded) { lockCameraTimer += Time.deltaTime; if (isMoving != wasMoving) lockCameraTimer = 0f; if (targetDirection != Vector3.zero) { if (moveSpeed < walkSpeed * 0.9f) { moveDirection = targetDirection.normalized; } else { moveDirection = Vector3.RotateTowards( moveDirection, targetDirection, rotateSpeed * Mathf.Deg2Rad * Time.deltaTime, 1000 ).normalized; } } float smooth = speedSmoothing * Time.deltaTime; float targetSpeed = Mathf.Min(targetDirection.magnitude, 1f); _characterState = CharacterState.Idle; if (Input.GetButton("Jog/Sprint")) { 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, smooth); if (moveSpeed < walkSpeed * 0.3f) walkTimeStart = Time.time; } else { if (jumping) lockCameraTimer = 0f; if (isMoving) { inAirVelocity += targetDirection.normalized * Time.deltaTime * inAirControlAcceleration; } } } void ApplyJumping() { if ( lastJumpTime + jumpRepeatTime <= Time.time && IsGrounded() && canJump && Time.time < lastJumpButtonTime + jumpTimeout ) { verticalSpeed = CalculateJumpVerticalSpeed(jumpHeight); SendMessage("DidJump", SendMessageOptions.DontRequireReceiver); } } void ApplyGravity() { if (!isControllable) return; if (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) { return Mathf.Sqrt(2f * targetJumpHeight * gravity); } void DidJump() { jumping = true; jumpingReachedApex = false; lastJumpTime = Time.time; lastJumpStartHeight = transform.position.y; lastJumpButtonTime = -10; _characterState = CharacterState.Jumping; } void Update() { if (!isControllable) Input.ResetInputAxes(); if (Input.GetButtonDown("Jump")) lastJumpButtonTime = Time.time; UpdateSmoothedMovementDirection(); ApplyGravity(); ApplyJumping(); Vector3 movement = moveDirection * moveSpeed + Vector3.up * verticalSpeed + inAirVelocity; movement *= Time.deltaTime; CharacterController controller = GetComponent(); collisionFlags = controller.Move(movement); UpdateAnimation(controller); if (IsGrounded()) { transform.rotation = Quaternion.LookRotation(moveDirection); } else { Vector3 dir = movement; dir.y = 0; if (dir.sqrMagnitude > 0.001f) transform.rotation = Quaternion.LookRotation(dir); } if (IsGrounded()) { lastGroundedTime = Time.time; inAirVelocity = Vector3.zero; if (jumping) { jumping = false; SendMessage("DidLand", SendMessageOptions.DontRequireReceiver); } } } void UpdateAnimation(CharacterController controller) { if (_animation == null) return; 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, runMaxAnimationSpeed); _animation.CrossFade(runAnimation.name); } else if (_characterState == CharacterState.Trotting) { _animation[walkAnimation.name].speed = Mathf.Clamp(controller.velocity.magnitude, 0, trotMaxAnimationSpeed); _animation.CrossFade(walkAnimation.name); } else if (_characterState == CharacterState.Walking) { _animation[walkAnimation.name].speed = Mathf.Clamp(controller.velocity.magnitude, 0, walkMaxAnimationSpeed); _animation.CrossFade(walkAnimation.name); } } void OnControllerColliderHit(ControllerColliderHit hit) { } public float GetSpeed() { return moveSpeed; } public bool IsJumping() { return jumping; } public bool IsGrounded() { return (collisionFlags & CollisionFlags.Below) != 0; } public Vector3 GetDirection() { return moveDirection; } public bool IsMovingBackwards() { return movingBack; } public float GetLockCameraTimer() { return lockCameraTimer; } public bool IsMoving() { return Mathf.Abs(Input.GetAxisRaw("Vertical")) + Mathf.Abs(Input.GetAxisRaw("Horizontal")) > 0.5f; } public bool HasJumpReachedApex() { return jumpingReachedApex; } public bool IsGroundedWithTimeout() { return lastGroundedTime + groundedTimeout > Time.time; } void Reset() { gameObject.tag = "Player"; } }