mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 17:06:04 +00:00
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
86 lines
2.5 KiB
Text
86 lines
2.5 KiB
Text
// 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
|