Proper way to detect downward movement?

Hi everyone,

I need a little guidance… I can’t seem to figure out the proper way to detect negative y movement (downward movement).

In my current script, I am constantly setting my player into a “falling” state.

This section of the code is the culprit. I know it’s wrong, clearly, because player starts out doing the falling animation. (and I will note, this is despite the fact that the “onLand” bool is true, which should disable that animator bool)

        // stuff below for determining if player is not "on land" and/or is falling

        verticalVel = rb.velocity.y;

        if (onLand == true)
        {
            animator.SetBool("IsFalling", false);
        }

        if (verticalVel >= 1f)
            {
             onLand = false;
             Debug.Log("Is jumping");
            }

        if (verticalVel <= -1f)
        {
            onLand = false;
            animator.SetBool("IsFalling", true);
            Debug.Log("Is Falling");
        }

        else animator.SetBool("IsFalling", false);

And here’s the full code of my charactercontroller (which this appears)

using System.Collections; // call these systems to be used by this script
using System.Collections.Generic;
using UnityEngine;

public class PlayerMovement : MonoBehaviour // public name of this script. Public means the name that other scripts can see it/reference it
{

    // all of our initial declarations need to go directly below, BEFORE any function like start, update...
 
    CharacterController2D controller; // cache charactercontroller script, must be defined below in awake or start
    Animator animator; // cache the animator, must be defined in awake or start
    PlayerHealth playerHealth; // cache playerhealth script, must be defined below in awake or start
    Rigidbody2D rb; // cache the rigidbody2d name

    public float runSpeed = 30f; // make a public float number called runSpeed. Maths and calculations are below in void start()
    public float verticalVel; // declare a float called verticalVel

    float horizontalMove = 0f; // make a float number called horizontalMove and set it to 0 by default

    bool jump = false; // create a "bool" called "jump". A bool is a parameter, flag, or state, that can either be true or false
    bool crouch = false; // create a "bool" called crouch, false by default

    public bool attack = true; // create a "bool" called "attack", false by default. This bool is "public", meaning it can be seen/controlled by outside scripts
    public bool onLand = true; // I want to be able to see in the inspector when the player is considered "on land". "landed" state is set by functions below
    public bool canAttack; // create a "bool" called "canAttack". Oh, and in c#, the first letter of any bool or float name is automatically capitalized. Any following capitalized letter is automatically spaced. So, canAttack will actually be "Can Attack".


    void Awake()
    {
       
        controller = GetComponent<CharacterController2D>(); // now we define what the controller points too
        rb = GetComponent<Rigidbody2D>(); // along with the rest....
        animator = GetComponent<Animator>(); // same
        playerHealth = GetComponent<PlayerHealth>(); // same

    }


    void Update() // these functions happen once every frame
    {

        // stuff below for determining if player is not "on land" and/or is falling

        verticalVel = rb.velocity.y;

        if (onLand == true)
        {
            animator.SetBool("IsFalling", false);
        }

        if (verticalVel >= 1f)
            {
             onLand = false;
             Debug.Log("Is jumping");
            }

        if (verticalVel <= -1f)
        {
            onLand = false;
            animator.SetBool("IsFalling", true);
            Debug.Log("Is Falling");
        }

        else animator.SetBool("IsFalling", false);



        //moving left/right below


            horizontalMove = Input.GetAxisRaw("Horizontal") * runSpeed; // Horizontal move will equal the movement of "Horizonal" axis multiplied by our defined run speed float. Also sets controls that allow player to move left and right

            animator.SetFloat("Speed", Mathf.Abs(horizontalMove)); // Trigger the "Speed" float variable in the animator and inserts the value of whatever Horizontal move above ends up being, but before that, use "Mathf.Abs" function to only allow positive values of this number (and never allow a negative value)
     

        //Jump Setup Below

        if (Input.GetButtonDown("Jump")) // If player presses the "jump" button...
        {
            jump = true; // change the "jump" bool to true
            animator.SetBool("IsJumping", true); // change animator bool of "IsJumping" to true. Since inside the animator we have made transitions from idle to jumping, which requires "IsJumping" to be true to happen
            onLand = false; // onLand boolean is set to false as soon as the jump button is pressed. onLand will return to true as soon as ground is touched.

        }


        //Attack Setup Below

        if (canAttack == true) // if the bool "canAttack" is currently set to true...
        {
            if (Input.GetButtonDown("Attack1")) // ...and if "attack" button is pressed....
            {
                StartCoroutine(PlayerIsInvulnerable());
                StartCoroutine(PlayerIsAttacking()); // we want the "PlayerIsAttacking" function from above to run
                animator.SetBool("IsAttacking", true); // and change animator bool of "IsAttacking" to true. Attack Animator requires "IsAttacking" to be true for it to fire/play
             
            }

            else if (Input.GetButtonUp("Attack1")) // otherwise, once the button is released....
            {
                animator.SetBool("IsAttacking", false); // set the IsAttacking bool back to false
            }

        }

        else animator.SetBool("IsAttacking", false); // an else statement directly following an "If" statement means "otherwise, do this". We want to make sure the "IsAttacking" state is always set to false, unless the player presses the attack button


        if (attack == true)
        {   
            rb.constraints = RigidbodyConstraints2D.FreezePositionY | RigidbodyConstraints2D.FreezeRotation; // if player is attacking, make sure that they are frozen on the y access and rotation, so they don't bounce around
        }

        else rb.constraints = RigidbodyConstraints2D.FreezeRotation; // otherwise, the rotation only is frozen



        //Crouch Setup Below

        if (Input.GetButtonDown("Crouch")) // If the player presses the crouch button....
        {
            crouch = true; // set crouching state to true
            animator.SetBool("IsCrouching", true); // tell the animator that condition "IsCrouching" is now true. In the animator transitions, the crouch animation will only fire when the animator condition "IsCrouching" is true
        }

        else if (Input.GetButtonUp("Crouch")) // "else if" statements can contain bracketed functions, unlike "else" statements. if the crouch button is no longer being pressed....
        {
            crouch = false; // set crouching state to false
            animator.SetBool("IsCrouching", false); // tell the animator that the character "IsCrouching" no more,
        }

    }



