CharacterThirdPerson.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. using UnityEngine;
  2. using System.Collections;
  3. namespace RootMotion.Demos {
  4. /// <summary>
  5. /// Third person character controller. This class is based on the ThirdPersonCharacter.cs of the Unity Exmaple Assets.
  6. /// </summary>
  7. public class CharacterThirdPerson : CharacterBase {
  8. // Is the character always rotating to face the move direction or is he strafing?
  9. [System.Serializable]
  10. public enum MoveMode {
  11. Directional,
  12. Strafe
  13. }
  14. // Animation state
  15. public struct AnimState {
  16. public Vector3 moveDirection; // the forward speed
  17. public bool jump; // should the character be jumping?
  18. public bool crouch; // should the character be crouching?
  19. public bool onGround; // is the character grounded
  20. public bool isStrafing; // should the character always rotate to face the move direction or strafe?
  21. public float yVelocity; // y velocity of the character
  22. public bool doubleJump;
  23. }
  24. [Header("References")]
  25. public CharacterAnimationBase characterAnimation; // the animation controller
  26. public UserControlThirdPerson userControl; // user input
  27. public CameraController cam; // Camera controller (optional). If assigned will update the camera in LateUpdate only if character moves
  28. [Header("Movement")]
  29. public MoveMode moveMode; // Is the character always rotating to face the move direction or is he strafing?
  30. public bool smoothPhysics = true; // If true, will use interpolation to smooth out the fixed time step.
  31. public float smoothAccelerationTime = 0.2f; // The smooth acceleration of the speed of the character (using Vector3.SmoothDamp)
  32. public float linearAccelerationSpeed = 3f; // The linear acceleration of the speed of the character (using Vector3.MoveTowards)
  33. public float platformFriction = 7f; // the acceleration of adapting the velocities of moving platforms
  34. public float groundStickyEffect = 4f; // power of 'stick to ground' effect - prevents bumping down slopes.
  35. public float maxVerticalVelocityOnGround = 3f; // the maximum y velocity while the character is grounded
  36. public float velocityToGroundTangentWeight = 0f; // the weight of rotating character velocity vector to the ground tangent
  37. [Header("Rotation")]
  38. public bool lookInCameraDirection; // should the character be looking in the same direction that the camera is facing
  39. public float turnSpeed = 5f; // additional turn speed added when the player is moving (added to animation root rotation)
  40. public float stationaryTurnSpeedMlp = 1f; // additional turn speed added when the player is stationary (added to animation root rotation)
  41. [Header("Jumping and Falling")]
  42. public bool smoothJump = true; // If true, adds jump force over a few fixed time steps, not in a single step
  43. public float airSpeed = 6f; // determines the max speed of the character while airborne
  44. public float airControl = 2f; // determines the response speed of controlling the character while airborne
  45. public float jumpPower = 12f; // determines the jump force applied when jumping (and therefore the jump height)
  46. public float jumpRepeatDelayTime = 0f; // amount of time that must elapse between landing and being able to jump again
  47. public bool doubleJumpEnabled;
  48. public float doubleJumpPowerMlp = 1f;
  49. [Header("Wall Running")]
  50. public LayerMask wallRunLayers; // walkable vertical surfaces
  51. public float wallRunMaxLength = 1f; // max duration of a wallrun
  52. public float wallRunMinMoveMag = 0.6f; // the minumum magnitude of the user control input move vector
  53. public float wallRunMinVelocityY = -1f; // the minimum vertical velocity of doing a wall run
  54. public float wallRunRotationSpeed = 1.5f; // the speed of rotating the character to the wall normal
  55. public float wallRunMaxRotationAngle = 70f; // max angle of character rotation
  56. public float wallRunWeightSpeed = 5f; // the speed of blending in/out the wall running effect
  57. [Header("Crouching")]
  58. public float crouchCapsuleScaleMlp = 0.6f; // the capsule collider scale multiplier while crouching
  59. public bool onGround { get; private set; }
  60. public AnimState animState = new AnimState();
  61. protected Vector3 moveDirection; // The current move direction of the character in Strafe move mode
  62. private Animator animator;
  63. private Vector3 normal, platformVelocity, platformAngularVelocity;
  64. private RaycastHit hit;
  65. private float jumpLeg, jumpEndTime, forwardMlp, groundDistance, lastAirTime, stickyForce;
  66. private Vector3 wallNormal = Vector3.up;
  67. private Vector3 moveDirectionVelocity;
  68. private float wallRunWeight;
  69. private float lastWallRunWeight;
  70. private float fixedDeltaTime;
  71. private Vector3 fixedDeltaPosition;
  72. private Quaternion fixedDeltaRotation = Quaternion.identity;
  73. private bool fixedFrame;
  74. private float wallRunEndTime;
  75. private Vector3 gravity;
  76. private Vector3 verticalVelocity;
  77. private float velocityY;
  78. private bool doubleJumped;
  79. private bool jumpReleased;
  80. // Use this for initialization
  81. protected override void Start () {
  82. base.Start();
  83. animator = GetComponent<Animator>();
  84. if (animator == null) animator = characterAnimation.GetComponent<Animator>();
  85. wallNormal = -gravity.normalized;
  86. onGround = true;
  87. animState.onGround = true;
  88. if (cam != null) cam.enabled = false;
  89. }
  90. void OnAnimatorMove() {
  91. Move (animator.deltaPosition, animator.deltaRotation);
  92. }
  93. // When the Animator moves
  94. public override void Move(Vector3 deltaPosition, Quaternion deltaRotation) {
  95. // Accumulate delta position, update in FixedUpdate to maintain consitency
  96. fixedDeltaTime += Time.deltaTime;
  97. fixedDeltaPosition += deltaPosition;
  98. fixedDeltaRotation *= deltaRotation;
  99. }
  100. void FixedUpdate() {
  101. gravity = GetGravity();
  102. verticalVelocity = V3Tools.ExtractVertical(r.velocity, gravity, 1f);
  103. velocityY = verticalVelocity.magnitude;
  104. if (Vector3.Dot(verticalVelocity, gravity) > 0f) velocityY = -velocityY;
  105. /*
  106. if (animator != null && animator.updateMode == AnimatorUpdateMode.AnimatePhysics) {
  107. smoothPhysics = false;
  108. characterAnimation.smoothFollow = false;
  109. }
  110. */
  111. // Smoothing out the fixed time step
  112. r.interpolation = smoothPhysics? RigidbodyInterpolation.Interpolate: RigidbodyInterpolation.None;
  113. characterAnimation.smoothFollow = smoothPhysics;
  114. // Move
  115. MoveFixed(fixedDeltaPosition);
  116. fixedDeltaTime = 0f;
  117. fixedDeltaPosition = Vector3.zero;
  118. r.MoveRotation(transform.rotation * fixedDeltaRotation);
  119. fixedDeltaRotation = Quaternion.identity;
  120. Rotate();
  121. GroundCheck (); // detect and stick to ground
  122. // Friction
  123. if (userControl.state.move == Vector3.zero && groundDistance < airborneThreshold * 0.5f) HighFriction();
  124. else ZeroFriction();
  125. bool stopSlide = onGround && userControl.state.move == Vector3.zero && r.velocity.magnitude < 0.5f && groundDistance < airborneThreshold * 0.5f;
  126. // Individual gravity
  127. if (gravityTarget != null) {
  128. r.useGravity = false;
  129. if (!stopSlide) r.AddForce(gravity);
  130. }
  131. if (stopSlide) {
  132. r.useGravity = false;
  133. r.velocity = Vector3.zero;
  134. } else if (gravityTarget == null) r.useGravity = true;
  135. if (onGround) {
  136. // Jumping
  137. animState.jump = Jump();
  138. jumpReleased = false;
  139. doubleJumped = false;
  140. } else {
  141. if (!userControl.state.jump) jumpReleased = true;
  142. //r.AddForce(gravity * gravityMultiplier);
  143. if (jumpReleased && userControl.state.jump && !doubleJumped && doubleJumpEnabled) {
  144. jumpEndTime = Time.time + 0.1f;
  145. animState.doubleJump = true;
  146. Vector3 jumpVelocity = userControl.state.move * airSpeed;
  147. r.velocity = jumpVelocity;
  148. r.velocity += transform.up * jumpPower * doubleJumpPowerMlp;
  149. doubleJumped = true;
  150. }
  151. }
  152. // Scale the capsule colllider while crouching
  153. ScaleCapsule(userControl.state.crouch? crouchCapsuleScaleMlp: 1f);
  154. fixedFrame = true;
  155. }
  156. protected virtual void Update() {
  157. // Fill in animState
  158. animState.onGround = onGround;
  159. animState.moveDirection = GetMoveDirection();
  160. animState.yVelocity = Mathf.Lerp(animState.yVelocity, velocityY, Time.deltaTime * 10f);
  161. animState.crouch = userControl.state.crouch;
  162. animState.isStrafing = moveMode == MoveMode.Strafe;
  163. }
  164. protected virtual void LateUpdate() {
  165. if (cam == null) return;
  166. cam.UpdateInput();
  167. if (!fixedFrame && r.interpolation == RigidbodyInterpolation.None) return;
  168. // Update camera only if character moves
  169. cam.UpdateTransform(r.interpolation == RigidbodyInterpolation.None? Time.fixedDeltaTime: Time.deltaTime);
  170. fixedFrame = false;
  171. }
  172. private void MoveFixed(Vector3 deltaPosition) {
  173. // Process horizontal wall-running
  174. WallRun();
  175. Vector3 velocity = fixedDeltaTime > 0f? deltaPosition / fixedDeltaTime: Vector3.zero;
  176. // Add velocity of the rigidbody the character is standing on
  177. velocity += V3Tools.ExtractHorizontal(platformVelocity, gravity, 1f);
  178. if (onGround) {
  179. // Rotate velocity to ground tangent
  180. if (velocityToGroundTangentWeight > 0f) {
  181. Quaternion rotation = Quaternion.FromToRotation(transform.up, normal);
  182. velocity = Quaternion.Lerp(Quaternion.identity, rotation, velocityToGroundTangentWeight) * velocity;
  183. }
  184. } else {
  185. // Air move
  186. //Vector3 airMove = new Vector3 (userControl.state.move.x * airSpeed, 0f, userControl.state.move.z * airSpeed);
  187. Vector3 airMove = V3Tools.ExtractHorizontal(userControl.state.move * airSpeed, gravity, 1f);
  188. velocity = Vector3.Lerp(r.velocity, airMove, Time.deltaTime * airControl);
  189. }
  190. if (onGround && Time.time > jumpEndTime) {
  191. r.velocity = r.velocity - transform.up * stickyForce * Time.deltaTime;
  192. }
  193. // Vertical velocity
  194. Vector3 verticalVelocity = V3Tools.ExtractVertical(r.velocity, gravity, 1f);
  195. Vector3 horizontalVelocity = V3Tools.ExtractHorizontal(velocity, gravity, 1f);
  196. if (onGround) {
  197. if (Vector3.Dot(verticalVelocity, gravity) < 0f) {
  198. verticalVelocity = Vector3.ClampMagnitude(verticalVelocity, maxVerticalVelocityOnGround);
  199. }
  200. }
  201. r.velocity = horizontalVelocity + verticalVelocity;
  202. // Dampering forward speed on the slopes (Not working since Unity 2017.2)
  203. //float slopeDamper = !onGround? 1f: GetSlopeDamper(-deltaPosition / Time.deltaTime, normal);
  204. //forwardMlp = Mathf.Lerp(forwardMlp, slopeDamper, Time.deltaTime * 5f);
  205. forwardMlp = 1f;
  206. }
  207. // Processing horizontal wall running
  208. private void WallRun() {
  209. bool canWallRun = CanWallRun();
  210. // Remove flickering in and out of wall-running
  211. if (wallRunWeight > 0f && !canWallRun) wallRunEndTime = Time.time;
  212. if (Time.time < wallRunEndTime + 0.5f) canWallRun = false;
  213. wallRunWeight = Mathf.MoveTowards(wallRunWeight, (canWallRun? 1f: 0f), Time.deltaTime * wallRunWeightSpeed);
  214. if (wallRunWeight <= 0f) {
  215. // Reset
  216. if (lastWallRunWeight > 0f) {
  217. Vector3 frw = V3Tools.ExtractHorizontal(transform.forward, gravity, 1f);
  218. transform.rotation = Quaternion.LookRotation(frw, -gravity);
  219. wallNormal = -gravity.normalized;
  220. }
  221. }
  222. lastWallRunWeight = wallRunWeight;
  223. if (wallRunWeight <= 0f) return;
  224. // Make sure the character won't fall down
  225. if (onGround && velocityY < 0f) r.velocity = V3Tools.ExtractHorizontal(r.velocity, gravity, 1f);
  226. // transform.forward flattened
  227. Vector3 f = V3Tools.ExtractHorizontal(transform.forward, gravity, 1f);
  228. // Raycasting to find a walkable wall
  229. RaycastHit velocityHit = new RaycastHit();
  230. velocityHit.normal = -gravity.normalized;
  231. Physics.Raycast(onGround? transform.position: capsule.bounds.center, f, out velocityHit, 3f, wallRunLayers);
  232. // Finding the normal to rotate to
  233. wallNormal = Vector3.Lerp(wallNormal, velocityHit.normal, Time.deltaTime * wallRunRotationSpeed);
  234. // Clamping wall normal to max rotation angle
  235. wallNormal = Vector3.RotateTowards(-gravity.normalized, wallNormal, wallRunMaxRotationAngle * Mathf.Deg2Rad, 0f);
  236. // Get transform.forward ortho-normalized to the wall normal
  237. Vector3 fW = transform.forward;
  238. Vector3 nW = wallNormal;
  239. Vector3.OrthoNormalize(ref nW, ref fW);
  240. // Rotate from upright to wall normal
  241. transform.rotation = Quaternion.Slerp(Quaternion.LookRotation(f, -gravity), Quaternion.LookRotation(fW, wallNormal), wallRunWeight);
  242. }
  243. // Should the character be enabled to do a wall run?
  244. private bool CanWallRun() {
  245. if (Time.time < jumpEndTime - 0.1f) return false;
  246. if (Time.time > jumpEndTime - 0.1f + wallRunMaxLength) return false;
  247. if (velocityY < wallRunMinVelocityY) return false;
  248. if (userControl.state.move.magnitude < wallRunMinMoveMag) return false;
  249. return true;
  250. }
  251. // Get the move direction of the character relative to the character rotation
  252. private Vector3 GetMoveDirection() {
  253. switch(moveMode) {
  254. case MoveMode.Directional:
  255. moveDirection = Vector3.SmoothDamp(moveDirection, new Vector3(0f, 0f, userControl.state.move.magnitude), ref moveDirectionVelocity, smoothAccelerationTime);
  256. moveDirection = Vector3.MoveTowards(moveDirection, new Vector3(0f, 0f, userControl.state.move.magnitude), Time.deltaTime * linearAccelerationSpeed);
  257. return moveDirection * forwardMlp;
  258. case MoveMode.Strafe:
  259. moveDirection = Vector3.SmoothDamp(moveDirection, userControl.state.move, ref moveDirectionVelocity, smoothAccelerationTime);
  260. moveDirection = Vector3.MoveTowards(moveDirection, userControl.state.move, Time.deltaTime * linearAccelerationSpeed);
  261. return transform.InverseTransformDirection(moveDirection);
  262. }
  263. return Vector3.zero;
  264. }
  265. // Rotate the character
  266. protected virtual void Rotate() {
  267. if (gravityTarget != null) r.MoveRotation (Quaternion.FromToRotation(transform.up, transform.position - gravityTarget.position) * transform.rotation);
  268. if (platformAngularVelocity != Vector3.zero) r.MoveRotation (Quaternion.Euler(platformAngularVelocity) * transform.rotation);
  269. float angle = GetAngleFromForward(GetForwardDirection());
  270. if (userControl.state.move == Vector3.zero) angle *= (1.01f - (Mathf.Abs(angle) / 180f)) * stationaryTurnSpeedMlp;
  271. // Rotating the character
  272. //RigidbodyRotateAround(characterAnimation.GetPivotPoint(), transform.up, angle * Time.deltaTime * turnSpeed);
  273. r.MoveRotation(Quaternion.AngleAxis(angle * Time.deltaTime * turnSpeed, transform.up) * r.rotation);
  274. }
  275. // Which way to look at?
  276. private Vector3 GetForwardDirection() {
  277. bool isMoving = userControl.state.move != Vector3.zero;
  278. switch (moveMode) {
  279. case MoveMode.Directional:
  280. if (isMoving) return userControl.state.move;
  281. return lookInCameraDirection? userControl.state.lookPos - r.position: transform.forward;
  282. case MoveMode.Strafe:
  283. if (isMoving) return userControl.state.lookPos - r.position;
  284. return lookInCameraDirection? userControl.state.lookPos - r.position: transform.forward;
  285. }
  286. return Vector3.zero;
  287. }
  288. protected virtual bool Jump() {
  289. // check whether conditions are right to allow a jump:
  290. if (!userControl.state.jump) return false;
  291. if (userControl.state.crouch) return false;
  292. if (!characterAnimation.animationGrounded) return false;
  293. if (Time.time < lastAirTime + jumpRepeatDelayTime) return false;
  294. // Jump
  295. onGround = false;
  296. jumpEndTime = Time.time + 0.1f;
  297. Vector3 jumpVelocity = userControl.state.move * airSpeed;
  298. jumpVelocity += transform.up * jumpPower;
  299. if (smoothJump)
  300. {
  301. StopAllCoroutines();
  302. StartCoroutine(JumpSmooth(jumpVelocity - r.velocity));
  303. } else
  304. {
  305. r.velocity = jumpVelocity;
  306. }
  307. return true;
  308. }
  309. // Add jump velocity smoothly to avoid puppets launching to space when unpinned during jump acceleration
  310. private IEnumerator JumpSmooth(Vector3 jumpVelocity)
  311. {
  312. int steps = 0;
  313. int stepsToTake = 3;
  314. while (steps < stepsToTake)
  315. {
  316. r.AddForce((jumpVelocity) / stepsToTake, ForceMode.VelocityChange);
  317. steps++;
  318. yield return new WaitForFixedUpdate();
  319. }
  320. }
  321. // Is the character grounded?
  322. private void GroundCheck () {
  323. Vector3 platformVelocityTarget = Vector3.zero;
  324. platformAngularVelocity = Vector3.zero;
  325. float stickyForceTarget = 0f;
  326. // Spherecasting
  327. hit = GetSpherecastHit();
  328. //normal = hit.normal;
  329. normal = transform.up;
  330. //groundDistance = r.position.y - hit.point.y;
  331. groundDistance = Vector3.Project(r.position - hit.point, transform.up).magnitude;
  332. // if not jumping...
  333. bool findGround = Time.time > jumpEndTime && velocityY < jumpPower * 0.5f;
  334. if (findGround) {
  335. bool g = onGround;
  336. onGround = false;
  337. // The distance of considering the character grounded
  338. float groundHeight = !g? airborneThreshold * 0.5f: airborneThreshold;
  339. //Vector3 horizontalVelocity = r.velocity;
  340. Vector3 horizontalVelocity = V3Tools.ExtractHorizontal(r.velocity, gravity, 1f);
  341. float velocityF = horizontalVelocity.magnitude;
  342. if (groundDistance < groundHeight) {
  343. // Force the character on the ground
  344. stickyForceTarget = groundStickyEffect * velocityF * groundHeight;
  345. // On moving platforms
  346. if (hit.rigidbody != null) {
  347. platformVelocityTarget = hit.rigidbody.GetPointVelocity(hit.point);
  348. platformAngularVelocity = Vector3.Project(hit.rigidbody.angularVelocity, transform.up);
  349. }
  350. // Flag the character grounded
  351. onGround = true;
  352. }
  353. }
  354. // Interpolate the additive velocity of the platform the character might be standing on
  355. platformVelocity = Vector3.Lerp(platformVelocity, platformVelocityTarget, Time.deltaTime * platformFriction);
  356. stickyForce = stickyForceTarget;//Mathf.Lerp(stickyForce, stickyForceTarget, Time.deltaTime * 5f);
  357. // remember when we were last in air, for jump delay
  358. if (!onGround) lastAirTime = Time.time;
  359. }
  360. }
  361. }