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

Megatiler 3 - Tile Movement (Part 1)

Posted March 1, 2021

We’ve got our world and we’ve got our player. A world is boring if you can’t explore, so let’s use tile movement to get our player going!

We’ll handle most of our player’s movement in a single function, which we’ll call movePlayer. Create an empty one with the following signature for now:

void movePlayer(moveDirection Direction){
    //Move the player
}

As you can see we’re passing in an argument of type moveDirection, which, as you’ll remember, is an enum we defined containing the directions up, down, left, right and none. This will tell our player which direction to move in.

But in order to move the player we need to grab input from the joypad. So, as usual, we’ll implement a callback function that does just that:

void myJoyHandler(u16 joy, u16 changed, u16 state)
{
    if (joy == JOY_1)
    {
        if (state & BUTTON_UP)
        {
            movePlayer(up);
        }
        else if (state & BUTTON_DOWN)
        {
            movePlayer(down);
        }
        else if (state & BUTTON_LEFT)
        {
            movePlayer(left);
        }
        else if (state & BUTTON_RIGHT)
        {
            movePlayer(right);
        }
    }
}

Looks simple enough! Of course this won’t do anything yet, but one step at a time. Before we actually get to the movement code, tell SGDK to use the callback at the beginning of main():

JOY_init();
JOY_setEventHandler(&myJoyHandler);

Moving the Player

A bit of theory before we begin. As our world is tile-based, we will make player movement tile-based as well. And I mean that in the strictest sense: The player will only be able to move from tile to tile, and only one tile at a time. As that is the most basic form of this kind of movement, it’s a good introduction to the overall concept and implementation.

Basically, how it works is like this: When we press a direction, the game checks whether the corresponding tile next to the character is empty. If so, the character starts moving towards that tile and stops when it reaches it. If the tile isn’t empty however, the character simply doesn’t move at all. No fancy collision detection here, just looking up numbers in a simple array!

But enough with the theory, let’s get coding to get a better sense of how it all works. Of course our movePlayer function still needs to do… well, anything at all, really. Let’s start adding the function body to get our player character in gear!

First off, we’ll add an if-statement that will wrap the entire function body.

void movePlayer(moveDirection Direction){
    if (player.moving == FALSE)
    {
        //Do movement things
    }

Our player entity contains the value moving which we will toggle whenever its moving or not moving. If the character is already on the move we’ll leave it alone until it has reached its destination; otherwise we might move it out of the tile grid, which would kind of break the game. So everything in movePlayer will be wrapped in this condition.

Next, we’ll have to check which direction the player is supposed to move in. We do this with a big switch statement that checks the value of the Direction parameter.

switch( Direction ){
    case up:
        break;
    case down:
        break;
    case left:
        break;
    case right:
        break;
    default:
        break;

We’ll now fill in each of the conditions in turn, starting with up. What do we actually want to do, though? Well, we said we’d check whether the tile the character wants to move to is actually empty, so let’s code that! But first, let’s make a small helper function that’ll come in handy throughout the entire project.

int getTileAt(u8 X, u8 Y)
{
    return *(&level1[0][0] + (Y * MAP_HEIGHT + X));
}

As the name implies, this function returns the tile value at a specific coordinate of our map. It starts at the address of the first item in the array, then moves down the rows and columns to the value we want. It’s a quick and handy way to check values in our level map.

Note: As Andi correctly pointed out in the comments below, this function could also simply return &level1[y][x]. However, having the function work as we defined it will be useful later on. How’s that for ominous foreshadowing?

Oh, and let’s make things a bit easier still, by adding another define at the top of the file:

#define SOLID_TILE 1

We used the value 1 to represent a solid wall tile in our level array. It’s a good idea to give these values names, so it’s easier to keep track of them.

But now back to movePlayer!

Since we have our helper function now, let’s use it to see if the tile above the player is empty using this if-statement:

if(player.tilePos.y > 0 && getTileAt(player.tilePos.x,player.tilePos.y-1) != SOLID_TILE){

}

As you’ll remember, the tilePos struct inside the player entity stores the x- and y-coordinate of the player in tiles. So, the first thing we check is whether the player is already at the top edge of our map, by checking if tilePos.y > 0. Without this check we could simply leave the map and the edge of the screen, which isn’t good.

The second condition grabs the tile above the player and checks if it is supposed to be solid. That’s pretty much it, really! It’s rather simple once you get the logic behind it. If there is a tile above the player, and if that tile is empty, the player can move up.

But what should happen if the condition is met? This:

player.tilePos.y -= 1;
player.moving = TRUE;
player.dir = Direction;

We update the tile-coordinate of the player entity, tell the game that it’s moving, and set the direction (to up in this case).

Now, this won’t actually move the player on the screen. Remember that tilePos is simply a struct we created; it doesn’t have any bearing on the movement of the actual sprite on the screen. We’ll take care of that soon, but first, let’s take care of the other directions. For reference, here is what the entire up block should look like:

switch( Direction ){
    case up:
    {
        if(player.tilePos.y > 0 && getTileAt(player.tilePos.x,player.tilePos.y-1) != SOLID_TILE){
            player.tilePos.y -= 1;
            player.moving = TRUE;
            player.dir = Direction;
        }
        break;
    }
    case down:
    //...

The down case is going to look very similar:

if(player.tilePos.y < MAP_HEIGHT - 1 && getTileAt(player.tilePos.x,player.tilePos.y+1) != SOLID_TILE){
    player.tilePos.y += 1;
    player.moving = TRUE;
    player.dir = Direction;
}

We’re checking if the player is above the bottom edge of the map, then check the tile below. If the coast is clear, tilePos.y is updated. Remember that MAP_HEIGHT stores the total height of our map (8) but that our map is an array with indexes 0–7. That’s why we have to check for MAP_HEIGHT - 1.

I encourage you to try and add the code for the left and right cases for yourself. But here are the juicy bits, just to make sure:

case left:
    if(player.tilePos.x > 0 && getTileAt(player.tilePos.x-1, player.tilePos.y) != SOLID_TILE){
        player.tilePos.x -= 1;
        //...
    }
    break;
case right:
    if(player.tilePos.x < MAP_WIDTH - 1 && getTileAt(player.tilePos.x+1, player.tilePos.y) != SOLID_TILE){
        player.tilePos.x += 1;
        //...
    }

And that gives us the framework for moving our player around the grid. So far the movement is only theoretical; we can update the tile position of the player, but nothing is actually moving yet. We’ll take care of that in the next part!

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)
  • 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