Skip to content

Commit c354e01

Browse files
evie-calicoISSOtmavivace
authored
Part II, Lesson 4 (#37)
Co-authored-by: Eldred Habert <eldredhabert0@gmail.com> Co-authored-by: Antonio Vivace <avivace4@gmail.com>
1 parent f7bd782 commit c354e01

5 files changed

Lines changed: 856 additions & 0 deletions

File tree

src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
- [Objects](part2/objects.md)
2828
- [Functions](part2/functions.md)
2929
- [Input](part2/input.md)
30+
- [Collision](part2/collision.md)
3031
- [Work in progress](part2/wip.md)
3132

3233
# Part Ⅲ — Our second game

src/assets/part2/img/ball.png

685 Bytes
Loading

src/assets/part2/img/paddle.png

5.09 KB
Loading

src/part2/collision.md

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
# Collision
2+
3+
Being able to move around is great, but there's still one object we need for this game: a ball!
4+
Just like with the paddle, the first step is to create a tile for the ball and load it into VRAM.
5+
6+
## Graphics
7+
8+
Add this to the bottom of your file along with the other graphics:
9+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/collision/main.asm:ball-sprite}}
10+
{{#include ../../unbricked/collision/main.asm:ball-sprite}}
11+
```
12+
13+
Now copy it to VRAM somewhere in your initialization code, e.g. after copying the paddle's tile.
14+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/collision/main.asm:ball-copy}}
15+
{{#include ../../unbricked/collision/main.asm:ball-copy}}
16+
```
17+
18+
In addition, we need to initialize an entry in OAM, following the code that initializes the paddle.
19+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/collision/main.asm:oam}}
20+
{{#include ../../unbricked/collision/main.asm:oam}}
21+
```
22+
23+
As the ball bounces around the screen its momentum will change, sending it in different directions.
24+
Let's create two new variables to track the ball's momentum in each axis: `wBallMomentumX` and `wBallMomentumY`.
25+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/collision/main.asm:ram}}
26+
{{#include ../../unbricked/collision/main.asm:ram}}
27+
```
28+
29+
We will need to initialize these before entering the game loop, so let's do so right after we write the ball to OAM.
30+
By setting the X momentum to 1, and the Y momentum to -1, the ball will start out by going up and to the right.
31+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/collision/main.asm:init}}
32+
{{#include ../../unbricked/collision/main.asm:init}}
33+
```
34+
35+
## Prep work
36+
37+
Now for the fun part!
38+
Add a bit of code at the beginning of your main loop that adds the momentum to the OAM positions.
39+
Notice that since this is the second OAM entry, we use `+ 4` for Y and `+ 5` for X.
40+
This can get pretty confusing, but luckily we only have two objects to keep track of.
41+
In the future, we'll go over a much easier way to use OAM.
42+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/collision/main.asm:momentum}}
43+
{{#include ../../unbricked/collision/main.asm:momentum}}
44+
```
45+
46+
You might want to compile your game again to see what this does.
47+
If you do, you should see the ball moving around, but it will just go through the walls and then fly offscreen.
48+
49+
To fix this, we need to add collision detection so that the ball can bounce around.
50+
We'll need to repeat the collision check a few times, so we're going to make use of two functions to do this.
51+
52+
::: tip
53+
54+
Please do not get stuck on the details of this next function, as it uses some techniques and instructions we haven't discussed yet.
55+
The basic idea is that it converts the position of the sprite to a location on the tilemap.
56+
This way, we can check which tile our ball is touching so that we know when to bounce!
57+
58+
:::
59+
60+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/collision/main.asm:get-tile}}
61+
{{#include ../../unbricked/collision/main.asm:get-tile}}
62+
```
63+
64+
The next function is called `IsWallTile`, and it's going to contain a list of tiles which the ball can bounce off of.
65+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/collision/main.asm:is-wall-tile}}
66+
{{#include ../../unbricked/collision/main.asm:is-wall-tile}}
67+
```
68+
69+
This function might look a bit strange at first.
70+
Instead of returning its result in a *register*, like `a`, it returns it in [a *flag*](../part1/operations.md#flags): `Z`!
71+
If at any point a tile matches, the function has found a wall and exits with `Z` set.
72+
If the target tile ID (in `a`) matches one of the wall tile IDs, the corresponding `cp` will leave `Z` set; if so, we return immediately (via `ret z`), with `Z` set.
73+
But if we reach the last comparison and it still doesn't set `Z`, then we will know that we haven't hit a wall and don't need to bounce.
74+
75+
## Putting it together
76+
77+
Time to use these new functions to add collision detection!
78+
Add the following after the code that updates the ball's position:
79+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/collision/main.asm:first-tile-collision}}
80+
{{#include ../../unbricked/collision/main.asm:first-tile-collision}}
81+
```
82+
83+
You'll see that when we load the sprite's positions, we subtract from them before calling `GetTileByPixel`.
84+
You might remember from the last chapter that OAM positions are slightly offset; that is, (0, 0) in OAM is actually completely offscreen.
85+
These `sub` instructions undo this offset.
86+
87+
However, there's a bit more to this: you might have noticed that we subtracted an extra pixel from the Y position.
88+
That's because (as the label suggests), this code is checking for a tile above the ball.
89+
We actually need to check *all four* sides of the ball so we know how to change the momentum according to which side collided, so... let's add the rest!
90+
91+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/collision/main.asm:tile-collision}}
92+
{{#include ../../unbricked/collision/main.asm:tile-collision}}
93+
```
94+
95+
That was a lot, but now the ball bounces around your screen!
96+
There's just one last thing to do before this chapter is over, and thats ball-to-paddle collision.
97+
98+
## Paddle bounce
99+
100+
Unlike with the tilemap, there's no position conversions to do here, just straight comparisons.
101+
However, for these, we will need [the *carry* flag](../part1/operations.md#flags).
102+
The carry flag is notated as `C`, like how the zero flag is notated as `Z`, but don't confuse it with the `c` register!
103+
104+
::: tip A refresher on comparisons
105+
106+
Just like `Z`, you can use the carry flag to jump conditionally.
107+
However, while `Z` is used to check if two numbers are equal, `C` can be used to check if a number is greater than or smaller than another one.
108+
For example, `cp a, b` sets `C` if `a < b`, and clears it if `a >= b`.
109+
(If you want to check `a <= b` or `a > b`, you can use `Z` and `C` in tandem with two `jp` instructions.)
110+
111+
:::
112+
113+
Armed with this knowledge, let's work through the paddle bounce code:
114+
```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/collision/main.asm:paddle-bounce}}
115+
{{#include ../../unbricked/collision/main.asm:paddle-bounce}}
116+
```
117+
118+
The Y position's check is simple, since our paddle is flat.
119+
However, the X position has two checks which widen the area the ball can bounce on.
120+
First we add 16 to the ball's position; if the ball is more than 16 pixels to the right of the paddle, it shouldn't bounce.
121+
Then we undo this by subtracting 16, and while we're at it, subtract another 8 pixels; if the ball is more than 8 pixels to the left of the paddle, it shouldn't bounce.
122+
123+
<svg viewBox="-10 -10 860 520">
124+
<style>
125+
text { text-anchor: middle; fill: var(--fg); font-size: 20px; }
126+
.left { text-anchor: start; }
127+
.right { text-anchor: end; }
128+
.grid { stroke: var(--fg); opacity: 0.7; }
129+
.ball { stroke: teal; }
130+
.paddle { stroke: orange; }
131+
.excl { stroke: red; } text.excl { stroke: initial; fill: red; font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important; }
132+
/* Overlays */
133+
rect, polyline { opacity: 0.5; stroke-width: 3; }
134+
/* Arrow */
135+
polygon { stroke: inherit; fill: var(--bg); }
136+
use + line { stroke-dasharray: 0 32 999; stroke-width: 2; }
137+
</style>
138+
<defs>
139+
<polygon id="arrow-head" points="0,0 -40,-16 -32,0 -40,16" stroke="context-stroke"/>
140+
<pattern id="ball-hatched" viewBox="0 0 4 4" width="8" height="8" patternUnits="userSpaceOnUse">
141+
<line x1="5" y1="-1" x2="-1" y2="5" class="ball"/>
142+
<line x1="5" y1="3" x2="3" y2="5" class="ball"/>
143+
<line x1="1" y1="-1" x2="-1" y2="1" class="ball"/>
144+
</pattern>
145+
<pattern id="paddle-hatched" viewBox="0 0 4 4" width="8" height="8" patternUnits="userSpaceOnUse">
146+
<line x1="5" y1="-1" x2="-1" y2="5" class="paddle"/>
147+
<line x1="5" y1="3" x2="3" y2="5" class="paddle"/>
148+
<line x1="1" y1="-1" x2="-1" y2="1" class="paddle"/>
149+
</pattern>
150+
<pattern id="excl-hatched" viewBox="0 0 4 4" width="8" height="8" patternUnits="userSpaceOnUse">
151+
<line x1="5" y1="-1" x2="-1" y2="5" class="excl"/>
152+
<line x1="5" y1="3" x2="3" y2="5" class="excl"/>
153+
<line x1="1" y1="-1" x2="-1" y2="1" class="excl"/>
154+
</pattern>
155+
</defs>
156+
<image x="128" y="0" width="256" height="256" href="../assets/part2/img/ball.png"/>
157+
<rect x="128" y="0" width="32" height="32" fill="url(#ball-hatched)"/>
158+
<image x="288" y="256" width="256" height="256" href="../assets/part2/img/paddle.png"/>
159+
<rect x="288" y="256" width="32" height="32" fill="url(#paddle-hatched)"/>
160+
<line class="grid" x1="-10" y1="0" x2="850" y2="0"/>
161+
<line class="grid" x1="-10" y1="32" x2="850" y2="32"/>
162+
<line class="grid" x1="-10" y1="64" x2="850" y2="64"/>
163+
<line class="grid" x1="-10" y1="96" x2="850" y2="96"/>
164+
<line class="grid" x1="-10" y1="128" x2="850" y2="128"/>
165+
<line class="grid" x1="-10" y1="160" x2="850" y2="160"/>
166+
<line class="grid" x1="-10" y1="192" x2="850" y2="192"/>
167+
<line class="grid" x1="-10" y1="224" x2="850" y2="224"/>
168+
<line class="grid" x1="-10" y1="256" x2="850" y2="256"/>
169+
<line class="grid" x1="-10" y1="288" x2="850" y2="288"/>
170+
<line class="grid" x1="-10" y1="320" x2="850" y2="320"/>
171+
<line class="grid" x1="-10" y1="352" x2="850" y2="352"/>
172+
<line class="grid" x1="0" y1="-20" x2="0" y2="351"/>
173+
<line class="grid" x1="32" y1="-20" x2="32" y2="351"/>
174+
<line class="grid" x1="64" y1="-20" x2="64" y2="351"/>
175+
<line class="grid" x1="96" y1="-20" x2="96" y2="351"/>
176+
<line class="grid" x1="128" y1="-20" x2="128" y2="351"/>
177+
<line class="grid" x1="160" y1="-20" x2="160" y2="351"/>
178+
<line class="grid" x1="192" y1="-20" x2="192" y2="351"/>
179+
<line class="grid" x1="224" y1="-20" x2="224" y2="351"/>
180+
<line class="grid" x1="256" y1="-20" x2="256" y2="351"/>
181+
<line class="grid" x1="288" y1="-20" x2="288" y2="351"/>
182+
<line class="grid" x1="320" y1="-20" x2="320" y2="351"/>
183+
<line class="grid" x1="352" y1="-20" x2="352" y2="351"/>
184+
<line class="grid" x1="384" y1="-20" x2="384" y2="351"/>
185+
<line class="grid" x1="416" y1="-20" x2="416" y2="351"/>
186+
<line class="grid" x1="448" y1="-20" x2="448" y2="351"/>
187+
<line class="grid" x1="480" y1="-20" x2="480" y2="351"/>
188+
<line class="grid" x1="512" y1="-20" x2="512" y2="351"/>
189+
<line class="grid" x1="544" y1="-20" x2="544" y2="351"/>
190+
<line class="grid" x1="576" y1="-20" x2="576" y2="351"/>
191+
<line class="grid" x1="608" y1="-20" x2="608" y2="351"/>
192+
<line class="grid" x1="640" y1="-20" x2="640" y2="351"/>
193+
<line class="grid" x1="672" y1="-20" x2="672" y2="351"/>
194+
<line class="grid" x1="704" y1="-20" x2="704" y2="351"/>
195+
<line class="grid" x1="736" y1="-20" x2="736" y2="351"/>
196+
<line class="grid" x1="768" y1="-20" x2="768" y2="351"/>
197+
<line class="grid" x1="800" y1="-20" x2="800" y2="351"/>
198+
<line class="grid" x1="832" y1="-20" x2="832" y2="351"/>
199+
<rect x="128" y="0" width="256" height="256" class="ball" style="fill: none;"/>
200+
<polyline points="288,352 288,256 544,256 544,352" class="paddle" style="fill: none;"/>
201+
<rect x="-15" y="-15" width="47" height="440" class="excl" fill="url(#excl-hatched)"/>
202+
<text x="40" y="430" class="excl left">jp c, DoNotBounce</text>
203+
<rect x="800" y="-15" width="52" height="510" class="excl" fill="url(#excl-hatched)"/>
204+
<text x="790" y="500" class="excl right">jp nc, DoNotBounce</text>
205+
<use href="#arrow-head" x="48" y="380" transform="rotate(-180,48,380)" class="paddle"/><line x1="48" y1="380" x2="304" y2="380" class="paddle"/>
206+
<text x="176" y="400">- 8</text>
207+
<use href="#arrow-head" x="304" y="450" class="paddle"/><line x1="304" y1="450" x2="48" y2="450" class="paddle"/>
208+
<use href="#arrow-head" x="816" y="450" class="paddle"/><line x1="816" y1="450" x2="304" y2="450" class="paddle"/>
209+
<text x="432" y="470">+ 8 + 16</text>
210+
</svg>
211+
212+
::: tip Paddle width
213+
214+
You might be wondering why we checked 16 pixels to the right but only 8 pixels to the left.
215+
Remember that OAM positions represent the upper-*left* corner of a sprite, so the center of our paddle is actually 4 pixels to the right of the position in OAM.
216+
When you consider this, we're actually checking 12 pixels out on either side from the center of the paddle.
217+
218+
12 pixels might seem like a lot, but it gives some tolerance to the player in case their positioning is off.
219+
If you'd prefer to make this easier or more difficult, feel free to adjust the values!
220+
221+
:::
222+
223+
## BONUS: tweaking the bounce height
224+
225+
You might notice that the ball seems to "sink" into the paddle a bit before bouncing. This is because the ball bounces when its top row of pixels aligns with the paddle's top row (see the image above). If you want, try to adjust this so that the ball bounces when its bottom row of pixels touches the paddle's top.
226+
227+
Hint: you can do this with just a single instruction!
228+
229+
<details><summary>Answer:</summary>
230+
231+
```diff linenos,start={{#line_no_of "" ../../unbricked/collision/main.asm:paddle-bounce}}
232+
ld a, [_OAMRAM]
233+
ld b, a
234+
ld a, [_OAMRAM + 4]
235+
+ sub a, 6
236+
cp a, b
237+
```
238+
239+
Alternatively, you can add `add a, 6` just after `ld a, [_OAMRAM]`.
240+
241+
In both cases, try playing with that `6` value; see what feels right!
242+
243+
</details>

0 commit comments

Comments
 (0)