Jump state resets immediately: can't figure out why!

Hey all, this my first post here, so if I’m doing something wrong, please forgive me!

I’ve always been a sucker for tile-based platformers, so after taking the 2D gamedev course, I figured I’d try my hand at something a little more involved. Turns out (for me) it’s a lot more involved, and I haven’t even gotten to the good stuff! :frowning:

There’s probably a better way to do this, but I don’t know what it is, so here goes:

When the user presses jump, I’m adding a jumpSpeed variable, and that all works; at the same time, I’m setting a Boolean _isActivelyJumping = true. Okay, so far, so good.

I also have a Boolean called _isActivelyFalling. This is set if the player walks off of something without jumping (a little arm-flailing animation).

[Sidenote: What I originally wanted to do was have a “graduated” jump: where if the player holds down, they’d get the “full jump,” but if they released after a light tap, for example, they’d only jump, say, half the maximum jump height. I don’t know how to do this yet.]

Anyway, I’m setting _isActivelyJumping true as soon as the user presses the jump button. In my Update method, I check if _isJumping == true as well as if the player is on the ground. What I want to happen is for the player to leave the ground before the first check, because my thought is that if the player was jumping (_isActivelyJumping == true) and he’s back on the ground, then he’s not jumping anymore and I can reset the Boolean. But because I’m setting that Boolean and checking its value on the same frame (I think that’s what’s happening), obviously it’s not working: I’m immediately seeing _isActivelyJumping reset to false.

I tried setting another Boolean via a Coroutine that was supposed to give the game a chance to advance a frame or two, but that didn’t work the way I expected, and it’s probably not the best approach anyway.

Although to be honest, I have no idea what the best approach might look like!

Here’s the code I’ve written so far just in case anyone sees anything glaring. Thank you so much for looking! Sorry for the wall of text!

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerMovement : MonoBehaviour
{
    const string IS_RUNNING = "isRunning";
    const string IS_JUMPING = "isJumping";
    const string IS_CLIMBING = "isClimbing";
    const string IS_FALLING = "isFalling";

    [SerializeField] float moveSpeed = 6f;
    [SerializeField] float jumpSpeed = 8.8f;
    [SerializeField] float climbSpeed = 4.8f;

    Animator _animator;

    PlayerControls playerControls;

    Rigidbody2D _rb;
    CapsuleCollider2D _body;
    BoxCollider2D _foot;

    Vector2 _moveDir;
    Vector2 _jumpDir;

    float _rbOriginalGravity;

    bool _isMovingSideways = false;
    bool _isMovingUpOrDown = false;
    bool _isDying = false;

    bool _isActivelyRunning = false;
    bool _isActivelyJumping = false;
    bool _isActivelyClimbing = false;
    bool _isActivelyFalling = false;

    void Awake()
    {
        _animator = GetComponentInChildren<Animator>();

        playerControls = new PlayerControls();
        playerControls.Enable();
    }

    void Start()
    {
        _rb = GetComponent<Rigidbody2D>();
        _body = GetComponent<CapsuleCollider2D>();
        _foot = GetComponent<BoxCollider2D>();

        _rbOriginalGravity = _rb.gravityScale;
    }

    void Update()
    {
        if (!_isDying)
        {
            PlayerMove();
            PlayerClimb();

            UpdatePlayerAnimationState();

            FaceProperDirection();
        }
    }

    void PlayerMove()
    {
        Vector2 _playerVelocity = new Vector2(_moveDir.x * moveSpeed, _rb.velocity.y);
        _rb.velocity = _playerVelocity;

        _isMovingSideways = Mathf.Abs(_rb.velocity.x) > Mathf.Epsilon;
    }

    void PlayerClimb()
    {
        if (!_foot.IsTouchingLayers(LayerMask.GetMask("Climbables")))
        {
            _rb.gravityScale = _rbOriginalGravity;

            return;
        }
        
        Vector2 _playerVelocity = new Vector2(_rb.velocity.x, _moveDir.y * climbSpeed);
        _rb.velocity = _playerVelocity;
        _rb.gravityScale = 0f;

        _isMovingUpOrDown = Mathf.Abs(_rb.velocity.y) > Mathf.Epsilon;
    }

    void UpdatePlayerAnimationState()
    {
        // TODO: Figure out how to jump "through" ladders if you're not pressing Up/Down

        if (_isActivelyJumping)
        {
            if (!_foot.IsTouchingLayers(LayerMask.GetMask("Platforms", "Spans", "Climbables")))
            {
                // We're in the air, so we're not climbing or running or falling:
                _isActivelyRunning = false;
                _isActivelyClimbing = false;
            }
            else
            {
                // We're on the ground, so we're not jumping anymore
                // (and we're not falling, either, but are we running or climbing?):
                _isActivelyJumping = false;
            }

            _isActivelyFalling = false;
        }
        else
        {
            if (!_foot.IsTouchingLayers(LayerMask.GetMask("Platforms", "Spans", "Climbables")))
            {
                // We're in the air and NOT jumping, so we're falling:
                _isActivelyRunning = false;
                _isActivelyClimbing = false;
                _isActivelyFalling = true;
            }
            else
            {
                // We're on the ground or a ladder:
                if (!_foot.IsTouchingLayers(LayerMask.GetMask("Climbables")))
                {
                    // Not on a ladder:
                    _isActivelyClimbing = false;
                }
                else
                {
                    // We ARE on a ladder:
                    _isActivelyClimbing = _isMovingUpOrDown;
                }

                _isActivelyRunning = _isMovingSideways;
                _isActivelyJumping = false;
                _isActivelyFalling = false;
            }
        }

        _animator.SetBool(IS_RUNNING, _isActivelyRunning);
        _animator.SetBool(IS_JUMPING, _isActivelyJumping);
        _animator.SetBool(IS_CLIMBING, _isActivelyClimbing);
        _animator.SetBool(IS_FALLING, _isActivelyFalling);
    }

// *****
// Public methods attached to the Player Input component in the Editor:

    public void Move(InputAction.CallbackContext ctx)
    {
        // Includes climbing (y coord):
        _moveDir = ctx.ReadValue<Vector2>();
    }
    
    public void Jump(InputAction.CallbackContext ctx)
    {
        if (ctx.action.WasPressedThisFrame())
        {
            if (!_foot.IsTouchingLayers(LayerMask.GetMask("Platforms", "Spans", "Climbables"))) return;

            _isActivelyJumping = true;
            _rb.velocity += new Vector2(0f, jumpSpeed);
        }
    }

    public void Fire(InputAction.CallbackContext ctx)
    {
        //
    }

// End public methods
// *****

    void FaceProperDirection()
    {
        if (_isMovingSideways) transform.localScale = new Vector2(Mathf.Sign(_rb.velocity.x), 1f);
    }
}
1 Like

