Files
2026-05-23 14:12:25 +02:00

391 lines
10 KiB
C#

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<Animation>();
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<CharacterController>();
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";
}
}