Advertisement
mvaganov

Unity3D's default ThirdPersonController in C#

Jun 16th, 2013
433
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 11.91 KB | None | 0 0
  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. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement