Learn how to make a top-down tile-based game for the Sega Mega Drive using SGDK!

Megatiler 2 - Spawning the Player

Posted February 1, 2021

A nice level is pointless if we can’t actually be in it. So this time we’ll spawn our player character and learn how to implement special function tiles in the process!

Spawning the Player

Once again we first start by getting the assets for the player character ready. Download the spritesheet below:

Click HERE to download the player spritesheet

Feel free to take a look at it before putting it in your res folder. You’ll see that our character is drawn in 3 different directions, with each direction having two frames of animation. Each frame is 8x8 pixels, just like our tiles.

Let’s invite him to our game! But first, we need to set some things up.

As always, we’ll have to add the spritesheet to the resources file so that SGDK knows to import it:

SPRITE spr_player "player.png" 1 1 NONE 20

As the sprite has animation, we set a framerate of 20.

Like in Megalaga, we will use an Entity struct to make creating and handling entities easier. But before that, create another struct:

typedef struct{
    u8 x;
    u8 y;
} Point;

We’ll be working a lot with coordinates, so having a Point struct is slightly more convenient than defining separate x and y variables for each entity.

Speaking of which, now define the type Entity outside of the main function:

typedef struct
{
    Point pos;
    Point tilePos;
    int w;
    int h;
    int health;
    bool moving;
    moveDirection dir;
    Sprite *sprite;
    char name[6];
} Entity;

As you can see, we’re using a Point struct to store our position. And apart from the regular pos variable, which stores the entity’s position on the screen in pixels, we also have tilePos, which stores the position of the entity in tiles. That will come in handy later. We also have two variables related to movement, one of which is of type moveDirection. What kind of type is that, you ask? Well, it’s an enum that we will define right now!

typedef enum {up,down,left,right,none} moveDirection;

It’s rather self-explanatory and we’ll see it in action soon enough.

So that’s our Entity struct taken care of. Now, how do we get a player on the screen? There are basically two ways.

Spawning the Player

We could simply spawn the player manually by creating an Entity at a specified position. However, we’re working with tiles here, so let’s use that to our advantage! Instead of manually adding a player, we will instead add a spawn tile to our level map and let the game spawn the player there. For the spawn tile we will use the index 4. To make things easier to read, let’s define this value:

#define SPAWN_TILE 4

The value 4 is arbitrary, by the way, you could pick any number you want. And while we’re at it, also define these values:

#define TILESIZE 8
#define MAP_WIDTH 8
#define MAP_HEIGHT 8

The first is the size of one of our level tiles, which in our case is 8. The other two specify the width and height of the complete map in tiles, which, in our case, is also 8. You can of course set the map size to something else, and it doesn’t have to be square at all.

Now put a 4 somewhere in your level array, it doesn’t matter where.

Time to update our loadLevel function so that it also spawns the player. First, we’ll have to change its structure a bit:

//...
SPR_init();
for(y = 0; y < MAP_HEIGHT; y++){
    for (x = 0; x < MAP_WIDTH; x++){
        t = level1[y][x];
        if (t == SPAWN_TILE){
            //Spawn the player
        } else{
            VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL1, 0, FALSE, FALSE, t + 1), x, y);
        }
    }
}

This is where it gets interesting. Now we start checking each tile in the map and deal with it accordingly. If the tile isn’t anything special, we simply draw it to the screen. So far so good. Now, how do we spawn our player?

First of all, we actually need a player entity. So, create it outside of main():

Entity player = {{0, 0}, {0, 0}, 8, 8, 0, FALSE, none, NULL, "PLAYER"};

Then head into loadLevel, specifically into the if (t == SPAWN_TILE){ ... } statement. Here we’ll have to do a few things.

First, pass in the current tile coordinates into the player, so that the entity knows where it is.

player.tilePos.x = x;
player.tilePos.y = y;

Then set the position of the entity in pixels, which will be stored inside pos. We can extrapolate the pixel position by multiplying the tile position with TILESIZE:

player.pos.x = player.tilePos.x * TILESIZE;
player.pos.y = player.tilePos.y * TILESIZE;

Then, finally, we create the actual player sprite at the position we just specified:

player.sprite = SPR_addSprite(&spr_player, player.pos.x, player.pos.y, TILE_ATTR(PAL2, 0, FALSE, FALSE));

A note on the positioning: You might wonder why we bother keeping track of pos and tilePos separately, when they are so easy to convert back and forth. The first reason is performance. When you’re working with retro hardware, it’s best to do as little math as possible, especially when you’re talking about multiplication and division. But I also find it more convenient to keep track of both positions separately, and you’ll soon see why. Or maybe you’ll disagree, I don’t know.

Anyway, we’re not quite done yet. The code we’ve written will add our player to the stage at the right position, but we’ve forgotten something: The tile. Due to the way loadLevel is structured now, it won’t actually draw a tile on screen; and even if it did, it would try to draw the tile graphic with index 5 (remember, our spawn tile has the value 4) which doesn’t exist. As we want the player to spawn on an empty tile (i.e. grass), we simply draw one after we’ve added the player:

VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL1, 0, FALSE, FALSE, 1), x, y);

And we’re still not quite done yet. Most importantly, we need to call SPR_update() in our main game loop before SYS_doVBlankProcess(). But we also need to load the palette for our player, otherwise he’ll look all wrong! Do so after loading the palette for the tiles, so you end up with this:

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

Once again we’re using a separate palette for sprites, even though we won’t actually be using that many colors. But personally, I’m happy whenever I don’t have to micro-manage my palettes…

And there we go! Compile the game and you’ll see our player anxiously standing on a grass tile, eager to explore the world! Or maybe he just really has to pee.

images/waiting.gif

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!

  • Megatiler 1 - Tiles
  • Megatiler 2 - Spawning the Player
  • Megatiler 3 - Tile Movement (Part 1)
  • Megatiler 4 - Tile Movement (Part 2)
  • Megatiler 5 - Coins
  • 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