How Menus Work ‐ Part 1 - pret/pokeemerald GitHub Wiki

Game Freak didn't want to have to directly manipulate the GBA's graphics at the hardware level in order to display their various menus and dialog boxes, so they wrote a large amount of code to try and move that stuff out of view and build things at a higher level of abstraction. However, they couldn't fully escape the GBA's hardware limitations. It takes at least a basic familiarity with GBA graphics programming in order to build menus without running headfirst into those limitations.

Here, we'll be focusing on how the GBA handles background layers and screen effects, as these are the graphics features you'll be working with the most when building menus. A lot of the game's menus don't use any sprites at all; just background layers.

Background layers

The Game Boy Advance has four background layers and one object layer. The object layer is used for sprites, and the background layer is generally used for tiled graphics. The GBA can use up to sixteen palettes at a time for the background, each of which can have sixteen colors.

When you hear the word "tile," you likely think of the 16x16px grid cells that the overworld and its maps are divided into; these are the basic unit of measure for movement. In reality, however, tiles within the GBA's hardware are 8x8px. The overworld's 16x16px map tiles are displayed on two of the GBA's background layers, with four background tiles on each layer arranged to form a larger 16x16px square.

An example of overworld tiles being composed of 8-by-8-pixel BG tiles. Overworld tiles use two layers, allowing Game Freak to avoid duplicating graphics for when things like treetops appear overtop of varying terrain.

Although hardware tiles are smaller than map tiles, they still work very similarly. Backgrounds consist of two main components: the tileset, which defines what tiles are available to draw, and the tilemap, which defines the background layer's actual content as a grid that tiles can be stamped into. Each entry in the tileset is a collection of pixel data, while each entry in the tilemap is the ID of a tileset entry along with some other information (e.g. what color palette to use for the tile; whether to flip the tile horizontally or vertically; etc.).

