Level up!

A lot of the fun in games is trying to increase your score. Part of good game design is to make the game challenging to play, but not so challenging that the player cannot score or improve.

Most players also get better at a game as they play, so if the game difficulty does not increase, the player will eventually get bored because the player will no longer be challenged.

We will start by simply displaying the score on the screen so that the player can see how well they are doing. Then we will discuss techniques that are used to continually increase the difficulty of the game, thus steadily increasing the challenge.

Displaying the score

We already learned how to display text on the screen when we were creating the credits screen. Now, we will use the same techniques to display the score.

If you recall, we already have a mechanism to keep track of the score. Every sprite has a value property. For pickups, we assign a positive value so that the player gains points for each pickup. For enemies, we assign a negative value so that the player loses points whenever they collide with an enemy. We store the current score in the value property of the player.

Add the following code to RoboRacer2D.cpp to create the DrawScore function:

void DrawScore()
{
 char score[50];
 sprintf_s(score, 50, "Score: %i", player->GetValue());
 DrawText(score, 350.0f, 25.0f, 0.0f, 0.0f, 1.0f);
}

This code works just like the DrawCredits function that we created earlier. First, we create a character string that holds the current score and a caption, then we use DrawText to render the text.

We also need to wire this into the main game. Modify the GS_Running case of the m_gameState switch in the Render function with the highlighted line:

 case GameState::GS_Running:
 case GameState::GS_Paused:
 {
  background->Render();
  robot_left->Render();
  robot_right->Render();
  robot_left_strip->Render();
  robot_right_strip->Render();
  pauseButton->Render();
  resumeButton->Render();
  pickup->Render();
  enemy->Render();
  DrawScore();
 }
 break;

The score will display both when the game is running and when the game is paused.

Game progression

In order to add progression to the game, we need to have certain thresholds established. For our game, we will set three thresholds:

  • Each level will last two minutes
  • If the player receives less than five pickups during a level, the game will end, and the game over screen will be displayed
  • If the player receives five or more pickups, then the level ends and the next level screen is displayed

For each level that the player successfully completes, we will make things a little more difficult. There are many ways that we could increase the difficulty of each level:

  • Increase the spawn time for pickups
  • Decrease the speed of the robot

To keep things simple, we will only do one of these. We will increase the spawn time threshold for pickups by .25 seconds for each level. With pickups spawning less often, the player will eventually receive too few pickups, and the game will end.

Defining game levels

Let's set up the code for level progression. We will start by defining a timer to keep track of how much time has passed. Add the following declarations to RoboRacer2D.cpp:

float levelTimer;
float levelMaxTime;
float pickupSpawnAdjustment;

int pickupsReceived;
int pickupsThreshold;
int enemiesHit;

We will initialize the variables in the StartGame function:

levelTimer = 0.0f;
levelMaxTime = 30.0f;
pickupSpawnAdjustment = 0.25f;

pickupsReceived = 0;
pickupsThreshold = 5;
enemiesHit =0;

We are setting up a timer that will run for 120 seconds, or two minutes. At the end of two minutes the level will end and the spawn time for pickups will be incremented by .25 seconds. We will also check to see whether the player has received five pickups. If not, the game will be over.

To handle the logic for the level progression, let's add a new function called NextLevel by adding the following code:

void NextLevel()
{
 if (pickupsReceived < pickupsThreshold)
 {
  m_gameState = GameState::GS_GameOver;
 }
 else
 {
  pickupSpawnThreshold += pickupSpawnAdjustment;
  levelTimer = 0.0f;
  m_gameState = GameState::GS_NextLevel;
 }
}

As stated previously, we check to see whether the number of pickups that the robot has is less than the pickup threshold. If so, we change the game state to GS_GameOver. Otherwise, we reset the level timer, reset the pickups received counter, increment the pickup spawn timer, and set the game state back to GS_Running.

We still need to add some code to update the level timer and check to see whether the level is over. Add the following code to the GS_Running case in the Update function:

levelTimer += p_deltaTime;
if (levelTimer > levelMaxTime)
{
  NextLevel();
}

This code updates the level timer. If the timer exceeds our threshold, then call NextLevel to see what happens next.

