Building out a “proper” level (aka Why Mario can never go home)

I’m going to come clean about Jumpy’s level (if anyone is actually playing the ROMs?). That clever looping behaviour isn’t specially coded – it’s just what the Game Boy does.

The Game Boy’s VRAM – the part of the memory keeping hold of all the graphics stuff – only actually holds a total width of 32 tiles for the background. If you try to scroll out beyond that it just loops back. Just like traversing the globe, go far enough in any directions and you’ll find yourself back where you were. Only it doesn’t take as long on an 8 KiB Game Boy.

BGB’s excellent VRAM viewer let’s us see this in action. The square outline on the left is what the Game Boy’s screen is currently displaying.

Now, think of pretty much any scrolling platform game. Those courses are significantly longer than this little slice, and I wanted to figure how to do it.

(Once again, I didn’t set out to make a platform game, but pursuing these curious tangents is 90% the point of this project.)

The basic principle seems straightforward enough. Why would the Game Boy give you those extra “off screen” areas unless it expected you to do something with them?

There’s a certain amount loaded ahead of the player, so when that is pulled into view we can replace what has drifted off the other side of the screen with what is to come next. When the viewport loops around, it’ll show those new parts of the level and create the illusion of one long course.

When endlessly looping the same 32 columns of tiles, we were using the same map (generated by Gameboy Map Builder) in code to keep track of both the tiles we drew into the background layer to show the level graphics and to run our collision detection against to see if we’d hit the ground. It gets a little more complex when updating.

A tile map is stored as one long set of tiles, and rendered to screen by specifying the width of the area to fill. This value sets the bound where the single long line of values loops around and starts making new rows. The starting area is 32 columns wide (20 displayed and 12 off to the right) and 18 tall (actually also 32, but as we are only scrolling horizontally we can ignore the other 14 off-screen below). To fill the area we give it an array of 576 tile references and tell it to loop at position 32. This gives us a 32 x 18 background nicely rendered to screen.

Remember in the last post I said that I had done something that seemed to work fine even though it shouldn’t, but in reality it was playing merry hell with lots of subsequent, unrelated instructions? I initially made a map that was wider than 32 tiles. It rendered fine, cropping off the extra. “Oh well”, thought I, and left it. Then I lost my mind on unrelated bugs until I had that “aha” moment, explicitly created a 32×18 map, and all those other issues vanished.

When we do our collision detection we calculate where on this grid the sprite is (or is going to be) by dividing the x & y by 8 (the pixel-size of a tile). Then we can back-calculate it to find which of our 576 values this corresponds to: (32 * y) + x.

We’ve established that when a column of tiles leaves the left-hand side we want to replace it with the next column coming in from the right, so really we have two things to update: the display, and the collision detection map.

First issue: our level is stored as a big looping set of values creating rows, but we want to be appending new columns.

There’s a smart way of calculating the contents of that single column by doing a bit of maths on the big long list of values denoting the floor map. However, I couldn’t quite get C to play nicely. Also, if you can store something statically in an optimised way instead of calculating it each time that’s going to help the Game Boy’s diminutive hardware perform better anyway. So after that first set of 576, I actually store the rest of the level as an array of arrays, each being the contents of a column.

This makes rendering the new column very easy in GBDK. Just keep track of which columns have already been rendered and every time the screen scrolls 8 pixels (again, the width of a tile) render the next set into the right place.

The tricky bit becomes – how do we fix the collision detection? That thing is reading from a 576-tile map. We need to independently update that. This is where we need to calculate which of the VRAM’s 32 columns are updating and jump through the map in intervals of 32 to hit the correct 18 tiles. To illustrate, here’s my code to update the background and the map used for collision detection. The next_vram_location variable is the vram “column” we want to update, incrementing every 8 pixel scroll and looping back to position 0 whenever it hits 32.

set_bkg_tiles(next_vram_location, 0, 1, 18, floormap_full_segments[bkg_columns_scrolled]);
for (i = 0; i < 18; i = i + 1)
{
	floormap[(i * 32) + next_vram_location] = floormap_full_segments[bkg_columns_scrolled][i];
}

Here it is in action:

So satisfying. I <3 you VRAM viewer.

