OpenSNES
Modern Open-Source SNES Development SDK
Loading...
Searching...
No Matches
main.c File Reference

RPG template (#7) — Tiled-driven overworld with dialog boxes. More...

#include <snes.h>
#include <snes/background.h>
#include <snes/sprite.h>
#include <snes/text.h>
#include <snes/input.h>
#include <snes/collision.h>
#include <snes/panel.h>
#include "res/entities.inc"

Classes

struct  Npc
 A villager: where it stands and what it says. The struct shape and the rows both come from the Entities layer of town.tmj, so adding one is a map edit. More...

Macros

#define BOX_B   9
#define BOX_BL   8
#define BOX_BR   10
#define BOX_C   5
#define BOX_L   4
#define BOX_PAL   2 /* BG2 palette 2 -> CGRAM 32-47 */
#define BOX_R   6
#define BOX_T   1
#define BOX_TL   0
#define BOX_TR   2
#define FACE_DOWN   0
#define FACE_LEFT   2
#define FACE_RIGHT   3
#define FACE_UP   1
#define HERO_MAX_HP   3
#define HUD_H   3
#define HUD_W   16
#define HUD_X   0
#define HUD_Y   0
#define ICON_COIN   11
#define ICON_HEART   3
#define ICON_HEART_EMPTY   7
#define MAP_TILES   64
#define MAP_TILES_HOUSE   32
#define PANEL_H   6
#define PANEL_W   28
#define PANEL_X   2
#define PANEL_Y   22
#define SCREEN_CX   124 /* where the hero tile sits on screen */
#define SCREEN_CY   108
#define TEXT_W   (PANEL_W - 4) /* usable characters per line */
#define VRAM_FONT   0x3000
#define VRAM_HERO   0x6000
#define VRAM_HOUSE_TILES   0x1000
#define VRAM_TEXT_MAP   0x3800
#define VRAM_TOWN_MAP   0x2000
#define VRAM_TOWN_TILES   0x0000
#define VRAM_UI_MAP   0x4400
#define VRAM_UI_TILES   0x4000
#define WORLD_H   (MAP_TILES * 8)
#define WORLD_W   (MAP_TILES * 8)

Enumerations

enum  { ST_FADEIN , ST_EXPLORE , ST_DIALOG }
enum  { SCENE_TOWN , SCENE_HOUSE }

Functions

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)

Variables

static u8 anim_tick
u8 chest_opened
static u8 fade_level
u8 game_state
u16 gold
u8 hero_facing
u8 hero_hp
u8 hero_pal []
u8 hero_tiles []
u8 hero_tiles_end []
u16 hero_x
 Probe oracles / state. hero_x/hero_y are the PIXEL position of the tile the hero occupies (a multiple of 8 when idle).
u16 hero_y
const u8 house_collision []
u8 house_map []
u8 house_pal []
u8 house_tiles []
u8 house_tiles_end []
u8 npc_pal []
static const Npc npcs [NPC_COUNT] = NPC_TABLE
 The villagers. const, so the table lives in ROM and is read with bank-honouring far addressing — npcs[i].tx compiles to lda.l npcs,x (this needed issue #132 fixed; before that a const array of structs was read from bank $00).
static u16 panel_map [32 *32]
u8 scene
static u8 step_count
static s8 step_dx
static s8 step_dy
const u8 town_collision []
u8 town_map []
u8 town_pal []
u8 town_tiles []
u8 town_tiles_end []
static const Panel ui
u8 ui_pal []
u8 ui_tiles []
u8 ui_tiles_end []
static u8 walk_phase

Detailed Description

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:

  1. 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.
  2. 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.
  3. 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

Macro Definition Documentation

◆ BOX_B

#define BOX_B   9

◆ BOX_BL

#define BOX_BL   8

◆ BOX_BR

#define BOX_BR   10

◆ BOX_C

#define BOX_C   5

◆ BOX_L

#define BOX_L   4

◆ BOX_PAL

#define BOX_PAL   2 /* BG2 palette 2 -> CGRAM 32-47 */

◆ BOX_R

#define BOX_R   6

◆ BOX_T

#define BOX_T   1

◆ BOX_TL

#define BOX_TL   0

◆ BOX_TR

#define BOX_TR   2

◆ FACE_DOWN

#define FACE_DOWN   0

◆ FACE_LEFT

#define FACE_LEFT   2

◆ FACE_RIGHT

#define FACE_RIGHT   3

◆ FACE_UP

#define FACE_UP   1

◆ HERO_MAX_HP

#define HERO_MAX_HP   3

◆ HUD_H

#define HUD_H   3

◆ HUD_W

#define HUD_W   16

◆ HUD_X

#define HUD_X   0

◆ HUD_Y

#define HUD_Y   0

◆ ICON_COIN

#define ICON_COIN   11

◆ ICON_HEART

#define ICON_HEART   3

◆ ICON_HEART_EMPTY

#define ICON_HEART_EMPTY   7

◆ MAP_TILES

#define MAP_TILES   64

◆ MAP_TILES_HOUSE

#define MAP_TILES_HOUSE   32

◆ PANEL_H

#define PANEL_H   6

◆ PANEL_W

#define PANEL_W   28

◆ PANEL_X

#define PANEL_X   2

◆ PANEL_Y

#define PANEL_Y   22

◆ SCREEN_CX

#define SCREEN_CX   124 /* where the hero tile sits on screen */

◆ SCREEN_CY

#define SCREEN_CY   108

◆ TEXT_W

#define TEXT_W   (PANEL_W - 4) /* usable characters per line */

◆ VRAM_FONT

#define VRAM_FONT   0x3000

◆ VRAM_HERO

#define VRAM_HERO   0x6000

◆ VRAM_HOUSE_TILES

#define VRAM_HOUSE_TILES   0x1000

◆ VRAM_TEXT_MAP

#define VRAM_TEXT_MAP   0x3800

◆ VRAM_TOWN_MAP

#define VRAM_TOWN_MAP   0x2000

◆ VRAM_TOWN_TILES

#define VRAM_TOWN_TILES   0x0000

◆ VRAM_UI_MAP

#define VRAM_UI_MAP   0x4400

◆ VRAM_UI_TILES

#define VRAM_UI_TILES   0x4000

◆ WORLD_H

#define WORLD_H   (MAP_TILES * 8)

◆ WORLD_W

#define WORLD_W   (MAP_TILES * 8)

Enumeration Type Documentation

◆ anonymous enum

anonymous enum
Enumerator
ST_FADEIN 
ST_EXPLORE 
ST_DIALOG 

◆ anonymous enum

anonymous enum
Enumerator
SCENE_TOWN 
SCENE_HOUSE 

Function Documentation

◆ begin_step()

void begin_step ( s8 dx,
s8 dy )
static

◆ build_panel()

void build_panel ( void )
static

◆ dialog_close()

void dialog_close ( void )
static

◆ dialog_open()

void dialog_open ( const char * line)
static

◆ draw_char()

void draw_char ( u8 oam_id,
u16 wx,
u16 wy,
u16 cam_x,
u16 cam_y,
u8 facing,
u8 phase,
u8 palette )
static

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).

