PlayerMovement.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. using UnityEngine;
  2. namespace HQFPSWeapons
  3. {
  4. public class PlayerMovement : PlayerComponent
  5. {
  6. public bool IsGrounded { get => m_Controller.isGrounded; }
  7. public Vector3 Velocity { get => m_Controller.velocity; }
  8. public Vector3 SurfaceNormal { get; private set; }
  9. public float SlopeLimit { get => m_Controller.slopeLimit; }
  10. public float DefaultHeight { get; private set; }
  11. [SerializeField]
  12. private CharacterController m_Controller = null;
  13. [SerializeField]
  14. private LayerMask m_ObstacleCheckMask = ~0;
  15. [BHeader("Core Movement...")]
  16. [SerializeField]
  17. [Range(0f, 20f)]
  18. private float m_Acceleration = 5f;
  19. [SerializeField]
  20. [Range(0f, 20f)]
  21. private float m_Damping = 8f;
  22. [SerializeField]
  23. [Range(0f, 1f)]
  24. private float m_AirborneControl = 0.15f;
  25. [SerializeField]
  26. [Range(0f, 3f)]
  27. private float m_StepLength = 1.2f;
  28. [SerializeField]
  29. [Range(0f, 10f)]
  30. private float m_ForwardSpeed = 2.5f;
  31. [SerializeField]
  32. [Range(0f, 10f)]
  33. private float m_BackSpeed = 2.5f;
  34. [SerializeField]
  35. [Range(0f, 10f)]
  36. private float m_SideSpeed = 2.5f;
  37. [SerializeField]
  38. private AnimationCurve m_SlopeSpeedMult = new AnimationCurve(new Keyframe(0f, 1f), new Keyframe(1f, 1f));
  39. [SerializeField]
  40. private float m_AntiBumpFactor = 1f;
  41. [BHeader("Running...")]
  42. [SerializeField]
  43. private bool m_EnableRunning = true;
  44. [SerializeField]
  45. [Range(1f, 10f)]
  46. private float m_RunSpeed = 4.5f;
  47. [SerializeField]
  48. [Range(0f, 3f)]
  49. private float m_RunStepLength = 1.9f;
  50. [BHeader("Jumping...")]
  51. [SerializeField]
  52. private bool m_EnableJumping = true;
  53. [SerializeField]
  54. [Range(0f, 3f)]
  55. private float m_JumpHeight = 1f;
  56. [SerializeField]
  57. [Range(0f, 1.5f)]
  58. private float m_JumpTimer = 0.3f;
  59. [BHeader("Crouching...")]
  60. [SerializeField]
  61. private bool m_EnableCrouching = false;
  62. [SerializeField]
  63. [Range(0f, 1f)]
  64. private float m_CrouchSpeedMult = 0.7f;
  65. [SerializeField]
  66. [Range(0f, 3f)]
  67. private float m_CrouchStepLength = 0.8f;
  68. [SerializeField]
  69. [Range(0f, 2f)]
  70. private float m_CrouchHeight = 1f;
  71. [SerializeField]
  72. [Range(0f, 1f)]
  73. private float m_CrouchDuration = 0.3f;
  74. [BHeader("Sliding...")]
  75. [SerializeField]
  76. private bool m_EnableSliding = false;
  77. [SerializeField]
  78. [Range(20f, 90f)]
  79. private float m_SlideTreeshold = 32f;
  80. [SerializeField]
  81. [Range(0f, 50f)]
  82. private float m_SlideSpeed = 15f;
  83. [BHeader("Misc...")]
  84. [SerializeField]
  85. [Range(0f, 100f)]
  86. private float m_Gravity = 20f;
  87. private float m_UncrouchedHeight = 0f;
  88. private Vector3 m_DesiredVelocityLocal;
  89. private Vector3 m_SlideVelocity;
  90. private CollisionFlags m_CollisionFlags;
  91. private bool m_PreviouslyGrounded;
  92. private float m_LastLandTime;
  93. private float m_NextTimeCanCrouch;
  94. private float m_DistMovedSinceLastCycleEnded;
  95. private float m_CurrentStepLength;
  96. private void Awake()
  97. {
  98. DefaultHeight = m_Controller.height;
  99. RaycastHit hitInfo;
  100. if(Physics.Raycast(transform.position + transform.up, -transform.up, out hitInfo, 3f, ~0, QueryTriggerInteraction.Ignore))
  101. transform.position = hitInfo.point + Vector3.up * 0.08f;
  102. }
  103. private void Start()
  104. {
  105. Player.IsGrounded.AddChangeListener(OnGroundingStateChanged);
  106. Player.Run.AddStartTryer(TryRun);
  107. Player.Jump.AddStartTryer(TryJump);
  108. Player.Crouch.AddStartTryer(TryCrouch);
  109. Player.Crouch.AddStopTryer(TryUncrouch);
  110. Player.Death.AddListener(OnDeath);
  111. }
  112. private void Update()
  113. {
  114. float deltaTime = Time.deltaTime;
  115. Vector3 translation = Vector3.zero;
  116. if (IsGrounded)
  117. {
  118. translation = transform.TransformVector(m_DesiredVelocityLocal) * deltaTime;
  119. if (!Player.Jump.Active)
  120. translation.y = -m_AntiBumpFactor;
  121. }
  122. else
  123. translation = transform.TransformVector(m_DesiredVelocityLocal * deltaTime);
  124. m_CollisionFlags = m_Controller.Move(translation);
  125. if ((m_CollisionFlags & CollisionFlags.Below) == CollisionFlags.Below && !m_PreviouslyGrounded)
  126. {
  127. bool wasJumping = Player.Jump.Active;
  128. if (Player.Jump.Active)
  129. Player.Jump.ForceStop();
  130. Player.FallImpact.Send(Mathf.Abs(m_DesiredVelocityLocal.y));
  131. m_LastLandTime = Time.time;
  132. if (wasJumping)
  133. m_DesiredVelocityLocal = Vector3.ClampMagnitude(m_DesiredVelocityLocal, 1f);
  134. }
  135. Vector3 targetVelocity = CalcTargetVelocity(Player.MoveInput.Get());
  136. if (!IsGrounded)
  137. UpdateAirborneMovement(deltaTime, targetVelocity, ref m_DesiredVelocityLocal);
  138. else if (!Player.Jump.Active)
  139. UpdateGroundedMovement(deltaTime, targetVelocity, ref m_DesiredVelocityLocal);
  140. Player.IsGrounded.Set(IsGrounded);
  141. Player.Velocity.Set(Velocity);
  142. m_PreviouslyGrounded = IsGrounded;
  143. }
  144. private void UpdateGroundedMovement(float deltaTime, Vector3 targetVelocity, ref Vector3 velocity)
  145. {
  146. // Make sure to lower the speed when moving on steep surfaces.
  147. float surfaceAngle = Vector3.Angle(Vector3.up, SurfaceNormal);
  148. targetVelocity *= m_SlopeSpeedMult.Evaluate(surfaceAngle / SlopeLimit);
  149. // Calculate the rate at which the current speed should increase / decrease.
  150. // If the player doesn't press any movement button, use the "m_Damping" value, otherwise use "m_Acceleration".
  151. float targetAccel = targetVelocity.sqrMagnitude > 0f ? m_Acceleration : m_Damping;
  152. velocity = Vector3.Lerp(velocity, targetVelocity, targetAccel * deltaTime);
  153. // If we're moving and not running, start the "Walk" activity.
  154. if (!Player.Walk.Active && targetVelocity.sqrMagnitude > 0.05f && !Player.Run.Active && !Player.Crouch.Active)
  155. Player.Walk.ForceStart();
  156. // If we're running, or not moving, stop the "Walk" activity.
  157. else if (Player.Walk.Active && (targetVelocity.sqrMagnitude < 0.05f || Player.Run.Active || Player.Crouch.Active))
  158. Player.Walk.ForceStop();
  159. if (Player.Run.Active)
  160. {
  161. bool wantsToMoveBackwards = Player.MoveInput.Get().y < 0f;
  162. bool runShouldStop = wantsToMoveBackwards || targetVelocity.sqrMagnitude == 0f || Player.Stamina.Is(0f);
  163. if (runShouldStop)
  164. Player.Run.ForceStop();
  165. }
  166. if (m_EnableSliding)
  167. {
  168. // Sliding...
  169. if (surfaceAngle > m_SlideTreeshold && Player.MoveInput.Get().sqrMagnitude == 0f)
  170. {
  171. Vector3 slideDirection = (SurfaceNormal + Vector3.down);
  172. m_SlideVelocity += slideDirection * m_SlideSpeed * deltaTime;
  173. }
  174. else
  175. m_SlideVelocity = Vector3.Lerp(m_SlideVelocity, Vector3.zero, deltaTime * 10f);
  176. velocity += transform.InverseTransformVector(m_SlideVelocity);
  177. }
  178. // Advance step
  179. m_DistMovedSinceLastCycleEnded += m_DesiredVelocityLocal.magnitude * deltaTime;
  180. // Which step length should be used?
  181. float targetStepLength = m_StepLength;
  182. if (Player.Crouch.Active)
  183. targetStepLength = m_CrouchStepLength;
  184. else if (Player.Run.Active)
  185. targetStepLength = m_RunStepLength;
  186. m_CurrentStepLength = Mathf.MoveTowards(m_CurrentStepLength, targetStepLength, deltaTime * 0.6f);
  187. // If the step cycle is complete, reset it, and send a notification.
  188. if (m_DistMovedSinceLastCycleEnded > m_CurrentStepLength)
  189. {
  190. m_DistMovedSinceLastCycleEnded -= m_CurrentStepLength;
  191. Player.MoveCycleEnded.Send();
  192. }
  193. Player.MoveCycle.Set(m_DistMovedSinceLastCycleEnded / m_CurrentStepLength);
  194. }
  195. private void UpdateAirborneMovement(float deltaTime, Vector3 targetVelocity, ref Vector3 velocity)
  196. {
  197. if (m_PreviouslyGrounded && !Player.Jump.Active)
  198. velocity.y = 0f;
  199. // Modify the current velocity by taking into account how well we can change direction when not grounded (see "m_AirControl" tooltip).
  200. velocity += targetVelocity * m_Acceleration * m_AirborneControl * deltaTime;
  201. // Apply gravity.
  202. velocity.y -= m_Gravity * deltaTime;
  203. }
  204. private bool TryRun()
  205. {
  206. if (!m_EnableRunning || Player.Stamina.Get() < 15f)
  207. return false;
  208. bool wantsToMoveBack = Player.MoveInput.Get().y < 0f;
  209. return Player.IsGrounded.Get() && !wantsToMoveBack && !Player.Crouch.Active && !Player.Aim.Active;
  210. }
  211. private bool TryJump()
  212. {
  213. // If crouched, stop crouching first
  214. if (Player.Crouch.Active)
  215. {
  216. Player.Crouch.TryStop();
  217. return false;
  218. }
  219. bool canJump = m_EnableJumping &&
  220. IsGrounded &&
  221. !Player.Crouch.Active &&
  222. Time.time > m_LastLandTime + m_JumpTimer;
  223. if (!canJump)
  224. return false;
  225. float jumpSpeed = Mathf.Sqrt(2 * m_Gravity * m_JumpHeight);
  226. m_DesiredVelocityLocal = new Vector3(m_DesiredVelocityLocal.x, jumpSpeed, m_DesiredVelocityLocal.z);
  227. return true;
  228. }
  229. private bool TryCrouch()
  230. {
  231. bool canCrouch =
  232. m_EnableCrouching &&
  233. (Time.time > m_NextTimeCanCrouch || m_NextTimeCanCrouch == 0f) &&
  234. Player.IsGrounded.Get() &&
  235. !Player.Run.Active;
  236. if (canCrouch)
  237. {
  238. SetHeight(m_CrouchHeight);
  239. m_NextTimeCanCrouch = Time.time + m_CrouchDuration;
  240. }
  241. return canCrouch;
  242. }
  243. private bool TryUncrouch()
  244. {
  245. bool obstacleAbove = DoCollisionCheck(true, Mathf.Abs(m_CrouchHeight - m_UncrouchedHeight));
  246. bool canStopCrouching = Time.time > m_NextTimeCanCrouch && !obstacleAbove;
  247. if (canStopCrouching)
  248. {
  249. SetHeight(DefaultHeight);
  250. m_NextTimeCanCrouch = Time.time;
  251. }
  252. return canStopCrouching;
  253. }
  254. private void OnGroundingStateChanged(bool isGrounded)
  255. {
  256. if (!isGrounded)
  257. {
  258. Player.Walk.ForceStop();
  259. Player.Run.ForceStop();
  260. }
  261. }
  262. private Vector3 CalcTargetVelocity(Vector2 moveInput)
  263. {
  264. moveInput = Vector2.ClampMagnitude(moveInput, 1f);
  265. bool wantsToMove = moveInput.sqrMagnitude > 0f;
  266. // Calculate the direction (relative to the us), in which the player wants to move.
  267. Vector3 targetDirection = (wantsToMove ? new Vector3(moveInput.x, 0f, moveInput.y) : m_DesiredVelocityLocal.normalized);
  268. float desiredSpeed = 0f;
  269. if (wantsToMove)
  270. {
  271. // Set the default speed.
  272. desiredSpeed = m_ForwardSpeed;
  273. // If the player wants to move sideways...
  274. if (Mathf.Abs(moveInput.x) > 0f)
  275. desiredSpeed = m_SideSpeed;
  276. // If the player wants to move backwards...
  277. if (moveInput.y < 0f)
  278. desiredSpeed = m_BackSpeed;
  279. // If we're currently running...
  280. if (Player.Run.Active)
  281. {
  282. // If the player wants to move forward or sideways, apply the run speed multiplier.
  283. if (desiredSpeed == m_ForwardSpeed || desiredSpeed == m_SideSpeed)
  284. desiredSpeed = m_RunSpeed;
  285. }
  286. // If we're crouching...
  287. if (Player.Crouch.Active)
  288. desiredSpeed *= m_CrouchSpeedMult;
  289. }
  290. return targetDirection * (desiredSpeed * Player.MovementSpeedFactor.Val);
  291. }
  292. private bool DoCollisionCheck(bool checkAbove, float maxDistance, out RaycastHit hitInfo)
  293. {
  294. Vector3 rayOrigin = transform.position + (checkAbove ? Vector3.up * m_Controller.height : Vector3.zero);
  295. Vector3 rayDirection = checkAbove ? Vector3.up : Vector3.down;
  296. return Physics.SphereCast(new Ray(rayOrigin, rayDirection), m_Controller.radius, out hitInfo, maxDistance, m_ObstacleCheckMask, QueryTriggerInteraction.Ignore);
  297. }
  298. private bool DoCollisionCheck(bool checkAbove, float maxDistance)
  299. {
  300. Vector3 rayOrigin = transform.position + (checkAbove ? Vector3.up * m_Controller.height : Vector3.zero);
  301. Vector3 rayDirection = checkAbove ? Vector3.up : Vector3.down;
  302. return Physics.SphereCast(new Ray(rayOrigin, rayDirection), m_Controller.radius, maxDistance, m_ObstacleCheckMask, QueryTriggerInteraction.Ignore);
  303. }
  304. private void SetHeight(float height)
  305. {
  306. m_Controller.height = height;
  307. m_Controller.center = Vector3.up * height * 0.5f;
  308. }
  309. private void OnControllerColliderHit(ControllerColliderHit hit)
  310. {
  311. SurfaceNormal = hit.normal;
  312. }
  313. private void OnDeath()
  314. {
  315. m_DesiredVelocityLocal = Vector3.zero;
  316. }
  317. }
  318. }