Having sorted that, I built a much bigger course and added a couple of other changes to go along with this: First, when all the tile columns have been rendered, it stops scrolling and you can jump all the way to (but not beyond) the right hand side of the screen.

Secondly, I removed the ability to scroll left.

While it is entirely possible to run similar logic as above but “in reverse” to allow movement in both directions, I kept it simple and didn’t bother. And then it struck me; this is likely just the same reason as why you can’t go back in the early Super Mario games. So I took a look under the hood of a classic.

Nice to have your techniques validated by the professionals.
(Copyright Nintendo etc. etc. yes I own the original game thanks for asking)

Mario could never go home because it had been literally destroyed by the future.

Still, looks like Jumpy is on the right track.

Time Spent: It was glorious experimentation so I wasn’t really counting, but probably about 4 hours.

Let’s fix those platform bugs. How hard can it be?

Last update I identified a few bugs, notably to do with bad collision detection when falling on to (or, uh, through) the platforms.

First, the “snapping” bug:

Whup! See that?

Catch that? Let’s look at the frames:

Last time I said

Reading through the code I’m not sure why that is, but I expect I’ll figure it out.

And I figured it out pretty much right after posting. It’s all to do with the hit areas.

When doing collision detection – at least in very simple games – one cannot really afford to do it with very great accuracy. I’m using a very basic method and comparing a single x and y coordinates for each 8×8 tile. Usually you will check the outer “bounding box” around the sprite to see if anything hit you on any side (enemies etc), but right now the only collision I need to check is landing, so that’s the lower bound.

The x and y coordinates for sprite placement are at the top left most extreme of the sprite area. Even though Jumpy is not-square, the area it inhabits is still an 8×8 square.

My collision detection has a look to see if the x & y coordinates of jumpy is going to move on top of a tile it should actually land on, and if yes instead snap to the top of the that tile and stop falling. In this bug it’s falling for one tile more than it should. Let’s take a look at that one erroneous frame but with Jumpy’s x & y coordinate marked in red.

I think I see the problem

According to the collision check, Jumpy was still above the ground and so being where it is is perfectly valid. It’s not until the next frame that the x & y spot overlaps the ground tile and our “landing” code kicks in.

Easy fix. Amend the checking code to instead look at the bottom of the sprite box (the y coordinate + 8) and, heck, let’s move the x coordinate to the mid-point (x + 4) while we’re at it so Jumpy doesn’t fall through platforms when only off by 1 pixel to the left.

So, that other bug:

If you’re falling quickly (i.e. big jumps in position) you can go right through a platform. Try jumping from the high platform to the low one 🙂

Any guesses? Let’s map that out with the x & y coordinate (remembering that we hadn’t fixed the previous bug yet) and for visibility invert the tile we were expecting to hit.

(I accidentally put the red dot at the mid-x point in this image, but what we’re interested in is actually the y position so the illustration is still valid)

Yup. We’re falling so quickly that the x & y check point never actually overlaps that 8×8 ground tile. Another straightforward fix: if we’re falling more than 8 pixels at a time then also check the tile above the one we are currently overlapping.

I could do this all day.

All done and good. As mentioned previously, my brain offered the bug-fix solution shortly after the last post was published, and the extra collision detection checks are very straightforward. So why did it take me about 5 hours to actually fix? 😳

C is a pretty low level language. If you don’t know what you’re doing, things can look OK but really not be OK under the hood.

In this case implementing a simple fix inside the collision detection just plain didn’t work. The logic checked out but running the code very much did not. I put in a printf to spit out what was in the variables, and something magical happened. The code worked!

But it was bad magic. Take the printf out again and the code didn’t work. It drove me nuts. I tried putting in a delay (in case of race conditions. Race conditions probably aren’t a thing on a 4.19MHz processor, but who knows), no joy. I tried removing pointers (I read up that printf calls malloc() and something-something pointers), again, nothing.

My learning was this: C is unforgiving. You do something wrong somewhere else and it is functionally fine but as you build on that hidden fault other strange things happen in code where it really shouldn’t.

I already knew this about C, but I hadn’t first-hand experienced it. This really isn’t one of the “ah, well, I knew what you meant, buddy, I’m still going to do what you almost asked.” set of languages. Sure it’s frustrating at times, but likely in the pursuit of become a far more attentive programmer. That’s going to serve me well if I’m going to do anything interesting on such a limited device as the Game Boy.

