Skip to content

Commit 5ec1331

Browse files
authored
Add getting-started to part 2 (#21)
Co-authored-by: Eldred Habert <[email protected]> Co-authored-by: Antonio Vivace <[email protected]> Unbricked source code lies outside of `src` so that it doesn't get served
1 parent 161564c commit 5ec1331

16 files changed

+899
-1
lines changed

book.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,5 @@ edit-url-template = "https://github.com/ISSOtm/gb-asm-tutorial/edit/master/{path
3535
site-url = "/gb-asm-tutorial/"
3636

3737
[output.linkcheck]
38-
traverse-parent-directories = false
38+
traverse-parent-directories = true # We intentionally read some files outside of `src/`
3939
optional = true

src/SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
# Part Ⅱ — Our first game
2525

26+
- [Getting started](part2/getting-started.md)
2627
- [Work in progress](part2/wip.md)
2728

2829
# Part Ⅲ — Our second game

src/assets/part2/img/duck.png

5.07 KB
Loading

src/assets/part2/img/rgbds.png

5.1 KB
Loading

src/assets/part2/img/screenshot.png

1.7 KB
Loading

src/assets/part2/img/tail.png

5.13 KB
Loading

src/assets/part2/img/tilemap.png

708 Bytes
Loading

src/part2/getting-started.md

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# Getting started
2+
3+
In this lesson, we will start a new project from scratch.
4+
We will make a [Breakout](https://en.wikipedia.org/wiki/Breakout_%28video_game%29) / [Arkanoid](https://en.wikipedia.org/wiki/Arkanoid) clone, which we'll call "Unbricked"!
5+
(Though you are free to give it any other name you like, as it will be *your* project.)
6+
7+
Open a terminal and make a new directory (`mkdir unbricked`), and then enter it (`cd unbricked`), just like you did for ["Hello, world!"](../part1/hello_world.md).
8+
9+
Start by creating a file called `main.asm`, and include `hardware.inc` in your code.
10+
11+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/getting-started/main.asm:includes}}
12+
{{#include ../../unbricked/getting-started/main.asm:includes}}
13+
```
14+
You may be wondering what purpose `hardware.inc` serves.
15+
Well, the code we write only really affects the CPU, but does not do anything with the rest of the console (not directly, anyway).
16+
To interact with other components (like the graphics system, say), [Memory-Mapped <abbr title="Input/Output">I/O</abbr>](https://en.wikipedia.org/wiki/Memory-mapped_I/O) (MMIO) is used: basically, [memory](../part1/memory.md) in a certain range (addresses $FF00–FF7F) does special things when accessed.
17+
18+
These bytes of memory being interfaces to the hardware, they are called *hardware registers* (not to be mistaken with [the CPU registers](../part1/registers.md)).
19+
For example, the "PPU status" register is located at address $FF41.
20+
Reading from that address reports various bits of info regarding the graphics system, and writing to it allows changing some parameters.
21+
But, having to remember all the numbers ([non-exhaustive list](https://gbdev.io/pandocs/Power_Up_Sequence.html#hardware-registers)) would be very tedious—and this is where `hardware.inc` comes into play!
22+
`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.
23+
24+
::: tip
25+
26+
Don't worry if this flew over your head, we'll see an example below with `rLCDC` and `LCDCF_ON`.
27+
28+
By the way, the `r` stands for "register", and the `F` in `LCDCF` stands for "flag".
29+
30+
:::
31+
32+
Next, make room for the header.
33+
[Remember from Part Ⅰ](../part1/header.md) 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.
34+
35+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/getting-started/main.asm:header}}
36+
{{#include ../../unbricked/getting-started/main.asm:header}}
37+
```
38+
39+
The header jumps to `EntryPoint`, so let's write that now:
40+
41+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/getting-started/main.asm:entry}}
42+
{{#include ../../unbricked/getting-started/main.asm:entry}}
43+
```
44+
45+
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).
46+
We'll explain what VBlank is and talk about it more later in the tutorial.
47+
48+
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.
49+
50+
Speaking of tiles, we're going to load some into VRAM next, using the following code:
51+
52+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/getting-started/main.asm:copy_tiles}}
53+
{{#include ../../unbricked/getting-started/main.asm:copy_tiles}}
54+
```
55+
56+
This loop might be [reminiscent of part Ⅰ](../part1/jumps.md#conditional-jumps).
57+
It copies starting at `Tiles` to `$9000` onwards, which is the part of VRAM where our [tiles](../part1/tiles.md) are going to be stored.
58+
Recall that `$9000` is where the data of background tile $00 lies, and the data of subsequent tiles follows right after.
59+
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.
60+
61+
That said, we haven't written `Tiles` nor any of the related data yet.
62+
We'll get to that later!
63+
64+
Almost done now—next, write another loop, this time for copying [the tilemap](../part1/tilemap.md).
65+
66+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/getting-started/main.asm:copy_map}}
67+
{{#include ../../unbricked/getting-started/main.asm:copy_map}}
68+
```
69+
70+
Note that while this loop's body is exactly the same as `CopyTiles`'s, the 3 values loaded into `de`, `hl`, and `bc` are different.
71+
These determine the source, destination, and size of the copy, respectively.
72+
73+
::: tip "[<abbr title="Don't Repeat Yourself">DRY</abbr>](https://en.wikipedia.org/wiki/Don't_Repeat_Yourself)"
74+
75+
If you think that this is super redundant, you are not wrong, and we will see later how to write actual, reusable *functions*.
76+
But there is more to them than meets the eye, so we will start tackling them much later.
77+
78+
:::
79+
80+
Finally, let's turn the screen back on, and set a [background palette](../part1/palettes.md).
81+
Rather than writing the non-descript number `%10000001` (or $81 or 129, to taste), we make use of two constants graciously provided by `hardware.inc`: `LCDCF_ON` and `LCDCF_BGON`.
82+
When written to [`rLCDC`](https://gbdev.io/pandocs/LCDC), the former causes the PPU and screen to turn back on, and the latter enables the background to be drawn.
83+
(There are other elements that could be drawn, but we are not enabling them yet.)
84+
Combining these constants must be done using `|`, the *binary "or"* operator; we'll see why later.
85+
86+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/getting-started/main.asm:end}}
87+
{{#include ../../unbricked/getting-started/main.asm:end}}
88+
```
89+
90+
There's one last thing we need before we can build the ROM, and that's the graphics.
91+
We will draw the following screen:
92+
93+
![Layout of unbricked](../assets/part2/img/tilemap.png)
94+
95+
In `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!
96+
This time, we will employ a more friendly way, which will let us write each row of pixels more easily.
97+
For each row of pixels, instead of writing [the bitplanes](../part1/tiles.md#encoding) directly, we will use a backtick (`` ` ``) followed by 8 characters.
98+
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](../part1/palettes.md).
99+
100+
::: tip
101+
102+
If the character selection isn't to your liking, you can use [RGBASM's `-g` option](https://rgbds.gbdev.io/docs/v0.5.2/rgbasm.1#g) or [`OPT g`](https://rgbds.gbdev.io/docs/v0.5.2/rgbasm.5/#Changing_options_while_assembling) to pick others.
103+
For example, `rgbasm -g '.xXO' (...)` or `OPT g.xXO` would swap the four characters to `.`, `x`, `X`, and `O` respectively.
104+
105+
:::
106+
107+
For example:
108+
109+
```rgbasm
110+
dw `01230123 ; This is equivalent to `db $55,$33`
111+
```
112+
113+
You may have noticed that we are using `dw` instead of `db`; the difference between these two will be explained later.
114+
We already have tiles made for this project, so you can copy [this premade file](../../unbricked/getting-started/tileset.asm), and paste it at the end of your code.
115+
116+
Then copy the tilemap from [this file](../../unbricked/getting-started/tilemap.asm), and paste it after the `TilesEnd` label.
117+
118+
You can build the ROM now, by running the following commands in your terminal:
119+
120+
```console
121+
$ rgbasm -L -o main.o main.asm
122+
$ rgblink -o unbricked.gb main.o
123+
$ rgbfix -v -p 0xFF unbricked.gb
124+
```
125+
126+
If you run this in your emulator, you should see the following:
127+
128+
![Screenshot of our game](../assets/part2/img/screenshot.png)
129+
130+
That white square seems to be missing!
131+
You may have noticed this comment earlier, somewhere in the tile data:
132+
133+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/getting-started/main.asm:custom_logo}}
134+
{{#include ../../unbricked/getting-started/main.asm:custom_logo}}
135+
```
136+
137+
The logo tiles were left intentionally blank so that you can choose your own.
138+
You can use one of the following pre-made logos, or try coming up with your own!
139+
140+
- **RGBDS Logo**
141+
142+
![The RGBDS Logo](../assets/part2/img/rgbds.png)
143+
144+
[Source](../../unbricked/getting-started/rgbds.asm)
145+
146+
- **Duck**
147+
148+
![A pixel-art duck](../assets/part2/img/duck.png)
149+
150+
[Source](../../unbricked/getting-started/duck.asm)
151+
152+
- **Tail**
153+
154+
![A silhouette of a tail](../assets/part2/img/tail.png)
155+
156+
[Source](../../unbricked/getting-started/tail.asm)
157+
158+
Replace the blank tiles with the new graphics, build the game again, and you should see your logo of choice in the bottom-right!

unbricked/2bpp2asm.c

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#include <stdio.h>
2+
#include <stdint.h>
3+
#include <stdlib.h>
4+
5+
int main(int argc, char ** argv) {
6+
if (argc != 4) {
7+
fprintf(stderr, "Expected 4 arguments\nUsage: %s <pixel chars> <input> <output>", argv[0]);
8+
exit(1);
9+
}
10+
11+
FILE * infile = fopen(argv[2], "rb");
12+
FILE * outfile = fopen(argv[3], "w");
13+
14+
if (!infile) {
15+
perror("infile");
16+
exit(1);
17+
}
18+
19+
if (!outfile) {
20+
perror("outfile");
21+
exit(1);
22+
}
23+
24+
char * pixels = argv[1];
25+
26+
while (1) {
27+
uint8_t light_row = fgetc(infile);
28+
uint8_t dark_row = fgetc(infile);
29+
if (feof(infile)) break;
30+
fputs("\tdw `", outfile);
31+
for (int i = 0; i < 8; i++) {
32+
uint8_t shade = (light_row & 0x80) >> 7 | (dark_row & 0x80) >> 6;
33+
fputc(pixels[shade], outfile);
34+
light_row <<= 1;
35+
dark_row <<= 1;
36+
}
37+
fputc('\n', outfile);
38+
}
39+
}

unbricked/getting-started/duck.asm

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
dw `22222222
2+
dw `22222222
3+
dw `22222222
4+
dw `22222222
5+
dw `22222222
6+
dw `22222211
7+
dw `22222211
8+
dw `22222211
9+
dw `22222222
10+
dw `22222222
11+
dw `22222222
12+
dw `11111111
13+
dw `11111111
14+
dw `11221111
15+
dw `11221111
16+
dw `11000011
17+
dw `22222222
18+
dw `22222222
19+
dw `22222222
20+
dw `22222222
21+
dw `22222222
22+
dw `11222222
23+
dw `11222222
24+
dw `11222222
25+
dw `22222222
26+
dw `22222222
27+
dw `22222222
28+
dw `22222222
29+
dw `22222222
30+
dw `22222222
31+
dw `22222222
32+
dw `22222222
33+
dw `22222211
34+
dw `22222200
35+
dw `22222200
36+
dw `22000000
37+
dw `22000000
38+
dw `22222222
39+
dw `22222222
40+
dw `22222222
41+
dw `11000011
42+
dw `11111111
43+
dw `11111111
44+
dw `11111111
45+
dw `11111111
46+
dw `11111111
47+
dw `11111111
48+
dw `11000022
49+
dw `11222222
50+
dw `11222222
51+
dw `11222222
52+
dw `22222222
53+
dw `22222222
54+
dw `22222222
55+
dw `22222222
56+
dw `22222222
57+
dw `22222222
58+
dw `22222222
59+
dw `22222222
60+
dw `22222222
61+
dw `22222222
62+
dw `22222222
63+
dw `22222222
64+
dw `22222222
65+
dw `22222222
66+
dw `22222200
67+
dw `22222200
68+
dw `22222211
69+
dw `22222211
70+
dw `22221111
71+
dw `22221111
72+
dw `22221111
73+
dw `11000022
74+
dw `00112222
75+
dw `00112222
76+
dw `11112200
77+
dw `11112200
78+
dw `11220000
79+
dw `11220000
80+
dw `11220000
81+
dw `22222222
82+
dw `22222222
83+
dw `22222222
84+
dw `22000000
85+
dw `22000000
86+
dw `00000000
87+
dw `00000000
88+
dw `00000000
89+
dw `22222222
90+
dw `22222222
91+
dw `22222222
92+
dw `22222222
93+
dw `22222222
94+
dw `11110022
95+
dw `11110022
96+
dw `11110022
97+
dw `22221111
98+
dw `22221111
99+
dw `22221111
100+
dw `22221111
101+
dw `22221111
102+
dw `22222211
103+
dw `22222211
104+
dw `22222222
105+
dw `11220000
106+
dw `11110000
107+
dw `11110000
108+
dw `11111111
109+
dw `11111111
110+
dw `11111111
111+
dw `11111111
112+
dw `22222222
113+
dw `00000000
114+
dw `00111111
115+
dw `00111111
116+
dw `11111111
117+
dw `11111111
118+
dw `11111111
119+
dw `11111111
120+
dw `22222222
121+
dw `11110022
122+
dw `11000022
123+
dw `11000022
124+
dw `00002222
125+
dw `00002222
126+
dw `00222222
127+
dw `00222222
128+
dw `22222222

0 commit comments

Comments
 (0)