daily pastebin goal
24%
SHARE
TWEET

Unity3D's default ThirdPersonController in C#

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