In this lesson, we will start a new project from scratch. We will make a Breakout / Arkanoid clone, which we’ll call “Unbricked”! (Though you are free to give it any other name you like, as it will be your project.)
Open a terminal and make a new directory (
mkdir unbricked), and then enter it (
cd unbricked), just like you did for “Hello, world!”.
Start by creating a file called
main.asm, and include
hardware.inc in your code.
You may be wondering what purpose
Well, the code we write only really affects the CPU, but does not do anything with the rest of the console (not directly, anyway).
To interact with other components (like the graphics system, say), Memory-Mapped I/O (MMIO) is used: basically, memory in a certain range (addresses $FF00–FF7F) does special things when accessed.
These bytes of memory being interfaces to the hardware, they are called hardware registers (not to be mistaken with the CPU registers).
For example, the “PPU status” register is located at address $FF41.
Reading from that address reports various bits of info regarding the graphics system, and writing to it allows changing some parameters.
But, having to remember all the numbers (non-exhaustive list) would be very tedious—and this is where
hardware.inc comes into play!
hardware.inc defines one constant for each of these registers (for example,
rSTAT for the aforementioned “PPU status” register), plus some additional constants for values read from or written to these registers.
Don’t worry if this flew over your head, we’ll see an example below with
By the way, the
r stands for “register”, and the
LCDCF stands for “flag”.
Next, make room for the header. Remember from Part Ⅰ that the header is where some information that the Game Boy relies on is stored, so you don’t want to accidentally leave it out.
SECTION "Header", ROM0[$100] jp EntryPoint ds $150 - @, 0 ; Make room for the header
The header jumps to
EntryPoint, so let’s write that now:
EntryPoint: ; Do not turn the LCD off outside of VBlank WaitVBlank: ld a, [rLY] cp 144 jp c, WaitVBlank ; Turn the LCD off ld a, 0 ld [rLCDC], a
The next few lines wait until “VBlank”, which is the only time you can safely turn off the screen (doing so at the wrong time could damage a real Game Boy, so this is very crucial). We’ll explain what VBlank is and talk about it more later in the tutorial.
Turning off the screen is important because loading new tiles while the screen is on is tricky—we’ll touch on how to do that in Part 3.
Speaking of tiles, we’re going to load some into VRAM next, using the following code:
; Copy the tile data ld de, Tiles ld hl, $9000 ld bc, TilesEnd - Tiles CopyTiles: ld a, [de] ld [hli], a inc de dec bc ld a, b or a, c jp nz, CopyTiles
This loop might be reminiscent of part Ⅰ.
It copies starting at
$9000 onwards, which is the part of VRAM where our tiles are going to be stored.
$9000 is where the data of background tile $00 lies, and the data of subsequent tiles follows right after.
To get the number of bytes to copy, we will do just like in Part Ⅰ: using another label at the end, called
TilesEnd, the difference between it (= the address after the last byte of tile data) and
Tiles (= the address of the first byte) will be exactly that length.
That said, we haven’t written
Tiles nor any of the related data yet.
We’ll get to that later!
Almost done now—next, write another loop, this time for copying the tilemap.
; Copy the tilemap ld de, Tilemap ld hl, $9800 ld bc, TilemapEnd - Tilemap CopyTilemap: ld a, [de] ld [hli], a inc de dec bc ld a, b or a, c jp nz, CopyTilemap
Note that while this loop’s body is exactly the same as
CopyTiles’s, the 3 values loaded into
bc are different.
These determine the source, destination, and size of the copy, respectively.
If you think that this is super redundant, you are not wrong, and we will see later how to write actual, reusable functions. But there is more to them than meets the eye, so we will start tackling them much later.
Finally, let’s turn the screen back on, and set a background palette.
Rather than writing the non-descript number
%10000001 (or $81 or 129, to taste), we make use of two constants graciously provided by
When written to
rLCDC, the former causes the PPU and screen to turn back on, and the latter enables the background to be drawn.
(There are other elements that could be drawn, but we are not enabling them yet.)
Combining these constants must be done using
|, the binary “or” operator; we’ll see why later.
; Turn the LCD on ld a, LCDCF_ON | LCDCF_BGON ld [rLCDC], a ; During the first (blank) frame, initialize display registers ld a, %11100100 ld [rBGP], a Done: jp Done
There’s one last thing we need before we can build the ROM, and that’s the graphics. We will draw the following screen:
hello-world.asm, tile data had been written out by hand in hexadecimal; this was to let you see how the sausage is made at the lowest level, but boy is it impractical to write!
This time, we will employ a more friendly way, which will let us write each row of pixels more easily.
For each row of pixels, instead of writing the bitplanes directly, we will use a backtick (```) followed by 8 characters.
Each character defines a single pixel, intuitively from left to right; it must be one of 0, 1, 2, and 3, representing the corresponding color index in the palette.
If the character selection isn’t to your liking, you can use RGBASM’s
-g option or
OPT g to pick others.
rgbasm -g '.xXO' (...) or
OPT g.xXO would swap the four characters to
dw `01230123 ; This is equivalent to `db $55,$33`
You may have noticed that we are using
dw instead of
db; the difference between these two will be explained later.
We already have tiles made for this project, so you can copy this premade file, and paste it at the end of your code.
Then copy the tilemap from this file, and paste it after the
You can build the ROM now, by running the following commands in your terminal:
rgbasm -L -o main.o main.asm rgblink -o unbricked.gb main.o rgbfix -v -p 0xFF unbricked.gb
If you run this in your emulator, you should see the following:
That white square seems to be missing! You may have noticed this comment earlier, somewhere in the tile data:
dw `22322232 dw `23232323 dw `33333333 ; Paste your logo here: TilesEnd:
The logo tiles were left intentionally blank so that you can choose your own. You can use one of the following pre-made logos, or try coming up with your own!
Add your chosen logo’s data (click one of the “Source” links above) after the comment, build the game again, and you should see your logo of choice in the bottom-right!