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
Claude 6b080316a4
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

86 lines
2.5 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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