line-by-line tile scrolling animations on the game boy (gbdk/zgb)

kid marscat
3 min readOct 4, 2022

--

ive been playing around with gbdk/zgb, learning C and Game Boy development while also trying out writing some cool game demos and prototypes.

one of the first things i wanted to achieve was to do line-by-line scrolling effects on the tiles in V(ideo)RAM, so i could make cool looking water tiles without storing multiple frames for the animation. i then also used this to animate the foliage on the tile-map, using a pseudo-sine wave made with simple switch statements.

the effects in action

to achieve this, i wrote these functions:

wrap_vram_byte()

void wrap_vram_byte(uintptr_t *address, int shift)
{
for ( int x = 0; x < abs(shift); x++ )
{
if (shift < 0)
{
uint8_t vram_line = get_vram_byte( address );
vram_line = (vram_line >> 7 ) + (vram_line << 1);
set_vram_byte( address, vram_line);
}
else if (shift > 0)
{
uint8_t vram_line = get_vram_byte( address );
vram_line = (vram_line << 7 ) + (vram_line >> 1);
set_vram_byte( address, vram_line);
}
}
}

this function scrolls the content of a whole 8px-wide line in a particular tile (or rather, wraps its hex values around through bit-shifting and adding)… or it would, if the data for each tile wasn’t stored in two consecutive bytes rather than just one — after all, the Game Boy uses 2bpp graphics. for more information on this, check out this cool page!

to actually scroll the whole line, I need to run this function on each of its two byte addresses. for that, i created this function:

wrap_vram_line

void wrap_vram_line(int address, int shift)
{
wrap_vram_byte( (uintptr_t *) address, shift );
wrap_vram_byte( (uintptr_t *) ( (int) address + 1 ), shift );
}

so, to use the function and scroll a line of pixels in a particular tile, you only have to give it an address in VRAM, and then tell it how many pixels to shift them. negative values scroll it to the left, positive values scroll it to the right.

for example, to scroll the first line of the tile starting at address $9000 to the left, I’d use the function like this:

wrap_vram_line( (int) 0x9000, -1 );

this will “scroll” (as in, wrap around) both the byte at $9000 and the one in $9001 to the left. you can then do this for each line from $9002 to $900F, at the same time or at different speeds in any direction, to achieve all kinds of effects on the tile, from constant scrolling the whole tile in one direction, to something more fancy like the waving of a patch of grass in the wind.

now, two caveats:

  1. i’m very new to C, and i’m already certainly sure that this could be done a lot faster just by writing a whole function on assembly. and i bet that even just using C there are less verbose ways to do this than my clunky functions, so if you know it and don’t mind people using your code, please respond with your take on it!
  2. i’m not entirely sure how much this VRAM manipulation would affect performance with a whole game built around it. i’ll only find out if i actually use the function on an actual complete game. i’ll edit this post with any updates regarding that.

in any case, feel free to use this code whichever way you want! -m.

--

--