SGDK. Move around the map.

Preface.

Before moving to scrolling of the map, let’s figure out how the map is drawn in Sega Genesis.

How does the Sega Genesis draw the map?

Open any sonic game with the Gens KMod emulator. Go to the first level.

Now, we find out what happens on the layer BG_A, for this, go to the CPU->Debug->Plane Explorer

Run a little on the map.

And what we see:

  1. The layer size is 64×32 tile, which is higher than the screen area.
  2. Tiles of the card are completed on the fly,during the movement of the camera.
  3. Tiles are completed outside the visibility of the camera.
  4. Moving the camera is carried out due to the scrolling of the plain (layer)

When Sega Genesis, scrollsthe map, she, in fact, draws the missing tiles beyond the screen, and scrolls the plain (layer). And it turns out that the area of visibility of the screen, running on the plain.

An example of this scroll is available in SGDK/sample/sonic.

Don’t worry, we don’t need to correlate the player’s coordinates with the coordinates of the tile grid, build the missing tiles, and then scroll the plain. Stephane-D (the creator of SGDK)did it all for us, and brought it to the function of MAP_scrollTo.

Okay, let’s move on to the project.

We load resources.

Download the level map with MEGA.

And throw it into the resfolder of your project. The palette of this picture, I limited to 16 colors. I talked about how to do that here.

Map fragment

If you want to create your own image of a level map, then read SGDK. Programs for drawing a map from tiles

In resources.res,write the following lines.

PALETTE palette_all "bg.png"
TILESET bga_tileset "bg.png" BEST ALL
MAP bga_map "bg.png" bga_tileset BEST 0

Let’s break them down.

PALETTE palette_all "bg.png"

Here, the palette is imported from the picture bg.png,the name of the palette is chosen pallete_all.

TILESET bga_tileset "bg.png" BEST ALL

Create a set of tiles from the picture. These tiles will consist of a map. Let’s analyze the syntax.

TILESET name picture compression optimization
  • name – specifies the name by which we will access the tileset.
  • picture – the way to the image (png or bmp)
  • Compression – Compression Mode (BEST/NONE/APLIB/FAST)
  • optimization – sets the level of optimization (NONE/ALL/DUPLICATE)

Next is the map.

MAP bga_map "bg.png" bga_tileset BEST 0

Which, is specified by the following syntax.

MAP name picture tile_set compression mapbase
  • Tile set is a set of tiles of this card (tailset, can be used in several cards)
  • mapbase – Define the base tilemap value, useful to set a default priority, palette and base tile index offset. Using a base tile index offset (static tile allocation) allow to use faster MAP decoding function internally. (I didn’t understand here, so I left the explanation from recomp.txt)

The rest of the arguments have already been parsed. Let’s move on to writing the code.

Write the code.

First, import the necessary libraries, and create variables.

#include <genesis.h>
#include "resources.h"

Map *bga; 

s16 camPosX = -1;
s16 camPosY = -1;

fix32 posX = 0;
fix32 posY = 0;

bool paused = FALSE;
  • In bga we will store the card.
  • camPosX and camPosY – store the coordinates of the camera.
  • posX and posY – store the player’s coordinates.
  • Variable paused created for the future. She will be responsible for the pause.

PosX and posYhave a new type of fix32.

About fake fractional numbers.

This type stores a fake floating-point number, which, unlike the real number(float),must be converted through the functions. Like in ACS (if you’re learning ZDoom).

You may be wondering why use fake fractional numbers when you can use float? The answer lies in optimization. Sega doesn’t need the precision that floatoffers. It’s too expensive to spend resources, it’s not a computer, with an infinite level of abstractions, and absurdly unoptimized technologies (hello electron).

Back to the code.

void setCameraPosition(s16 x, s16 y)
{
    if ((x != camPosX) || (y != camPosY))
    {
        camPosX = x;
        camPosY = y;
        MAP_scrollTo(bga, x, y);
    }
}

setCameraPosition – scrolls the map, but only if the coordinates have changed. Which provides additional optimization.

For scrolling the map, the Map_scrollTois responsible.

MAP_scrollTo(plain, x, y);

Next, we implement the movement of the camera(scrolling the map).

