FirstPersonController.cs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. using UnityEngine;
  2. #if ENABLE_INPUT_SYSTEM
  3. using UnityEngine.InputSystem;
  4. #endif
  5. namespace StarterAssets
  6. {
  7. [RequireComponent(typeof(CharacterController))]
  8. #if ENABLE_INPUT_SYSTEM
  9. [RequireComponent(typeof(PlayerInput))]
  10. #endif
  11. public class FirstPersonController : MonoBehaviour
  12. {
  13. [Header("Player")]
  14. [Tooltip("Move speed of the character in m/s")]
  15. public float MoveSpeed = 4.0f;
  16. [Tooltip("Sprint speed of the character in m/s")]
  17. public float SprintSpeed = 6.0f;
  18. [Tooltip("Rotation speed of the character")]
  19. public float RotationSpeed = 1.0f;
  20. [Tooltip("Acceleration and deceleration")]
  21. public float SpeedChangeRate = 10.0f;
  22. [Space(10)]
  23. [Tooltip("The height the player can jump")]
  24. public float JumpHeight = 1.2f;
  25. [Tooltip("The character uses its own gravity value. The engine default is -9.81f")]
  26. public float Gravity = -15.0f;
  27. [Space(10)]
  28. [Tooltip("Time required to pass before being able to jump again. Set to 0f to instantly jump again")]
  29. public float JumpTimeout = 0.1f;
  30. [Tooltip("Time required to pass before entering the fall state. Useful for walking down stairs")]
  31. public float FallTimeout = 0.15f;
  32. [Header("Player Grounded")]
  33. [Tooltip("If the character is grounded or not. Not part of the CharacterController built in grounded check")]
  34. public bool Grounded = true;
  35. [Tooltip("Useful for rough ground")]
  36. public float GroundedOffset = -0.14f;
  37. [Tooltip("The radius of the grounded check. Should match the radius of the CharacterController")]
  38. public float GroundedRadius = 0.5f;
  39. [Tooltip("What layers the character uses as ground")]
  40. public LayerMask GroundLayers;
  41. [Header("Cinemachine")]
  42. [Tooltip("The follow target set in the Cinemachine Virtual Camera that the camera will follow")]
  43. public GameObject CinemachineCameraTarget;
  44. [Tooltip("How far in degrees can you move the camera up")]
  45. public float TopClamp = 90.0f;
  46. [Tooltip("How far in degrees can you move the camera down")]
  47. public float BottomClamp = -90.0f;
  48. // cinemachine
  49. private float _cinemachineTargetPitch;
  50. // player
  51. private float _speed;
  52. private float _rotationVelocity;
  53. private float _verticalVelocity;
  54. private float _terminalVelocity = 53.0f;
  55. // timeout deltatime
  56. private float _jumpTimeoutDelta;
  57. private float _fallTimeoutDelta;
  58. #if ENABLE_INPUT_SYSTEM
  59. private PlayerInput _playerInput;
  60. #endif
  61. private CharacterController _controller;
  62. private StarterAssetsInputs _input;
  63. private GameObject _mainCamera;
  64. private const float _threshold = 0.01f;
  65. private bool IsCurrentDeviceMouse
  66. {
  67. get
  68. {
  69. #if ENABLE_INPUT_SYSTEM
  70. return _playerInput.currentControlScheme == "KeyboardMouse";
  71. #else
  72. return false;
  73. #endif
  74. }
  75. }
  76. private void Awake()
  77. {
  78. // get a reference to our main camera
  79. if (_mainCamera == null)
  80. {
  81. _mainCamera = GameObject.FindGameObjectWithTag("MainCamera");
  82. }
  83. }
  84. private void Start()
  85. {
  86. _controller = GetComponent<CharacterController>();
  87. _input = GetComponent<StarterAssetsInputs>();
  88. #if ENABLE_INPUT_SYSTEM
  89. _playerInput = GetComponent<PlayerInput>();
  90. #else
  91. Debug.LogError( "Starter Assets package is missing dependencies. Please use Tools/Starter Assets/Reinstall Dependencies to fix it");
  92. #endif
  93. // reset our timeouts on start
  94. _jumpTimeoutDelta = JumpTimeout;
  95. _fallTimeoutDelta = FallTimeout;
  96. }
  97. private void Update()
  98. {
  99. JumpAndGravity();
  100. GroundedCheck();
  101. Move();
  102. }
  103. private void LateUpdate()
  104. {
  105. CameraRotation();
  106. }
  107. private void GroundedCheck()
  108. {
  109. // set sphere position, with offset
  110. Vector3 spherePosition = new Vector3(transform.position.x, transform.position.y - GroundedOffset, transform.position.z);
  111. Grounded = Physics.CheckSphere(spherePosition, GroundedRadius, GroundLayers, QueryTriggerInteraction.Ignore);
  112. }
  113. private void CameraRotation()
  114. {
  115. // if there is an input
  116. if (_input.look.sqrMagnitude >= _threshold)
  117. {
  118. //Don't multiply mouse input by Time.deltaTime
  119. float deltaTimeMultiplier = IsCurrentDeviceMouse ? 1.0f : Time.deltaTime;
  120. _cinemachineTargetPitch += _input.look.y * RotationSpeed * deltaTimeMultiplier;
  121. _rotationVelocity = _input.look.x * RotationSpeed * deltaTimeMultiplier;
  122. // clamp our pitch rotation
  123. _cinemachineTargetPitch = ClampAngle(_cinemachineTargetPitch, BottomClamp, TopClamp);
  124. // Update Cinemachine camera target pitch
  125. CinemachineCameraTarget.transform.localRotation = Quaternion.Euler(_cinemachineTargetPitch, 0.0f, 0.0f);
  126. // rotate the player left and right
  127. transform.Rotate(Vector3.up * _rotationVelocity);
  128. }
  129. }
  130. private void Move()
  131. {
  132. // set target speed based on move speed, sprint speed and if sprint is pressed
  133. float targetSpeed = _input.sprint ? SprintSpeed : MoveSpeed;
  134. // a simplistic acceleration and deceleration designed to be easy to remove, replace, or iterate upon
  135. // note: Vector2's == operator uses approximation so is not floating point error prone, and is cheaper than magnitude
  136. // if there is no input, set the target speed to 0
  137. if (_input.move == Vector2.zero) targetSpeed = 0.0f;
  138. // a reference to the players current horizontal velocity
  139. float currentHorizontalSpeed = new Vector3(_controller.velocity.x, 0.0f, _controller.velocity.z).magnitude;
  140. float speedOffset = 0.1f;
  141. float inputMagnitude = _input.analogMovement ? _input.move.magnitude : 1f;
  142. // accelerate or decelerate to target speed
  143. if (currentHorizontalSpeed < targetSpeed - speedOffset || currentHorizontalSpeed > targetSpeed + speedOffset)
  144. {
  145. // creates curved result rather than a linear one giving a more organic speed change
  146. // note T in Lerp is clamped, so we don't need to clamp our speed
  147. _speed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed * inputMagnitude, Time.deltaTime * SpeedChangeRate);
  148. // round speed to 3 decimal places
  149. _speed = Mathf.Round(_speed * 1000f) / 1000f;
  150. }
  151. else
  152. {
  153. _speed = targetSpeed;
  154. }
  155. // normalise input direction
  156. Vector3 inputDirection = new Vector3(_input.move.x, 0.0f, _input.move.y).normalized;
  157. // note: Vector2's != operator uses approximation so is not floating point error prone, and is cheaper than magnitude
  158. // if there is a move input rotate player when the player is moving
  159. if (_input.move != Vector2.zero)
  160. {
  161. // move
  162. inputDirection = transform.right * _input.move.x + transform.forward * _input.move.y;
  163. }
  164. // move the player
  165. _controller.Move(inputDirection.normalized * (_speed * Time.deltaTime) + new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);
  166. }
  167. private void JumpAndGravity()
  168. {
  169. if (Grounded)
  170. {
  171. // reset the fall timeout timer
  172. _fallTimeoutDelta = FallTimeout;
  173. // stop our velocity dropping infinitely when grounded
  174. if (_verticalVelocity < 0.0f)
  175. {
  176. _verticalVelocity = -2f;
  177. }
  178. // Jump
  179. if (_input.jump && _jumpTimeoutDelta <= 0.0f)
  180. {
  181. // the square root of H * -2 * G = how much velocity needed to reach desired height
  182. _verticalVelocity = Mathf.Sqrt(JumpHeight * -2f * Gravity);
  183. }
  184. // jump timeout
  185. if (_jumpTimeoutDelta >= 0.0f)
  186. {
  187. _jumpTimeoutDelta -= Time.deltaTime;
  188. }
  189. }
  190. else
  191. {
  192. // reset the jump timeout timer
  193. _jumpTimeoutDelta = JumpTimeout;
  194. // fall timeout
  195. if (_fallTimeoutDelta >= 0.0f)
  196. {
  197. _fallTimeoutDelta -= Time.deltaTime;
  198. }
  199. // if we are not grounded, do not jump
  200. _input.jump = false;
  201. }
  202. // apply gravity over time if under terminal (multiply by delta time twice to linearly speed up over time)
  203. if (_verticalVelocity < _terminalVelocity)
  204. {
  205. _verticalVelocity += Gravity * Time.deltaTime;
  206. }
  207. }
  208. private static float ClampAngle(float lfAngle, float lfMin, float lfMax)
  209. {
  210. if (lfAngle < -360f) lfAngle += 360f;
  211. if (lfAngle > 360f) lfAngle -= 360f;
  212. return Mathf.Clamp(lfAngle, lfMin, lfMax);
  213. }
  214. private void OnDrawGizmosSelected()
  215. {
  216. Color transparentGreen = new Color(0.0f, 1.0f, 0.0f, 0.35f);
  217. Color transparentRed = new Color(1.0f, 0.0f, 0.0f, 0.35f);
  218. if (Grounded) Gizmos.color = transparentGreen;
  219. else Gizmos.color = transparentRed;
  220. // when selected, draw a gizmo in the position of, and matching radius of, the grounded collider
  221. Gizmos.DrawSphere(new Vector3(transform.position.x, transform.position.y - GroundedOffset, transform.position.z), GroundedRadius);
  222. }
  223. }
  224. }