Megalaga 3 - Enemies

Posted March 2, 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!

Last time we defined a struct of type Entity and used it to create a player. Now it’s time to throw some enemies into the fray! It ain’t a shooter without somethin' to shoot at, after all. Luckily our Entity struct will make that rather easy for us, because this way, each entity will keep track of its own position, velocity and other values.

And to be extra lazy clever, we’ll put all enemy entities into an array, so we can deal with them more easily in bulk. So at the top of main.c (but after our struct definition) define the following:

#define MAX_ENEMIES 6
Entity enemies[MAX_ENEMIES];

In our game we’ll have a maximum of 6 enemies on screen at once, but you can of course change that number. We’ll write the code in such a way that we’ll just need to change this one #define in order to modify the number of enemies.

We’ll also want to keep track of how many enemies there currently are on screen. So after the above definitions, add this line:

u16 enemiesLeft = 0;

Alright, now to actually create all those enemy entities. Since we’re doing a simple game with a simple setup, we’ll use a for loop for this. Create one after creating the player entity in main():

/*Create all enemy sprites*/
Entity* e = enemies;
for(i = 0; i < MAX_ENEMIES; i++){
	
}

Before the loop we’re defining a pointer e which is assigned the entire enemies array. This will make the pointer point at the first value in that array (that’s a behavior of the C language). We will use this pointer to reference the current enemy entity and to loop through them.

Now might be a good time to brush up on your pointer skills, by the way. There’s just no escaping those things.

Anyway, you might have noticed that we’re reusing the variable i that we had defined when we were drawing our tiles. There’s no need to create another variable for iteration here, as i isn’t used for anything else. Saving memory is fun!

Now to actually make our loop do things. First, let’s set some basic values of our current enemy:

e->x = i*32;
e->y = 32;
e->w = 16;
e->h = 16;
e->velx = 1;
e->health = 1;

Note that now we’ll have to use the arrow operator (instead of a dot) to access an item in a struct, since e is a pointer. For your information, e->x is equivalent to (*e).x. Nifty.

The x position will be different for each enemy. We’re placing them in a row with a bit of space in between. The y-position, width and height will be the same for all enemies. We’re also setting the x-velocity to 1, so that our enemies start moving right when the game starts. The health of enemies will be 1 at the start, meaning that they are alive (remember that we’re not using health bars, we just care whether an entity is alive or dead).

Now to add sprites. Remember that the Entity struct has a Sprite* item in it, which will store our sprite.

e->sprite = SPR_addSprite(&ship,e->x,e->y,TILE_ATTR(PAL2,0,TRUE,FALSE));

Note that the third argument of TILE_ATTR is TRUE. This will flip the sprite so that the enemy is pointing downwards. “But wait a minute!” you might think here. “We’re using palette 2?” Yes we are, so we need to define it. So up in main(), where we grab the palette data from our tiles, add another line so we end up with this:

VDP_setPalette(PAL1, background.palette->data);
VDP_setPalette(PAL2, background.palette->data);

Changing Colors

Okay, so…remember how I said that we’d be using the same palette (PAL1) for both tiles and sprites, so we could be “efficient”? Well…that’s getting thrown out the window now. I’m doing this to show you a cool trick in a second. Usually you would do things differently, but it’s easier to explain this way… and after all, we’re making a very basic game here. But anyway, let’s finish up the enemy loop first.

We’ll give each enemy a name. Generate it like this:

sprintf(e->name, "En%d",i);

This will call each enemy “En” plus its number (En1, En2, …). As I said, we won’t be using these names, but they’re good for debug purposes so I wanted to show you how to add them.

Finally, we’ll increment enemiesLeft by 1 since this enemy is now done. And even more finally, we point the pointer e at next enemy in the array so that the loop can keep going.

enemiesLeft++;
e++;

And that’s it! Now our loop creates 6 enemies that are positioned in a row near the top of the screen. You might have noticed that we’re using the same graphic for the enemy ships as we’ve used for the player ship. And yes, that’s the reason we just called the image ship. However, having the same sprites for both would look boring, so let’s use that trick I told you about to spice things up a little. We’ll use a palette swap to make the enemy ships look different. Add this line after the for-loop has closed:

VDP_setPaletteColor(34,RGB24_TO_VDPCOLOR(0x0078f8));

The helper function RGB24_TO_VDPCOLOR converts colors from a RGB hex value to a format the Mega Drive VDP can process. We set the color at index 34 to 0x0078f8, which is a blue color. Index 34 is stored in PAL2 and just so happens to be the orange color of the ship sprite. So with this line we’re switching that color to blue, making the enemies look different without actually having to load another sprite. That saves on resources!

Now of course this approach is a bit heavy-handed here. We’re using PAL2 only for this one color, wasting the other 15. And it would be better to use unique sprites for the enemies anyway, because palette swaps can look kind of cheap. But I wanted to show you this trick, because it’s very useful and a common technique in retro game development. Feel free to change the other colors in PAL2 to make things slightly less wasteful and to make the enemies look even more interesting. The indexes for PAL2 are 32–47.

That was a lot of code. Compile the game now and you should see a row of enemies menacing our player!

images/enemies.png

Although they don’t actually move… We’ll fix that next time. We’ll also be adding bullets to the game, so that you can shoot the enemies down. Until then 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