Jump button + down key stops player movement laterally

Hi to everyone,

I’m doing a 2D platformer and i’m adding the high jump while crouching mechanic (the same as in Super Mario 64) To do this the player has to press the down arrow (crouch) and then the jump button. The problem comes when you are in the air after pressing this combination. If you keep pressing the down arrow and the jump button, you cannot move from side to side. But in the moment you realease one of them, unity allows you to move again. I’ve been checking my code to see if I limited the movement in those scenarios, and I didn’t.

I hope the problem comes from some way of the unity controll system that I didn’t know about, and not from me being blind and don’t seing something obvious.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using static Unity.Burst.Intrinsics.X86.Avx;

public class Player : MonoBehaviour
{
    [SerializeField]
    StaminaManagement stamina;

    private float horizontal;
    private float vertical;
    public float speed = 8f;
    public float jumpingPower = 16f;
    public float highJumpingPower = 24f;
    private bool isFacingRight = true;

    private bool isWallSliding;
    public float wallSlidingSpeed = 2f;

    private bool isWallJumping;
    private float wallJumpingDirection;
    private float wallJumpingTime = 0.2f;
    private float wallJumpingCounter;
    private float wallJumpingDuration = 0.2f;
    private Vector2 wallJumpingPower = new Vector2(12f, 16f);

    private bool canDash = true;
    private bool isDashing;
    private float dashingPower = 20f;
    private float dashingTime = 0.2f;
    private float dashingCooldown = 1f;


    [SerializeField] private Rigidbody2D rb;
    [SerializeField] private Transform groundCheck;
    [SerializeField] private LayerMask groundLayer;
    [SerializeField] private Transform wallCheck;
    [SerializeField] private LayerMask wallLayer;

    private void Update()
    {


        if (isDashing)
        {
            return;
        }

        horizontal = Input.GetAxisRaw("Horizontal");
        vertical = Input.GetAxisRaw("Vertical");
       
        Jump();
        HighJump();
        WallSlide();
        WallJump();
        ConsumeFood();

        if (Input.GetButtonDown("Fire3") && stamina.GetCurrentStamina() >= stamina.GetDashStaminaCost()) {
            stamina.DashStaminaLos();
            StartCoroutine(Dash());
        }

        if (!isWallJumping)
        {
            Flip();
        }
    }

    private void FixedUpdate()
    {
        if (isDashing)
        {
            return;
        }
        Walk();
    }

    private IEnumerator Dash() {
        canDash = false;
        isDashing = true;
        float originalGravity = rb.gravityScale;
        rb.gravityScale = 0;
        rb.velocity = new Vector2(transform.localScale.x * dashingPower, 0f);

        yield return new WaitForSeconds(dashingTime);

        rb.gravityScale = originalGravity;
        isDashing = false;
        yield return new WaitForSeconds(dashingCooldown);
        canDash = true;

    }

    private void Walk(){
       
        if (!isWallJumping && !IsCrouching())
        {
           
            rb.velocity = new Vector2(horizontal * speed, rb.velocity.y);
        }
        if (IsCrouching())
        {
           
            rb.velocity = new Vector2(0, rb.velocity.y);
        }
    }

    private void Jump() {

        if (Input.GetButtonDown("Jump") && IsGrounded() && !IsCrouching())
        {
            rb.velocity = new Vector2(rb.velocity.x, jumpingPower);
        }

        if (Input.GetButtonUp("Jump") && rb.velocity.y > 0f)
        {
            rb.velocity = new Vector2(rb.velocity.x, rb.velocity.y * 0.5f);
        }
    }

    private void ConsumeFood() {

        if (Input.GetKeyDown(KeyCode.Z)) {
            stamina.ConsumeFood();
        }

    }

    private void HighJump() {
        if (Input.GetButtonDown("Jump")  && IsCrouching() && stamina.GetCurrentStamina() >= stamina.GetHighJumpStaminaCost())
        {
           
            stamina.HighJumpStaminaLoss();
            rb.velocity = new Vector2(rb.velocity.x, highJumpingPower);
        }

        if (Input.GetButtonUp("Jump") && rb.velocity.y > 0f)
        {
            rb.velocity = new Vector2(rb.velocity.x, rb.velocity.y * 0.5f);
        }
    }
    private bool IsGrounded()
    {
        return Physics2D.OverlapBox(groundCheck.position, new Vector2(0.98f, 0.1f) ,0f, groundLayer);
    }

   
    private void OnDrawGizmosSelected()
    {
        Gizmos.color = new Color(1, 1, 0, 0.75F);
        Gizmos.DrawCube(groundCheck.position, new Vector2(0.98f, 0.1f));
    }
   

