Game Boy ASM style guide

Written by ISSOtmopen in new window


This style guide aims to formalize a style that most Game Boy ASM programmers agree on, and provide a good baseline for new programmers just starting in this field. (If that's you, welcome! 😄)

To quote the Linux kernel style guideopen in new window:

Coding style is very personal, and I won't force my views on anybody, but this is what goes for anything that I have to be able to maintain, and I'd prefer it for most other things too. Please at least consider the points made here.

Many people follow alternate style guides, and that's fine; but if you're starting to code in ASM, a clean style goes a long way to keep your code organized. Again: you don't have to do everything listed here, but please at least consider the reasons behind each bullet point.

Oh, by the way, you're free to contribute to this documentopen in new window and/or chat with us about itopen in new window!

Naming

RGBASM accepts a lot of symbol namesopen in new window:

Symbol names can contain letters, numbers, underscores ‘_’, hashes ‘#’ and at signs ‘@’. However, they must begin with either a letter, or an underscore.

However, naming conventions make code easier to read, since they help convey the different semantics between each symbol's name.

  • Labels use PascalCase: DrawNPCs, GetOffsetFromCamera.

  • Labels in RAM (VRAM, SRAM, WRAM, HRAM; you shouldn't be using Echo RAM or OAM) use the same convention but are prefixed with the initial of the RAM they're in, in lowercase: wCameraOffsetBuffer, hVBlankFlag, vTilesetTiles, sSaveFileChecksum. Rationale: to know in which memory type the label is; this is important because VRAM and SRAM have special access precautions and HRAM can (should (must)) be accessed using the ldh instruction.

  • Local labels use camelCase, regardless of memory type: .waitVRAM, wPlayer.xCoord.

  • Macro names use snake_case: wait_vram, end_struct.

  • Constants use CAPS_SNAKE: NB_NPCS, OVERWORLD_STATE_LOAD_MAP.

    Exception: constants that are used like labels should follow the label naming conventions. For example, see hardware.incopen in new window's rXXX constants.

Best practices

  • Avoid hardcoding things. This means:

  • Allocate space for your variables using labelsopen in new window + ds & coopen in new window instead of equopen in new window. This has several benefits:

    • Removing, adding, or changing the size of a variable that isn't the last one doesn't require updating every variable after it.
    • The size of each variable is obvious (ds 4) instead of having to be calculated from the addresses.
    • equ allocation is equivalent to hardcoding section addresses (see above), whereas labels are placed automatically by RGBLINK.
    • Labels support BANK()open in new window and many cool other features!
    • Labels are output in map and symopen in new window files.
  • If a file gets too big, you should split it. Files too large are harder to read and navigate. However, the splitting should stay coherent and consistent; having to jump around files constantly is equally as hard to read and navigate.

  • Unless you're making a 32k ROMopen in new window, put things in ROMXopen in new window by default. ROM0 space is precious, and can deplete quickly; and when you run out, it's difficult to move things to ROMX.

    However, if you have code in ROM bank A refer to code or data in ROM bank B, then either should probably be moved to ROM0, or both be placed in the same bank (options for that are mentioned further above). farcallopen in new window is a good way to make your code really spaghettiopen in new window.

  • Don't clear RAM at init! Good debugging emulators will warn you when you're reading uninitialized RAM (BGBopen in new window has one in the option's Exceptions tab, for example), which will let you know that you forgot to initialize a variable. Clearing RAM does not fix most of these bugs, but silences the helpful warnings.

    Also, a lot of the time, variables need to get initialized to values other than 0, so clearing RAM is actually counter-productive in these cases.

Recommendations

The difference between these and the "best practices" above is that these are more subjective, but they're still worth talking about here.

  • Historically, RGBDS has required label definitions to begin at "column 1" (i.e. no whitespace before them on their line). However, later versions (with full support added in 0.5.0) allow indenting labelsopen in new window, for example to make loops stand out like in higher-level languages. However, a lot of people don't do thisopen in new window, so it's up to you.

  • Please use the .asm (or .s) file extensions, not .z80. The GB CPU isn't a Z80, so syntax highlighters get it mostly right, but not quite. And it helps spreading the false idea that the GB CPU is a Z80. 😢

  • Compressing data is useful for several reasons; however, it's not necessary in a lot of cases nowadays, so you may want to only look at it after more high-priority aspects.

  • Avoid abusing macros. Macros tend to make code opaque and hard to read for people trying to help you, in addition to having side effects and sometimes leading to very inefficient code.

  • Never let the hardware draw a corrupted frame even if it's just one frame. If it's noticeable by squinting a bit, it must go.

  • Makefiles are baeopen in new window; they speed up build time by not re-processing what hasn't changed, and they can automate a lot of tedium. Writing a good Makefile can be quite daunting, but gb-boilerplateopen in new window and gb-starter-kitopen in new window can help you get started faster.