In the end I got it working by trial-and-error refactoring, but I later discovered that critical “first” hidden mistake I was making. More on that next update.

Time Spent: About 5 minutes determining what to do & how to do it, and 5 hours becoming a better C developer.

Jumpy 2: Basic Platform Action!

I didn’t intend to return to Jumpy, but apparently it was a fan favourite (_a_ fan, singular). I also don’t explicitly intend to write a platform game, but I fancied iterating on the little, uh, thing so here we are!

I imparted a new super power – moving sideways while jumping – in about 10 minutes. But then of course it needed something to jump on. How the heck do you do that?

Well, Gameboy Map Builder is how you draw the background, and I learned a neat trick from the GamingMonsters YouTube tutorials (again) whereby you can simply reference the background sprite map directly to figure out whether sprites are colliding with anything interesting. Super neat. I lifted that code out almost verbatim.

Of course, physics in a platformer are never that simple, but I got there.

I didn’t make a GIF this time, so you’ll just have to play the ROM below!

I continued to play and iterate (and learn!) and managed to implement a scrolling effect that kicks in depending on how far left/right you are on the screen, Mario style.

The complexity there is that we need to track the scroll position of the background in order to make sure we can still use our logic to determine where the platforms are. A bit brain-bending for a programmer so used to having a console.log() to spit out everything that’s going on, but ended up fairly trivial.

There are a few bugs to sort:

  • If you’re falling quickly (i.e. big jumps in position) you can go right through a platform. Try jumping from the high platform to the low one 😉
  • Sometimes there’s a glitch when landing before you “snap” to the level of the platform. Reading through the code I’m not sure why that is, but I expect I’ll figure it out.
  • Nothing happens when you fall in the hole. Just jump out!

Finally there’s a couple of things I failed to figure out if anyone reading has any tips:

If you are using a UINT16 variable, any value returned FALSE for a comparison operator > 0

UINT8 bkg_position_offset = 0;
bkg_position_offset += 2;
return bkg_position_offset > 0; // returns true

UINT16 bkg_position_offset = 0;
bkg_position_offset += 2;
return bkg_position_offset > 0; // returns false

I wrote up that to illustrate my issue but I wasn’t running exactly as such. Maybe I’m slipping up somewhere but in the end I just used the UINT8 and figured I’d look more closely when I needed a longer level.

SPEAKING OF WHICH

The neat background-scrolling tile-offset-calculating method I have used will totally fall down when I want longer levels. The VRAM is total 32 tiles wide (this version here is the maximum width!) so I’m going to need to swap background tiles in and out. Unless there’s another way? I’m not sure there is so I might give that ago if anyone wants Jumpy III?

(Here’s code. floortiles.c and eggy.c are the same as the previous post)

# floormap.c

#define floormapWidth 32
#define floormapHeight 18
#define floormapBank 0

unsigned char floormap[] =
{
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x00,0x00,
  0x00,0x00,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x01,0x01,0x01,0x01,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x01,0x01,0x01,0x01,0x02,0x02,0x00,0x00,
  0x00,0x00,0x00,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x01,0x01,0x01,0x01,0x02,0x02,
  0x01,0x01,0x01,0x01,0x01,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x01,0x01,0x01,0x01,
  0x02,0x02,0x01,0x01,0x01,0x01,0x01,0x02,0x02,0x02,
  0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x02,
  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
  0x02,0x02,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
  0x01,0x01,0x02,0x02,0x01,0x01,0x01,0x01,0x01,0x01,
  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
  0x01,0x01,0x01,0x01,0x02,0x02
};

/* End of FLOORMAP.C */
#include <gb/gb.h>
#include <stdio.h>
#include "eggy.c"
#include "floortiles.c"
#include "floormap.c"

BYTE isjumping = 0;
UINT8 gravity = 1;
INT8 jumpmomentum = 0;
UINT8 jumpy_x = 25;
UINT8 jumpy_y = 120;
UINT8 jumpstrength = 10;
UINT8 idle_loop_frame = 0;
UINT8 floor_y = 120;
UINT8 bkg_position_offset = 0;
const char groundtile[1] = {0x00};

