Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Hello Graphics and Keypad

Now that you have a stable VBlank loop, the next step is drawing a visible shape and moving it.

This page pairs two tiny demos that share the same consteval circle sprite:

  • demo_hello_graphics.cpp: draw the sprite in the centre.
  • demo_hello_keypad.cpp: move the same sprite with the D-pad.

Part 1: draw a shape

#include <gba/bios>
#include <gba/color>
#include <gba/interrupt>
#include <gba/shapes>
#include <gba/video>

#include <cstring>

using namespace gba::shapes;
using gba::operator""_clr;

namespace {

    constexpr auto spr_ball = sprite_16x16(circle(8.0, 8.0, 7.0));

} // namespace

int main() {
    gba::irq_handler = {};
    gba::reg_dispstat = {.enable_irq_vblank = true};
    gba::reg_ie = {.vblank = true};
    gba::reg_ime = true;

    gba::reg_dispcnt = {
        .video_mode = 0,
        .linear_obj_tilemap = true,
        .enable_obj = true,
    };

    gba::pal_bg_mem[0] = "#102040"_clr;
    gba::pal_obj_bank[0][1] = "white"_clr;

    auto* objDst = gba::memory_map(gba::mem_vram_obj);
    std::memcpy(objDst, spr_ball.data(), spr_ball.size());
    const auto tileIdx = gba::tile_index(objDst);

    auto obj = spr_ball.obj(tileIdx);
    obj.x = (240 - 16) / 2;
    obj.y = (160 - 16) / 2;
    obj.palette_index = 0;
    gba::obj_mem[0] = obj;

    for (int i = 1; i < 128; ++i) {
        gba::obj_mem[i] = {.disable = true};
    }

    while (true) {
        gba::VBlankIntrWait();
    }
}

What is happening?

  1. The setup is the same as Hello VBlank: initialise interrupts and wait on gba::VBlankIntrWait() in the main loop.
  2. sprite_16x16(circle(...)) creates the sprite tile data at compile time (consteval).
  3. We copy that tile data into OBJ VRAM, then place it with obj_mem[0].
  4. The display runs in Mode 0 with objects enabled (.enable_obj = true).
  5. Colours use _clr literals for readability ("#102040"_clr, "white"_clr).

Hello Graphics screenshot

Part 2: move it with keypad

#include <gba/color>
#include <gba/interrupt>
#include <gba/keyinput>
#include <gba/shapes>
#include <gba/video>

#include <algorithm>
#include <cstring>

using namespace gba::shapes;
using gba::operator""_clr;

namespace {

    constexpr int screen_width = 240;
    constexpr int screen_height = 160;
    constexpr int sprite_size = 16;

    constexpr auto spr_ball = sprite_16x16(circle(8.0, 8.0, 7.0));

    int clamp(int value, int lo, int hi) {
        if (value < lo) {
            return lo;
        }
        if (value > hi) {
            return hi;
        }
        return value;
    }

} // namespace

int main() {
    gba::irq_handler = {};
    gba::reg_dispstat = {.enable_irq_vblank = true};
    gba::reg_ie = {.vblank = true};
    gba::reg_ime = true;

    gba::reg_dispcnt = {
        .video_mode = 0,
        .linear_obj_tilemap = true,
        .enable_obj = true,
    };

    gba::pal_bg_mem[0] = "#102040"_clr;
    gba::pal_obj_bank[0][1] = "white"_clr;

    auto* objDst = gba::memory_map(gba::mem_vram_obj);
    std::memcpy(objDst, spr_ball.data(), spr_ball.size());
    const auto tileIdx = gba::tile_index(objDst);

    auto obj = spr_ball.obj(tileIdx);
    obj.palette_index = 0;

    int spriteX = (screen_width - sprite_size) / 2;
    int spriteY = (screen_height - sprite_size) / 2;
    obj.x = static_cast<unsigned short>(spriteX);
    obj.y = static_cast<unsigned short>(spriteY);
    gba::obj_mem[0] = obj;

    gba::object disabled{.disable = true};
    std::fill(std::begin(gba::obj_mem) + 1, std::end(gba::obj_mem), disabled);

    gba::keypad keys;

    while (true) {
        gba::VBlankIntrWait();
        keys = gba::reg_keyinput;

        spriteX += keys.xaxis();
        spriteY += keys.i_yaxis();

        spriteX = clamp(spriteX, 0, screen_width - sprite_size);
        spriteY = clamp(spriteY, 0, screen_height - sprite_size);

        obj.x = static_cast<unsigned short>(spriteX);
        obj.y = static_cast<unsigned short>(spriteY);
        gba::obj_mem[0] = obj;
    }
}
  • keys.xaxis() handles left/right.
  • keys.i_yaxis() handles up/down in screen-space coordinates.
  • Position is clamped to keep the sprite inside the 240x160 screen.

Next step

Continue to Hello Audio to trigger a PSG jingle on button press.