Rectangular collision detection

Now, we are going to learn how to implement rectangular collision detection. It turns out that both Robo and our enemy (a water bottle) are very rectangular, making rectangular collision detection the best choice.

The enemy within

Let's introduce our Robo's enemy—a bottle of water to rust his gears. The code for this is included next.

Add the following sprite definition to RoboRacer2D:

Sprite* enemy;

Now, we will setup the sprite. Add the following code to LoadTextures:

enemy = new Sprite(1);
enemy->SetFrameSize(32.0f, 50.0f);
enemy->SetNumberOfFrames(1);
enemy->AddTexture("resources/water.png");
enemy->IsVisible(false);
enemy->IsActive(false);
enemy->SetValue(-50);
enemy->IsCollideable(true);

This code is essentially the same code that we used to create all of our sprites. One notable difference is that we use the new SetValue method to add a negative value to the sprite. This is how many points the player will lose if they hit this enemy. We also make sure that we set the enemy to be collideable.

Spawning the enemy

Just like the pickups, we need to spawn our enemies. We could use the same code as the pickups, but I thought it would be nicer if our enemies worked on a different timer.

Declare the following variables in RoboRacer2D.cpp:

float enemySpawnThreshold;
float enemySpawnTimer;

The threshold will be the amount of seconds that we want to pass before an enemy is spawned. The timer will start and zero and count up to that number of seconds.

Let's initialize these values in the StartGame function. Add the following two lines of code to the end of StartGame:

enemySpawnThreshold = 7.0f;
enemySpawnTimer = 0.0f;

We set a spawn threshold of 7 seconds, and set the spawn timer to 0.

Now, let's create a function to spawn our enemies. Add the following code to RoboRacer2D.cpp:

void SpawnEnemy(float p_DeltaTime)
{
  if (enemy->IsVisible() == false)
  {
    enemySpawnTimer += p_DeltaTime;
    if (enemySpawnTimer >enemySpawnThreshold)
    {
      float marginX = enemy->GetSize().width;
      float marginY = enemy->GetSize().height;
      float spawnX = (rand() % (int)(screen_width - (marginX * 2))) + marginX;
      float spawnY = screen_height - ((rand() % (int)(player->GetSize().height - (marginY * 2))) + marginY);
      enemy->SetPosition(spawnX, spawnY);
      enemy->IsVisible(true);
      enemy->IsActive(true);
    }
  }
}

This code does the following:

  • It checks to make sure that the enemy is not already on the screen
  • If there is no enemy, then the spawn timer is incremented
  • If the spawn timer exceeds the spawn threshold, the enemy is spawned at a random position somewhere within the width of the screen and within the vertical reach of Robo

Don't get too worried about the particular math being used. Your algorithm to position the enemy can be completely different. The key here is that a single enemy will be generated within Robo's path.

Make sure to add a call to SpawnEnemy in the Update function as well as a line to update the enemy:

if (m_gameState == GS_Running)
{
  background->Update(p_deltaTime);
  robot_left->Update(p_deltaTime);
  robot_right->Update(p_deltaTime);
  robot_left_strip->Update(p_deltaTime);
  robot_right_strip->Update(p_deltaTime);
  
  pause->Update(p_deltaTime);
  resume->Update(p_deltaTime);
  
  pickup->Update(p_deltaTime);
  SpawnPickup(p_deltaTime);
  
  enemy->Update(p_deltaTime);
  SpawnEnemy(p_deltaTime);
  
  CheckCollisions();
  
}

We also need to add a line to Render to render the enemy:

void Render()
{
  glClear(GL_COLOR_BUFFER_BIT);
  glLoadIdentity();
  
  background->Render();
  robot_left->Render();
  robot_right->Render();
  robot_left_strip->Render();
  robot_right_strip->Render();
  
  pause->Render();
  resume->Render();
  
  pickup->Render();
  enemy->Render();
  SwapBuffers(hDC);
}

If you run the game right now, then a water bottle should be spawned about seven seconds after the game starts.

Adding the rectangular collision code

As we have mentioned several times, all sprites are essentially rectangles. Visually, if any border of these rectangles overlap, we can assume that the two sprites have collided.

We are going to add a function to our Sprite class that determines whether two rectangles are intersecting. Open Sprite.h and add the following method declaration:

const bool IntersectsRect(const Sprite*p_sprite) const;

Now, let's add the implementation to Sprite.cpp:

const bool Sprite::IntersectsRect(const Sprite* p_sprite) const
{
 if (this->IsCollideable() && p_sprite->IsCollideable() && this->IsActive() && p_sprite->IsActive()) 
 {
  const Rect recta = this->GetCollisionRect();
  const Rect rectb = p_sprite->GetCollisionRect();
  if (recta.left >= rectb.left && recta.left <= rectb.right && recta.top >= rectb.top && recta.top <= rectb.bottom)
  {
   return true;
  }
  else if (recta.right >= rectb.left && recta.right <= rectb.right && recta.top >= rectb.top && recta.top <= rectb.bottom)
  {
   return true;
  }
  else if (recta.left >= rectb.left && recta.right <= rectb.right && recta.top < rectb.top && recta.bottom > rectb.bottom)
  {
   return true;
  }
  else if (recta.top >= rectb.top && recta.bottom <= rectb.bottom && recta.left < rectb.left && recta.right > rectb.right)
  {
   return true;
  }
  else if (rectb.left >= recta.left && rectb.left <= recta.right &&
   rectb.top >= recta.top && rectb.top <= recta.bottom)
  {
   return true;
  }
  else if (rectb.right >= recta.left && rectb.right <= recta.right && rectb.top >= recta.top && rectb.top <= recta.bottom)
  {
   return true;
  }
  else if (rectb.left >= recta.left && rectb.right <= recta.right && rectb.top < recta.top && rectb.bottom > recta.bottom)
  {
   return true;
  }
  else if (recta.top >= rectb.top && recta.bottom <= rectb.bottom && recta.left < rectb.left && recta.right > rectb.right)
  {
   return true;
  }
  else if (rectb.top >= recta.top && rectb.bottom <= recta.bottom && rectb.left < recta.left && rectb.right > recta.right)
  {
   return true;
  }
 }
 return false;
}

Here's how this code works:

  • This function looks really complicated, but it is really only doing a few things.
  • The function accepts a sprite parameter.
  • We set recta to be the collision rectangle of the sprite that called the IntersectsRect method and set rectb to be the collision rectangle of the sprite that was passed in.
  • We then test every possible combination of the position of the vertices in of recta to those of rectb. If any test is true, then we return true. Otherwise we return false.

The following figure illustrates some of the ways that two rectangles could interact:

Adding the rectangular collision code

Wiring continued

We have already wired in the collision check using CheckCollisions. We just need to add the following code to CheckCollisions to the check whether the player is colliding with an enemy:

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

Now, the real fun starts! Play the game and when the water can enemy spawns make sure Robo avoids it! If you collide with an enemy, you will lose points (as the value of enemy is set to a negative number). Until we implement a visible score, you may want to write the score out to the console.

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

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