void jump() {
	isjumping = 1;
	// Play jump sound
	NR11_REG = 0x1F;
	NR12_REG = 0xF1;     
	NR13_REG = 0x30;
	NR14_REG = 0xC0;

	set_sprite_tile(0, 0);
	jumpmomentum = jumpstrength;
}

void performantdelay(UINT8 delay) {
	UINT8 i;
	for (i = 0; i < delay; i++)
	{
		wait_vbl_done();
	}
	
}

UBYTE hashitground(UINT8 new_x, UINT8 new_y) {
    UINT16 indexTLx, indexTLy, tileindexTL;

	// convert x and y of jumpy into the index of a background tile it'll overlap.
    indexTLx = (new_x - 4 + bkg_position_offset) / 8;
    indexTLy = (new_y - 16) / 8;
    tileindexTL = floormapWidth * indexTLy + indexTLx;

	// Check to see if it's a tile we'd land on
	if (floormap[tileindexTL] == groundtile[0]) {
		// If yes then snap to the top of that tile
		jumpy_y = (indexTLy * 8) + 8;
		return 1;
	}
	return 0;
}

void animatejump() {
	jumpy_y = jumpy_y - jumpmomentum;
	jumpmomentum = jumpmomentum - gravity;
	// Check if we're moving down and if we hit a "ground" tile
	if (jumpmomentum < 0 && hashitground(jumpy_x, jumpy_y)) {
		isjumping = 0;
	} else {
		// Affect sprite based on height
		if (jumpmomentum >= 2) {
			set_sprite_tile(0, 2);
		} else if (jumpmomentum >= -2) {
			set_sprite_tile(0, 1);
		} else {
			set_sprite_tile(0, 0);
		}
	}
	move_sprite(0, jumpy_x, jumpy_y);
}

void animateidle(){
	idle_loop_frame = idle_loop_frame + 1;
	if (idle_loop_frame == 4) {
		idle_loop_frame = 0;
	}
	set_sprite_tile(0, idle_loop_frame);
}

void main() {
	// Enable sounds
	NR52_REG = 0x80; // Turns on sound
	NR50_REG = 0x77; // Turn up volume on both left & right
	NR51_REG = 0xFF; // Turn on all 8 channels

	set_bkg_data(0, 2, floortiles);		// Load 3 tiles into background memory
	
	set_bkg_tiles(0, 0, floormapWidth, 18, floormap);

	set_sprite_data(0, 4, eggy);
	set_sprite_tile(0, 0);
	move_sprite(0, jumpy_x, jumpy_y);
	SHOW_SPRITES;
	SHOW_BKG;

	while(1){
		UBYTE joypad_state = joypad();

		if (joypad_state) {
			if(joypad_state & J_A) {
				if (isjumping == 0){
					jump();
				}
			}
			if(joypad_state & J_LEFT && isjumping) {  
				if (bkg_position_offset > 0 && jumpy_x < 30) {
					bkg_position_offset -= 2;
					scroll_bkg(-2, 0);
				} else if (jumpy_x > 8) {
					jumpy_x -= 2;
					move_sprite(0, jumpy_x, jumpy_y);
				}
			}

			if(joypad_state & J_RIGHT && isjumping) {
				if (jumpy_x < 80) {
					jumpy_x += 2;
					move_sprite(0, jumpy_x, jumpy_y);
				} else {
					bkg_position_offset += 2;
					scroll_bkg(2, 0);
				}
			}

		}
		if (isjumping == 1) {
			animatejump();
		} else {
			animateidle();
		}
		wait_vbl_done();
		performantdelay(2);
	}
}

Time Spent: All-in-all from jumpy to jumpy2, about 2 hours 15.

Variable speed tracking

Quick one tonight.

I’ve been chewing over the thoughts I had last time about stuff on screen needing to do stuff at different speeds and keeping track of it all, so tonight I want to try out a proof of concept throwing things into structs and having each sprite keep track of whether it was time to do a thing or not as the run loop passed by.

I made a basic animation (a little orbiting thingy) and created three instances set to animate at different speeds.

The basic instance was the same, except each one tracked which sprite id it was assigned (necessary) and tallied up the number of run loops since it last animated. If the loop count was the same as its individually assigned number-of-loops-before-you-animate number, it updated its sprite and reset the count.

