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.

Leave a comment

Your email address will not be published. Required fields are marked *