Finally, we need to add two lines of code to CheckCollisions to count the number of pickups received by the player. Add the following highlighted line of code to CheckCollisions:

if (player->IntersectsCircle(pickup))
{
  pickup->IsVisible(false);
  pickup->IsActive(false);
  player->SetValue(player->GetValue() + pickup->GetValue());
  pickupSpawnTimer = 0.0f;
  pickupsReceived++;
}

 if (player->IntersectsRect(enemy))
 {
  enemy->IsVisible(false);
  enemy->IsActive(false);
  player->SetValue(player->GetValue() + enemy->GetValue());
  enemySpawnTimer = 0.0f;
  enemiesHit++;
 }

Game stats

It would be nice for the player to be able to see how they did between each level. Let's add a function to display the player stats:

void DrawStats()
{
 char pickupsStat[50];
 char enemiesStat[50];
 char score[50];
 sprintf_s(pickupsStat, 50, "Enemies Hit: %i", enemiesHit);
 sprintf_s(enemiesStat, 50, "Pickups: %i", pickupsReceived);
 sprintf_s(score, 50, "Score: %i", player->GetValue());
 DrawText(enemiesStat, 350.0f, 270.0f, 0.0f, 0.0f, 1.0f);
 DrawText(pickupsStat, 350.0f, 320.0f, 0.0f, 0.0f, 1.0f);
 DrawText(score, 350.0f, 370.0f, 0.0f, 0.0f, 1.0f);
}

We will now wire this into the next level screen.

The next level screen

Now that we have the logic in place to detect the end of the level, it is time to implement our next level screen. By now, the process should be second nature, so let's try an abbreviated approach:

  1. Declare a pointer to the screen:
    Sprite* nextLevelScreen;
  2. Instantiate the sprite in LoadTextures:
    nextLevelScreen = new Sprite(1);
    nextLevelScreen->SetFrameSize(800.0f, 600.0f);
    nextLevelScreen->SetNumberOfFrames(1);
    nextLevelScreen->AddTexture("resources/level.png", false);
    nextLevelScreen->IsActive(true);
    nextLevelScreen->IsVisible(true);
  3. Modify the GS_NextLevel case in the Update function:
     case GameState::GS_NextLevel:
     {
      nextLevelScreen->Update(p_deltaTime);
      continueButton->IsActive(true);
      continueButton->Update(p_deltaTime);
      inputManager->Update(p_deltaTime);
      ProcessInput(p_deltaTime);
      break;
     }
  4. Modify the GS_NextLevel case in the Render function to look like the following code::
     case GameState::GS_NextLevel:
     {
      nextLevelScreen->Render();
      DrawStats();
      continueButton->Render();
     }
     break;

Continuing the game

Now, we need to add a button that allows the player to continue the game. Again, you have done this so many times, so we will use a shorthand approach:

  1. Declare a pointer for the button:
    Sprite* continueButton;
  2. Instantiate the button in LoadTextures:
    continueButton = new Sprite(1);
    continueButton->SetFrameSize(75.0f, 38.0f);
    continueButton->SetNumberOfFrames(1);
    continueButton->SetPosition(390.0f, 400.0f);
    continueButton->AddTexture("resources/continueButton.png");
    continueButton->IsVisible(true);
    continueButton->IsActive(false);
    inputManager->AddUiElement(continueButton);
  3. Add this code to Update:
     case GameState::GS_NextLevel:
     {
      nextLevelScreen->Update(p_deltaTime);
      continueButton->IsActive(true);
      continueButton->Update(p_deltaTime);
      inputManager->Update(p_deltaTime);
      ProcessInput(p_deltaTime);
     }
     break;
  4. Add this code to Render:
     case GameState::GS_NextLevel:
     {
      nextLevelScreen->Render();
      DrawStats();
      continueButton->Render();
     }
     break;
  5. Add this code to ProcessInput:
    if (continueButton->IsClicked())
    {
      continueButton->IsClicked(false);
      continueButton->IsActive(false);
      m_gameState = GameState::GS_Running;
    pickupsReceived = 0;
    enemiesHit = 0;
    }

Clicking the continue button simply changes the game state back to GS_Running. The level calculations have already occurred when NextLevel was called.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.25.60