It worked!

The gif doesn’t loop properly, so download the ROM below!

Oh hey, would you like to see the code? 3 files: orbit.c for the sprite data, Orbiter.c for the struct, and varitrack.c for the actual “game” code.

# orbit.c
unsigned char orbit[] =
{
  0x00,0x02,0x00,0x07,0x18,0x1A,0x3C,0x3C,
  0x3C,0x3C,0x18,0x18,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x04,0x10,0x1E,0x38,0x3C,
  0x3C,0x3C,0x18,0x18,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x18,0x18,0x2C,0x3C,
  0x04,0x3C,0x08,0x18,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x18,0x18,0x3C,0x3C,
  0x1C,0x3C,0x08,0x78,0x00,0x20,0x00,0x00,
  0x00,0x00,0x00,0x00,0x18,0x18,0x3C,0x3C,
  0x3C,0x3C,0x18,0x58,0x00,0xE0,0x00,0x40,
  0x00,0x00,0x00,0x00,0x18,0x18,0x3C,0x3C,
  0x3C,0x3C,0x18,0x78,0x00,0x20,0x00,0x00,
  0x00,0x00,0x00,0x00,0x18,0x18,0x3C,0x3C,
  0x3C,0x3C,0x18,0x18,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x04,0x18,0x1E,0x3C,0x3C,
  0x3C,0x3C,0x18,0x18,0x00,0x00,0x00,0x00
};
# Orbiter.c
#include <gb/gb.h>

struct Orbiter {
	UINT8 spriteid;
	UINT8 framessincelastanimation;
	UINT8 animationspeed;
	UINT8 currentanimationframe;
};
# varitrack.c

#include <gb/gb.h>
#include <gb/drawing.h>
#include "orbit.c" // Sprites
#include "Orbiter.c" // "Orbiter" struct

struct Orbiter orbiter_slow;
struct Orbiter orbiter_medium;
struct Orbiter orbiter_fast;

void setuporbiter(struct Orbiter* orbiter) {
	orbiter->currentanimationframe = 0;
	orbiter->framessincelastanimation = 0;
}

void animateorbiter(struct Orbiter* orbiter) {
	if (orbiter->framessincelastanimation == orbiter->animationspeed) {
		// animate!
		orbiter->currentanimationframe += 1;
		if (orbiter->currentanimationframe == 8) {
			orbiter->currentanimationframe = 0;
		}
		orbiter->framessincelastanimation = 0;
	} else {
		// increment frame
		orbiter->framessincelastanimation += 1;
	}
	set_sprite_tile(orbiter->spriteid, orbiter->currentanimationframe);
}

void main() {
	// Load the sprite data
	set_sprite_data(0, 8, orbit);

	// Set the defaults for each orbiter
	setuporbiter(&orbiter_slow);
	setuporbiter(&orbiter_medium);
	setuporbiter(&orbiter_fast);
	
	// set up sprite references
	orbiter_slow.spriteid = 0;
	orbiter_medium.spriteid = 1;
	orbiter_fast.spriteid = 2;

	// Set up the different animation speeds
	orbiter_slow.animationspeed = 20;
	orbiter_medium.animationspeed = 10;
	orbiter_fast.animationspeed = 5;

	// Initial setup
	set_sprite_tile(0, 0);
	move_sprite(0, 20, 20);
	set_sprite_tile(1, 0);
	move_sprite(1, 30, 30);
	set_sprite_tile(2, 0);
	move_sprite(2, 40, 40);
	SHOW_SPRITES;

	while(1){
		// Animate each of them
		animateorbiter(&orbiter_slow);
		animateorbiter(&orbiter_medium);
		animateorbiter(&orbiter_fast);
		wait_vbl_done();
	}
}

I imagine that keeping the orbiter objects in some kind of array would simplify the code further. I’d also look to have them move about at different speeds. Largely though that’s just extrapolation of this proof of concept, which works! Hurrah!

Time Spent: 30 minutes. I’m getting quicker!

Jumpy: putting some stuff together.

