Skip to content

Commit 1bf6037

Browse files
evie-calicoISSOtmavivace
authored
Add Chapter 2 Lesson 2 (#31)
Co-authored-by: Eldred Habert <eldredhabert0@gmail.com> Co-authored-by: Antonio Vivace <avivace4@gmail.com>
1 parent 78d294a commit 1bf6037

5 files changed

Lines changed: 530 additions & 4 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
/book/
22
/target/
3+
.DS_Store

src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
# Part Ⅱ — Our first game
2525

2626
- [Getting started](part2/getting-started.md)
27+
- [Objects](part2/objects.md)
2728
- [Work in progress](part2/wip.md)
2829

2930
# Part Ⅲ — Our second game

src/part2/objects.md

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# Objects
2+
3+
The background is very useful when the whole screen should move at once, but this is not ideal for everything.
4+
For example, a cursor in a menu, NPCs and the player in a RPG, bullets in a shmup, or balls in an *Arkanoid* clone... all need to move independently of the background.
5+
Thankfully, the Game Boy has a feature that's perfect for these!
6+
In this lesson, we will talk about *objects* (sometimes called "OBJ").
7+
8+
::: tip
9+
10+
The above description may have made you think of the term "sprite" instead of "object".
11+
The term "sprite" has a *lot* of meanings depending on context, so, to avoid confusion, this tutorial tries to use specific alternatives instead, such as *object*, *metasprite*, *actor*, etc.
12+
13+
:::
14+
15+
Each object allows drawing one or two tiles (so 8×8 or 8×16 pixels, respectively) at any on-screen position—unlike the background, where all the tiles are drawn in a grid.
16+
Therefore, an object consists of its on-screen position, a tile ID (like [with the tilemap](../part1/tilemap.md)), and some extra properties called "attributes".
17+
These extra properties allow, for example, to display the tile flipped.
18+
We'll see more about them later.
19+
20+
Just like how the tilemap is stored in VRAM, objects live in a region of memory called OAM, meaning **Object Attribute Memory**.
21+
Recall from above that an object consists of:
22+
- Its on-screen position
23+
- A tile ID
24+
- The "attributes"
25+
26+
These are stored in 4 bytes: one for the Y coordinate, one for the X coordinate, one for the tile ID, and one for the attributes.
27+
OAM is 160 bytes long, and since 160 ∕ 4 = 40, the Game Boy stores a total of **40** objects at any given time.
28+
29+
There is a catch, though: an object's Y and X coordinate bytes in OAM do *not* store its on-screen position!
30+
Instead, the *on-screen* X position is the *stored* X position **minus 8**, and the *on-screen* Y position is the *stored* Y position **minus 16**.
31+
To stop displaying an object, we can simply put it off-screen, e.g. by setting its Y position to 0.
32+
33+
::: tip
34+
35+
These offsets are not arbitrary!
36+
Consider an object's maximum size: 8 by 16 pixels.
37+
These offsets allow objects to be clipped by the left and top edges of the screen.
38+
The NES, for example, lacks such offsets, so you will notice that objects always disappear after hitting the left or top edge of the screen.
39+
40+
:::
41+
42+
Let's discover objects by experimenting with them!
43+
44+
First off, when the Game Boy is powered on, OAM is filled with a bunch of semi-random values, which may cover the screen with some random garbage.
45+
Let's fix that by first clearing OAM before enabling objects for the first time.
46+
Let's add the following just after the `CopyTilemap` loop:
47+
48+
```rgbasm
49+
{{#include ../../unbricked/objects/main.asm:clear-oam}}
50+
```
51+
52+
This is a good time to do that, since just like VRAM, the screen must be off to safely access OAM.
53+
54+
Once OAM is clear, we can draw an object by writing its properties.
55+
56+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/objects/main.asm:init-object}}
57+
{{#include ../../unbricked/objects/main.asm:init-object}}
58+
```
59+
60+
Remember that each object in OAM is 4 bytes, in the order Y, X, Tile ID, Attributes.
61+
So, the object's top-left pixel lies 128 pixels from the top of the screen, and 16 from its left.
62+
The tile ID and attributes are both set to 0.
63+
64+
You may remember from the previous lesson that we're already using tile ID 0, as it's the start of our background's graphics.
65+
However, by default objects and backgrounds use a different set of tiles, at least for the first 128 IDs.
66+
Tiles with IDs 128–255 are shared by both, which is useful if you have a tile that's used both on the background and by an object.
67+
68+
If you press F5 in BGB to open the VRAM viewer, you should see three distinct sections.
69+
70+
![image](https://user-images.githubusercontent.com/14899090/196176886-8ede7369-c172-45fa-9128-cc238c15b1e8.png)
71+
72+
Because we need to load this to a different area, we'll use the address $8000 and load a graphic for our game's paddle.
73+
Let's do so right after `CopyTilemap`:
74+
75+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/objects/main.asm:copy-paddle}}
76+
{{#include ../../unbricked/objects/main.asm:copy-paddle}}
77+
```
78+
79+
And don't forget to add `Paddle` to the bottom of your code.
80+
81+
```rgbasm
82+
{{#include ../../unbricked/objects/main.asm:paddle-gfx}}
83+
```
84+
85+
Finally, let's enable objects and see the result.
86+
Objects must be enabled by the familiar `rLCDC` register, otherwise they just don't show up.
87+
(This is why we didn't have to clear OAM in the previous lessons.)
88+
We will also need to initialize one of the object palettes, `rOBP0`.
89+
There are actually two object palettes, but we're only going to use one.
90+
91+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/objects/main.asm:enable-oam}}
92+
{{#include ../../unbricked/objects/main.asm:enable-oam}}
93+
```
94+
95+
## Movement
96+
97+
Now that you have an object on the screen, let's move it around.
98+
Previously, the `Done` loop did nothing; let's rename it to `Main` and use it to move our object.
99+
We're going to wait for VBlank before changing OAM, just like we did before turning off the screen.
100+
101+
```rgbasm,linenos,start={{#line_no_of "^Main:" ../../unbricked/objects/main.asm}}
102+
Main:
103+
; Wait until it's *not* VBlank
104+
ld a, [rLY]
105+
cp 144
106+
jr nc, Main
107+
WaitVBlank2:
108+
ld a, [rLY]
109+
cp 144
110+
jp c, Main
111+
112+
; Move the paddle one pixel to the right.
113+
ld a, [_OAMRAM + 1]
114+
inc a
115+
ld [_OAMRAM + 1], a
116+
jp Main
117+
```
118+
119+
::: tip:🤨
120+
121+
Here, we are accessing OAM without turning the LCD off, but it's still safe.
122+
Explaining why requires a more thorough explanation of the Game Boy's rendering, so let's ignore it for now.
123+
124+
:::
125+
126+
Now you should see the paddle moving... very quickly.
127+
Because it moves by a pixel ever frame, it's going at a speed of 60 pixels per second!
128+
To slow this down, we'll use a *variable*.
129+
130+
So far, we have only worked with the CPU registers, but you can create global variables too!
131+
To do this, let's create another section, but putting it in `WRAM0` instead of `ROM0`.
132+
Unlike ROM ("Read-Only Memory"), RAM ("Random-Access Memory") can be written to; thus, WRAM, or Work RAM, is where we can store our game's variables.
133+
134+
Add this to the bottom of your file:
135+
136+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/objects/main.asm:variables}}
137+
{{#include ../../unbricked/objects/main.asm:variables}}
138+
```
139+
140+
Now we'll use the `wFrameCounter` variable to count how many frames have passed since we last moved the paddle.
141+
Every 10th frame, we'll move the paddle by one pixel, slowing it down to 6 pixels per second.
142+
Don't forget that RAM is filled with garbage values when the Game Boy starts, so we need to initialize our variables before first using them.
143+
144+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/objects/main.asm:main-loop}}
145+
{{#include ../../unbricked/objects/main.asm:main-loop}}
146+
```
147+
148+
Alright!
149+
Up next is us taking control of that little paddle.

unbricked/getting-started/main.asm

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ EntryPoint:
1818
WaitVBlank:
1919
ld a, [rLY]
2020
cp 144
21-
jr c, WaitVBlank
21+
jp c, WaitVBlank
2222

2323
; Turn the LCD off
2424
ld a, 0
@@ -37,7 +37,7 @@ CopyTiles:
3737
dec bc
3838
ld a, b
3939
or a, c
40-
jr nz, CopyTiles
40+
jp nz, CopyTiles
4141
; ANCHOR_END: copy_tiles
4242

4343
; ANCHOR: copy_map
@@ -52,7 +52,7 @@ CopyTilemap:
5252
dec bc
5353
ld a, b
5454
or a, c
55-
jr nz, CopyTilemap
55+
jp nz, CopyTilemap
5656
; ANCHOR_END: copy_map
5757

5858
; ANCHOR: end
@@ -65,7 +65,7 @@ CopyTilemap:
6565
ld [rBGP], a
6666

6767
Done:
68-
jr Done
68+
jp Done
6969
; ANCHOR_END: end
7070

7171
Tiles:

0 commit comments

Comments
 (0)