Preface.
Before moving to map scrolling, 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
Move little bit on the map.
And what we see:
- The layer size is 64×32 tile, which is higher than the screen area.
- Tiles of the map are appearing on the fly, during the movement of the camera.
- Tiles are appearing outside the visibility of the camera.
- Moving the camera is carried out due to the scrolling of the plane (layer)
When Sega Genesis, scrolls the map, she, in fact, draws the missing tiles beyond the screen, and scrolls the plane (layer). And it happens outside of screen visibility.
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.
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 tileset from the picture. These tiles will consist of a map. Let’s analyze the syntax.
TILESET name picture compression optimization
- name – specifies the name in code by which we will access the tileset.
- picture – path 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 – tileset on the map (tileset, can be used in several maps)
- 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 posY have a new type – 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 float offers. 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_scrollTo is 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:
- tileset – pointer to the tileset.
- 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…
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.
And thanks to Stephane-D for solving the problem.