Skip to content
Snippets Groups Projects
ThirdPersonCharacter.cs 7.63 KiB
Newer Older
  • Learn to ignore specific revisions
  • kgingras's avatar
    kgingras committed
    using UnityEngine;
    
    namespace UnityStandardAssets.Characters.ThirdPerson
    {
    	[RequireComponent(typeof(Rigidbody))]
    	[RequireComponent(typeof(CapsuleCollider))]
    	[RequireComponent(typeof(Animator))]
    	public class ThirdPersonCharacter : MonoBehaviour
    	{
    		[SerializeField] float m_MovingTurnSpeed = 360;
    		[SerializeField] float m_StationaryTurnSpeed = 180;
    		[SerializeField] float m_JumpPower = 12f;
    		[Range(1f, 4f)][SerializeField] float m_GravityMultiplier = 2f;
    		[SerializeField] float m_RunCycleLegOffset = 0.2f; //specific to the character in sample assets, will need to be modified to work with others
    		[SerializeField] float m_MoveSpeedMultiplier = 1f;
    		[SerializeField] float m_AnimSpeedMultiplier = 1f;
    		[SerializeField] float m_GroundCheckDistance = 0.1f;
    
    		Rigidbody m_Rigidbody;
    		Animator m_Animator;
    		bool m_IsGrounded;
    		float m_OrigGroundCheckDistance;
    		const float k_Half = 0.5f;
    		float m_TurnAmount;
    		float m_ForwardAmount;
    		Vector3 m_GroundNormal;
    		float m_CapsuleHeight;
    		Vector3 m_CapsuleCenter;
    		CapsuleCollider m_Capsule;
    		bool m_Crouching;
    
    		Rigidbody rb;
    		public Vector3 newNormal;
    
    
    		void Start()
    		{
    			m_Animator = GetComponent<Animator>();
    			m_Rigidbody = GetComponent<Rigidbody>();
    			m_Capsule = GetComponent<CapsuleCollider>();
    			m_CapsuleHeight = m_Capsule.height;
    			m_CapsuleCenter = m_Capsule.center;
    
    			m_Rigidbody.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationY | RigidbodyConstraints.FreezeRotationZ;
    			m_OrigGroundCheckDistance = m_GroundCheckDistance;
    			rb = gameObject.GetComponent<Rigidbody>();
    		}
    
    
    		public void Move(Vector3 move, bool crouch, bool jump)
    		{
    
    			// convert the world relative moveInput vector into a local-relative
    			// turn amount and forward amount required to head in the desired
    			// direction.
    //			if (move.magnitude > 1f) move.Normalize();
    //			move = transform.InverseTransformDirection(move);
    //			CheckGroundStatus();
    //			move = Vector3.ProjectOnPlane(move, m_GroundNormal);
    //			m_TurnAmount = Mathf.Atan2(move.x, move.z);
    //			m_ForwardAmount = move.z;
    			Ray ray;
    			RaycastHit hit;
    
    			if (Physics.Raycast(transform.position, -transform.up, out hit, Mathf.Infinity)){ // use it to update myNormal and isGrounded
    				newNormal = hit.normal;
    			}
    //			
    			if (move.magnitude > 1f) move.Normalize();
    			move = transform.InverseTransformDirection(move);
    			CheckGroundStatus();
    			move = Vector3.ProjectOnPlane(move, newNormal);
    //			m_TurnAmount = Mathf.Atan2(move.x, move.z);
    			m_ForwardAmount = move.z;
    
    			ApplyExtraTurnRotation();
    
    			// control and velocity handling is different when grounded and airborne:
    			if (m_IsGrounded)
    			{
    				HandleGroundedMovement(crouch, jump);
    				if(move == Vector3.zero){
    					rb.constraints = RigidbodyConstraints.FreezePosition;
    				}
    				else{
    					rb.constraints = RigidbodyConstraints.None;
    				}
    			}
    			else
    			{
    				HandleAirborneMovement();
    			}
    
    			ScaleCapsuleForCrouching(crouch);
    			PreventStandingInLowHeadroom();
    
    			// send input and other state parameters to the animator
    			UpdateAnimator(move);
    		}
    
    
    		void ScaleCapsuleForCrouching(bool crouch)
    		{
    			if (m_IsGrounded && crouch)
    			{
    				if (m_Crouching) return;
    				m_Capsule.height = m_Capsule.height / 2f;
    				m_Capsule.center = m_Capsule.center / 2f;
    				m_Crouching = true;
    			}
    			else
    			{
    				Ray crouchRay = new Ray(m_Rigidbody.position + Vector3.up * m_Capsule.radius * k_Half, Vector3.up);
    				float crouchRayLength = m_CapsuleHeight - m_Capsule.radius * k_Half;
    				if (Physics.SphereCast(crouchRay, m_Capsule.radius * k_Half, crouchRayLength))
    				{
    					m_Crouching = true;
    					return;
    				}
    				m_Capsule.height = m_CapsuleHeight;
    				m_Capsule.center = m_CapsuleCenter;
    				m_Crouching = false;
    			}
    		}
    
    		void PreventStandingInLowHeadroom()
    		{
    			// prevent standing up in crouch-only zones
    			if (!m_Crouching)
    			{
    				Ray crouchRay = new Ray(m_Rigidbody.position + Vector3.up * m_Capsule.radius * k_Half, Vector3.up);
    				float crouchRayLength = m_CapsuleHeight - m_Capsule.radius * k_Half;
    				if (Physics.SphereCast(crouchRay, m_Capsule.radius * k_Half, crouchRayLength))
    				{
    					m_Crouching = true;
    				}
    			}
    		}
    
    
    		void UpdateAnimator(Vector3 move)
    		{
    			// update the animator parameters
    			m_Animator.SetFloat("Forward", m_ForwardAmount, 0.1f, Time.deltaTime);
    			m_Animator.SetFloat("Turn", m_TurnAmount, 0.1f, Time.deltaTime);
    			m_Animator.SetBool("Crouch", m_Crouching);
    			m_Animator.SetBool("OnGround", m_IsGrounded);
    			if (!m_IsGrounded)
    			{
    				m_Animator.SetFloat("Jump", m_Rigidbody.velocity.y);
    			}
    
    			// calculate which leg is behind, so as to leave that leg trailing in the jump animation
    			// (This code is reliant on the specific run cycle offset in our animations,
    			// and assumes one leg passes the other at the normalized clip times of 0.0 and 0.5)
    			float runCycle =
    				Mathf.Repeat(
    					m_Animator.GetCurrentAnimatorStateInfo(0).normalizedTime + m_RunCycleLegOffset, 1);
    			float jumpLeg = (runCycle < k_Half ? 1 : -1) * m_ForwardAmount;
    			if (m_IsGrounded)
    			{
    				m_Animator.SetFloat("JumpLeg", jumpLeg);
    			}
    
    			// the anim speed multiplier allows the overall speed of walking/running to be tweaked in the inspector,
    			// which affects the movement speed because of the root motion.
    			if (m_IsGrounded && move.magnitude > 0)
    			{
    				m_Animator.speed = m_AnimSpeedMultiplier;
    			}
    			else
    			{
    				// don't use that while airborne
    				m_Animator.speed = 1;
    			}
    		}
    
    
    		void HandleAirborneMovement()
    		{
    			// apply extra gravity from multiplier:
    			Vector3 extraGravityForce = (Physics.gravity * m_GravityMultiplier) - Physics.gravity;
    			m_Rigidbody.AddForce(extraGravityForce);
    
    			m_GroundCheckDistance = m_Rigidbody.velocity.y < 0 ? m_OrigGroundCheckDistance : 0.01f;
    		}
    
    
    		void HandleGroundedMovement(bool crouch, bool jump)
    		{
    			// check whether conditions are right to allow a jump:
    			if (jump && !crouch && m_Animator.GetCurrentAnimatorStateInfo(0).IsName("Grounded"))
    			{
    				// jump!
    				m_Rigidbody.velocity = new Vector3(m_Rigidbody.velocity.x, m_JumpPower, m_Rigidbody.velocity.z);
    				m_IsGrounded = false;
    				m_Animator.applyRootMotion = false;
    				m_GroundCheckDistance = 0.1f;
    			}
    		}
    
    		void ApplyExtraTurnRotation()
    		{
    			// help the character turn faster (this is in addition to root rotation in the animation)
    			float turnSpeed = Mathf.Lerp(m_StationaryTurnSpeed, m_MovingTurnSpeed, m_ForwardAmount);
    			transform.Rotate(0, m_TurnAmount * turnSpeed * Time.deltaTime, 0);
    		}
    
    
    		public void OnAnimatorMove()
    		{
    			// we implement this function to override the default root motion.
    			// this allows us to modify the positional speed before it's applied.
    			if (m_IsGrounded && Time.deltaTime > 0)
    			{
    				Vector3 v = (m_Animator.deltaPosition * m_MoveSpeedMultiplier) / Time.deltaTime;
    
    				// we preserve the existing y part of the current velocity.
    				v.y = m_Rigidbody.velocity.y;
    				m_Rigidbody.velocity = v;
    			}
    		}
    
    
    		void CheckGroundStatus()
    		{
    			RaycastHit hitInfo;
    #if UNITY_EDITOR
    			// helper to visualise the ground check ray in the scene view
    			Debug.DrawLine(transform.position + (Vector3.up * 0.1f), transform.position + (Vector3.up * 0.1f) + (Vector3.down * m_GroundCheckDistance));
    #endif
    			// 0.1f is a small offset to start the ray from inside the character
    			// it is also good to note that the transform position in the sample assets is at the base of the character
    			if (Physics.Raycast(transform.position + (Vector3.up * 0.1f), Vector3.down, out hitInfo, m_GroundCheckDistance))
    			{
    				m_GroundNormal = hitInfo.normal;
    				m_IsGrounded = true;
    				m_Animator.applyRootMotion = true;
    			}
    			else
    			{
    				m_IsGrounded = false;
    				m_GroundNormal = Vector3.up;
    				m_Animator.applyRootMotion = false;
    			}
    		}
    	}
    }