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 7 - Enemy Bullets

Posted April 8, 2020

So far, enemies are nothing more than cannon fodder. They don’t shoot or even move towards the player, so there’s not really any way to fail the game. Let’s change that by having enemies shoot at the player!

Shooting Bullets

We will be efficient and use our existing shootBullet() function to handle the enemy bullets. To do that, first we’ll need to change two defines. All bullets in the game will come from the same pool, so we need to increase the max amount of bullets that can be visible at one time. When only 3 bullets can be on the screen at once, there will not be much shooting going on, which is not really exciting. Additionally, we will define the bottom edge of the screen, so we can check if the enemy bullets go offscreen (since they move downwards, they can only leave the screen at the bottom).

#define BOTTOM_EDGE 224
#define MAX_BULLETS 6

Now we modify shootBullet(). So far this function only dealt with the player, but now we want to make it more universal. To that end, we’ll make it so you pass in the entity that is shooting. This way we can use the same function for both player and enemies. Add an argument to the definition:

void shootBullet(Entity Shooter){


And then update the one call to the function we have, which is inside the joypad callback:

if (state & BUTTON_B & changed)

Now we need to figure out a way to see who is shooting. If the player is shooting the bullets will go up, while enemy bullets will move downwards. We’ll also have to flip the bullet sprite vertically depending on where it’s going. So how do we figure out the shooter? There are many approaches, but we’re gonna be clever and keep it simple. In our game, the player is always at the bottom of the screen, while enemies are always at the top. So we can simply check the y-position of the Shooter we’re passing into the function. If the entity is below, say, 100, it has to be the player. Anything above that is an enemy. So add this line at the beginning of shootBullet:

bool fromPlayer = (Shooter.y > 100);

Next we need to modify these two lines:

b->x = player.x + 4;
b->y = player.y;

These place the bullet at the player’s position before firing them. But since our function is supposed to be universal now, we have to replace the player reference like this:

b->x = Shooter.x + 4;
b->y = Shooter.y;

This shows how useful structs are. Since the player and enemies are both Entity-structs, both are guaranteed to have y-values, so we can simply replace player with Shooter here.

This places the bullet at the right spot, now to handle the actual firing. Since now we have two possible directions for bullets (up or down), we need to add an if-statement to set the proper velocity. After reviveEntity(b); replace the line b->vely = -3; with this:

if(fromPlayer == TRUE){
    b->vely = -3;
} else{
    b->vely = 3;

So if the player is shooting, we shoot the bullet upwards, otherwise we shoot it downwards. We also flip the sprite vertically, so it points in the right direction.

And finally, we have to expand our check if the bullets are on screen or not. So far, we’ve only checked if a bullet leaves the upper edge of the screen, but now we’ll have to check the bottom edge as well. Otherwise the bullets shot by enemies would loop forever, which is not exactly fair. So add an else if-statement to our function positionBullets so that it looks like this:

if (b->y + b->h < 0)
} else if(b->y > BOTTOM_EDGE){
} else { ... }

And now we can use our shootBullet function for enemies as well! But of course the enemies are not shooting yet, because we never tell them to. But if we did, we’d run into a problem: We’re always checking for collisions between bullets and enemies. This means that the moment an enemy shoots a bullet, they get killed by it. Also, the player would never get hit a by bullet, because we don’t check for that collision. So let’s change that!


We will fix the collision issues in handleCollisions(). Take a minute to re-familiarize yourself with the function. We cycle through bullets in our pool and grab one. If it is dead, we take it and check it against all enemies. And this is where we have to add a new condition. Because now we don’t want to just check collisions with the enemy, but also with the player. At the same time, we want to not check for certain collisions; if an enemy shoots a bullet, that bullet should only collide with the player, not with enemies.

How do we decide what collisions to check for? We can again use a clever trick. If a bullet travels upwards, it has to come from the player and should therefore only collide with enemies. If a bullet moves downwards, it’s other way around; we’ll only check for collisions with the player, so the enemies won’t blow themselves up.

In code terms this means wrapping our existing code in a new if-statement, then adding an else-statement where we check for collision with the player:

void handleCollisions()
    // ...
    if (b->health > 0)
        if(b->vely < 0){ //This is new!
            for (j = 0; j < MAX_ENEMIES; j++)
                e = &enemies[j];
        } else{ //Also this

And that’s all we need to do. Now bullets will not collide with whoever shot them, only the other party. Note that killEntity takes a pointer, so we have to pass in the address of the player entity, not the entity itself.

Making enemies shoot

We’ve already done a lot, but enemies still aren’t actually shooting. Let’s finally take care of that now. We’ll do it like this: We will have a timer running that triggers every 2 seconds. When it triggers, we give each enemy in turn a random chance to fire. If one fires, we restart the timer. That way it is random which enemy shoots and when, but we don’t flood the screen with bullets. Of course there are other ways to handle this, but I find this one works well enough.

First we’ll define the interval of shots. At the top of main.c add a define:

#define SHOT_INTERVAL 120

Then we need a variable that acts as our timer:

u16 shotTicker = 0;

Now to implement the shooting. We will do this in the positionEnemies() function. This makes the function seem slightly misnamed, but it’s the most convenient place to put our new code. We want to give each enemy a chance to shoot, and in positionEnemies() we’re already looping through all of them. So at the very top of the function, increase the shotTicker:

void positionEnemies()

We’ll add the shooting code at the end of that function, after we use SPR_setPosition to position the enemy sprite on screen. Start with an if-statement:

e->x += e->velx;
SPR_setPosition(e->sprite, e->x, e->y);

if(shotTicker >= SHOT_INTERVAL){


If shotTicker equals the defined SHOT_INTERVAL, it is time to shoot. Our game will run at 60fps, so a SHOT_INTERVAL of 120 equals 2 seconds. Now, inside the if-statement add this:

if( (random() % (10-1+1)+1) > 4 ){
    shotTicker = 0;

That condition might seem familiar to you. That’s right, it’s the same formula we used in part 1 of this tutorial to generate a random space background! Basically we’re generating a number between 1 and 10. If that number is bigger than 4, the enemy fires, in which case we also reset the shotTicker. If the number is less than 4, positionEnemies() will loop to the next enemy and give him a chance to shoot. This will loop until one of the enemies fires and the cycle starts anew.

And that is it! Now we have enemies that randomly fire bullets that can kill us, but not them. And we did it all by extending some of our functions! Now that’s good programming.

However…there is actually a slight potential issue with our code. It concerns the fact that both players and enemies share the same bullet pool. We’ll fix it next time, but feel free to look at the code again and try to figure out what I’m talking about! Until next time 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!

Download the Project Files!

All patrons on Patreon get the complete source code for this tutorial, as well as other perks such as early access! And Patreon support also ensures that I can keep working on tutorials like this one. 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