|
| static void | begin_step (s8 dx, s8 dy) |
| static void | build_panel (void) |
| static void | dialog_close (void) |
| static void | dialog_open (const char *line) |
| static void | draw_char (u8 oam_id, u16 wx, u16 wy, u16 cam_x, u16 cam_y, u8 facing, u8 phase, u8 palette) |
| | Draw a 16x16 character straddling its 8x8 tile, or hide it.
|
| static void | front_tile (u16 *tx, u16 *ty) |
| static u16 | hero_tx (void) |
| static u16 | hero_ty (void) |
| static void | hud_icons (void) |
| | Draw the HUD icons: hearts for HP, a coin for the purse.
|
| static void | hud_text (void) |
| | The HUD's numeric half, on BG3. Re-drawn after every textClearRect, since clearing the dialog does not touch these rows.
|
| int | main (void) |
| static void | scene_load (u8 which, u16 tx, u16 ty, u8 facing) |
| static u8 | tile_walkable (u16 tx, u16 ty) |
RPG template (#7) — Tiled-driven overworld with dialog boxes.
The SDK's modules composed into a playable RPG skeleton. Two things make it a template rather than a demo:
- The map is a real Tiled map (res/town.tmj): terrain, per-tile collision (the attribute property), entity positions AND each villager's dialogue line (a text property on the object) all live in the map, not in the code. Adding a villager is a map edit. Edit it in Tiled, re-run gen_assets.py, rebuild.
- A real bordered dialog box and a HUD, both 9-slice panels on BG2 with their text on BG3 above — the classic SNES RPG window.
- Two scenes: the town, and the inside of the blue-roofed house. A scene is a tileset + a palette + a tilemap + a collision map + entities; switching one is four DMAs under force blank. The interior is its own Tiled map (res/house.tmj) with its own 16-colour palette, so neither scene gives up colours for the other.
Layer roles (Mode 1):
- BG1: the town (4bpp, 64x64 scrolling)
- BG2: the HUD (always) and the dialog panel (while talking)
- BG3: the dialog text (2bpp, high priority)
- OBJ: the hero and the villagers (same tiles, two palettes)
ROM mode: LoROM (project default).
- SNES Concepts
- Tiled (.tmj) as the content pipeline: collision and entities are data, not hardcoded
- Tile-exact collision: the hero OCCUPIES one tile and its 16x16 sprite is drawn straddling it (feet on the tile), so what you see is what collides — the classic top-down RPG convention
- A 9-slice dialog box DMA'd to BG2 on open, with layer priorities stacking town < box < text
- Forced blank (setScreenOff, INIDISP bit 7) around a multi-KB VRAM upload — setBrightness(0) only blacks the screen, it does NOT open the VRAM write window, and the tail of the transfer is dropped
- A fixed 16-colour palette per scene, authored by hand rather than quantised: adding one tile to a quantised sheet re-derives the whole palette and every existing tile shifts hue
- Off-camera entities MUST be parked at OBJ_HIDE_Y, not just drawn: OAM coordinates wrap, so an entity two screens away reappears somewhere plausible on screen (a villager standing in the wall)
- Collision through the SDK's collideTile() over the Tiled map — its const tilemap parameter means the read is bank-honouring (#121), so a multi-KB map needs neither bank $00 nor RAM
- What to Observe
- The town fades in, with hearts and a purse in the HUD. Walk with the D-pad — houses, water, trees and the fence block you exactly where they look. Face either villager and press A (each has its own line, from the map), or step onto the chest and press A: the purse goes up by 10. Walk into the door of the BLUE-roofed house, bottom right of the crossroads, and you step inside — the host greets you with no button press. The mat by the door takes you back out.
- Modules Used
- console, dma, background, sprite, text, input, collision, panel
- See also
- gen_assets.py, res/town.tmj — the Tiled content pipeline
Draw a 16x16 character straddling its 8x8 tile, or hide it.
Two things happen here, both of them load-bearing.
The straddle is what makes collision feel exact: the character's logical position is one tile; the sprite is drawn 4 px left and 8 px up of it, so its feet stand on that tile and its body overhangs upward — the standard top-down RPG convention. Drawing the sprite at the tile's corner instead (the naive version) puts the visible body half a tile away from what collides.
The cull is not an optimisation, it is correctness. OAM X is 9 bits and Y is 8, so an entity standing outside the camera does not quietly vanish — its coordinates WRAP and it reappears somewhere plausible-looking on screen. A villager two screens away shows up standing inside the town wall. Anything off-camera goes to oamHide(), which parks it properly (Y AND the X high bit — Y alone still wraps for sprites over 16 px tall).