    public IEnumerator PlayerIsInvulnerable() // this is a special function called PlayerIsInvulerable. Some below function in void update will have to trigger this to run
    {
        //the below instructions happen in order
        playerHealth.noDamage = true; // "No Damage" is a public bool inside of the Player Health script. To Access it, we do this - scriptname.function, but only if we have declared it above in class.
        yield return new WaitForSeconds(.80f); // we are going to wait 80/100 of a second
        playerHealth.noDamage = false; // as well as our "no damage" bool inside player health
    }

    public IEnumerator PlayerIsAttacking() // this is a special function called PlayerIsAttacking. Some below function in void update will have to trigger this to run
    {
        //the below instructions happen in order
        attack = true; // "No Damage" is a public bool inside of the Player Health script. To Access it, we do this - scriptname.function, but only if we have declared it above in class.
        yield return new WaitForSeconds(.30f); // we are going to wait 80/100 of a second
        attack = false; // as well as our "no damage" bool inside player health
    }


    public void OnLanding () // This is a trigger called "OnLanding", and in our case, will be used to tell the game what to do when the player stops (lands) on a collider
    {
        if (verticalVel <= 0) // If vertical velocity is less than 0, declared above as the y velocity of rigidbody2d.
            {
            animator.SetBool("IsJumping", false); // automatically disable the jumping state when player "lands"
            animator.SetBool("IsFalling", false);
            onLand = true; // set the onland bool to true
            }

    }


    void FixedUpdate () // a non public void function called FixedUpdate
    {
        controller.Move(horizontalMove * Time.fixedDeltaTime, crouch, jump); // Since we defined the Character Controller script at the top as public, we can call the functions within it. "controller.Move(stuffhere)" actually is read as "Use CharacterController Move Function (and insert function here)". If you look in Character Controller script, you can find the "Move" function. Anyway, that function defines 3 "bools" in order of Move, Crouch, and Jump. We are setting those bools with the actions we have defined here in this script --- and they need to be in the same order of the Move Function for them to work how we want
        crouch = false; // set the default state of crouching within character controller to false
        jump = false; // set the default state of Jumping within character controller to false

    }


}

Moving downwards is < 0 not < -1 with upwards being > 0 not > +1.

You might check the velocity every frame but physics doesn’t (by default) run every frame so that’s completely wasted effort because it won’t change.

So you’re aware, that odd single line after the else is equivalent to:

        if (verticalVel <= -1f)
        {
            onLand = false;
            animator.SetBool("IsFalling", true);
            Debug.Log("Is Falling");
        }
        else
        {
            animator.SetBool("IsFalling", false);
        }

Melv,

Thank you. I put it at +1/-1 in an attempt to give a little “wiggle” room on the y axis (so like 0.5 or -0.5 doesn’t trigger). If I put those at 0, well, my character becomes very very slow.

In Inspector, the “verticalVel” value does change when jumping/falling, so there is some updating happening.

Why would it affect how it moves? Apart from the fact that you’re absolutely spamming the console with messages.

You should try to only set things when they change i.e. a transition/event rather than constantly asserting state of animators, setting the Rigidbody constraints etc, especially per-frame. It doesn’t scale well. You’re spending CPU cycles doing effectively nothing.

Also, your only question is this yet you seem to be waiting for the answer to another question? You seem to already know the answer to this; when the velocity is < 0.

I am thinking it has something to do with “onLand” and triggering the LandingEvent constantly. I know it’s weird, but for whatever reason, it happens.

Forgive my buddy… I’m still a green novice, and still trying to understand the proper way for such things. Would it be better to move some of this stuff out of update and into their own functions?

Thanks for your help, Melv. You made me realize that it is as simple as you say (on the proper way of detecting downward movement), and my problem lie elsewhere. I think I’m on the right track now.

1 Like