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.
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.
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:
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.
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; }
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.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:
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.
3.16.139.62