How to recreate Megaman X/Zero like dash?

I tried to implement a Megaman-x-like dash in my code a few times but failed miserably. Took a few step backs and tried to breakdown how a Megaman x dash really works.

I got this:

  • when you press the dash button without directional influence, you’re sent to the direction you’re looking;
  • the dash works like a “better jumping”, as in whenever you hold it, it applies a force to the desired direction. however after a period of time/releasing the dash button, it stops. literally like a normal “mario jump” but with horizontal movement.
  • the dash cant be done on the air
  • your speed increases whenever you dash (duh)
  • you can “spam dash” [though i think if you do it your speed decreases a little?]
  • you can quickly change the direction of your dash with directional input, even when you are dashing
    though I’ve been struggling a little to get this one down to code.

I’d love to ask two questions, one is how to become better at this “second stage”, that is, turning your “breakdown” into actual code, and if you guys can help me out with some guidance. It would be really appreciated. Just for the sake of it, here is my player movement code:

Note: I don’t have a ground check bc I was having some problems with it. I’m temporarily using rb.velocity.y as ground control

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

public class PlayerMovement : MonoBehaviour
{
    #region VARIABLES
    [Header("Components")]
    public Rigidbody2D rb;

    [Space(20)]
    
    [Header("Movement Settings")]
    float defaultSpeed;
    float defaultMaxAcel;
    public float maxAcel = 2;
    public float acel = 1;
    public float speed;
    public float targetSpeed;
    [Range (-1f, 1f)] public float deAccel = 0.5f;
    private float mov;

    [Space(20)]

    [Header("Jump and Gravity settings")]
    public float jumpBuffer;
    public float jumpBufferTime = 0.2f;
    public float jumpPower;
    public float defaultGravity;
    public float gravityInc = 2;
    public float lowFallMult = 2;
    public bool jumped;

    [Space(20)]
    
    [Header("Other settings")]
    public float lerpingSpeed = 10;
    float defaultLerpingSpeed;
    public float antiAirLerpingSpeed;
    public float TimeMult = 2;

    #endregion

    void Start()
    {
        if (rb == null)
        {
            this.enabled = false;
            Debug.Log("RB not found. check if gameObject has component R2D.");
        }
        defaultGravity = rb.gravityScale;
        defaultSpeed = speed;
        defaultMaxAcel = maxAcel;
        defaultLerpingSpeed = lerpingSpeed;
    }

    void Update()
    {
        if (rb.velocity.y == 0)
        {
            jumped = false;
            speed = defaultSpeed;
            maxAcel = defaultMaxAcel;
            lerpingSpeed = defaultLerpingSpeed;
        } 
        if (Mathf.Abs(rb.velocity.y) > 0 && jumped)
        {
            antiAccelOnAir();
        }
        rb.gravityScale = defaultGravity;
        mov = Input.GetAxisRaw("Horizontal");

        if (acel < maxAcel && mov != 0 && rb.velocity.y == 0)
        {
            acel += Time.deltaTime * TimeMult;
        }
        else if (acel <= maxAcel && (mov == 0 || rb.velocity.y != 0))
        {
            acel = 1;
        }
        else
        {
            acel = maxAcel;
        }

        if (Input.GetKeyDown(KeyCode.Space))
        {
            jumpBuffer = jumpBufferTime;
        }
        else
        {
            jumpBuffer -= Time.deltaTime;
            jumpBuffer = Mathf.Max(jumpBuffer, -1);
        }

        if (jumpBuffer > 0 && rb.velocity.y == 0)
        {
            Jump();
            jumped = true;
            jumpBuffer = 0;
        }

        if (rb.velocity.y < 0)
        {
            rb.gravityScale = defaultGravity + gravityInc;
        }
        else if (rb.velocity.y > 0 && !Input.GetKey(KeyCode.Space))
        {
            rb.velocity += Vector2.up * Physics2D.gravity.y * lowFallMult * Time.deltaTime;
        }

        targetSpeed = speed * mov * acel;
    }

    void FixedUpdate()
    {
        float smoothVelocity = Mathf.Lerp(rb.velocity.x, targetSpeed, lerpingSpeed * Time.fixedDeltaTime);
        rb.velocity = new Vector2(smoothVelocity, rb.velocity.y);

        if (mov == 0 && rb.velocity.y == 0)
        {
            rb.velocity = new Vector2(smoothVelocity * deAccel, rb.velocity.y);
        }
    }

    void Jump()
    {
        rb.velocity = new Vector2(rb.velocity.x, jumpPower);
    }
    void antiAccelOnAir()
    {
        speed = speed * maxAcel;
        maxAcel = 1;
        acel = 1;
        lerpingSpeed = antiAirLerpingSpeed;
    }
}

Hi @IkarusBR,

Let me first start off apologising for not giving you the answer that you are looking for. What I can do though is help with this:

“how to become better at this “second stage”, that is, turning your “breakdown” into actual code”

I can’t speak for everyone but I can give you my take on breaking an issue down, realising it into sudo code and then starting the implementation.

As a rule of thumb, the simpler you break a problem, feature, etc down the better.

Lets take your first point here:

  • when you press the dash button without directional influence, you’re sent to the direction you’re looking;

I would approach the breakdown in this manner;

  • If I press the dash button, then I want to dash in the direction I’m looking
  • If I press the dash button, and I am already moving, then I want to dash in the direction I’m moving

With this we now have the criteria that we need to build from:

Think of it in the way of <action/trigger> then <result>

To build on this we can further elaborate with:
<action/trigger> On <condition> then <result>

And breaking down the above criteria will give us this:
1).
<action/trigger> If I press the dash button,
<result> then I want to dash in the direction I’m looking

2).
<action/trigger> If I press the dash button,
<condition> and I am already moving,
<result> then I want to dash in the direction I’m moving

Translating that into sudo code becomes much easier:

if(movement <= 0 && dash is activated) // Trigger
	dash in forward direction; // Result
else if (movement > 0 && dash is activated) // Trigger
	dash in directon of movement // Result

We could even take this one step further by having a 1-2-1 with our criteria:

if(dash is activated) { // Trigger
	if(movement <= 0) // Condition
		dash in forward direction; // Result
	else if (movement > 0) // Condition
		dash in directon of movement // Result
}

Forward direction because forwards should be the direction your character is facing.

With that we now have more details that we need to build on, for example;

  • How do we define the direction of travel?

An Enum perhaps (but this will be dependant on what you’re trying to achieve)

switch(moveDirection)
	case FORWARDS:
		break;
	case BACKWARDS:
		break;
	case LEFT:
		break;
	case RIGHT:
		break;
	default:
		break;

I hope this gave you some insight on how approach such problems.