After the last installment, I’ve been dipping back into GamingMonsters’ video tutorials around GBDK and absorbing some of the concepts. I didn’t follow along in code, but instead used it to sort out what was the “done way” of structuring simple game code. See, I’ve done a lot of coding in the past but that’s always been for web frontend/backend (and a little bit of Arduino stuff in C), which is obviously totally different from games.

After watching a few (and learning a neat trick about more performant delays using wait_vbl_done() instead of delay()) I considered that the thing I’ll probably have the hardest time getting my head around is having multiple things going on at once. By that I mean sprites animating at different parts of their cycle, possibly at different speeds, while other things on screen need to be doing their own thing. There’s one loop to control it all and – if you stick a delay in somewhere to slow things down well, then everything will get slowed down. I imagine this is second nature to seasoned game programmers and there are some well-known methods to deal with it but, well, I’ll get to that when I do.

OK, so that aside I took a few days off the project to do other things, then came back tonight to try and put some things together just shooting from the top of my head – not necessarily looking anything up – to see what has stuck in the old grey matter so far.

I decided to make a small… thing.

I called it “Jumpy”. Guess why.

Sprites and backgrounds! Nice. Game Boy Tile Designer and Game Boy Map Builder in action.

I then decided to add some “idling” animation just to start flexing that “keeping track of the little things” muscle.

It’s a little gross perhaps, but good enough for now.

Then, yes, as you probably guessed I made it jump when you press the “A” button.
But more than that! I wanted to:

  • Play a sound when it jumped
  • Have some kind of physics to bring it back to the ground
  • Have the animation change depending on where it was in the jump

Came out pretty well:

You’ll have to download the ROM to hear the sound. It goes “BOOP.”

Time Spent: Just over an hour

Basic sounds working!

Yesterday’s adventure was probably a little “run before you can walk”, so today I went back to basics with the help of a really nice tutorial from GamingMonsters.

It did the trick – within half-an-hour I had the basics up and running as per the tutorial (no copy/pasting! I want to learn this!). I then spent another hour messing around with values, figuring out concurrent notes, and even got a basic percussion noise working.

Takeaways: this stuff if pretty badly documented (unless you know of any good docs, particularly on the percussion stuff).

No ROM today because I started mangling my code into some kind of weird interactive instrument and decided to call it a day before it was remotely functional. Maybe next time.

Time spent: 1hr 45

This music stuff is tricky

Back after a break (busy time at work). I figured that basic graphics and stuff was going pretty well so I’d do a quick music test. Turns out it’s a steep on-boarding.

So far I have figured out three options:

  1. Make awesome music with something like LSDJ. This is geared towards making awesome music, but eats all the Game Boy resources and definitely not the thing to do if you want to use the music as backing for a game. Strike that one then.
  2. Make music using a lighter Game Boy tracker like Carillon and use some provided in-game playback functions. Unfortunately GBDK wasn’t an original consideration by the author but someone else has made tooling for that. Anecdotally this route seems popular.
  3. Use MOD2GBT – a way to write music on a PC based tracker then convert for use on Game Boy. GBDK support is, however, discontinued and only exists in legacy format.

I had a play for an hour and managed to do two things:

  1. Made some fun music using Carillon but totally failed to get it to work with GBDK.
  2. Figured out how to use MOD2GBT to convert music, but failed to actually make any with OpenMPT.

I’m not sure what my preferred workflow is and therefore which to persevere with. I really enjoyed using Carillon (in an emulator for now) to make music. I’m also no stranger to trackers having grown up messing around with ScreamTracker3 and Fast Tracker II (long live Lizardking) so I could probably work out OpenMPT if I give it more than 10 minutes. The thing with evening projects is that I get pretty tired pretty quickly.

I’ll probably give that Carillon->GBDK workflow another try.

Time Spent: 1.5hr

Some more research

Mostly browsing around and dipping into topics like how to display text and what music entails. Came across a few interesting looking resources by accident too. So here’s a linkdump:

And the big news was I upgraded my IDE from *ahem* Notepad to Visual Studio Code – seems pretty good and snappy. Oh, and I installed gcc via Cygwin in order to compile mod2gb.

Time Spent: 1hr 45 minutes of glorious, semi-focussed browsing around.

Fullscreen Fade Effects

OK. We got Sprites. We got Backgrounds. What else?

