Files
2026-03-04 10:03:45 +08:00

310 lines
8.5 KiB
C#

using UnityEngine;
using UnityEngine.InputSystem;
namespace StarterAssets
{
[RequireComponent(typeof(CharacterController))]
[RequireComponent(typeof(PlayerInput))]
public class ThirdPersonController : MonoBehaviour
{
[Header("Player")]
[Tooltip("Move speed of the character in m/s")]
public float MoveSpeed = 2f;
[Tooltip("Sprint speed of the character in m/s")]
public float SprintSpeed = 5.335f;
[Tooltip("How fast the character turns to face movement direction")]
[Range(0f, 0.3f)]
public float RotationSmoothTime = 0.12f;
[Tooltip("Acceleration and deceleration")]
public float SpeedChangeRate = 10f;
public AudioClip LandingAudioClip;
public AudioClip[] FootstepAudioClips;
[Range(0f, 1f)]
public float FootstepAudioVolume = 0.5f;
[Space(10f)]
[Tooltip("The height the player can jump")]
public float JumpHeight = 1.2f;
[Tooltip("The character uses its own gravity value. The engine default is -9.81f")]
public float Gravity = -15f;
[Space(10f)]
[Tooltip("Time required to pass before being able to jump again. Set to 0f to instantly jump again")]
public float JumpTimeout = 0.5f;
[Tooltip("Time required to pass before entering the fall state. Useful for walking down stairs")]
public float FallTimeout = 0.15f;
[Header("Player Grounded")]
[Tooltip("If the character is grounded or not. Not part of the CharacterController built in grounded check")]
public bool Grounded = true;
[Tooltip("Useful for rough ground")]
public float GroundedOffset = -0.14f;
[Tooltip("The radius of the grounded check. Should match the radius of the CharacterController")]
public float GroundedRadius = 0.28f;
[Tooltip("What layers the character uses as ground")]
public LayerMask GroundLayers;
[Header("Cinemachine")]
[Tooltip("The follow target set in the Cinemachine Virtual Camera that the camera will follow")]
public GameObject CinemachineCameraTarget;
[Tooltip("How far in degrees can you move the camera up")]
public float TopClamp = 70f;
[Tooltip("How far in degrees can you move the camera down")]
public float BottomClamp = -30f;
[Tooltip("Additional degress to override the camera. Useful for fine tuning camera position when locked")]
public float CameraAngleOverride;
[Tooltip("For locking the camera position on all axis")]
public bool LockCameraPosition;
private float _cinemachineTargetYaw;
private float _cinemachineTargetPitch;
private float _speed;
private float _animationBlend;
private float _targetRotation;
private float _rotationVelocity;
private float _verticalVelocity;
private float _terminalVelocity = 53f;
private float _jumpTimeoutDelta;
private float _fallTimeoutDelta;
private int _animIDSpeed;
private int _animIDGrounded;
private int _animIDJump;
private int _animIDFreeFall;
private int _animIDMotionSpeed;
private Animator _animator;
private CharacterController _controller;
private GameObject _mainCamera;
private const float _threshold = 0.01f;
private bool _hasAnimator;
private bool IsCurrentDeviceMouse => true;
private void Awake()
{
if (_mainCamera == null)
{
_mainCamera = GameObject.FindGameObjectWithTag("MainCamera");
}
}
private void Start()
{
_cinemachineTargetYaw = CinemachineCameraTarget.transform.rotation.eulerAngles.y;
_hasAnimator = TryGetComponent<Animator>(out _animator);
_controller = GetComponent<CharacterController>();
AssignAnimationIDs();
_jumpTimeoutDelta = JumpTimeout;
_fallTimeoutDelta = FallTimeout;
}
private void Update()
{
_hasAnimator = TryGetComponent<Animator>(out _animator);
JumpAndGravity();
GroundedCheck();
Move();
}
private void LateUpdate()
{
CameraRotation();
}
private void AssignAnimationIDs()
{
_animIDSpeed = Animator.StringToHash("Speed");
_animIDGrounded = Animator.StringToHash("Grounded");
_animIDJump = Animator.StringToHash("Jump");
_animIDFreeFall = Animator.StringToHash("FreeFall");
_animIDMotionSpeed = Animator.StringToHash("MotionSpeed");
}
private void GroundedCheck()
{
Vector3 position = new Vector3(base.transform.position.x, base.transform.position.y - GroundedOffset, base.transform.position.z);
Grounded = Physics.CheckSphere(position, GroundedRadius, GroundLayers, QueryTriggerInteraction.Ignore);
if (_hasAnimator)
{
_animator.SetBool(_animIDGrounded, Grounded);
}
}
private void CameraRotation()
{
Vector2 vector = new Vector2(Input.GetAxis("Mouse X"), 0f - Input.GetAxis("Mouse Y"));
if (vector.sqrMagnitude >= 0.01f && !LockCameraPosition)
{
float num = (IsCurrentDeviceMouse ? 1f : Time.deltaTime);
_cinemachineTargetYaw += vector.x * num;
_cinemachineTargetPitch += vector.y * num;
}
_cinemachineTargetYaw = ClampAngle(_cinemachineTargetYaw, float.MinValue, float.MaxValue);
_cinemachineTargetPitch = ClampAngle(_cinemachineTargetPitch, BottomClamp, TopClamp);
CinemachineCameraTarget.transform.rotation = Quaternion.Euler(_cinemachineTargetPitch + CameraAngleOverride, _cinemachineTargetYaw, 0f);
}
private void Move()
{
float num = ((Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) ? SprintSpeed : MoveSpeed);
Vector2 vector = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
if (vector == Vector2.zero)
{
num = 0f;
}
float magnitude = new Vector3(_controller.velocity.x, 0f, _controller.velocity.z).magnitude;
float num2 = 0.1f;
float magnitude2 = vector.magnitude;
if (magnitude < num - num2 || magnitude > num + num2)
{
_speed = Mathf.Lerp(magnitude, num * magnitude2, Time.deltaTime * SpeedChangeRate);
_speed = Mathf.Round(_speed * 1000f) / 1000f;
}
else
{
_speed = num;
}
_animationBlend = Mathf.Lerp(_animationBlend, num, Time.deltaTime * SpeedChangeRate);
if (_animationBlend < 0.01f)
{
_animationBlend = 0f;
}
Vector3 normalized = new Vector3(vector.x, 0f, vector.y).normalized;
if (vector != Vector2.zero)
{
_targetRotation = Mathf.Atan2(normalized.x, normalized.z) * 57.29578f + _mainCamera.transform.eulerAngles.y;
float y = Mathf.SmoothDampAngle(base.transform.eulerAngles.y, _targetRotation, ref _rotationVelocity, RotationSmoothTime);
base.transform.rotation = Quaternion.Euler(0f, y, 0f);
}
Vector3 vector2 = Quaternion.Euler(0f, _targetRotation, 0f) * Vector3.forward;
_controller.Move(vector2.normalized * (_speed * Time.deltaTime) + new Vector3(0f, _verticalVelocity, 0f) * Time.deltaTime);
if (_hasAnimator)
{
_animator.SetFloat(_animIDSpeed, _animationBlend);
_animator.SetFloat(_animIDMotionSpeed, magnitude2);
}
}
private void JumpAndGravity()
{
bool keyDown = Input.GetKeyDown(KeyCode.Space);
if (Grounded)
{
_fallTimeoutDelta = FallTimeout;
if (_hasAnimator)
{
_animator.SetBool(_animIDJump, value: false);
_animator.SetBool(_animIDFreeFall, value: false);
}
if (_verticalVelocity < 0f)
{
_verticalVelocity = -2f;
}
if (keyDown && _jumpTimeoutDelta <= 0f)
{
_verticalVelocity = Mathf.Sqrt(JumpHeight * -2f * Gravity);
if (_hasAnimator)
{
_animator.SetBool(_animIDJump, value: true);
}
}
if (_jumpTimeoutDelta >= 0f)
{
_jumpTimeoutDelta -= Time.deltaTime;
}
}
else
{
_jumpTimeoutDelta = JumpTimeout;
if (_fallTimeoutDelta >= 0f)
{
_fallTimeoutDelta -= Time.deltaTime;
}
else if (_hasAnimator)
{
_animator.SetBool(_animIDFreeFall, value: true);
}
}
if (_verticalVelocity < _terminalVelocity)
{
_verticalVelocity += Gravity * Time.deltaTime;
}
}
private static float ClampAngle(float lfAngle, float lfMin, float lfMax)
{
if (lfAngle < -360f)
{
lfAngle += 360f;
}
if (lfAngle > 360f)
{
lfAngle -= 360f;
}
return Mathf.Clamp(lfAngle, lfMin, lfMax);
}
private void OnDrawGizmosSelected()
{
Color color = new Color(0f, 1f, 0f, 0.35f);
Color color2 = new Color(1f, 0f, 0f, 0.35f);
if (Grounded)
{
Gizmos.color = color;
}
else
{
Gizmos.color = color2;
}
Gizmos.DrawSphere(new Vector3(base.transform.position.x, base.transform.position.y - GroundedOffset, base.transform.position.z), GroundedRadius);
}
private void OnFootstep(AnimationEvent animationEvent)
{
if (animationEvent.animatorClipInfo.weight > 0.5f && FootstepAudioClips.Length != 0)
{
Random.Range(0, FootstepAudioClips.Length);
}
}
private void OnLand(AnimationEvent animationEvent)
{
_ = animationEvent.animatorClipInfo.weight;
_ = 0.5f;
}
}
}