    private bool IsCrouching(){
        if (vertical == -1 && IsGrounded())
        {
            return true;
        }
        else return false;
    }

    private bool IsWalled()
    {
        return Physics2D.OverlapCircle(wallCheck.position, 0.2f, wallLayer);
       
    }

    private void WallSlide()
    {
        if (IsWalled() && !IsGrounded())
        {
            isWallSliding = true;
            rb.velocity = new Vector2(rb.velocity.x, Mathf.Clamp(rb.velocity.y, -wallSlidingSpeed, float.MaxValue));
        }
        else
        {
            isWallSliding = false;
        }
    }

    private void WallJump()
    {
        if (isWallSliding)
        {
            isWallJumping = false;
            wallJumpingDirection = -transform.localScale.x;
            wallJumpingCounter = wallJumpingTime;

            CancelInvoke(nameof(StopWallJumping));
        }
        else
        {
            wallJumpingCounter -= Time.deltaTime;
        }

        if (Input.GetButtonDown("Jump") && wallJumpingCounter > 0f && stamina.GetCurrentStamina() >= stamina.GetWallJumpStaminaCost())
        {
            stamina.WallJumpStaminaLoss();
            isWallJumping = true;
            rb.velocity = new Vector2(wallJumpingDirection * wallJumpingPower.x, wallJumpingPower.y);
            wallJumpingCounter = 0f;

            if (transform.localScale.x != wallJumpingDirection)
            {
                isFacingRight = !isFacingRight;
                Vector3 localScale = transform.localScale;
                localScale.x *= -1f;
                transform.localScale = localScale;
            }

            Invoke(nameof(StopWallJumping), wallJumpingDuration);
        }
    }

    private void StopWallJumping()
    {
        isWallJumping = false;
    }

    private void Flip()
    {
        if (isFacingRight && horizontal < 0f || !isFacingRight && horizontal > 0f)
        {
            isFacingRight = !isFacingRight;
            Vector3 localScale = transform.localScale;
            localScale.x *= -1f;
            transform.localScale = localScale;
        }
    }

}

I’m not used to ask for help in forums, so idk if posting the entire class is the way to go, or if you preffer some other aproach. Let me know if there is any problem with the way I asked the question.

I took a look through what’s going on up there… I like that you have specific callouts for your stamina, such as the consumes, checks, etc.

But overall the controller is a little bit over-wrought with the coroutines and invokes. You shouldn’t really need this level of complexity for a simple 2D controller.

Both invokes and coroutines are problematic because more than one of them can be started at a time unless you take extraordinary measures to ensure (via even more bank-account-precision code) that it won’t happen, such as with “lockout” booleans.

Instead, for durations and intervals, just use a float timer and count it down (or up) by Time.deltaTime each frame, then make decisions based on it.

If you want an example of that, download my Proximity Buttons project and see the 2D coyote jumping and jump buffering controller. This is the script:

proximity_buttons is presently hosted at these locations:

https://bitbucket.org/kurtdekker/proximity_buttons

https://github.com/kurtdekker/proximity_buttons

Generally you want to organize your code into phases like this:

  • gather all the input into temporary variables
  • count all your timers up or down
  • filter all the input for what CAN happen (eg, if stamina is out, clear the jump, dash, etc inputs)
  • decide how to process input:
    → in the air?
    → dashing?
    → walking?

That way it becomes very top-down linear, easy to reason about.

If you want to continue without refactoring to remove coroutines and invokes, then basically you’ll have to track down what’s happening in realtime via debugging.

Time to start debugging! Here is how you can begin your exciting new debugging adventures:

You must find a way to get the information you need in order to reason about what the problem is.

Once you understand what the problem is, you may begin to reason about a solution to the problem.

