Learn how to make a space shooter for the Sega Mega Drive using SGDK! I highly recommend working through the Megapong Tutorial first, as that explains a lot of the basics of MD coding.

Megalaga 6 - Collision and HUD

Posted March 25, 2020

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 have any questions, comments or criticism, post them in the comments below or reach out to me on Twitter @ohsat_games! Special thanks to Stephane Dallongeville for creating SGDK and everyone in the SGDK Discord for their help and keeping the dream alive!

Download the source code

All patrons on Patreon get the complete source code for this tutorial, as well as other perks such as early access! Become a Patron!
Just Want to Buy Me a Coffee?

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
  • Get Words in Your Inbox!

    Be oldschool and sign up for my newsletter to get updates! Just enter your email address, prove you're not part of Skynet and you're good to go!



    Powered by CleverReach. I will not send you spam or sell/give your email address to someone else.  You can of course unsubscribe at any time. By clicking the subscribe button above, you confirm that you have read and agreed to our privacy policy.

    By using the Disqus service you confirm that you have read and agreed to the privacy policy.

    comments powered by Disqus

    Related Posts

    HaxeFlixel Tutorials!

    If you’ve popped over to the tutorial section recently you might have noticed that I’ve added my very first HaxeFlixel tutorial! It shows how to implement a simple, pixel-perfect 2D water shader which I used for Go! Go! PogoGirl. But a few of you might be wondering what a HaxeFlixel is. Well, it’s a 2D game framework that is as powerful as it is underrated! It runs on the (also underrated) Haxe language, is extremely well documented, open source, and has built-in functions for almost anything you’d need.
    Read More

    Streets of Was

    As I’m sure many of you will remember, the original Streets of Rage for the Mega Drive had multiple endings. The real canonical ending has you beat the crap out of Mr. X, thereby ending his reign of terror forever (yeah, right). However, if you confronted Mr. X with a buddy in tow, a new possible path unlocked. A quick refresher is in order. When you confront Mr. X he will ask you to join his organization.
    Read More

    Streets of Rage 2 Design Docs

    A few years ago, Yuzo Koshiro posted a pile of old game design documents for Bare Knuckle 2 aka Streets of Rage 2 on the Ancient blog to commemorate the release of Streets of Rage 2 3D on the Nintendo 3DS. These documents gave a deep insight into the game’s inner workings, technical aspects, designs and even some cut content. They were an awesome resource for one of the most awesome games ever created.

    Read More