-
-
Notifications
You must be signed in to change notification settings - Fork 46
Add getting-started to part 2 #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
e4d55eb
Add getting-started to part 2
evie-calico 3f84b6a
Apply suggestions from review
evie-calico 7ad5c95
Apply suggestions from code review
evie-calico 3ab14c9
Add suggestions from review
evie-calico 1eb14fc
Include code in part 2
evie-calico 705fbe7
Update src/part2/getting-started.md
evie-calico 64de276
Fix broken links
ISSOtm cc5464b
Add link to explain what DRY is
ISSOtm 1232301
Make backtick character stand out more
ISSOtm 5fdee28
Move Unbricked source code to dedicated directory
ISSOtm 55bc01f
Add link to MMIO on wikipedia
evie-calico 63ec34e
Clarify the meaning of 000
evie-calico 74d8f10
Merge origin
evie-calico 188d2c3
Correct missing closing :::
evie-calico 32e4465
Add missing assets
evie-calico 05985ca
Update references to local assets
evie-calico e5050ae
Add tileset.asm and tilemap.asm
evie-calico 537c308
Apply suggestions from code review
evie-calico 3cc665a
Apply suggestions from code review
ISSOtm d3ca446
Fix broken links
ISSOtm File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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
This file contains hidden or 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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
# Getting started | ||
|
||
In this lesson, we will start a new project from scratch. | ||
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"! | ||
(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!"](../part1/hello_world.md). | ||
|
||
Start by creating a file called `main.asm`, and include `hardware.inc` in your code. | ||
|
||
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/getting-started/main.asm:includes}} | ||
{{#include ../../unbricked/getting-started/main.asm:includes}} | ||
``` | ||
You may be wondering what purpose `hardware.inc` serves. | ||
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 <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. | ||
|
||
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)). | ||
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](https://gbdev.io/pandocs/Power_Up_Sequence.html#hardware-registers)) 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. | ||
|
||
::: tip | ||
|
||
Don't worry if this flew over your head, we'll see an example below with `rLCDC` and `LCDCF_ON`. | ||
|
||
By the way, the `r` stands for "register", and the `F` in `LCDCF` stands for "flag". | ||
|
||
::: | ||
|
||
Next, make room for the header. | ||
[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. | ||
|
||
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/getting-started/main.asm:header}} | ||
{{#include ../../unbricked/getting-started/main.asm:header}} | ||
``` | ||
|
||
The header jumps to `EntryPoint`, so let's write that now: | ||
|
||
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/getting-started/main.asm:entry}} | ||
{{#include ../../unbricked/getting-started/main.asm:entry}} | ||
``` | ||
|
||
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: | ||
evie-calico marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/getting-started/main.asm:copy_tiles}} | ||
{{#include ../../unbricked/getting-started/main.asm:copy_tiles}} | ||
``` | ||
|
||
This loop might be [reminiscent of part Ⅰ](../part1/jumps.md#conditional-jumps). | ||
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. | ||
Recall that `$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](../part1/tilemap.md). | ||
|
||
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/getting-started/main.asm:copy_map}} | ||
{{#include ../../unbricked/getting-started/main.asm:copy_map}} | ||
``` | ||
|
||
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. | ||
These determine the source, destination, and size of the copy, respectively. | ||
|
||
evie-calico marked this conversation as resolved.
Show resolved
Hide resolved
|
||
::: tip "[<abbr title="Don't Repeat Yourself">DRY</abbr>](https://en.wikipedia.org/wiki/Don't_Repeat_Yourself)" | ||
|
||
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](../part1/palettes.md). | ||
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`. | ||
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. | ||
(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. | ||
|
||
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/getting-started/main.asm:end}} | ||
{{#include ../../unbricked/getting-started/main.asm:end}} | ||
``` | ||
|
||
There's one last thing we need before we can build the ROM, and that's the graphics. | ||
We will draw the following screen: | ||
|
||
 | ||
|
||
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! | ||
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](../part1/tiles.md#encoding) 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](../part1/palettes.md). | ||
|
||
::: tip | ||
|
||
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. | ||
For example, `rgbasm -g '.xXO' (...)` or `OPT g.xXO` would swap the four characters to `.`, `x`, `X`, and `O` respectively. | ||
|
||
::: | ||
|
||
For example: | ||
|
||
```rgbasm | ||
dw `01230123 ; This is equivalent to `db $55,$33` | ||
``` | ||
|
||
evie-calico marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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](../../unbricked/getting-started/tileset.asm), and paste it at the end of your code. | ||
|
||
Then copy the tilemap from [this file](../../unbricked/getting-started/tilemap.asm), and paste it after the `TilesEnd` label. | ||
|
||
You can build the ROM now, by running the following commands in your terminal: | ||
|
||
```console | ||
$ 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: | ||
|
||
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/getting-started/main.asm:custom_logo}} | ||
{{#include ../../unbricked/getting-started/main.asm:custom_logo}} | ||
``` | ||
|
||
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! | ||
|
||
- **RGBDS Logo** | ||
|
||
 | ||
|
||
[Source](../../unbricked/getting-started/rgbds.asm) | ||
|
||
- **Duck** | ||
|
||
 | ||
|
||
[Source](../../unbricked/getting-started/duck.asm) | ||
|
||
- **Tail** | ||
|
||
 | ||
|
||
[Source](../../unbricked/getting-started/tail.asm) | ||
|
||
Replace the blank tiles with the new graphics, build the game again, and you should see your logo of choice in the bottom-right! |
This file contains hidden or 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
#include <stdio.h> | ||
#include <stdint.h> | ||
#include <stdlib.h> | ||
|
||
int main(int argc, char ** argv) { | ||
if (argc != 4) { | ||
fprintf(stderr, "Expected 4 arguments\nUsage: %s <pixel chars> <input> <output>", argv[0]); | ||
exit(1); | ||
} | ||
|
||
FILE * infile = fopen(argv[2], "rb"); | ||
FILE * outfile = fopen(argv[3], "w"); | ||
|
||
if (!infile) { | ||
perror("infile"); | ||
exit(1); | ||
} | ||
|
||
if (!outfile) { | ||
perror("outfile"); | ||
exit(1); | ||
} | ||
|
||
char * pixels = argv[1]; | ||
|
||
while (1) { | ||
uint8_t light_row = fgetc(infile); | ||
uint8_t dark_row = fgetc(infile); | ||
if (feof(infile)) break; | ||
fputs("\tdw `", outfile); | ||
for (int i = 0; i < 8; i++) { | ||
uint8_t shade = (light_row & 0x80) >> 7 | (dark_row & 0x80) >> 6; | ||
fputc(pixels[shade], outfile); | ||
light_row <<= 1; | ||
dark_row <<= 1; | ||
} | ||
fputc('\n', outfile); | ||
} | ||
} |
This file contains hidden or 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222211 | ||
dw `22222211 | ||
dw `22222211 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `11111111 | ||
dw `11111111 | ||
dw `11221111 | ||
dw `11221111 | ||
dw `11000011 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `11222222 | ||
dw `11222222 | ||
dw `11222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222211 | ||
dw `22222200 | ||
dw `22222200 | ||
dw `22000000 | ||
dw `22000000 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `11000011 | ||
dw `11111111 | ||
dw `11111111 | ||
dw `11111111 | ||
dw `11111111 | ||
dw `11111111 | ||
dw `11111111 | ||
dw `11000022 | ||
dw `11222222 | ||
dw `11222222 | ||
dw `11222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222200 | ||
dw `22222200 | ||
dw `22222211 | ||
dw `22222211 | ||
dw `22221111 | ||
dw `22221111 | ||
dw `22221111 | ||
dw `11000022 | ||
dw `00112222 | ||
dw `00112222 | ||
dw `11112200 | ||
dw `11112200 | ||
dw `11220000 | ||
dw `11220000 | ||
dw `11220000 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22000000 | ||
dw `22000000 | ||
dw `00000000 | ||
dw `00000000 | ||
dw `00000000 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `22222222 | ||
dw `11110022 | ||
dw `11110022 | ||
dw `11110022 | ||
dw `22221111 | ||
dw `22221111 | ||
dw `22221111 | ||
dw `22221111 | ||
dw `22221111 | ||
dw `22222211 | ||
dw `22222211 | ||
dw `22222222 | ||
dw `11220000 | ||
dw `11110000 | ||
dw `11110000 | ||
dw `11111111 | ||
dw `11111111 | ||
dw `11111111 | ||
dw `11111111 | ||
dw `22222222 | ||
dw `00000000 | ||
dw `00111111 | ||
dw `00111111 | ||
dw `11111111 | ||
dw `11111111 | ||
dw `11111111 | ||
dw `11111111 | ||
dw `22222222 | ||
dw `11110022 | ||
dw `11000022 | ||
dw `11000022 | ||
dw `00002222 | ||
dw `00002222 | ||
dw `00222222 | ||
dw `00222222 | ||
dw `22222222 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.