Easy Way to Determine if Player is in the Air?

Basically is there an easy way to determine if a player, with a rigidbody, is in the air,
and then make it so that that player is always in a “falling” animation or “airborne” animation in that case?

My player uses groundchecks and often times he clips corners and he runs through the air. If he clips a corner into wall jumping, it is very jarring. I want to make it so that he always seems to be floating if he is determined to be in the air.

Sorry for the simple question, this is my first project

Don’t think my script is necessary but this is my Player Movement script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

public class PlayerMovement : MonoBehaviour
{
   public CharacterController2D controller;
   public Animator animator;

   public float runSpeed = 40f;

   float horizontalMove = 0f;
   bool jump = false;
   bool crouch = false;

   [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.
const float k_GroundedRadius = .2f;
private bool m_Grounded;

public UnityEvent OnLandEvent;

   [SerializeField] private AudioSource jumpSoundEffect;
   [SerializeField] private AudioSource dashSoundEffect;

// //dash stuff
[SerializeField] Rigidbody2D rb;
   public float dashDistance = 10f;
   bool isDashing;
   float doubleTapTime;
   KeyCode lastKeyCode;
   float mx;

   [Header("Dashing")]
   [SerializeField] private float m_JumpForce = 800f;
   public bool canDash = true;
   public float dashingTime;
   public float dashSpeed;
   public float dashJumpIncrease;
   public float timeBtwDashes;

//double tap stuff
public bool singleTap = false;
     public bool doubleTap = false;
     bool tapping = false;
     float tapTime = 0;
     float duration = .4f;

    //  //walljump 2
    //  [Header("For WallSliding")]
    //  [SerializeField] float wallSlideSpeed = 0f;
    // [SerializeField] LayerMask wallLayer;
    // [SerializeField] Transform wallCheckPoint;
    // [SerializeField] Vector2 wallCheckSize;
    // public bool isTouchingWall;
    // private bool isWallSliding;

     //walljump stuff
     public Transform wallGrabPoint;
     private bool canGrab, isGrabbing;
     private float gravityStore;
     public float wallJumpTime = .2f;
     private float wallJumpCounter;
     [SerializeField] LayerMask wallLayer;
     private bool m_FacingRight = true;
     float move;
     private Vector3 m_Velocity = Vector3.zero;
     [Range(0, .3f)] [SerializeField] private float m_MovementSmoothing = .05f;

     void Start()
     {
         //wall jump stuff
         gravityStore = rb.gravityScale;
     }


    void Update()
    {
        //if(wallJumpCounter <= 0)
        Vector3 targetVelocity = new Vector2(move * 10f, rb.velocity.y);
       {

//handle wall jumping
canGrab = Physics2D.OverlapCircle(wallGrabPoint.position, .2f, wallLayer);

isGrabbing = false;
if(canGrab && !m_Grounded)
{
    if((transform.localScale.x == 1f && Input.GetAxisRaw("Horizontal") > 0))
    {
        isGrabbing = true;
    }
    if((transform.localScale.x == 1f && Input.GetAxisRaw("Horizontal") < 0))
    {
        isGrabbing = true;
    }
}

if(isGrabbing)
{
    rb.gravityScale = 0f;
    rb.velocity = Vector2.zero;

    if(Input.GetButtonDown("Jump"))
    {

StartCoroutine("DisableScript");

        //wallJumpCounter = wallJumpTime;

                rb.velocity = new Vector2(-Input.GetAxisRaw("Horizontal") * 20f, 20f);
        Flip();
        StartCoroutine("WaitMoment");

       
       //rb.AddForce(transform.right * 1000f, ForceMode2D.Impulse);
       Debug.Log("Imwalljumping");
      
        rb.gravityScale = gravityStore;
        isGrabbing = false;
    }
} else{
    rb.gravityScale = gravityStore;
}

animator.SetBool("isGrabbing", isGrabbing);

        {
            if (Input.GetKeyDown(KeyCode.LeftShift))
            {
                DashAbility();
            }
        }

//double tap stuff
if (Input.GetKeyDown(KeyCode.LeftArrow))
         {
             if (tapping)
             {
                 doubleTap = true;
                 Debug.Log("DoubleTap");
                  DashAbility();
                 tapping = false;
             }
             else
             {
                 tapping = true;
                 tapTime = duration;
                
             }
         }
         if (tapping)
         {
             tapTime = tapTime - Time.deltaTime;
             if (tapTime <= 0)
             {
                 tapping = false;
                 singleTap = true;
                 Debug.Log("SingleTap");
             }
         }

         if (Input.GetKeyDown(KeyCode.RightArrow))
         {
             if (tapping)
             {
                 doubleTap = true;
                 Debug.Log("DoubleTap");
                  DashAbility();
                 tapping = false;
             }
             else
             {
                 tapping = true;
                 tapTime = duration;
                
             }
         }
         if (tapping)
         {
             tapTime = tapTime - Time.deltaTime;
             if (tapTime <= 0)
             {
                 tapping = false;
                 singleTap = true;
                 Debug.Log("SingleTap");
             }
         }

      
//testing move for wall jump
//rb.velocity = new Vector2(Input.GetAxisRaw("Horizontal") * runSpeed, rb.velocity.y);
if (m_Grounded)
{
animator.SetFloat("Speed", Mathf.Abs(horizontalMove));
}

       if (m_Grounded && (Input.GetButtonDown("Jump")))
       {
jump = true;
animator.SetBool("IsJumping", true);
jumpSoundEffect.Play();
       }

       if (Input.GetButtonDown("Crouch"))
       {
crouch = true;
       } else if (Input.GetButtonUp("Crouch"))
       {
           crouch = false;
       }
    }
    //else
   // {
       // wallJumpCounter -= Time.deltaTime;
   // }
    }

    // void WallSlide()
    // {
    //     if (isTouchingWall && !m_Grounded && rb.velocity.y < 0f)
    //     {
    //         isWallSliding = true;
    //     }
    //     else{
    //         isWallSliding = false;
    //     }
    //     //wall slide
    //     if (isWallSliding)
    //     {
    //         rb.velocity = new Vector2(rb.velocity.x, wallSlideSpeed);
    //     }
    // }

    // void CheckWorld()
    // {
    //     isTouchingWall = Physics2D.OverlapBox(wallCheckPoint.position, wallCheckSize, 0f, wallLayer);
    // }

    // private void OnDrawGizmosSelected()
    // {
    // Gizmos.color = Color.red;
    // Gizmos.DrawCube(wallCheckPoint.position, wallCheckSize);
    // }

public void OnLanding ()
{
    animator.SetBool("IsJumping", false);
}

public void OnCrouching (bool isCrouching)
{
    animator.SetBool("IsCrouching", isCrouching);
}

    void FixedUpdate()
    {

// WallSlide();

bool wasGrounded = m_Grounded;
        m_Grounded = false;

        // The player is grounded if a circlecast to the groundcheck position hits anything designated as ground
        // This can be done using layers instead but Sample Assets will not overwrite your project settings.
        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();
            }
        }
        //Move
horizontalMove = Input.GetAxisRaw("Horizontal") * runSpeed;
        //Move our character
controller.Move(horizontalMove * Time.fixedDeltaTime, crouch, jump);
jump = false;
    }