What is often happening in these cases is one of the following:

  • the code you think is executing is not actually executing at all
  • the code is executing far EARLIER or LATER than you think
  • the code is executing far LESS OFTEN than you think
  • the code is executing far MORE OFTEN than you think
  • the code is executing on another GameObject than you think it is
  • you’re getting an error or warning and you haven’t noticed it in the console window

To help gain more insight into your problem, I recommend liberally sprinkling Debug.Log() statements through your code to display information in realtime.

Doing this should help you answer these types of questions:

  • is this code even running? which parts are running? how often does it run? what order does it run in?
  • what are the names of the GameObjects or Components involved?
  • what are the values of the variables involved? Are they initialized? Are the values reasonable?
  • are you meeting ALL the requirements to receive callbacks such as triggers / colliders (review the documentation)

Knowing this information will help you reason about the behavior you are seeing.

You can also supply a second argument to Debug.Log() and when you click the message, it will highlight the object in scene, such as Debug.Log("Problem!",this);

If your problem would benefit from in-scene or in-game visualization, Debug.DrawRay() or Debug.DrawLine() can help you visualize things like rays (used in raycasting) or distances.

You can also call Debug.Break() to pause the Editor when certain interesting pieces of code run, and then study the scene manually, looking for all the parts, where they are, what scripts are on them, etc.

You can also call GameObject.CreatePrimitive() to emplace debug-marker-ish objects in the scene at runtime.

You could also just display various important quantities in UI Text elements to watch them change as you play the game.

Visit Google for how to see console output from builds. If you are running a mobile device you can also view the console output. Google for how on your particular mobile target, such as this answer for iOS: How To - Capturing Device Logs on iOS or this answer for Android: How To - Capturing Device Logs on Android

If you are working in VR, it might be useful to make your on onscreen log output, or integrate one from the asset store, so you can see what is happening as you operate your software.

Another useful approach is to temporarily strip out everything besides what is necessary to prove your issue. This can simplify and isolate compounding effects of other items in your scene or prefab.

If your problem is with OnCollision-type functions, print the name of what is passed in!

Here’s an example of putting in a laser-focused Debug.Log() and how that can save you a TON of time wallowing around speculating what might be going wrong:

“When in doubt, print it out!™” - Kurt Dekker (and many others)

Note: the print() function is an alias for Debug.Log() provided by the MonoBehaviour class.

I want to call out one other possibility for you to investigate: some keyboards can “Jam” when you press too many keys, and from your original description this might be a factor in play. You can test this by making a simple script that outputs the state of a few keys via Debug.Log(), such as this:

Debug.Log( "Jump:" + Input.GetAxis( "Jump") + "   Fire:" + Input.GetAxis( "Fire"));

And verify that your keyboard hardware isn’t jamming. It’s called key rollover:

Thank you for your answer! My keyboard was indeed “Jammign”, so here is the problem. But I’m really thankfull for all the information that you gave me in your previous comment. I will take a look at my code and get rid of the coroutines.

I forgot to say that I tested the game with a xbox controller and it worked fine. And also I tested my keyboard not only with the code you sugested adapted to my case

Debug.Log("Jump:" + Input.GetButton("Jump") + " Vertical:" + Input.GetAxisRaw("Vertical") + " Horizontal" + Input.GetAxisRaw("Horizontal"));

but also with an online keyboard tracker.

So we can confirm that the keyboard is the issue

Wow, look at that. Good thing to check!!

That’s only a suggestion… at least that’s how I would structure it.

If it is actually all working for you, then by all means continue!!

I wanted to also point out this code, which I assume is your double-jumping perhaps?:

If you hit JUMP and you are already going up, what that code above is actually REDUCES the amount of up, eg, slows your upwards velocity.

If you were going upwards at 10 units, tapping JUMP would make it suddenly 5 units upward speed.

Perhaps that’s what you want, but perhaps not?

Usually a second jump will add an additional amount of UP velocity to whatever is already there, so something like:

rb.velocity = new Vector2(rb.velocity.x, rb.velocity.y + ExtraUpVelocity);

Anyway, have fun jumping around!

It was made as a key sensitive jump. I want there to cancel de jump if the player realeases the button.

Thank you for all your help!!