Learn how to make an endless runner 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.
Megarunner 4 - Player and Obstacles
Update: Fixed a small error when importing the rock graphic. Thanks to Thiago for pointing it out!
Alright, we got the background moving, now we need a player sprite to complete the illusion. Of course we could create very long levels and have the player actually run through them, but it is way more efficient to fake endless running by just scrolling the background past the player.
Importing the Image
First, download the player image here. If you take a look at it you’ll see that the image contains 3 different sprites of our player character:
This is what’s called a spritesheet. We will be using the images as frames of animation in a minute. For now, create a folder called
sprites in the
res folder of your project, then put the player spritesheet in there. Good? Good.
Now open up the
resources.res file in the
res folder and add the following line to import the player graphic:
SPRITE runner "sprites/player.bmp" 2 2 NONE 20
As you can see we’re importing the file as a
SPRITE under the name
runner. We also specify that the sprite is
2 tiles wide and
2 tiles high. But wait…the actual
player.bmp is 32x32 pixels big, which would be 4x4 tiles! What gives? We actually only tell SGDK how big one sprite is, which is 16x16 px in our case. The resource compiler then automatically imports and processes all the sprites within an image file (3 in our case).
NONE is the compression we want to use. Finally,
20 tells SGDK that we want our animations to run at 20 frames per second. Yes, you set the animation speed while importing!
Adding the Sprite
Now that we’ve imported the graphic, let’s put the player on screen. At the top of
main.c define these variables to store our player, as well as the x and y positions:
Sprite* player; const int player_x = 32; int player_y = 112;
Then we’ll extract the palette from the image. Feel free to put this line after our previous call to
VDP_setPalette where we got the colors of the tiles:
main(), after all our code dealing with the background tiles, add the player by typing in the following code:
SPR_init(0,0,0); player = SPR_addSprite(&runner,player_x,player_y,TILE_ATTR(PAL2,0,FALSE,FALSE));
Finally, add the following line within the
if(game_on == TRUE) block in the main loop:
All this code will initialize the sprite engine, add the player sprite and store a reference in
player, then update the sprite engine every frame so that the player sprite is actually drawn on screen. As you can see we’re using
PAL2 for the player, instead of
PAL1 as we did for the tiles. Again, this is kind of wasteful, but it keeps thing simple and our project doesn’t come close to reaching the limits of the Mega Drive anyway.
Now that we have the player on screen, let’s animate the little guy! At the very top of
main.c, after the includes, add the following two defines:
#define ANIM_RUN 0 #define ANIM_JUMP 1
This will make it easier for us to deal with animations, as we won’t have to remember the number of each one. And how do we actually trigger these animations? Put the following line right after
player = SPR_addSprite(...):
Pretty self-explanatory, isn’t it? We use
SPR_setAnim to tell the sprite engine what sprite should use which animation. The animation will then automatically start playing, we don’t have to start it manually or anything like that.
We’ve imported one sprite, so we might as well import another, right? Let’s add the obstacle to the game. Download the file here and put it in
res/sprites. Then import it by adding the following line to
SPRITE rock "sprites/rock.bmp" 1 1 NONE 0
The rock sprite is a simple 1x1 tile affair with no animation, so importing it is rather straightforward.
Now at the top of
main.c, after the player variables, add this chunk of code:
/*Obstacle stuff*/ Sprite* obstacle; int obstacle_x = 320; int obstacle_vel_x = 0;
We’ll just use a single obstacle that we reuse over and over for now. These variables will track the position and velocity of the obstacle.
main(), add the obstacle sprite right after you’ve added the player:
obstacle = SPR_addSprite(&rock,obstacle_x,128,TILE_ATTR(PAL2,0,FALSE,FALSE));
Afterwards update the sprite engine once:
Note that we’re using
PAL2 again! If you compile the game now you won’t actually see the obstacle, because we put it at x-coordinate
320, which is the right edge of the screen. This is because we of course want it to move to the left, so let’s do that now!
In the main loop, add the following lines after the code that makes the background scroll:
//Move the obstacle obstacle_vel_x = -scrollspeed; obstacle_x = obstacle_x + obstacle_vel_x; if(obstacle_x < -8) obstacle_x = 320;
We’re doing three things here. First, we set the x-velocity of the obstacle to negative
scrollspeed, so that the obstacle moves exactly as quickly as the background. Then we update the x-position of the obstacle by adding the velocity to it. And finally we check if the obstacle has gone past the left edge of the screen (remember, the sprite is 8 pixels wide), in which case we put it back outside the right edge, so that it can take another shot at making the player stumble.
Did you think that’s all we had to do? Then you fell into my cunning trap! None of this code will have any visible effect until we’ve called this function, right at the end of the
if(game_on == TRUE) block:
Remember: Variables like
obstacle_x are just variables that we create for ourselves, the sprite engine doesn’t actually do anything with them until we explicitly tell it to!
Now if you compile the game you should have an infinitely moving rock obstacle. It enters from the right, phases through the player, exits to the left, then wraps around back to the right. Our runner dude doesn’t seem very interested in it though, so next time we’ll start working on collisions and letting him jump!
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!
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.
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.
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.