public void StartWalking()
{
  runSpeed = 50f;
}
public void StopWalking()
{
runSpeed = 0f;
}

// IEnumerator Dash (float direction)
// {
//     isDashing = true;
//     rb.velocity = new Vector2(rb.velocity.x, 0f);
//     rb.AddForce(new Vector2(dashDistance * direction, 0f), ForceMode2D.Impulse);
//     float gravity = rb.gravityScale;
//     rb.gravityScale = 0f;
//     yield return new WaitForSeconds(0.4f);
//     isDashing = false;
//     rb.gravityScale = gravity;
// }

void DashAbility()
{
    if(canDash)
    {
        animator.SetBool("isDashing", true);
        StartCoroutine(Dash());
    }
}
IEnumerator Dash()
{
    dashSoundEffect.Play();
    canDash = false;
    runSpeed = dashSpeed;
    m_JumpForce = dashJumpIncrease;
    yield return new WaitForSeconds(dashingTime);
     animator.SetBool("isDashing", false);
    runSpeed = 50f;
    m_JumpForce = 800f;
    yield return new WaitForSeconds(timeBtwDashes);
    canDash = true;
}

void LateUpdate()
     {
         if (doubleTap) doubleTap = false;
         if (singleTap) singleTap = false;
     }

//      void Animation()
//      {
//          if (isDashing)
//          {
// animator.SetBool("isDashing", true);
//          }
//          else {
// animator.SetBool("isDashing", false);
//          }
//      }

IEnumerator DisableScript ()
     {
        GameObject.Find("Player").GetComponent<PlayerMovement>().enabled = false;
         yield return new WaitForSeconds(.1f);
        GameObject.Find("Player").GetComponent<PlayerMovement>().enabled = true;
     }

     IEnumerator WaitMoment ()
     {
       yield return new WaitForSeconds(.3f);
        Flip();
     }

     public void Move(float move, bool crouch, bool jump)
    {
        // Move the character by finding the target velocity
            Vector3 targetVelocity = new Vector2(move * 10f, rb.velocity.y);
            // And then smoothing it out and applying it to the character
            rb.velocity = Vector3.SmoothDamp(rb.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();
            }
        }

        private void Flip()
    {
        // Switch the way the player is labelled as facing.
        m_FacingRight = !m_FacingRight;
        transform.Rotate(0f, 180f, 0f);
    }

    private void UpdateAnimationState()
    {
        if (rb.velocity.y > .1f)
        {
         animator.SetBool("IsJumping", true);  
        }
        else if (rb.velocity.y > -.1f)
        {
            animator.SetBool("IsJumping", true);
        }
    }

}

I see basically 3 ways to determine if player is in the air.

  1. Good old raycasts.
  2. Trigger collider under the character, when something is inside the trigger - character is on the ground.
  3. Acceleration check. When character acceleration (kinematic) is equal or grater than gravity it means that character is falling.

Thanks for your response, I’m going to try the acceleration check.
Let me know if you have any more specific info on that but I’m going to start my Google journey

What exactly do you mean “clips a corner into wall jumping”? The code you provided seemed like it would have worked fine in most scenarios, and I’m having trouble visualizing what you meant by your problem.

All you really need to do is keep track of the velocity on the previous FixedUpdate, and get the difference between that and the current velocity to get an acceleration. If that number is less than a certain threshold (gravity is usually set at -9.8), then you play your falling animation. That way, they don’t play it when just walking down slopes.

Every time the player wall jumps off of a corner, he plays the running animation instead of jumping. And he will continue to run throughout his wall jumps which looks bad. Now that could be for various reasons but I’m sure the easiest fix is to just switch him to falling/floating animation in the air because the player is often running through the air in other situations too.

I’m going to try and figure out what to write for the acceleration and I’ll post if it works later