Megarunner 4 - Player and Obstacles

Posted November 25, 2019

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!

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:

VDP_setPalette(PAL2, runner.palette->data);

Next, in main(), after all our code dealing with the background tiles, add the player by typing in the following code:

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 resources.res:

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.

In 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!

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!

  • Megarunner 1 - The Framework
  • Megarunner 2 - Tiles
  • Megarunner 3 - Scrolling
  • Megarunner 4 - Player and Obstacles
  • Megarunner 5 - Jumping Math
  • Megarunner 6 - Collision and Score
  • Megarunner BONUS - Tile Scrolling
  • By using the Disqus service you confirm that you have read and agreed to the privacy policy.

    comments powered by Disqus