void handleInput()
{
    u16 value = JOY_readJoypad(JOY_1);

if (!paused)
    {
        if (value & BUTTON_RIGHT)
        {
            posX += FIX32(5);
	    setCameraPosition(fix32ToInt(posX),fix32ToInt(posY)-140);
        }
        else if (value & BUTTON_LEFT)
        {
            posX -= FIX32(5);
	    setCameraPosition(fix32ToInt(posX),fix32ToInt(posY)-140);
        }

if (value & BUTTON_UP)
        {
            posY -= FIX32(5);
	    setCameraPosition(fix32ToInt(posX),fix32ToInt(posY)-140);
        }
        else if (value & BUTTON_DOWN)
        {
            posY += FIX32(5);
	    setCameraPosition(fix32ToInt(posX),fix32ToInt(posY)-140);
        }
    }
}

Here, we change the coordinates of the camera depending on the button pressed.

  • FIX32 – converts an integer into a fake 32-bit fractional number.
  • fix32ToInt – converts a fake 32-bit fractional number into an integer.
int main()
{
    u16 ind;
    ind = TILE_USERINDEX;
	
VDP_loadTileSet(&bga_tileset, ind, DMA);
    bga = MAP_create(&bga_map, BG_A, TILE_ATTR_FULL(0, FALSE, FALSE, FALSE, TILE_USERINDEX));
	
setCameraPosition(fix32ToInt(posX),fix32ToInt(posY)-140);
    SYS_doVBlankProcess();
	
VDP_setPaletteColors(0, palette_all.data, 16*2);
	
while(1)
    {
	handleInput();
        SYS_doVBlankProcess();
    }
	
return (0);
}

Function main, let’s analyze in more detail.

u16 ind;
ind = TILE_USERINDEX;

TILE_USERINDEX – sets the index of the initial tile (will be needed when writing a tileset to VRAM). This index was written to the variable ind.

VDP_loadTileSet(&bga_tileset, ind, DMA);

VDP_loadTileSet – writes the tileset to VRAM. Accepts:

  • tailset – pointer to the tailset.
  • tile_index – index, starting from which, we will write tiles in VRAM.
  • transmission method – available values (CPU, DMA, DMA_QUEUE, DMA_QUEUE_COPY)
bga = MAP_create(&bga_map, BG_A, TILE_ATTR_FULL(0, FALSE, FALSE, FALSE, TILE_USERINDEX));

MAP_create – creates a map &bga_map on the desiredBG_A, using tiles TILE_ATTR_FULL,according to the following template.

TILE_ATTR_FULL(palette, priority, flip_vertical, flip_horizontal, index_tile)
  • Palette — Specify the palette that the tiles will use (in our case, the sprite)
  • Priority — sets the priority of the sprite (tile), i.e. a sprite with a smaller number, will overlap the sprite with a large one.
  • tile_index – sets the index of the initial tile in VRAM.
  • the rest, the title implies.

By the way, we discussed almost the same thing in SGDK. Move the sprite across the screen.

setCameraPosition(fix32ToInt(posX),fix32ToInt(posY)-140);
SYS_doVBlankProcess();

Here, we move the camera to the starting position, so even if we haven’t pressed anything, the map will still be drawn.

VDP_setPaletteColors(0, palette_all.data, 16*2);

Filled with CRAM,our palette, taken from bg.png. VDP_setPalleteColorsis used here, but it is no different from the PAL_setPalleteColorsI have already parsed in SGDK. Fade-in and Fade-out transitions.

The image palette can be viewed in infranView. To do this, open the image and select Image->Palette->Edit Palette…

Map palette
 while(1) 
{
handleInput();
SYS_doVBlankProcess();
}

Well, run an endless loop that checks the pressing of buttons.

As a result, it turned out to implement the movement on the map.

Update 6/29/2021.

As it turned out, MAP_scrollTo is corrupting (draw garbage tiles), at high coordinates x or y.

To fix this problem, you need to download the SGDK version higher than 1.62. But if such a version did not come out (and it did not come out at the time of writing), then download the zip file SGDK, from the official repository.

Stephane-D’s response on this issue

And thanks to Stephane-D for solving the problem.

Final result.