Adding Dashing

Introduction

At this point Beefball is a fully-playable game, but we'll be adding one final feature: dashing. This provides deeper gameplay that rewards skill and adds an element of anticipation and excitement.

Adding Dashing

A dash gives the player a burst of speed in the current direction that the player is pointing. The dash can be used to hit other players, shoot the puck, or dive in front of the puck to block a goal. To add a dash, modify CustomActivity in PlayerBall.cs as follows:

private void CustomActivity()
{
    MovementActivity();

    DashActivity();
}

Next, implement DashActivity in PlayerBall.cs:

private void DashActivity()
{
    if (BoostInput?.WasJustPressed == true)
    {
        float magnitude = MovementInput?.Magnitude ?? 0;

        bool isHoldingDirection = magnitude > 0;

        if (isHoldingDirection)
        {
            // dividing by magnitude tells us what X and Y would
            // be if the user were holding the input all the way in
            // the current direction.
            float normalizedX = MovementInput.X / magnitude;
            float normalizedY = MovementInput.Y / magnitude;

            XVelocity = normalizedX * DashSpeed;
            YVelocity = normalizedY * DashSpeed;
        }
    }
}

If you try to build your game, you'll notice that the variable DashSpeed is undefined. This is a variable that should be defined in the FRB Editor so that it can be tuned easily:

  1. Click on PlayerBall

  2. Select the Variables tab

  3. Click the Add New Variable button

  4. Verify that float is selected

  5. Enter the name DashSpeed

  6. Click OK

  7. Enter a value of 600 for DashSpeed

Your game should build and dashing should be fully functional.

Limiting Dash Frequency

Currently the player can dash indefinitely. We'll modify the dash so it has a cool-down after it's used. We'll need a variable to keep track of when the dash was first used, and compare against that variable to check if the user can dash again. First, add the following variables to your PlayerBall.cs at class scope (make it a field):

// Set a large negative number so that dashing can happen immediately
private double lastTimeDashed = -1000;

Next, add a check for the last dash time and a set to lastDashTime in DashActivity in PlayerBall.cs. Your entire method should look like:

private void DashActivity()
{
    float magnitude = MovementInput?.Magnitude ?? 0;

    bool shouldBoost = BoostInput?.WasJustPressed == true &&
        TimeManager.CurrentScreenSecondsSince(lastTimeDashed) > DashFrequency &&
        magnitude > 0;

    if (shouldBoost)
    {
        lastTimeDashed = TimeManager.CurrentScreenTime;

        // dividing by magnitude tells us what X and Y would
        // be if the user were holding the input all the way in
        // the current direction.
        float normalizedX = MovementInput.X / magnitude;
        float normalizedY = MovementInput.Y / magnitude;

        XVelocity = normalizedX * DashSpeed;
        YVelocity = normalizedY * DashSpeed;
    }
}

Just like before, we need to create a DashFrequency variable in the FRB Editor:

  1. Click on PlayerBall

  2. Select the Variables tab

  3. Click the Add New Variable button

  4. Verify that float is selected

  5. Enter the name DashFrequency

  6. Click OK

  7. Enter a value of 2 for DashFrequency to indicate a 2 second frequency

Now the player can only dash once every two seconds.

Why did we create DashFrequency and DashSpeed variables in the FRB Editor, but lastTimeDashed in Visual Studio? You may have noticed that we defined some of our variables (like DashFrequency and DashSpeed) in the FRB Editor, but we defined lastTimeDashed in Visual Studio. When working in the FRB Editor it is important to distinguish between "data" and "logic". Variables considered data are created in the FRB Editor so that they can be easily modified. Game development is an iterative process, and even the most experienced game designers make changes to their game throughout development. Creating variables in the FRB Editor makes changing variables easy, and communicates to other developers that these variables should be tuned. The lastTimeDashed variable, on the other hand, exists solely to support the logic of limiting dashing. The actual value of lastTimeDashed changes many times as the game runs, and setting it through the FRB Editor either has no impact on the game or introduces an unintended bug of making dash not work (if the value is set to a large positive value).

Adding a Cooldown Indicator

Now that the player's dashing is limited, we need to add some visible indication of when another dash can occur. We'll do this by adding a second Circle to the PlayerBall which grows when the user has dashed, then disappear when the user can dash again. To do this:

  1. Click on PlayerBall

  2. Select the Quick Actions tab

  3. Click Add Object to PlayerBall

  4. Select Circle

  5. Enter the name CooldownCircle

Defining States

Next we'll use States (something we haven't used yet) to define what the PlayerBall should look like when the cooldown first begins and when the cooldown ends. States have the following benefits:

  1. They let you "code against concept, not content". In other words, you can define a state and use it in code, and later make changes to the state without having to modify any code. This is desirable because when you set the PlayerBall to a "Tired" state, your code shouldn't depend on anything except for the presence of a Tired state. Code should simply set the state to Tired, while the logic of Tired should be handled elsewhere.

  2. States can interpolate from one to the other. In this case, we'll have the "Tired" state set the CooldownCircle to have a small radius, and the "Rested" will have a large circle. The end result is the CooldownCircle "grows" as the player's dash is recovering.

All states must be categorized, so the first step is to create a new category:

  1. Right-click on the States item under PlayerBall and select Add State Category

  2. Name the category DashCategory and click OK

Now the PlayerBall has a category named DashCategory.

Next we'll add states to the category:

  1. Right-click on DashCategory

  2. Select Add State

  3. Enter the name Tired and click OK

  4. Repeat the above steps to create a "Rested" state as well

As mentioned above, we'll want the CooldownCircle ball to grow (increase its Radius) to give the player an indication of how much time is left in the cooldown. To do this, we'll need to tunnel in to the CooldownCircle's Radius property:

  1. Drag+drop the CooldownCircle object onto the Variables item

  2. Select Radius for the Variable

  3. Click OK

Categories must be told which variables to modify. By default, categories do not modify any variables.

To add a variable to a state, drag+drop the variable onto the category:

In this case, the only variable is the CooldownCircleRadius. Next let's define the two states:

  1. Change Tired CooldownCircleRadius to 0.

  2. Change Rested CooldownCircleRadius to 16. This should match the default radius for Body.

Using States in code

Now that our states are defined, let's use them in code. The simplest way to use states is to assign an entity's current state variable. FlatRedBall also provides functions for interpolating between states, which is the process of gradually changing from one state to another. We will write code to set the state to "Tired", then interpolate to rested over the course of two seconds - of course we'll use DashFrequency rather than hard-coding the value 2. To use these newly-created states, go to PlayerBall.cs and modify the DashActivity function as follows: To use the states, add the following two lines of code inside the if-statement in DashActivity in PlayerBall.cs:

private void DashActivity()
{

    float magnitude = MovementInput?.Magnitude ?? 0;

    bool shouldBoost = BoostInput?.WasJustPressed == true &&
        TimeManager.CurrentScreenSecondsSince(lastTimeDashed) > DashFrequency &&
        magnitude > 0;

    if (shouldBoost)
    {
        lastTimeDashed = TimeManager.CurrentScreenTime;

        // dividing by magnitude tells us what X and Y would
        // be if the user were holding the input all the way in
        // the current direction.
        float normalizedX = MovementInput.X / magnitude;
        float normalizedY = MovementInput.Y / magnitude;

        XVelocity = normalizedX * DashSpeed;
        YVelocity = normalizedY * DashSpeed;