CREATING a GameBoy game with PHP

Previously on, I’ve been decompiling the DuckTale GameBoy ROM using only PHP. That got me curious if one could MAKE a GameBoy ROM with PHP.

Assembly

My first stop was to better understand how the games were originally created, which led me to the RGBDS site with tons of info about GB game creation. It was actually one of the sites I used to find out what headers the Nintendo ROMS used, and how to access them. But for actually making games, it has everything. My main focus was looking at the Assembly code, and seeing how I could re-construct that from PHP.

Helpfully, there are a lot of boilerplate examples out there, so I pulled in some and created a very basic “main.asm” file. It handles all the setup, game loop, and arrow key presses. Then I did another very basic “game.asm” file.

You have to manually set values to specific memory addresses, like for the player’s x and y locations:

DEF player_x EQU $C000
DEF player_y EQU $C001

On the GameBoy, those are the start of the “Work RAM” locations. There are ~8k of those, if you wanted to store 8,000 integers between 0 and 254. It gets more complicated when you want to store larger numbers or other objects. I’m keeping it simple for now.

Then we set the initial state of the game…

    ld a, 76           
    ld [player_x], a
    ld a, 68            
    ld [player_y], a
    ld b, 76
    ld c, 68

This gets interesting, because “a” is the specific memory register any calculations are done. So here, we put (load) the number 76 into “a”… then load the value of “a” into the player_x memory location ($C000). Then do the same thing with the Y location, and then finally put them into the “b” and “c” registers. Honestly, I’m not exactly sure what the “b” and “c” are for at this point… but they’re used in the main.asm file. Looking at GameBoy code, it’s so much “load {value} into ‘a’, then load ‘a’ into {memory}”

Next is the game loop.

GameLoop:
    call WaitVblank
    call UpdateInputs

    ; -- Right --
    call KeyRight
    jr z, .noRight
    ld a, [player_x]
    cp 152              
    jr nc, .noRight
    inc a
    ld [player_x], a

WaitVblank keeps the system and screen synced up, and UpdateInputs is checking if any buttons are pressed. For this first pass, we’re only checking for up/down/left/right, but this also handles start/select/a/b/bumpers. For checking if the “right” arrow is pressed, it calls KeyRight and then checks if the z bit is set. If it IS, that means the button was NOT pressed, so you jump the .noRight symbol.

The next couple of lines are boundary checks, which I didn’t include in the PHP compiler. But from what I understand cp 152 is comparing ‘a’ with 152 (the right side of the screen). If ‘a’ is less than 152, it sets a “carry” flag, which means it’s fine to keep moving. The jr nc, .noRight line is “there is NOT a carry flag, so stop moving and go to the .noRight section”. Next increments ‘a’ and then updates the player’s X value with ‘a’. So the whole thing is checking “is right pressed? is left pressed? is up pressed? is down pressed?”, in order, every 1/60th of a second.

Then the one sprite:

SpriteTileData:
    db $18, $18         ; ...XX...  binary for 0001 1000
    db $3C, $3C         ; ..XXXX..  binary for 0011 1100, etc
    db $7E, $7E         ; .XXXXXX.
    db $FF, $FF         ; XXXXXXXX
    db $FF, $FF         ; XXXXXXXX
    db $7E, $7E         ; .XXXXXX.
    db $3C, $3C         ; ..XXXX..
    db $18, $18         ; ...XX...
SpriteTileDataEnd:

I know the sprites are set as 2 bits, a high and low bit. For this, it’s the same value for both to keep it simple. The main.asm files loads all these in the correct place so they display on screen.

At this point, I was able to run the needed commands, and got a .gb file that ran.

The PHP game file

My next step was to see how I wanted the PHP game file to look. I stubbed out a super basic idea. Setup fairly similar to how the assembly file is organized.

wait_vblank();
update_inputs();

Then I have function calls, that I plan on PascalCasing when calling the “main.asm” files. Other than that, not a whole lot going on. To compile it, I decided to just read each line of the file, do some preg_match … and build up an assembly file.

The PHP compiler

So this is currently a bit of a mess (but it works on my machine).

I changed a couple of things, like instead of defining variables with memory locations, I’m just using the locations directly. And I’m not doing boundary checks. And I renamed the “noRight/Left/etc” symbols to be a bit more flexible later on.

And when I ran my game code through it, it worked!

I even made a build script to do the whole process.

I have ideas for next steps. Like splitting up the compiler code to be more object-oriented, and less spaghetti. And hopefully will be easier to add new functionality. Part of the process is getting more indepth on the actual assembly code side of things… which I’m not sure I have much time to do. We’ll see.

Leave a Reply