(Sometimes, including in Game Freak's code, the tileset is referred to as character data and its tiles as characters. The usual graphics mode — 16-color tiles — is sometimes called text mode.)

VRAM can hold up to 2048 BG tile graphics (i.e. tileset entries) split across four tilesets (512 tiles per). The GBA's screen resolution is 240x160px, but background layers are typically 256x256px using Game Freak's settings.

  • VRAM in its entirety is 96KB.
  • If you were to store a 256x256px image at four bits per pixel, it'd take up 32KB. Four such images would be 128KB.
  • Given 8x8px tiles at four bits per pixel, a tileset with 512 entries is 16KB; four such tilesets add up to 64KB. A 256x256px tilemap (using two bytes per tile) is 2KB, so four such tilemaps is 8KB. In practice, tilesets and tilemaps share storage space, so they use 64KB, leaving 32KB for sprite graphics.

If you design your graphics right, you can pack a lot of artwork into a small amount of space! Here's the screen where you choose your starter: on the left, a full screen capture; on the right, just the background layers (excluding textboxes and similar); and on the bottom right, the tileset used for those graphics.

An example of how tiles work via the starter Pokemon selection screen, as described.

An interesting property of background layers is that even their smallest size is larger than the GBA's screen resolution. Why would there be room for things that get cut off the edge of the screen? Because background layers can actually be moved around the screen. If you think about a side-scrolling tile-based game like Super Mario Bros., the game will often update portions of the tilemap that are off-screen, and smoothly scroll the background layer as Mario walks forward. The GBA can do the same thing. Some of Game Freak's UI designs use this effect to smoothly animate tile-based widgets, such as by having them slide onto the screen instead of just popping into existence. Some other UI designs use background scrolling as a way to let Game Freak set up the graphics in a "fire and forget" sort of way. The main menu, for example, works by drawing all available menu items even if they blow past the screen bounds, and scrolling the menu works by just scrolling the background layer.

DMA transfers and I/O registers

How does the game actually tell the GBA hardware to draw things?

In order to transfer graphics data to VRAM, the console needs to use DMA, or direct memory access. This is a specialized data transfer feature that temporarily shuts off the CPU (including its ability to respond to hardware events) in order to rapidly copy data to a target location, including locations in VRAM. When these copies are set up properly, they can be so fast as to let you modify background layer information while the hardware is drawing the next frame: you can modify things between each row of pixels, each "scanline," to do effects like wavy distortions.

(If you've messed around with GameShark or Action Replay codes before, you may have heard of "DMA" as an anti-cheat measure applied to Pokemon GBA games. You've likely found "anti-DMA" codes that disable these protections in order to allow you to tamper with your savedata. These references to "DMA" are actually a misuse of the term, however. The anti-cheat measure in question would more accurately be called ASLR, and it has nothing to do with DMA at all.)

We don't need to know terribly much about using DMA in order to make menus, but it's worth at least knowing the basic meaning of the term. Pokemon Emerald's code has functions we can use to send tile data to the screen without needing to know how to use DMA ourselves, but some of the game's menus use DMA function calls to clear VRAM when they're opened, so it's good to know what DMA is just so those calls aren't a complete mystery.

What's more important to understand are the GBA's I/O registers. These are special memory locations that can be used to communicate with the hardware directly. For example, memory location 0x04000010 is known as BG0HOFS, and controls the horizontal offset (shift in position) of background layer 0. Each register holds only a single number, but some registers encode multiple pieces of information, using individual bits in the stored number. We're going to be looking at some of these I/O registers in the next sections, in order to understand some of the visual effects that Game Freak uses in their menus. Most I/O registers are handled automatically by Game Freak's UI code, but there are some I/O registers that they use manually.

Screen color effects

The GBA can perform two types of full-screen blending: brightness changes, and alpha transparency. It can only perform one screen effect at a time, and the effect is controlled through a handful of I/O registers.

The BLDCNT register allows us to choose the effect type, the destination layers, and (for alpha blending) the source layers. When using alpha blending, the BLDALPHA register controls the opacities of each layer. When using brightness changes, the BLDY reigster controls the brightness in increments of 16 RGB units (so, specifying 4 would brighten or darken the image by 64).

These effects can be useful. The Options menu, for example, uses a screen effect to darken the list of options, and then cuts a hole in the screen effect in order to highlight the option you currently have focused. But wait, how do we "cut a hole" into a full-screen blend effect?

Screen windows

The GBA's hardware can be used to divide the screen up into four regions. Three of them are called "windows," and the fourth is the "not in any window" region. We're mainly concerned with the two rectangular regions, called Window 0 and Window 1. (There's also an "OBJ Window:" if you have any specially-marked "window sprites" on-screen, the sprites themselves are invisible, but their non-transparent pixels define what parts of the screen are in the OBJ Window.) When any windows are enabled, you can control which layers, if any, are visible within each of the four screen regions. You can also control whether the screen color effect is applied to a given region, and that's how we can cut a hole into the screen color effect.

(I'll mention this again when it comes up, but a quick note: within most of Emerald's UI code, the term "window" is used to refer to something else.)

Here's the code that the options menu uses to set up this effect, with some formatting changes for legibility:

SetGpuReg(REG_OFFSET_WIN0H,    0);
SetGpuReg(REG_OFFSET_WIN0V,    0);
SetGpuReg(REG_OFFSET_WININ,    WININ_WIN0_BG0);
SetGpuReg(REG_OFFSET_WINOUT,   WINOUT_WIN01_BG0 | WINOUT_WIN01_BG1 | WINOUT_WIN01_CLR);
SetGpuReg(REG_OFFSET_BLDCNT,   BLDCNT_TGT1_BG0 | BLDCNT_EFFECT_DARKEN);
SetGpuReg(REG_OFFSET_BLDALPHA, 0);
SetGpuReg(REG_OFFSET_BLDY,     4);
SetGpuReg(REG_OFFSET_DISPCNT,  DISPCNT_WIN0_ON | DISPCNT_OBJ_ON | DISPCNT_OBJ_1D_MAP);

We begin by setting Window 0's location and size to zero, by writing to the WIN0H and WIN0V I/O registers: these control the horizontal and vertical edges of the window. We then write to the WININ I/O register to control what layers are defined as existing inside of Windows 0 and 1: we set it so that background layer 0 is drawn inside of the window, but no other layers are drawn. The options list is on layer 0, and everything else is on layer 1, so theoretically, if Window 0 were positioned the wrong way, it'd punch a hole into the rest of the UI. However, when we move and resize it layer on, we'll make sure that it only displays overtop of the options list.

Next, we write to the WINOUT I/O register, which controls what layers are visible inside of the "not in any window" region. (This register also controls the OBJ Window, but we're not using that here.) We set this register so that background layers 0 and 1 — the only layers used by this menu — and the color effect are visible for any part of the screen that Window 0 isn't covering.

(This... actually isn't the best way to do things. Our only goal is to punch a hole into the screen color effect, so my preference would be to enable all background layers, and the OBJ layer (sprites), for both WININ and WINOUT. That way, the menu becomes easier to edit without mysterious issues popping up: there's no risk of you forgetting about the window setup and then wondering, "I added a new background layer and made sure to call ShowBg for it, so why isn't it showing up?")

Once we've set up the windows, we then write to BLDCNT, BLDALPHA, and BLDY to set up a screen color effect that will darken the screen, but only on background layer 0.

Finally, we write to DISPCNT to enable Window 0 and to set up sprite options.

When the options window actually wants to highlight an option, to show that your cursor is on it, it uses this function:

static void HighlightOptionMenuItem(u8 index)
{
    SetGpuReg(REG_OFFSET_WIN0H, WIN_RANGE(16, DISPLAY_WIDTH - 16));
    SetGpuReg(REG_OFFSET_WIN0V, WIN_RANGE(index * 16 + 40, index * 16 + 56));
}

The WIN0H register encodes both horizontal edges of Window 0 — the left edge and the right edge — into a single number, so the WIN_RANGE macro exists to help us pack two numbers into one. WIN0V works similarly, with the top edge and bottom edge. All measurements are in pixels. So a good way to read this code is:

  • The options list begins 40 pixels down from the top of the screen. A single row in the options list is 16 pixels high.
    • Therefore, the top edge of row n is n * 16 + 40, and the bottom edge is n * 16 + 56.
  • The options list is inset by 16 pixels from the left and right edges of the screen.
    • Therefore, the left edge is always at 16, and the right edge is always at DISPLAY_WIDTH - 16.

The effect of all of this is that we place Window 0 above the currently selected menu option. The screen color effect tries to darken all of background layer 0, but the effect is only visible outside of Window 0, so Window 0 ends up acting as a highlight.

Something worth noting is that the WIN_RANGE macro doesn't cap values above 255, and that can lead to problems that are hard to understand if you're not ready for it. Due to how the two edge coordinates get combined, a right or bottom edge greater than 255 will test as 0, and will actually add to the opposing edge. This will produce a mismatched range (where the minimum is larger than the maximum), and according to GBATEK, the GBA responds to that by forcing the bad maximum to the edge of the screen.

Next steps

We've now reviewed the basics of GBA graphics programming:

  • Four background tilesets, each containing 512 tiles (8x8px, 4bpp)
  • Four background layers, each one a 256x256px tilemap
  • Sixteen background color palettes, each one containing 16 colors
  • I/O registers, as a way to talk to the GBA hardware directly
  • Screen color effects
  • Screen windows

In the next part of this guide, we'll review the higher-level constructs that Game Freak has built in order to more easily manage both low-level graphics and higher-level UI designs. We're not done dealing with the GBA's hardware yet, though, because we're also going to run into its limitations — and have to learn how to deal with them.