Megalaga 6 - Collision and HUD

Posted March 25, 2020

NOTE:
This tutorial is most likely not compatible with versions of SGDK above 1.70. Unfortunately, I simply do not have the capacity or ability to update them right now. Read more about it here. Sorry.

Hold up!

If you like 90s platformers, check out my new game Kid Bubblegum!

We have enemies and bullets flying around on screen, and now it’s time to smash them together! Shooting enemies will award the player with points, so we will also implement a HUD that will show the current score, as well as the number of enemies left. Let’s get into it!

Collision

All collisions in the game will be handled in one big function. This function will loop through all bullets and all enemies to see if any of them collide. If that is the case, both the enemy and the bullet are destroyed and the player is awarded points. It’s simple in theory but maybe a bit tricky to get right in code. However, we’ll actually be recycling some code we’ve already written in the previous posts… But let’s start at the beginning and define our collision function first:

void handleCollisions(){

}

Now we need to prepare some variables we’ll use during our loop. First we’ll create two pointers, one for the current bullet and one for the current enemy. Then we’ll also need two loop variables this time, since we’ll be nesting two for loops:

Entity* b;
Entity* e;
int i = 0;
int j = 0;

Okay, now it’s time to start looping! We will first loop through all bullets in the bullets array, grab the current one, then check it against all the enemies on screen. Of course there are more sophisticated ways to structure collision checks, but for a simple game such as ours we don’t exactly need to plant any Quadtrees.

Open the loop like this:

for(i = 0; i < MAX_BULLETS; i++){
    b = &bullets[i];
    if(b->health > 0){

    }
}

Having a déja vu? Yup, we’re doing the same thing as in positionBullets(). We store the address of the current bullet in b, then check if it’s alive (meaning whether it has been fired and is currently on the screen). There’s no need to look at bullets that aren’t doing anything after all.

Now within that loop, we’ll open up the next one that will loop through the enemies array. Add the code so that both loops are nested like this:

for(i = 0; i < MAX_BULLETS; i++){
    b = &bullets[i];
    if(b->health > 0){
        for(j = 0; j < MAX_ENEMIES; j++){
            e = &enemies[j];
            if(e->health > 0){
                
            }
        }
    }
}

Make sure not to mix up i and j as that could lead to some undesirable results!

So to recap: We grab a bullet from our bullets array and check if it’s alive. If it is, we check this bullet against all the enemies that are currently alive. Although… we’re not actually checking anything yet. So let’s hop out of our handleCollisions() function for a second to create a helper function we’ll use to quickly check for collisions between entities:

int collideEntities(Entity* a, Entity* b)
{
    return (a->x < b->x + b->w && a->x + a->w > b->x && a->y < b->y + b->h && a->y + a->h >= b->y);
}

We pass in both our entities by reference. The function then checks whether the two are colliding and returns 1 (or TRUE) if they are, and 0 (or FALSE) if not. We’re using the AABB algorithm to check for collisions again, which is probably the most simple and efficient one there is. And for our purposes it is more than enough! If you need more information on AABB collisions, there are plenty of resources on the internet. For example, the MDN web docs have an interactive example.

Okay, back to our loops! Call the function we just defined to see if our current bullet and enemy are getting in each other’s way:

//...
if(e->health > 0){
    if(collideEntities( b, e )){
        
    }
}

Alright! Now we have all the checks in place to determine whether an enemy was hit by a bullet. Now let’s implement what will actually happen if there is a collision! First, we will kill both entities:

killEntity(e);
killEntity(b);

And then we will decrease the variables tracking our amount of bullets and enemies on the screen:

enemiesLeft--;
bulletsOnScreen--;

And finally we will break out of the inner loop because we have just destroyed our bullet, so there is no point in checking whether it is colliding with any other enemies. The collision check should now look like this:

if(collideEntities( b, e )){
    killEntity(e);
    killEntity(b);

    enemiesLeft--;
    bulletsOnScreen--;

    break;
}

Phew! After all this you’re probably anxious to try it all out. Feel free to do so, but remember that we actually have to call our function for it to do anything. So call it in the game loop after positioning the enemies and bullets, compile the game and let loose!

images/shooting.gif

Cathartic, isn’t it? But wait, we said something about scores, didn’t we? Let’s implement a scoring system and a HUD to make shooting down enemies even sweeter.

Score and HUD

We’ll go with a simple text HUD for our game, because that’s really all we need. First, define the following variables at the top of main.c:

int score = 0;
char hud_string[40] = "";

score will store the current amount of points. hud_string will be the string that makes up our HUD. We will format it in a helper function, which we will create right now:

void updateScoreDisplay(){
    sprintf(hud_string,"SCORE: %d - LEFT: %d",score,enemiesLeft);
    VDP_clearText(0,0,40);
    VDP_drawText(hud_string,0,0);
}

This function does three things. First of all, it uses the C function sprintf to create our score string. You can find documentation on sprintf on the web, but basically it works like this: The first parameter defines which variable should receive the new string, which in our case is of course hud_string. Then we pass in the string we want stored. However, we’re using format tags in that string, which are basically placeholders. The tag %d will be replaced by an integer value. We specify these values by passing them as arguments into sprintf after our string. Check out our line:

sprintf(hud_string,"SCORE: %d - LEFT: %d",score,enemiesLeft);

We have two %d tags, meaning that we have to pass in two integer values to be inserted into our final string. We’re passing in our score value, as well as enemiesLeft. This will result in a final string of SCORE: X - LEFT: Y with X and Y being the number of points and enemies left on screen.

After we have created our string and stored it in hud_string, the function will clear the first line of the screen of any text (to prevent leftovers), then finally draw our HUD string to the screen using VDP_drawText.

And that’s pretty much all we need for a HUD! Now let’s put it on screen. We’ll call our updateScoreDisplay() in two places. Once at the beginning of the game, somewhere before the game loop starts (but after all the enemies have been created). And then we’ll call it again when we have shot an enemy… after giving the player points, of course!

So before we break out the innermost loop in our handleCollisions() function, add these two lines:

score += 10;
updateScoreDisplay();

And we are done! Compile the game, shoot some enemies and watch that score skyrocket!

images/scoring.gif

And this is where I originally intended to stop this series…but my patrons on Patreon voted for me to keep it going! So next time we’ll make things fairer (and actually challenging) by having the enemies shoot back. Until then, happy coding and be excellent to each other!

If you've got problems or questions, join the official SGDK Discord! It's full of people a lot smarter and skilled than me. Of course you're also welcome to just hang out and have fun!

Join my Discord Server!

Hang out, get news, be excellent!

Come hang out!

Want To Buy Me a Coffee?

Coffee rules, and it keeps me going! I'll take beer too, though.

Check out the rest of this tutorial series!

  • Megalaga 1 - Space
  • Megalaga 2 - Entities
  • Megalaga 3 - Enemies
  • Megalaga 4 - Enemy Movement and Input
  • Megalaga 5 - Bullets
  • Megalaga 6 - Collision and HUD
  • Megalaga 7 - Enemy Bullets
  • Megalaga 8 - Spam Protection
  • Megalaga 9 - Sound
  • Megalaga BONUS - Powerup
  • By using the Disqus service you confirm that you have read and agreed to the privacy policy.

    comments powered by Disqus