1. using UnityEngine;
    2. using System.Collections;
    3. [RequireComponent(typeof(CharacterController))]
    4. public class ThirdPersonController : MonoBehaviour {
    5. public AnimationClip idleAnimation ;
    6. public AnimationClip walkAnimation ;
    7. public AnimationClip runAnimation ;
    8. public AnimationClip jumpPoseAnimation;
    9. public float walkMaxAnimationSpeed = 0.75f;
    10. public float trotMaxAnimationSpeed = 1.0f;
    11. public float runMaxAnimationSpeed = 1.0f;
    12. public float jumpAnimationSpeed = 1.15f;
    13. public float landAnimationSpeed = 1.0f;
    14. private Animation _animation;
    15. enum CharacterState
    16. {
    17. Idle = 0,
    18. Walking = 1,
    19. Trotting = 2,
    20. Running = 3,
    21. Jumping = 4,
    22. }
    23. private CharacterState _characterState;
    24. // The speed when walking
    25. float walkSpeed = 2.0f;
    26. // after trotAfterSeconds of walking we trot with trotSpeed
    27. float trotSpeed = 4.0f;
    28. // when pressing "Fire3" button (cmd) we start running
    29. float runSpeed = 6.0f;
    30. float inAirControlAcceleration = 3.0f;
    31. // How high do we jump when pressing jump and letting go immediately
    32. float jumpHeight = 0.5f;
    33. // The gravity for the character
    34. float gravity = 20.0f;
    35. // The gravity in controlled descent mode
    36. float speedSmoothing = 10.0f;
    37. float rotateSpeed = 500.0f;
    38. float trotAfterSeconds = 3.0f;
    39. bool canJump = true;
    40. private float jumpRepeatTime = 0.05f;
    41. private float jumpTimeout = 0.15f;
    42. private float groundedTimeout = 0.25f;
    43. // The camera doesnt start following the target immediately but waits for a split second to avoid too much waving around.
    44. private float lockCameraTimer = 0.0f;
    45. // The current move direction in x-z
    46. private Vector3 moveDirection = Vector3.zero;
    47. // The current vertical speed
    48. private float verticalSpeed = 0.0f;
    49. // The current x-z move speed
    50. private float moveSpeed = 0.0f;
    51. // The last collision flags returned from controller.Move
    52. private CollisionFlags collisionFlags;
    53. // Are we jumping? (Initiated with jump button and not grounded yet)
    54. private bool jumping = false;
    55. private bool jumpingReachedApex = false;
    56. // Are we moving backwards (This locks the camera to not do a 180 degree spin)
    57. private bool movingBack = false;
    58. // Is the user pressing any keys?
    59. private bool isMoving = false;
    60. // When did the user start walking (Used for going into trot after a while)
    61. private float walkTimeStart = 0.0f;
    62. // Last time the jump button was clicked down
    63. private float lastJumpButtonTime = -10.0f;
    64. // Last time we performed a jump
    65. private float lastJumpTime = -1.0f;
    66. // the height we jumped from (Used to determine for how long to apply extra jump power after jumping.)
    67. private float lastJumpStartHeight = 0.0f;
    68. private Vector3 inAirVelocity = Vector3.zero;
    69. private float lastGroundedTime = 0.0f;
    70. private bool isControllable = true;
    71. void Awake ()
    72. {
    73. moveDirection = transform.TransformDirection(Vector3.forward);
    74. _animation = GetComponent<Animation>();
    75. if(!_animation)
    76. Debug.Log("The character you would like to control doesn't have animations. Moving her might look weird.");
    77. /*
    78. public var idleAnimation : AnimationClip;
    79. public var walkAnimation : AnimationClip;
    80. public var runAnimation : AnimationClip;
    81. public var jumpPoseAnimation : AnimationClip;
    82. */
    83. if(!idleAnimation) {
    84. _animation = null;
    85. Debug.Log("No idle animation found. Turning off animations.");
    86. }
    87. if(!walkAnimation) {
    88. _animation = null;
    89. Debug.Log("No walk animation found. Turning off animations.");
    90. }
    91. if(!runAnimation) {
    92. _animation = null;
    93. Debug.Log("No run animation found. Turning off animations.");
    94. }
    95. if(!jumpPoseAnimation && canJump) {
    96. _animation = null;
    97. Debug.Log("No jump animation found and the character has canJump enabled. Turning off animations.");
    98. }
    99. }
    100. void UpdateSmoothedMovementDirection ()
    101. {
    102. Transform cameraTransform = Camera.main.transform;
    103. bool grounded = IsGrounded();
    104. // Forward vector relative to the camera along the x-z plane
    105. Vector3 forward = cameraTransform.TransformDirection(Vector3.forward);
    106. forward.y = 0;
    107. forward = forward.normalized;
    108. // Right vector relative to the camera
    109. // Always orthogonal to the forward vector
    110. Vector3 right = new Vector3(forward.z, 0, -forward.x);
    111. float v = Input.GetAxisRaw("Vertical");
    112. float h = Input.GetAxisRaw("Horizontal");
    113. // Are we moving backwards or looking backwards
    114. if (v < -0.2f)
    115. movingBack = true;
    116. else
    117. movingBack = false;
    118. bool wasMoving = isMoving;
    119. isMoving = Mathf.Abs (h) > 0.1f || Mathf.Abs (v) > 0.1f;
    120. // Target direction relative to the camera
    121. Vector3 targetDirection = h * right + v * forward;
    122. // Grounded controls
    123. if (grounded)
    124. {
    125. // Lock camera for short period when transitioning moving & standing still
    126. lockCameraTimer += Time.deltaTime;
    127. if (isMoving != wasMoving)
    128. lockCameraTimer = 0.0f;
    129. // We store speed and direction seperately,
    130. // so that when the character stands still we still have a valid forward direction
    131. // moveDirection is always normalized, and we only update it if there is user input.
    132. if (targetDirection != Vector3.zero)
    133. {
    134. // If we are really slow, just snap to the target direction
    135. if (moveSpeed < walkSpeed * 0.9f && grounded)
    136. {
    137. moveDirection = targetDirection.normalized;
    138. }
    139. // Otherwise smoothly turn towards it
    140. else
    141. {
    142. moveDirection = Vector3.RotateTowards(moveDirection, targetDirection, rotateSpeed * Mathf.Deg2Rad * Time.deltaTime, 1000);
    143. moveDirection = moveDirection.normalized;
    144. }
    145. }
    146. // Smooth the speed based on the current target direction
    147. float curSmooth = speedSmoothing * Time.deltaTime;
    148. // Choose target speed
    149. //* We want to support analog input but make sure you cant walk faster diagonally than just forward or sideways
    150. float targetSpeed = Mathf.Min(targetDirection.magnitude, 1.0f);
    151. _characterState = CharacterState.Idle;
    152. // Pick speed modifier
    153. if (Input.GetKey (KeyCode.LeftShift) | Input.GetKey (KeyCode.RightShift))
    154. {
    155. targetSpeed *= runSpeed;
    156. _characterState = CharacterState.Running;
    157. }
    158. else if (Time.time - trotAfterSeconds > walkTimeStart)
    159. {
    160. targetSpeed *= trotSpeed;
    161. _characterState = CharacterState.Trotting;
    162. }
    163. else
    164. {
    165. targetSpeed *= walkSpeed;
    166. _characterState = CharacterState.Walking;
    167. }
    168. moveSpeed = Mathf.Lerp(moveSpeed, targetSpeed, curSmooth);
    169. // Reset walk time start when we slow down
    170. if (moveSpeed < walkSpeed * 0.3f)
    171. walkTimeStart = Time.time;
    172. }
    173. // In air controls
    174. else
    175. {
    176. // Lock camera while in air
    177. if (jumping)
    178. lockCameraTimer = 0.0f;
    179. if (isMoving)
    180. inAirVelocity += targetDirection.normalized * Time.deltaTime * inAirControlAcceleration;
    181. }
    182. }
    183. void ApplyJumping ()
    184. {
    185. // Prevent jumping too fast after each other
    186. if (lastJumpTime + jumpRepeatTime > Time.time)
    187. return;
    188. if (IsGrounded()) {
    189. // Jump
    190. // - Only when pressing the button down
    191. // - With a timeout so you can press the button slightly before landing
    192. if (canJump && Time.time < lastJumpButtonTime + jumpTimeout) {
    193. verticalSpeed = CalculateJumpVerticalSpeed (jumpHeight);
    194. SendMessage("DidJump", SendMessageOptions.DontRequireReceiver);
    195. }
    196. }
    197. }
    198. void ApplyGravity ()
    199. {
    200. if (isControllable) // don't move player at all if not controllable.
    201. {
    202. // Apply gravity
    203. bool jumpButton = Input.GetButton("Jump");
    204. // When we reach the apex of the jump we send out a message
    205. if (jumping && !jumpingReachedApex && verticalSpeed <= 0.0f)
    206. {
    207. jumpingReachedApex = true;
    208. SendMessage("DidJumpReachApex", SendMessageOptions.DontRequireReceiver);
    209. }
    210. if (IsGrounded ())
    211. verticalSpeed = 0.0f;
    212. else
    213. verticalSpeed -= gravity * Time.deltaTime;
    214. }
    215. }
    216. float CalculateJumpVerticalSpeed (float targetJumpHeight)
    217. {
    218. // From the jump height and gravity we deduce the upwards speed
    219. // for the character to reach at the apex.
    220. return Mathf.Sqrt(2 * targetJumpHeight * gravity);
    221. }
    222. void DidJump ()
    223. {
    224. jumping = true;
    225. jumpingReachedApex = false;
    226. lastJumpTime = Time.time;
    227. lastJumpStartHeight = transform.position.y;
    228. lastJumpButtonTime = -10;
    229. _characterState = CharacterState.Jumping;
    230. }
    231. void Update() {
    232. if (!isControllable)
    233. {
    234. // kill all inputs if not controllable.
    235. Input.ResetInputAxes();
    236. }
    237. if (Input.GetButtonDown ("Jump"))
    238. {
    239. lastJumpButtonTime = Time.time;
    240. }
    241. UpdateSmoothedMovementDirection();
    242. // Apply gravity
    243. // - extra power jump modifies gravity
    244. // - controlledDescent mode modifies gravity
    245. ApplyGravity ();
    246. // Apply jumping logic
    247. ApplyJumping ();
    248. // Calculate actual motion
    249. Vector3 movement = moveDirection * moveSpeed + new Vector3 (0, verticalSpeed, 0) + inAirVelocity;
    250. movement *= Time.deltaTime;
    251. // Move the controller
    252. CharacterController controller = GetComponent<CharacterController>();
    253. collisionFlags = controller.Move(movement);
    254. // ANIMATION sector
    255. if(_animation) {
    256. if(_characterState == CharacterState.Jumping)
    257. {
    258. if(!jumpingReachedApex) {
    259. _animation[jumpPoseAnimation.name].speed = jumpAnimationSpeed;
    260. _animation[jumpPoseAnimation.name].wrapMode = WrapMode.ClampForever;
    261. _animation.CrossFade(jumpPoseAnimation.name);
    262. } else {
    263. _animation[jumpPoseAnimation.name].speed = -landAnimationSpeed;
    264. _animation[jumpPoseAnimation.name].wrapMode = WrapMode.ClampForever;
    265. _animation.CrossFade(jumpPoseAnimation.name);
    266. }
    267. }
    268. else
    269. {
    270. if(controller.velocity.sqrMagnitude < 0.1f) {
    271. _animation.CrossFade(idleAnimation.name);
    272. }
    273. else
    274. {
    275. if(_characterState == CharacterState.Running) {
    276. _animation[runAnimation.name].speed = Mathf.Clamp(controller.velocity.magnitude, 0.0f, runMaxAnimationSpeed);
    277. _animation.CrossFade(runAnimation.name);
    278. }
    279. else if(_characterState == CharacterState.Trotting) {
    280. _animation[walkAnimation.name].speed = Mathf.Clamp(controller.velocity.magnitude, 0.0f, trotMaxAnimationSpeed);
    281. _animation.CrossFade(walkAnimation.name);
    282. }
    283. else if(_characterState == CharacterState.Walking) {
    284. _animation[walkAnimation.name].speed = Mathf.Clamp(controller.velocity.magnitude, 0.0f, walkMaxAnimationSpeed);
    285. _animation.CrossFade(walkAnimation.name);
    286. }
    287. }
    288. }
    289. }
    290. // ANIMATION sector
    291. // Set rotation to the move direction
    292. if (IsGrounded())
    293. {
    294. transform.rotation = Quaternion.LookRotation(moveDirection);
    295. }
    296. else
    297. {
    298. Vector3 xzMove = movement;
    299. xzMove.y = 0;
    300. if (xzMove.sqrMagnitude > 0.001f)
    301. {
    302. transform.rotation = Quaternion.LookRotation(xzMove);
    303. }
    304. }
    305. // We are in jump mode but just became grounded
    306. if (IsGrounded())
    307. {
    308. lastGroundedTime = Time.time;
    309. inAirVelocity = Vector3.zero;
    310. if (jumping)
    311. {
    312. jumping = false;
    313. SendMessage("DidLand", SendMessageOptions.DontRequireReceiver);
    314. }
    315. }
    316. }
    317. void OnControllerColliderHit (ControllerColliderHit hit )
    318. {
    319. // Debug.DrawRay(hit.point, hit.normal);
    320. if (hit.moveDirection.y > 0.01f)
    321. return;
    322. }
    323. float GetSpeed () {
    324. return moveSpeed;
    325. }
    326. public bool IsJumping () {
    327. return jumping;
    328. }
    329. bool IsGrounded () {
    330. return (collisionFlags & CollisionFlags.CollidedBelow) != 0;
    331. }
    332. Vector3 GetDirection () {
    333. return moveDirection;
    334. }
    335. public bool IsMovingBackwards () {
    336. return movingBack;
    337. }
    338. public float GetLockCameraTimer ()
    339. {
    340. return lockCameraTimer;
    341. }
    342. bool IsMoving ()
    343. {
    344. return Mathf.Abs(Input.GetAxisRaw("Vertical")) + Mathf.Abs(Input.GetAxisRaw("Horizontal")) > 0.5f;
    345. }
    346. bool HasJumpReachedApex ()
    347. {
    348. return jumpingReachedApex;
    349. }
    350. bool IsGroundedWithTimeout ()
    351. {
    352. return lastGroundedTime + groundedTimeout > Time.time;
    353. }
    354. void Reset ()
    355. {
    356. gameObject.tag = "Player";
    357. }
    358. }