Programming Game Boy Games using GBDK: Part 4, Colliding Sprites and Project Management

 

Part 1: Configuring, Programming, and Compiling

Part 2: Placing Tiles and Moving Sprites

Part 3: Using GBTD and GBMB

Part 4: Colliding Sprites and Project Management

Part 5: ROM Banking

Updating Example Files

GBDK_20

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

/*
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 */

view raw
tiles.c
hosted with ❤ by GitHub

dungeon.c

/*
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 */

view raw
dungeon.c
hosted with ❤ by GitHub

main.c

#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]);
}

view raw
main.c
hosted with ❤ by GitHub

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.

#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;
}
}

view raw
main.c
hosted with ❤ by GitHub

With our new colliding code, sprite0, when it overlaps with sprite1, switches its tile.

2016-07-06-0432-28

 

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.