using UnityEngine; using UnityEngine.Events; public class CharacterController2D : MonoBehaviour { [SerializeField] private float m_JumpForce = 400f; // Amount of force added when the player jumps. [SerializeField] private float m_WallJumpMultiplier = 3f; // Amount of force added when the player jumps off a wall. [Range(0, 1)] [SerializeField] private float m_CrouchSpeed = .36f; // Amount of maxSpeed applied to crouching movement. 1 = 100% [Range(0, .3f)] [SerializeField] private float m_MovementSmoothing = .05f; // How much to smooth out the movement [SerializeField] private bool m_AirControl = false; // Whether or not a player can steer while jumping; [SerializeField] private LayerMask m_WhatIsGround; // A mask determining what is ground to the character [SerializeField] private Transform m_GroundCheck; // A position marking where to check if the player is grounded. [SerializeField] private Transform m_CeilingCheck; // A position marking where to check for ceilings [SerializeField] private Transform m_WallCheck; [SerializeField] private Collider2D m_CrouchDisableCollider; // A collider that will be disabled when crouching const float k_GroundedRadius = .2f; // Radius of the overlap circle to determine if grounded private bool m_Grounded; // Whether or not the player is grounded. private bool m_TouchingWall; const float k_CeilingRadius = .2f; // Radius of the overlap circle to determine if the player can stand up private Rigidbody2D m_Rigidbody2D; private bool m_FacingRight = true; // For determining which way the player is currently facing. private Vector3 m_Velocity = Vector3.zero; private enum Direction { NA=0, L, R }; private Direction m_LastWallJumpDirection = Direction.NA; [Header("Events")] [Space] public UnityEvent OnLandEvent; [System.Serializable] public class BoolEvent : UnityEvent { } public BoolEvent OnCrouchEvent; private bool m_wasCrouching = false; private void Awake() { m_Rigidbody2D = GetComponent(); if (OnLandEvent == null) OnLandEvent = new UnityEvent(); if (OnCrouchEvent == null) OnCrouchEvent = new BoolEvent(); } private void FixedUpdate() { CheckGrounded(); CheckTouchingWall(); } private void CheckGrounded() { bool wasGrounded = m_Grounded; m_Grounded = false; Collider2D[] colliders = Physics2D.OverlapCircleAll(m_GroundCheck.position, k_GroundedRadius, m_WhatIsGround); for (int i = 0; i < colliders.Length; i++) { if (colliders[i].gameObject != gameObject) { m_Grounded = true; if (!wasGrounded) OnLandEvent.Invoke(); } } } private void CheckTouchingWall() { m_TouchingWall = false; if(!m_Grounded) { if(Physics2D.OverlapCircle(m_WallCheck.position, 0.2f, m_WhatIsGround)) { m_TouchingWall = true; } } } public void Move(float move, bool crouch, bool jump) { // If crouching, check to see if the character can stand up if (!crouch) { // If the character has a ceiling preventing them from standing up, keep them crouching { crouch = true; } } //only control the player if grounded or airControl is turned on if (m_Grounded || m_AirControl) { // If crouching if (crouch) { if (!m_wasCrouching) { m_wasCrouching = true; OnCrouchEvent.Invoke(true); } // Reduce the speed by the crouchSpeed multiplier move *= m_CrouchSpeed; // Disable one of the colliders when crouching if (m_CrouchDisableCollider != null) m_CrouchDisableCollider.enabled = false; } else { // Enable the collider when not crouching if (m_CrouchDisableCollider != null) m_CrouchDisableCollider.enabled = true; if (m_wasCrouching) { m_wasCrouching = false; OnCrouchEvent.Invoke(false); } } // Move the character by finding the target velocity Vector3 targetVelocity = new Vector2(move * 10f, m_Rigidbody2D.velocity.y); // And then smoothing it out and applying it to the character m_Rigidbody2D.velocity = Vector3.SmoothDamp(m_Rigidbody2D.velocity, targetVelocity, ref m_Velocity, m_MovementSmoothing); // If the input is moving the player right and the player is facing left... if (move > 0 && !m_FacingRight) { // ... flip the player. Flip(); } // Otherwise if the input is moving the player left and the player is facing right... else if (move < 0 && m_FacingRight) { // ... flip the player. Flip(); } } // If the player should jump... if (m_Grounded && jump) { // Add a vertical force to the player. m_Grounded = false; m_Rigidbody2D.AddForce(new Vector2(0f, m_JumpForce)); // Reset wall jump switch m_LastWallJumpDirection = move > 0 ? Direction.R : Direction.L; } else if (m_TouchingWall && jump) { if (m_FacingRight && m_LastWallJumpDirection == Direction.L) { m_Rigidbody2D.AddForce(new Vector2(m_JumpForce * m_WallJumpMultiplier * (m_FacingRight ? 1f : -1f), m_JumpForce * m_WallJumpMultiplier)); m_LastWallJumpDirection = Direction.R; } else if (!m_FacingRight && m_LastWallJumpDirection == Direction.R) { m_Rigidbody2D.AddForce(new Vector2(m_JumpForce * m_WallJumpMultiplier * (m_FacingRight ? 1f : -1f), m_JumpForce * m_WallJumpMultiplier)); m_LastWallJumpDirection = Direction.L; } } } private void Flip() { // Switch the way the player is labelled as facing. m_FacingRight = !m_FacingRight; // Multiply the player's x local scale by -1. Vector3 theScale = transform.localScale; theScale.x *= -1; transform.localScale = theScale; } }