Part 1: Configuring, Programming, and Compiling
Part 2: Placing Tiles and Moving Sprites
Part 4: Colliding Sprites and Project Management
Updating Example Files
Before doing collision code, we should update our background tiles to something more useful than simply the alphabet. In the above file, I have used some tiles I developed for another project and included the original numbers and letters from the earlier parts of this series. Using these — and with a first, 0, blank tile — we can start to make areas that resemble other Game Boy games.
tiles.c
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
TILES.C | |
Tile Source File. | |
Info: | |
Form : All tiles as one unit. | |
Format : Gameboy 4 color. | |
Compression : None. | |
Counter : None. | |
Tile size : 8 x 8 | |
Tiles : 0 to 59 | |
Palette colors : None. | |
SGB Palette : None. | |
CGB Palette : None. | |
Convert to metatiles : No. | |
This file was generated by GBTD v2.2 | |
*/ | |
/* Start of tile array. */ | |
unsigned char tiles[] = | |
{ | |
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, | |
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, | |
0x27,0xFF,0x7F,0xFF,0x9E,0xFF,0x0F,0xFF, | |
0x5B,0xFF,0x3F,0xFF,0x17,0xFF,0x5B,0xFF, | |
0x68,0x17,0x31,0x0F,0x20,0x1F,0x69,0x17, | |
0x60,0x1F,0x24,0x1F,0x20,0x1F,0x68,0x17, | |
0xDA,0xFF,0xE8,0xFF,0xFC,0xFF,0xDA,0xFF, | |
0xF0,0xFF,0x79,0xFF,0xFE,0xFF,0xE4,0xFF, | |
0x16,0xE8,0x8C,0xF0,0x04,0xF8,0x96,0xE8, | |
0x06,0xF8,0x24,0xF8,0x04,0xF8,0x16,0xE8, | |
0x0A,0xFF,0x00,0xFF,0x20,0xFF,0x89,0x76, | |
0x02,0xFD,0xFF,0x00,0x99,0x00,0x00,0x00, | |
0x00,0x00,0x99,0x00,0xFF,0x00,0x02,0xFD, | |
0x89,0x76,0x20,0xFF,0x00,0xFF,0x0A,0xFF, | |
0x04,0xFF,0x92,0xFF,0x23,0xFF,0xF6,0xFF, | |
0xBE,0xFF,0x6F,0xFF,0xFF,0xFF,0xFB,0xFF, | |
0x04,0xFF,0x92,0xFF,0x23,0xFF,0xF6,0xFF, | |
0xBE,0xFF,0x6F,0xFF,0xFF,0xFF,0xFB,0xFF, | |
0x03,0x00,0x03,0x00,0x39,0x00,0x21,0x1E, | |
0x0B,0x1C,0x00,0x1F,0xC8,0x17,0xF8,0x07, | |
0xC0,0x00,0xC0,0x00,0x8C,0x00,0x84,0x78, | |
0xD4,0x38,0x00,0xF8,0x13,0xE8,0x1F,0xE0, | |
0x1F,0xE0,0x13,0xE8,0x00,0xF8,0xD0,0x38, | |
0x84,0x78,0x9C,0x00,0xC0,0x00,0xC0,0x00, | |
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, | |
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, | |
0xF8,0x07,0xC8,0x17,0x00,0x1F,0x2B,0x1C, | |
0x21,0x1E,0x31,0x00,0x03,0x00,0x03,0x00, | |
0xFF,0xFF,0x80,0x80,0x80,0x80,0x80,0x80, | |
0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F, | |
0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F, | |
0x80,0x80,0x80,0x80,0x80,0x80,0xFF,0xFF, | |
0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F, | |
0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F,0x8F, | |
0xF1,0xF1,0xF1,0xF1,0xF1,0xF1,0xF1,0xF1, | |
0xF1,0xF1,0xF1,0xF1,0xF1,0xF1,0xF1,0xF1, | |
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, | |
0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF, | |
0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00, | |
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, | |
0xFF,0xFF,0x01,0x01,0x01,0x01,0x01,0x01, | |
0xF1,0xF1,0xF1,0xF1,0xF1,0xF1,0xF1,0xF1, | |
0xF1,0xF1,0xF1,0xF1,0xF1,0xF1,0xF1,0xF1, | |
0x01,0x01,0x01,0x01,0x01,0x01,0xFF,0xFF, | |
0x83,0x7C,0x39,0xC6,0x4D,0xB2,0x5D,0xA2, | |
0x79,0x86,0x33,0xCC,0x84,0x7B,0xFA,0x05, | |
0x31,0xCE,0x62,0x9D,0xC4,0x3B,0x80,0x7F, | |
0x13,0xEC,0x46,0xB9,0x64,0x9B,0x18,0xE7, | |
0x38,0x38,0x44,0x44,0x82,0x82,0x82,0x82, | |
0xFE,0xFE,0x82,0x82,0x82,0x82,0x00,0x00, | |
0xFC,0xFC,0x82,0x82,0x82,0x82,0xFC,0xFC, | |
0x82,0x82,0x82,0x82,0xFC,0xFC,0x00,0x00, | |
0x7C,0x7C,0x82,0x82,0x80,0x80,0x80,0x80, | |
0x80,0x80,0x82,0x82,0x7C,0x7C,0x00,0x00, | |
0xF8,0xF8,0x84,0x84,0x82,0x82,0x82,0x82, | |
0x82,0x82,0x84,0x84,0xF8,0xF8,0x00,0x00, | |
0xFE,0xFE,0x80,0x80,0x80,0x80,0xFC,0xFC, | |
0x80,0x80,0x80,0x80,0xFE,0xFE,0x00,0x00, | |
0xFE,0xFE,0x80,0x80,0x80,0x80,0xFC,0xFC, | |
0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x00, | |
0x7C,0x7C,0x82,0x82,0x80,0x80,0x80,0x80, | |
0x8E,0x8E,0x82,0x82,0x7E,0x7E,0x00,0x00, | |
0x82,0x82,0x82,0x82,0x82,0x82,0xFE,0xFE, | |
0x82,0x82,0x82,0x82,0x82,0x82,0x00,0x00, | |
0x7C,0x7C,0x10,0x10,0x10,0x10,0x10,0x10, | |
0x10,0x10,0x10,0x10,0x7C,0x7C,0x00,0x00, | |
0x1E,0x1E,0x04,0x04,0x04,0x04,0x04,0x04, | |
0x04,0x04,0x84,0x84,0x78,0x78,0x00,0x00, | |
0x84,0x84,0x88,0x88,0x90,0x90,0xF0,0xF0, | |
0x88,0x88,0x84,0x84,0x82,0x82,0x00,0x00, | |
0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, | |
0x40,0x40,0x40,0x40,0x7E,0x7E,0x00,0x00, | |
0x82,0x82,0xC6,0xC6,0xAA,0xAA,0x92,0x92, | |
0x82,0x82,0x82,0x82,0x82,0x82,0x00,0x00, | |
0x82,0x82,0xC2,0xC2,0xA2,0xA2,0x92,0x92, | |
0x8A,0x8A,0x86,0x86,0x82,0x82,0x00,0x00, | |
0x7C,0x7C,0x82,0x82,0x82,0x82,0x82,0x82, | |
0x82,0x82,0x82,0x82,0x7C,0x7C,0x00,0x00, | |
0xFC,0xFC,0x82,0x82,0x82,0x82,0x82,0x82, | |
0xFC,0xFC,0x80,0x80,0x80,0x80,0x00,0x00, | |
0x7C,0x7C,0x82,0x82,0x82,0x82,0x92,0x92, | |
0x8A,0x8A,0x84,0x84,0x7A,0x7A,0x00,0x00, | |
0xFC,0xFC,0x82,0x82,0x82,0x82,0x84,0x84, | |
0xF8,0xF8,0x84,0x84,0x82,0x82,0x00,0x00, | |
0x7C,0x7C,0x80,0x80,0x80,0x80,0x7C,0x7C, | |
0x02,0x02,0x02,0x02,0xFC,0xFC,0x00,0x00, | |
0xFE,0xFE,0x10,0x10,0x10,0x10,0x10,0x10, | |
0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00, | |
0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82, | |
0x82,0x82,0x82,0x82,0x7C,0x7C,0x00,0x00, | |
0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x82, | |
0x44,0x44,0x28,0x28,0x10,0x10,0x00,0x00, | |
0x82,0x82,0x82,0x82,0x82,0x82,0x92,0x92, | |
0xAA,0xAA,0xC6,0xC6,0x82,0x82,0x00,0x00, | |
0x82,0x82,0x44,0x44,0x28,0x28,0x10,0x10, | |
0x28,0x28,0x44,0x44,0x82,0x82,0x00,0x00, | |
0x82,0x82,0x44,0x44,0x28,0x28,0x10,0x10, | |
0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00, | |
0xFE,0xFE,0x04,0x04,0x08,0x08,0x10,0x10, | |
0x20,0x20,0x40,0x40,0xFE,0xFE,0x00,0x00, | |
0x7C,0x7C,0x86,0x86,0x8A,0x8A,0x92,0x92, | |
0xA2,0xA2,0xC2,0xC2,0x7C,0x7C,0x00,0x00, | |
0x10,0x10,0x30,0x30,0x10,0x10,0x10,0x10, | |
0x10,0x10,0x10,0x10,0x7C,0x7C,0x00,0x00, | |
0x7C,0x7C,0x82,0x82,0x02,0x02,0x1C,0x1C, | |
0x60,0x60,0x80,0x80,0xFE,0xFE,0x00,0x00, | |
0xFE,0xFE,0x04,0x04,0x18,0x18,0x04,0x04, | |
0x02,0x02,0x82,0x82,0x7C,0x7C,0x00,0x00, | |
0x0C,0x0C,0x14,0x14,0x24,0x24,0x44,0x44, | |
0x84,0x84,0xFE,0xFE,0x04,0x04,0x00,0x00, | |
0xFC,0xFC,0x80,0x80,0xFC,0xFC,0x02,0x02, | |
0x02,0x02,0x82,0x82,0x7C,0x7C,0x00,0x00, | |
0x7C,0x7C,0x80,0x80,0x80,0x80,0xFC,0xFC, | |
0x82,0x82,0x82,0x82,0x7C,0x7C,0x00,0x00, | |
0xFE,0xFE,0x82,0x82,0x04,0x04,0x08,0x08, | |
0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00, | |
0x7C,0x7C,0x82,0x82,0x82,0x82,0x7C,0x7C, | |
0x82,0x82,0x82,0x82,0x7C,0x7C,0x00,0x00, | |
0x7C,0x7C,0x82,0x82,0x82,0x82,0x7E,0x7E, | |
0x02,0x02,0x02,0x02,0x7C,0x7C,0x00,0x00 | |
}; | |
/* End of TILES.C */ |
dungeon.c
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
DUNGEON.C | |
Map Source File. | |
Info: | |
Section : | |
Bank : 0 | |
Map size : 20 x 18 | |
Tile set : tiles.gbr | |
Plane count : 1 plane (8 bits) | |
Plane order : Tiles are continues | |
Tile offset : 0 | |
Split data : No | |
This file was generated by GBMB v1.8 | |
*/ | |
#define dungeonWidth 20 | |
#define dungeonHeight 18 | |
#define dungeonBank 0 | |
unsigned char dungeon[] = | |
{ | |
0x09,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06, | |
0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x0A, | |
0x02,0x01,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08, | |
0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x03,0x04, | |
0x02,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, | |
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x04, | |
0x02,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, | |
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x04, | |
0x02,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, | |
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x04, | |
0x02,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, | |
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x04, | |
0x02,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, | |
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x04, | |
0x02,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, | |
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x04, | |
0x02,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, | |
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x04, | |
0x02,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, | |
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x04, | |
0x02,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, | |
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x04, | |
0x02,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, | |
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x04, | |
0x02,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, | |
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x04, | |
0x02,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, | |
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x04, | |
0x02,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, | |
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x04, | |
0x02,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, | |
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x04, | |
0x02,0x01,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07, | |
0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x03,0x04, | |
0x0D,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05, | |
0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x0B | |
}; | |
/* End of DUNGEON.C */ |
main.c
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <gb/gb.h> | |
#include "tiles.c" | |
#include "sprites.c" | |
#include "dungeon.c" | |
void init(); | |
void checkInput(); | |
void updateSwitches(); | |
// The player array will hold the player's position as X ([0]) and Y ([1]) | |
UINT8 player[2]; | |
unsigned char noWalk[2]; | |
void main() { | |
init(); | |
while(1) { | |
checkInput(); // Check for user input (and act on it) | |
updateSwitches(); // Make sure the SHOW_SPRITES and SHOW_BKG switches are on each loop | |
wait_vbl_done(); // Wait until VBLANK to avoid corrupting memory | |
} | |
} | |
void init() { | |
DISPLAY_ON; // Turn on the display | |
set_bkg_data(0, 23, tiles); // Load 23 tiles into background memory | |
set_bkg_tiles(0,0,20,18,dungeon); | |
// Load the the 'sprites' tiles into sprite memory | |
set_sprite_data(0, 1, sprites); | |
// Set the first movable sprite (0) to be the first tile in the sprite memory (0) | |
set_sprite_tile(0,0); | |
player[0] = 64; | |
player[1] = 64; | |
} | |
void updateSwitches() { | |
HIDE_WIN; | |
SHOW_SPRITES; | |
SHOW_BKG; | |
} | |
void checkInput() { | |
if (joypad() & J_B) { | |
} | |
// UP | |
if (joypad() & J_UP) { | |
player[1]–; | |
} | |
// DOWN | |
if (joypad() & J_DOWN) { | |
player[1]++; | |
} | |
// LEFT | |
if (joypad() & J_LEFT) { | |
player[0]–; | |
} | |
// RIGHT | |
if (joypad() & J_RIGHT) { | |
player[0]++; | |
} | |
// Move the sprite in the first movable sprite list (0) | |
// the the position of X (player[0]) and y (player[1]) | |
move_sprite(0, player[0], player[1]); | |
} |
Collision
GBDK comes with no built-in physics or collision functionality. If we want collision code, we have to add it in as an additional function. For this example, we will use simple rectangle-to-rectangle checking. If one rectangle (from a starting position x, y and extending outward some amount) is overlapping with another rectangle (from another point some x and y amounts out), then the two are colliding.
We also need something to collide with, so another sprite is introduced and its position saved.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <gb/gb.h> | |
#include "tiles.c" | |
#include "sprites.c" | |
#include "dungeon.c" | |
void init(); | |
void checkInput(); | |
void updateSwitches(); | |
UINT8 collisionCheck(UINT8, UINT8, UINT8, UINT8, UINT8, UINT8, UINT8, UINT8); | |
// The player array will hold the player's position as X ([0]) and Y ([1]) | |
UINT8 player[2]; | |
//The enemy array will hold the enemy position as X ([0]) and Y ([1]) | |
UINT8 enemy[2]; | |
void main() { | |
init(); | |
while(1) { | |
checkInput(); // Check for user input (and act on it) | |
updateSwitches(); // Make sure the SHOW_SPRITES and SHOW_BKG switches are on each loop | |
wait_vbl_done(); // Wait until VBLANK to avoid corrupting memory | |
} | |
} | |
void init() { | |
DISPLAY_ON; // Turn on the display | |
set_bkg_data(0, 23, tiles); // Load 23 tiles into background memory | |
set_bkg_tiles(0,0,20,18,dungeon); | |
// Load the the 'sprites' tiles into sprite memory | |
set_sprite_data(0, 2, sprites); | |
// Set the first movable sprite (0) to be the first tile in the sprite memory (0) | |
set_sprite_tile(0,0); | |
// Set the second movable sprite (1) to be the second tile in the sprite memory (1) | |
set_sprite_tile(1,1); | |
player[0] = 64; | |
player[1] = 64; | |
enemy[0] = 128; | |
enemy[1] = 128; | |
} | |
void updateSwitches() { | |
HIDE_WIN; | |
SHOW_SPRITES; | |
SHOW_BKG; | |
} | |
void checkInput() { | |
if (joypad() & J_B) { | |
} | |
// UP | |
if (joypad() & J_UP) { | |
player[1]–; | |
} | |
// DOWN | |
if (joypad() & J_DOWN) { | |
player[1]++; | |
} | |
// LEFT | |
if (joypad() & J_LEFT) { | |
player[0]–; | |
} | |
// RIGHT | |
if (joypad() & J_RIGHT) { | |
player[0]++; | |
} | |
// Move the sprite in the first movable sprite list (0) | |
// to the position of X (player[0]) and y (player[1]) | |
move_sprite(0, player[0], player[1]); | |
// Move the sprite in the second movable sprite list (1) | |
// to the position of X (enemy[0]) and y (enemy[1]) | |
move_sprite(1, enemy[0], enemy[1]); | |
// Is the player colliding with the enemy? | |
if(collisionCheck(player[0], player[1], 8, 8, enemy[0], enemy[1], 8, 8) == 1) { | |
set_sprite_tile(0,1); | |
} else { | |
set_sprite_tile(0,0); | |
} | |
} | |
// Check if two rectangles from x1,y1, and extending out h1, h2, | |
// overlap with another, x2,y2, and extending out w2, h2 | |
UINT8 collisionCheck(UINT8 x1, UINT8 y1, UINT8 w1, UINT8 h1, UINT8 x2, UINT8 y2, UINT8 w2, UINT8 h2) { | |
if ((x1 < (x2+w2)) && ((x1+w1) > x2) && (y1 < (h2+y2)) && ((y1+h1) > y2)) { | |
return 1; | |
} else { | |
return 0; | |
} | |
} |
With our new colliding code, sprite0, when it overlaps with sprite1, switches its tile.
Project Management
As becomes obvious from Part 3, the need for a smooth “Save, Export, Re-Compile” workflow is very important when using GBTD and GBMB with other tools. I highly suggest getting into the habit of saving constantly and making sure, with the maps and tiles, all files are as up-to-date as possible before compiling again as well.
However, as the compiling to test code can take time, I suggest the following:
Make your code as module as possible
Without objects and using only functions can make testing and debugging much more time-consuming, but separating the code into logical “chunks” can save considerable time when trying to move from one section to another. Planning ahead and having in mind, for example, a change from a menu to a level, can help separate functions into sections that can be tested as a group.
When possible, re-use variables
While not as important in smaller projects, having simple variables around like “i” and “j” can be very helpful for looping without need to create a new variable. Simply re-use the existing ones to run the loop and then re-use them again in another loop, later. This can save space in both the RAM and the ROM as a result.
Arrays are your best friend
While arrays are already the go-to for data structures in C, and because ROM space is an issue, having an allocated set of positions to store things like a player’s coordinates can save space and makes more logical sense to group them at the same time. This can also be written about “enemies” (or whatever, other obstacles might be on the screen). Adjusting multiple array locations and then using them to display things like meta-sprites are a required technique.
Use the 20 sprite limit wisely
GBDK can only update 20 sprites at a time. Depending on the project, this could be safely ignored or become a vital part of moving tiles around. Keeping an external map either as part of the comments or in anther file can help save your sanity when swapping tiles around and then moving sprites after.