I hear that the third (and final) graphical layer is “Window”. So a bit of reading and that looks like it’s useful for HUD style content (to save sprites – the Game Boy can only display 12 of those in any row). Out-of-the-box it starts at the top left of the screen but while reading up I found some clever shenanigans on a forum to get it placed at the bottom instead. Useful stuff, but I didn’t fancy doing HUD stuff tonight.

I fancied playing with that “fade” effect you often see in games. It’s apparently super useful when replacing whole screens to prevent glitchy behaviour. i.e. you fade it to a single colour, replace the screen content, and fade it back in. Looks cool, and hides quirks.

Let’s do that with a background.

I need an image to play with, so I grabbed a picture of Thom Yorke from the “No Surprises” video (because who wouldn’t want a tiny Thom trying not to drown in their Game Boy?) and set about converting it to a useful format. In GIMP of all things.

So “The GIMP” has certainly moved on since I last used it (maybe 15 years ago?). At first I did so to follow a tutorial on displaying full screen images which failed when I found that there’s no way to get the pre-requisite pcx2gb.exe to run on my 64-bit Windows machine. Oh well. Still, the brief diversion into The GIMP did re-open my eyes to the concept of dithering (the colour palette trick) so I was able to process a nice image in the correct dimensions at a reduced palette.

Thanks, The GIMP! I should never have left you for Photoshop

Right, so getting it into a Game Boy format. Well pcx2gb didn’t work, but thanks to an old post and the Internet Archive (I’m sensing a theme here) I dug up a REALLY neat online tool for generating the tiles and the map! I should try and grab a copy before it goes away forever.

Paste that data into a .c file, write a super-basic loader and ta-da!

You can’t see it, but this image’s filename is thom_boy.png

(Why the border? Well, the Game Boy has limits, and enough unique tiles to fill the whole screen is just too much. Giving it a 1-tile border allowed a single tile to be repeated all the way around, vastly reducing the number of unique tiles needed.)

Let’s make him fade.

As usual, it didn’t take long to find a great example. I’d already read up on the principles of how it’s done (palette swap each colour towards the lightest/darkest one) and some kind person calling themselves “cabbage” uploaded some sample code.

It didn’t take long to pull apart, reassemble, and integrate onto my little test project.

Faaaaaddeee Ouuuuuuttt aggaaaaaaaaaaaaaaaaaaain
(Yes, I know that’s not this song)
(Yes, that’s why I thought of using Thom

Time Spent: About 1.5 hours. A lot of which was finding tools, installing GIMP and playing with Thom’s face.

My first bug! (Also 60fps background scrollers)

[Epilepsy trigger warning – weird repeating pattern gif at the bottom of this post]

That sprite work yesterday got me pumped up on my own sense of achievement, so I confess that after posting last night I leapt right into playing with backgrounds. There was an equally straightforward tutorial. I’ll just make a little tile and shove it behind my little speaky character. How hard could that be?

I… what?

Check code, recompile, recheck code, recompile, rejig code, recompile.

Well at least that grinning sprite has gone. He really wasn’t helping.

Go back and take the tutorial code verbatim.

o_O

OK, what the heck is going on here? Basically I spent about 2 hours doing everything I could possibly think of from adding delays and re-draws in the code to installing different emulators and running on that (and it worked in a worse emulator than BGB!). I was stumped. So I went to bed.

Woke up with a couple of ideas (they didn’t work) and so, for the first of what I expect will be many occasions, I turned to the community and got put on the right track. And learned some stuff! Winner! For all the details, read the community thread.

(Aside: If you spotted that the weird area correlated to the size and position of the “Nintendo” text that appears when you boot the Game Boy, you get 10 points.)

For what it’s worth, there was actually little straightforward instruction on what I learned out there on the web, so I took the time to be a good citizen and post my learnings more concisely over in the thread. My goal right now is not to write tutorials, but perhaps these snippets will help future newbies get up to speed that little bit faster during those crucial early days of a new hobby.

Well said, Jake.

So today I not only got backgrounds working, I also made it scroll in glorious 60fps! Unlike this nightmarish, janky GIF representation:

I can make Game Boys do what I cannot make GIFs capture properly

Time Spent: About 2 hours getting confused and irritated, then an additional 1 hour learning useful things and implementing something to give you a headache.