Skip to main content

Displaying a moving sprite

WORK IN PROGRESS!

This page is not quite ready for visitors yet. Please check again another time.

tip

💡 It’s a good idea to check for latest versions of the toolchain Docker image regularly, so do that now by running docker pull retcon85/toolchain-sms from any Terminal window.

  1. We are going to create a sprite. A sprite is like a background tile, except sprites can move freely around the screen, and they can also overlap background tiles with some transparency so are ideal for players, NPCs and things like projectiles.

    In the SMS, sprites are drawn from the same set of tiles as the background tiles, and you can reasonably have around 450 tiles defined at any one time, which is plenty for us. However, sprites can only be drawn from a palette of 16 colours which is normally different from the palette that background tiles use, and for this reason it’s easier to create a separate asset for sprites than background tiles.

    In Aseprite, we can create a new file, in the same way we created background.asesprite but this time called sprites.asesprite. We are only defining 1 sprite at the moment, so we’ll make it 8x8, remember to use an indexed palette and it doesn’t matter what the background colour is because we will change it anyway. Load the Master System palette once you’ve created the file.

  2. As mentioned, sprites support transparency, and the first colour in the palette is the transparent one — it will not show up, and any background tile underneath the sprite will be visible. That means we should pick a transparency colour which we do not want appear in our sprite, otherwise it won’t show up. Pure green is a popular choice (like green-screens in video), so select that colour now from the palette. Hover over the greens in the palette and look at the code displayed in the status bar, bottom left. When you see the colour with hex code #00ff00, click that colour to select it. Drag the colour by its edge so that it becomes the very first colour in your palette. You can also press the “Remap Palette” button at this point.

    Now load the bucket tool by pressing G and click anywhere on the tile to fill the background with our green transparency colour.

  3. Now draw your sprite. Here is one I made, but you can make anything you like. Just be careful not to use the transparency colour in your sprite. If you did want to make a green sprite, you could choose a different transparency colour than green, just make sure you make it the first colour in the palette.

    Untitled

  4. Save the asesprite file, and reduce the colours (using the New Palette from Sprite menu) and export it to assets/sprites.bmp just like we did before for the background tiles.

  5. In VS Code, run sms make assets again to import the new asset. Check out src/bank1.generated.c — it should now have more definitions added to it: one for the sprite palette and one for the new sprite tile.

  6. Now let’s get coding! Locate yourself after this line in the existing code: SMS_setTileatXY(15, 21, 1); We’re going to add the following new code:

      // load the sprite tiles
    SMS_loadSpritePalette(sprites_palette);
    SMS_loadTiles(sprites_tiles, 256, sprites_tiles_size);

    We said before you can have up to around 450 tiles, and we can conceptually split all the tiles into two “halves”, one “half” of 256 tiles and then the rest (slightly less than 200). By default, the sprite tiles are taken from the second “half” of the total list of tiles, so from position 256 onwards. That’s why we use the number 256 in the code above. Sprite 0 = tile 256, sprite 1 = tile 257, etc. etc. (Note: you can change this so that the sprite tiles come from the first half of tiles, and this is often the case, but we will leave it as the default behaviour for now)

  7. Next, we’ll write the code necessary to display a sprite on the screen. For reasons we’ll see later, we’re going to put that in the main program loop, which will get run every time the screen draws (60 times a second), except when the player has paused the game. Find the comment that says // game logic here, and replace it with the following lines:

        // draw the sprites
    SMS_initSprites();
    SMS_addSprite(8, 184, 0);

    The last line says: draw sprite number 0 at a position 8 pixels from the left edge of the screen and 184 pixels from the top edge of the screen. (there are 256 pixels across and 192 pixels down, so this will put our sprite sitting just above the very bottom of the screen)

  8. Now let’s build and run our changes. Run sms make again, and the game should reload in Emulicious.

    Do you see your sprite??

    You may notice something unexpected, which is that the border around the game is now green (or the same colour as your transparency colour). This is called the “backdrop” colour, and it comes from the same palette as the sprite palette. There’s no way of turning the backdrop off, but you can control which colour it is, so let’s just choose a less gaudy one. My sprite palette has a dark grey in it, and that happens to be colour 1 in my palette, so I’ll use that one. You could choose any colour from your sprite palette though, have a play around if you like! Write this line after the above two:

    SMS_setBackdropColor(1);

    Now rebuild and check that this has changed the border.

  9. To finish off today, we’re going to make our sprite move when the player presses the D-pad buttons.

    To start, we’ll create two variables at the global scope to hold the player position.

    Find the line that says: uint8_t paused **=** 0; and create a new line after it, typing the following:

    uint8_t player_x = 8;
    uint8_t player_y = 184;

    Now change one of the lines we wrote previously that says:

    SMS_addSprite(8, 184, 0);

    We now want this line to use the variables instead, so change it to:

    SMS_addSprite(player_x, player_y, 0);

    At this point, you should be able to rebuild and test. The game should still work, although nothing will have changed.

  10. Now we’re going to read the keys from the controller. First create a new local (aka “automatic”) variable inside the main function. We’ll put it right after the opening { character, so the first few lines should now look like this (new line in bold):

    void main(void)
    {
    **uint8_t keys;**

    // load the background tiles
    SMS_loadBGPalette(background_palette);

    Now that we’ve done that, we’ll add some code inside the if (!paused) conditional block below. To help clarify, here’s the whole block, with the newest code in bold again:

        if (!paused)
    {
    **keys = SMS_getKeysHeld();
    if (keys & PORT_A_KEY_LEFT)
    player_x -= 1;
    else if (keys & PORT_A_KEY_RIGHT)
    player_x += 1;

    if (keys & PORT_A_KEY_UP)
    player_y -= 1;
    else if (keys & PORT_A_KEY_DOWN)
    player_y += 1;**

    // draw the sprites
    SMS_initSprites();
    SMS_addSprite(player_x, player_y, 0);
    }

    Let’s walk through this line by line:

  • keys = SMS_getKeysHeld() calls a library function which returns a representation of which keys on the control pads are currently held down. We store that result in the local variable keys we created above.

  • The & operator does a “bitwise AND” operation. The result of SMS_getKeysHeld sets different bits for every key that is held down, so by AND-ing the result with one specific bit we can test for that bit. The constants PORT_A_KEY_LEFT, PORT_A_KEY_RIGHT, etc. specify individual bits, so by writing if (keys & PORT_A_KEY_LEFT) we can test the result to see if the left D-pad key is one of the keys that is held down.

  • If the left D-pad key is held down, we move the player to the left one pixel, by modifying the global variable player_x as follows: player_x -= 1;

    We could also have written this as either of the following: player_x = player_x - 1; player_x--; but of the three alternatives I prefer the first one.

  • We do a similar check for PORT_A_KEY_RIGHT, except we do this in an else if clause, simply because it wouldn’t make sense for the player to press both left and right at the same time, so we can skip the need to test for the possibility.

  • Similarly, we check the up and down buttons and if pressed decrease or increase the player_y global variable by one pixel to allow vertical movement.

  • The last line we changed earlier, but that makes sure that after we’ve changed the player_x and player_y positions, we update the sprite list accordingly. SMS_initSprites() removes all the sprites from memory and SMS_addSprite creates a new one.

    This means that at the end of every frame we end up with just a single sprite in our sprite table, which is at the current player position.

  • One other important thing to note is that further up towards the top of our main game loop, we make a call to this function: SMS_copySpritestoSAT();

    Both the SMS_initSprites and the SMS_addSprite functions actually make changes in memory to a “virtual” table of sprites, and it’s not until the call to SMS_copySpritestoSAT() that the sprite data is copied to the VDP hardware.

    The reason we do it like this is because there is only a small time window to safely copy the data to the VDP hardware, so we do it as soon as possible into the frame. Then only after we’ve copied the data do we do other time-consuming things like checking the controller keys and calculating new positions, etc.

  1. Build your game one more time and check it works. You might need to check your Emulicious settings to see which key bindings are used for the player 1 control pad. Press the D-pad keys and see your sprite move!