1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 17:06:04 +00:00
nescript/examples/metasprite_demo.ne

87 lines
2.5 KiB
Text
Raw Normal View History

parser/lowering: declarative metasprites for multi-tile sprite groups Multi-tile sprites used to require one hand-written `draw` per tile, e.g. the four-call sequence in `examples/platformer.ne`'s `draw_player()`. The new `metasprite Name { ... }` declaration collects parallel `dx`/`dy`/`frame` arrays plus a reference to the underlying sprite, and `draw Name at: (x, y)` expands to one OAM slot per tile in the IR lowering — the codegen sees N regular DrawSprite ops, so the runtime OAM cursor allocator picks them up without any metasprite-specific awareness. The metasprite's `frame:` array is interpreted *relative to the underlying sprite's base tile*: index 0 means "the first tile this sprite owns", which is the natural reading for a 16×16 hero whose pixel art the asset resolver split into four consecutive tiles. The lowering walks `program.sprites` to compute base tile indices the same way `assets::resolve_sprites` would, then folds the base into each frame entry before storing the metasprite info. Sprites sourced from external `@chr(...)` / `@binary(...)` files whose bytes aren't available at parse time fall back to a one-tile assumption — those programs are rare and can declare metasprites against pixel-art sprites instead. The new `examples/metasprite_demo.ne` declares a 16×16 hero sprite and arranges its four tiles into a metasprite, then sweeps the hero across the screen so the harness captures it mid-motion. The new keyword is added to the lexer/token list, and the parser accepts `sprite:` (the otherwise-keyword) as a property name in metasprite bodies so the natural spelling parses. https://claude.ai/code/session_01KEczoNUX3WmcFLfq6iAQxB
2026-04-15 03:13:30 +00:00
// Metasprite demo — declarative multi-tile sprite groups.
//
// The `metasprite` keyword bundles a list of (dx, dy, frame)
// offsets into one named entity. `draw Hero at: (x, y)` then
// expands to one OAM slot per tile, so user code stops having
// to hand-write the four `draw Tileset at: (x+8, y+8) frame:
// TILE_PLAYER_BR` calls that platformer.ne uses today. Each
// tile inherits the OAM-cursor allocator from the runtime, so
// metasprites compose with regular `draw` statements without
// any extra wiring.
//
// Build: cargo run -- build examples/metasprite_demo.ne
game "Metasprite Demo" {
mapper: NROM
}
// 16×16 hero sprite drawn as ASCII pixel art. The parser splits
// it into four 8×8 tiles in row-major order: tile 0 = top-left,
// tile 1 = top-right, tile 2 = bottom-left, tile 3 = bottom-right.
// We refer to those four tiles by base offset below.
sprite Hero16 {
pixels: [
"..####....####..",
".######..######.",
"###@##....##@###",
"###@##....##@###",
"################",
"################",
".##############.",
"..############..",
"...##########...",
"....########....",
"....##....##....",
"....##....##....",
"....##....##....",
"....##....##....",
"...####..####...",
"..######..######"
]
}
// Arrange Hero16's four tiles into a 16×16 metasprite. Each
// row in the metasprite block is logically one OAM slot; the
// `dx` / `dy` arrays carry the per-tile offset from the
// metasprite's anchor point and `frame` carries the tile index.
//
// `sprite: Hero16` resolves to the base tile index reserved by
// the asset resolver; the four entries below pick consecutive
// tiles starting at that base — i.e. the four 8×8 tiles the
// pixel-art block was sliced into.
metasprite Hero {
sprite: Hero16
dx: [0, 8, 0, 8]
dy: [0, 0, 8, 8]
frame: [0, 1, 2, 3]
}
var px: u8 = 120
var py: u8 = 96
var dir: u8 = 0 // 0 = right, 1 = left
on frame {
// Bounce horizontally between two rails so the harness
// captures movement at frame 180 regardless of which
// direction we started in.
if dir == 0 {
px += 1
if px == 200 {
dir = 1
}
} else {
px -= 1
if px == 32 {
dir = 0
}
}
// One `draw Hero` expands to four `draw Hero16` calls under
// the hood. Compare with the four-line sequence in
// platformer.ne's `draw_player()` — same OAM layout, much
// less typing.
draw Hero at: (px, py)
}
start Main