Hello there! Welcome to the community!

You created a really nice state machine, you did a fantastic job, but it’s also way over-engineered, but I’ll talk about that later.

First your problem, there’s a very, very simple way to know when character is falling, jumping, or grounded, without all those bools and strings:

  • If the player pressed the Jump button = character is jumping, the code only needs to read this during a single frame, there’s no need to cache this state.
  • If the character’s rigibody’s speed is less than 0 = character is falling, you can simply pass the velocity to the animator as long as the player isn’t…
  • If the player is touching ground = The character is grounded, the only real state you need, and you don’t even need to cache it to be honest.
  • Whatever your conditions are for = The character is Climbing

Here’s an example of what I’m describing but it uses the old input system, this code also has a “graduated” jump.

Here’s that code in action with animations.

It’s really that simple, you don’t need all that extra code which leads me to the next part, you over-engineered this way too hard, I’m gonna assume you have some coding experience due to your formatting.

  • Why not using enums instead of all those constant strings?
  • Why not use enums instead of all those bools?
  • Why do you have so many bools?
  • I suggest having the animator on the same game object as the script, that might come to haunt you later if you want to use animation events.
  • You might want to subscribe to the playerControls delegates instead of using this amazing state machine.

I suggest thinking less in code, and thinking more on input and Unity’s components information, also, cache as few information as possible.

Hope this helps.

1 Like

Thank you for your response! It seems quite thorough, if somewhat over my head at present!

Back in the day, I did a significant amount of ActionScript coding (ECMA), back before Apple murdered Flash in its sleep. I understand some basic things, but other concepts I have yet to a) encounter or b) at least wrap my head around. For example. I’m using all the Bools because I don’t know any better! :frowning: I’m not even sure what enums are (I will look that up right after this!) And I’ve forgotten how to set up delegates and put them to work.

I have the animator on a child object of the player object. There was a reason why, but I can’t remember it now! You’re probably right about keeping things better contained/related.

Also, while I’ve been trying to read through the Unity docs as I encounter them, it seems I’m only able to understand a fraction of them.

How you described your ladder system is almost exactly like what I want. Would your top of the ladder EdgeCollider2D work with tilemaps? I thought about trying to put something to mark the top of the ladder (there’s an annoying little dip if you jump on it!), but wasn’t sure how to approach that from a tilemap perspective.

I’ve got sooo much to learn. Thanks again for responding! (I read through it, but I’ll have to parse it more carefully when I have more time!)

1 Like

Unfortunately, my ladder system has a lot of drawbacks, one of them is that you can only set it manually, so no tilemaps, it’s not as much work as it sounds tho, but it’s kind of annoying that I have to move things around everytime I change the size of a ladder.

Enums are really cool and can allow you to really simplify your code, here’s the Microsoft documentation. I was sure the course touch on this subject, apparently I’m wrong.

This topic was automatically closed 20 days after the last reply. New replies are no longer allowed.

Privacy & Terms