|
| 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 | + |
| 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. |
0 commit comments