◆ front_tile()

void front_tile ( u16 * tx,
u16 * ty )
static

◆ hero_tx()

u16 hero_tx ( void )
static

◆ hero_ty()

u16 hero_ty ( void )
static

◆ hud_icons()

void hud_icons ( void )
static

Draw the HUD icons: hearts for HP, a coin for the purse.

The icons are tiles on BG2 (they came out of the same 4x3 sheet as the dialog border), the number is text on BG3. Splitting it that way costs nothing: both layers are already up for the dialog box.

◆ hud_text()

void hud_text ( void )
static

The HUD's numeric half, on BG3. Re-drawn after every textClearRect, since clearing the dialog does not touch these rows.

◆ main()

int main ( void )

◆ scene_load()

void scene_load ( u8 which,
u16 tx,
u16 ty,
u8 facing )
static

◆ tile_walkable()

u8 tile_walkable ( u16 tx,
u16 ty )
static

Variable Documentation

◆ anim_tick

u8 anim_tick
static

◆ chest_opened

u8 chest_opened

◆ fade_level

u8 fade_level
static

◆ game_state

u8 game_state

◆ gold

u16 gold

HUD: coins, +10 per chest

◆ hero_facing

u8 hero_facing

◆ hero_hp

u8 hero_hp

HUD: static in this template

◆ hero_pal

u8 hero_pal[]
extern

◆ hero_tiles

u8 hero_tiles[]
extern

◆ hero_tiles_end

u8 hero_tiles_end[]

◆ hero_x

u16 hero_x

Probe oracles / state. hero_x/hero_y are the PIXEL position of the tile the hero occupies (a multiple of 8 when idle).

◆ hero_y

u16 hero_y

◆ house_collision

const u8 house_collision[]
extern

◆ house_map

u8 house_map[]
extern

◆ house_pal

u8 house_pal[]
extern

◆ house_tiles

u8 house_tiles[]
extern

◆ house_tiles_end

u8 house_tiles_end[]

◆ npc_pal

u8 npc_pal[]
extern

◆ npcs

const Npc npcs[NPC_COUNT] = NPC_TABLE
static

The villagers. const, so the table lives in ROM and is read with bank-honouring far addressing — npcs[i].tx compiles to lda.l npcs,x (this needed issue #132 fixed; before that a const array of structs was read from bank $00).

◆ panel_map

u16 panel_map[32 *32]
static

◆ scene

u8 scene

SCENE_TOWN or SCENE_HOUSE

◆ step_count

u8 step_count
static

◆ step_dx

s8 step_dx
static

◆ step_dy

s8 step_dy
static

◆ town_collision

const u8 town_collision[]
extern

◆ town_map

u8 town_map[]
extern

◆ town_pal

u8 town_pal[]
extern

◆ town_tiles

u8 town_tiles[]
extern

◆ town_tiles_end

u8 town_tiles_end[]

◆ ui

const Panel ui
static
Initial value:
= {
0x4400 ,
0,
4,
2 ,
1,
}
static u16 panel_map[32 *32]
The panel tilemap — caller-owned (the module never allocates 2 KB).
Definition main.c:97

◆ ui_pal

u8 ui_pal[]
extern

◆ ui_tiles

u8 ui_tiles[]
extern

◆ ui_tiles_end

u8 ui_tiles_end[]

◆ walk_phase

u8 walk_phase
static