mirror of
https://github.com/imjasonh/nescript
synced 2026-07-18 22:57:43 +00:00
feat(platformer): add stomp-or-die enemy collisions, live HUD, GameOver state
The previous platformer example drew enemies but had almost no interaction with them: only enemy 1 had a stomp check, the stomp window was unreachable under the default +1-px-per-frame-plus-a- jump-every-40-frames autopilot, contact from any other angle was a silent no-op, and the header comment promised a "title → playing → game-over state machine" that didn't actually exist. The README demo gif and the committed golden both froze that state — a level the player could walk through indefinitely with no consequence. Flesh the enemy interaction model out into something real: - `resolve_enemy_hit(e_sx)`: one helper, called symmetrically for both enemies. Computes the player/enemy hitbox overlap (horizontal in `e_sx ∈ (72, 96)`, vertical in `player_y ∈ (152, 176)`) and branches three ways — falling onto the head is a stomp bounce (`rise_count = 6`, `fall_vy = 0`, `stomp_count += 1`, `play Boing`); overlap while `rise_count > 0` is a grace pass-through so the stomp bounce itself can't retrigger contact on the same enemy; anything else (walking into the side, standing on the ground against the enemy) is fatal — `alive = 0` and `play hit`. - New `GameOver` state: draws four enemy tiles across the middle of the screen plus a coin row sized to `stomp_count`, stops the music, lingers 60 frames then auto-retries, and also honours Start for an instant retry. - Proximity-based autopilot: pre-jump when an enemy is exactly 19 px ahead (`e1_sx == 99` or `e2_sx == 99`), capped at two jumps per life by `auto_jumps < AUTOPILOT_JUMPS`. Tuning: a JUMP_RISE=12, GRAVITY_CAP=4 jump lands the player's feet at enemy-head height exactly 21 frames after lift-off, by which point the autopilot camera has scrolled the enemy under the player. The first jump fires on Playing frame 1 and stomps enemy 1 on frame 22; the second fires on Playing frame 101 and stomps enemy 2 on frame 122. After that the autopilot is exhausted and the third enemy encounter (camera wraps back past enemy 1) is fatal — the golden harness now sees the full stomp, stomp, die, retry, stomp loop instead of a frozen walk. - Live HUD: up to four coin sprites in the top-left, one per stomp, rendered both during `Playing` and on the `GameOver` screen so the score is visible in the death frame. `Playing`'s player draw is now guarded by `if alive == 1` so the hero disappears on the fatal-contact frame and the enemy that killed them is visible underneath. Verified with a per-frame ZP trace through the patched puppeteer + jsnes harness: first stomp at emu frame 44 (camera_x=22), second at emu frame 144 (camera_x=122), death at emu frame 283 (camera_x=5 after a 256-px wrap), `Playing` restart at emu frame 343, third stomp at emu frame 365. All 22 emulator goldens still match after the update, and `docs/platformer.gif` regenerated from the new ROM now shows two clean stomps, a clean side-collision death, the GameOver screen, and the retry cycle all inside the 6-second demo window. Golden updates: - `tests/emulator/goldens/platformer.png` — the frame-180 capture now shows the hero walking forward with a two-coin HUD after both autopilot stomps (previously: a frozen bouncing hero). - `tests/emulator/goldens/platformer.audio.hash` — the track now includes two `Boing` stomp bounces, which shifts the hash. - `examples/platformer.nes` — rebuilt from the rewritten source. Also updates the platformer rows in `README.md` and `examples/README.md` to match the new gameplay. https://claude.ai/code/session_013Bi4H4YQ5or5HtMB4doUFi
This commit is contained in:
parent
54910f2498
commit
169a481099
7 changed files with 183 additions and 48 deletions
|
|
@ -86,7 +86,7 @@ start Main
|
||||||
| [`structs_enums_for.ne`](examples/structs_enums_for.ne) | Structs, enums, `for` loops, struct literals |
|
| [`structs_enums_for.ne`](examples/structs_enums_for.ne) | Structs, enums, `for` loops, struct literals |
|
||||||
| [`inline_asm_demo.ne`](examples/inline_asm_demo.ne) | Inline asm with `{var}` substitution, `poke`/`peek` |
|
| [`inline_asm_demo.ne`](examples/inline_asm_demo.ne) | Inline asm with `{var}` substitution, `poke`/`peek` |
|
||||||
| [`audio_demo.ne`](examples/audio_demo.ne) | Audio subsystem: user `sfx`/`music` blocks, builtin effects, `play`/`start_music`/`stop_music` |
|
| [`audio_demo.ne`](examples/audio_demo.ne) | Audio subsystem: user `sfx`/`music` blocks, builtin effects, `play`/`start_music`/`stop_music` |
|
||||||
| [`platformer.ne`](examples/platformer.ne) | **End-to-end side-scroller** — custom CHR tileset, full background nametable, metasprite player with gravity/jump physics, wrap-around scrolling, enemies, coins, user-declared SFX + music, and a Title → Playing state machine with auto-play for the headless harness |
|
| [`platformer.ne`](examples/platformer.ne) | **End-to-end side-scroller** — custom CHR tileset, full background nametable, metasprite player with gravity/jump physics, wrap-around scrolling, stomp-or-die enemy collisions, live stomp-count HUD, pickup coins, user-declared SFX + music, and a Title → Playing → GameOver state machine with a proximity-based autopilot so the headless harness demonstrates the full gameplay loop (stomp, stomp, die, retry) inside six seconds |
|
||||||
|
|
||||||
## Compiler Commands
|
## Compiler Commands
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 438 KiB After Width: | Height: | Size: 446 KiB |
|
|
@ -28,7 +28,7 @@ Open any `.nes` file in an NES emulator ([Mesen](https://www.mesen.ca/), [FCEUX]
|
||||||
| `mmc1_banked.ne` | MMC1, banks, multiply | Banked mapper with software multiply |
|
| `mmc1_banked.ne` | MMC1, banks, multiply | Banked mapper with software multiply |
|
||||||
| `palette_and_background.ne` | palette, background, set_palette, load_background | Reset-time initial load plus vblank-safe runtime swaps |
|
| `palette_and_background.ne` | palette, background, set_palette, load_background | Reset-time initial load plus vblank-safe runtime swaps |
|
||||||
| `friendly_assets.ne` | named colours, grouped palette, pixel art, tilemap+legend, palette_map, scalar sfx pitch, note-name music | Exercises every "friendlier" asset syntax at once — the `palette` uses `bg0..sp3` + a shared `universal:`, the sprite is authored as ASCII pixel art, the background uses a `legend { ... } + map:` tilemap with a `palette_map:` for attributes, the sfx uses a scalar `pitch:` + `envelope:` alias, and the music uses note names (`C4, E4 40, rest 10`) with a `tempo:` default. |
|
| `friendly_assets.ne` | named colours, grouped palette, pixel art, tilemap+legend, palette_map, scalar sfx pitch, note-name music | Exercises every "friendlier" asset syntax at once — the `palette` uses `bg0..sp3` + a shared `universal:`, the sprite is authored as ASCII pixel art, the background uses a `legend { ... } + map:` tilemap with a `palette_map:` for attributes, the sfx uses a scalar `pitch:` + `envelope:` alias, and the music uses note names (`C4, E4 40, rest 10`) with a `tempo:` default. |
|
||||||
| `platformer.ne` | **every subsystem** | End-to-end side-scrolling demo: custom CHR tileset, full 32×30 nametable with per-region attribute palettes, 2×2 metasprite hero with gravity/jump physics, wrap-around horizontal scrolling, enemies, coin pickups, user-declared SFX + music, and a Title → Playing state machine with an autopilot so the headless harness still hits interesting gameplay at frame 180. Regenerate the tile art with `cargo run --bin gen_platformer_tiles`. |
|
| `platformer.ne` | **every subsystem** | End-to-end side-scrolling demo: custom CHR tileset, full 32×30 nametable with per-region attribute palettes, 2×2 metasprite hero with gravity/jump physics, wrap-around horizontal scrolling, stomp-or-die enemy collisions with a live stomp-count HUD, coin pickups, user-declared SFX + music, and a Title → Playing → GameOver state machine with a proximity-based autopilot so the headless harness cycles through stomp, stomp, die, and retry inside six seconds. Regenerate the tile art with `cargo run --bin gen_platformer_tiles`. |
|
||||||
|
|
||||||
## Emulator Controls
|
## Emulator Controls
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,13 @@
|
||||||
// one program: custom CHR tiles, a full 32×30 background nametable
|
// one program: custom CHR tiles, a full 32×30 background nametable
|
||||||
// with per-region attribute palettes, a multi-tile metasprite
|
// with per-region attribute palettes, a multi-tile metasprite
|
||||||
// player with gravity/jump physics, wrap-around horizontal
|
// player with gravity/jump physics, wrap-around horizontal
|
||||||
// scrolling, moving enemies, collectible coins, a user-declared
|
// scrolling, moving enemies with stomp-or-die contact logic, a
|
||||||
// SFX envelope, a user-declared music track, and a title →
|
// coin HUD that tracks live score, collectible coins, user-declared
|
||||||
// playing → game-over state machine driven by both controller
|
// SFX envelopes, a user-declared music track, and a
|
||||||
// input and an autopilot so the jsnes golden harness (which runs
|
// title → playing → game-over state machine driven by both
|
||||||
// headless with no simulated buttons) still captures an
|
// controller input and an autopilot so the jsnes golden harness
|
||||||
// interesting frame-180 composition.
|
// (which runs headless with no simulated buttons) still captures
|
||||||
|
// the full gameplay loop inside the first ~6 seconds of demo.
|
||||||
//
|
//
|
||||||
// Controls (when played by a human):
|
// Controls (when played by a human):
|
||||||
// D-pad left/right — walk
|
// D-pad left/right — walk
|
||||||
|
|
@ -358,6 +359,12 @@ const ENEMY2_WORLD_X: u8 = 200
|
||||||
const COIN1_WORLD_X: u8 = 50
|
const COIN1_WORLD_X: u8 = 50
|
||||||
const COIN2_WORLD_X: u8 = 150
|
const COIN2_WORLD_X: u8 = 150
|
||||||
|
|
||||||
|
// How many auto-jumps the autopilot will pre-queue per life. Two
|
||||||
|
// is exactly enough to stomp enemy 1 and enemy 2 once each; after
|
||||||
|
// that the autopilot stops assisting so the headless harness gets
|
||||||
|
// to see the player walk into the next enemy, die, and retry.
|
||||||
|
const AUTOPILOT_JUMPS: u8 = 2
|
||||||
|
|
||||||
// ── Game state ──────────────────────────────────────────────
|
// ── Game state ──────────────────────────────────────────────
|
||||||
|
|
||||||
// Player physics
|
// Player physics
|
||||||
|
|
@ -369,9 +376,13 @@ var fall_vy: u8 = 0 // gravity accumulator
|
||||||
// World/camera
|
// World/camera
|
||||||
var camera_x: u8 = 0
|
var camera_x: u8 = 0
|
||||||
var frame_tick: u8 = 0 // free-running frame counter
|
var frame_tick: u8 = 0 // free-running frame counter
|
||||||
var jump_timer: u8 = 0 // autopilot: jump every N frames
|
|
||||||
var anim_tick: u8 = 0 // visual animation phase
|
var anim_tick: u8 = 0 // visual animation phase
|
||||||
|
|
||||||
|
// Gameplay
|
||||||
|
var alive: u8 = 1 // 0 = dying/dead, 1 = playable
|
||||||
|
var stomp_count: u8 = 0 // successful enemy stomps this life
|
||||||
|
var auto_jumps: u8 = 0 // proximity pre-jumps used this life
|
||||||
|
|
||||||
// ── Helper functions ────────────────────────────────────────
|
// ── Helper functions ────────────────────────────────────────
|
||||||
|
|
||||||
// Try to start a jump. Only valid when the player is on the
|
// Try to start a jump. Only valid when the player is on the
|
||||||
|
|
@ -423,6 +434,54 @@ fun draw_player() {
|
||||||
draw Tileset at: (PLAYER_SCREEN_X + 8, player_y + 8) frame: TILE_PLAYER_BR
|
draw Tileset at: (PLAYER_SCREEN_X + 8, player_y + 8) frame: TILE_PLAYER_BR
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve contact with an enemy whose screen X is `e_sx`. Mutates
|
||||||
|
// the player's physics / alive flag directly and returns 1 on a
|
||||||
|
// successful stomp, 0 on no contact or a fatal hit. The caller
|
||||||
|
// checks `alive` after invocation to decide whether to keep
|
||||||
|
// running the frame.
|
||||||
|
//
|
||||||
|
// The player's 16×16 hitbox is at ([80, 96), [player_y, player_y+16)).
|
||||||
|
// Each enemy's 8×8 hitbox is at ([e_sx, e_sx+8), [168, 176)). Those
|
||||||
|
// overlap horizontally when e_sx ∈ (72, 96) and overlap vertically
|
||||||
|
// when player_y ∈ (152, 176).
|
||||||
|
//
|
||||||
|
// Outcomes:
|
||||||
|
// - No overlap → no-op
|
||||||
|
// - Overlap while rising (rise_count > 0) → graceful
|
||||||
|
// pass-through. rise_count > 0 only happens right after a
|
||||||
|
// successful stomp bounce (try_jump() clears rise_count by
|
||||||
|
// the end of the same frame) or on the stomp's own upward
|
||||||
|
// leg, which we don't want to retrigger.
|
||||||
|
// - Overlap while falling, feet near enemy head → STOMP: bounce
|
||||||
|
// up, increment `stomp_count`, play Boing
|
||||||
|
// - Any other overlap (walking, on ground) → DEATH: set
|
||||||
|
// `alive = 0` and play the builtin `hit` sfx
|
||||||
|
fun resolve_enemy_hit(e_sx: u8) -> u8 {
|
||||||
|
if e_sx > 72 {
|
||||||
|
if e_sx < 96 {
|
||||||
|
if player_y > 152 {
|
||||||
|
if player_y < 176 {
|
||||||
|
// Real contact. Stomp if falling; otherwise, if
|
||||||
|
// we're not in the post-bounce grace window,
|
||||||
|
// die.
|
||||||
|
if fall_vy > 0 {
|
||||||
|
rise_count = 6
|
||||||
|
fall_vy = 0
|
||||||
|
stomp_count += 1
|
||||||
|
play Boing
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if rise_count == 0 {
|
||||||
|
alive = 0
|
||||||
|
play hit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
// ── States ──────────────────────────────────────────────────
|
// ── States ──────────────────────────────────────────────────
|
||||||
|
|
||||||
state Title {
|
state Title {
|
||||||
|
|
@ -468,8 +527,11 @@ state Playing {
|
||||||
fall_vy = 0
|
fall_vy = 0
|
||||||
camera_x = 0
|
camera_x = 0
|
||||||
frame_tick = 0
|
frame_tick = 0
|
||||||
jump_timer = 0
|
|
||||||
anim_tick = 0
|
anim_tick = 0
|
||||||
|
alive = 1
|
||||||
|
stomp_count = 0
|
||||||
|
auto_jumps = 0
|
||||||
|
start_music Theme
|
||||||
}
|
}
|
||||||
|
|
||||||
on frame {
|
on frame {
|
||||||
|
|
@ -490,31 +552,40 @@ state Playing {
|
||||||
camera_x -= 1
|
camera_x -= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Autopilot ────────────────────────────────────────
|
// Always advance camera one px per frame (baseline scroll).
|
||||||
// The headless golden harness never presses buttons, so
|
|
||||||
// we advance the camera and periodically hop all by
|
|
||||||
// ourselves. A human holding right still accelerates
|
|
||||||
// forward — the two effects compose.
|
|
||||||
camera_x += 1
|
camera_x += 1
|
||||||
jump_timer += 1
|
|
||||||
if jump_timer >= 40 {
|
|
||||||
jump_timer = 0
|
|
||||||
try_jump()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Physics ──────────────────────────────────────────
|
// ── Compute screen positions of world entities ──────
|
||||||
step_physics()
|
|
||||||
|
|
||||||
// ── Collisions ───────────────────────────────────────
|
|
||||||
// Coin pickup when the player's screen X is within ±8 of
|
|
||||||
// a coin's screen position and the coin's Y range overlaps
|
|
||||||
// the player. Uses u8 subtraction so "screen X" of a
|
|
||||||
// world entity is just (world_x - camera_x).
|
|
||||||
var e1_sx: u8 = ENEMY1_WORLD_X - camera_x
|
var e1_sx: u8 = ENEMY1_WORLD_X - camera_x
|
||||||
var e2_sx: u8 = ENEMY2_WORLD_X - camera_x
|
var e2_sx: u8 = ENEMY2_WORLD_X - camera_x
|
||||||
var c1_sx: u8 = COIN1_WORLD_X - camera_x
|
var c1_sx: u8 = COIN1_WORLD_X - camera_x
|
||||||
var c2_sx: u8 = COIN2_WORLD_X - camera_x
|
var c2_sx: u8 = COIN2_WORLD_X - camera_x
|
||||||
|
|
||||||
|
// ── Proximity autopilot ─────────────────────────────
|
||||||
|
// Pre-jump when an enemy is 19 px ahead. The fall phase of
|
||||||
|
// a JUMP_RISE=12, GRAVITY_CAP=4 jump lands the player's
|
||||||
|
// feet at enemy head height ~20 frames later, by which
|
||||||
|
// point the autopilot camera has scrolled the enemy under
|
||||||
|
// the player. Limited to AUTOPILOT_JUMPS hops per life so
|
||||||
|
// the third enemy encounter becomes a scripted death,
|
||||||
|
// giving the golden harness a visible game-over sequence.
|
||||||
|
if auto_jumps < AUTOPILOT_JUMPS {
|
||||||
|
if on_ground == 1 {
|
||||||
|
if e1_sx == 99 {
|
||||||
|
try_jump()
|
||||||
|
auto_jumps += 1
|
||||||
|
}
|
||||||
|
if e2_sx == 99 {
|
||||||
|
try_jump()
|
||||||
|
auto_jumps += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Physics ──────────────────────────────────────────
|
||||||
|
step_physics()
|
||||||
|
|
||||||
|
// ── Coin pickups ─────────────────────────────────────
|
||||||
// Coin collect: player occupies [80..96). A coin is "hit"
|
// Coin collect: player occupies [80..96). A coin is "hit"
|
||||||
// when its screen X is in [72..96) (8-px tolerance on each
|
// when its screen X is in [72..96) (8-px tolerance on each
|
||||||
// side of the player's left edge).
|
// side of the player's left edge).
|
||||||
|
|
@ -529,24 +600,17 @@ state Playing {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enemy stomp: if we're above an enemy horizontally *and*
|
// ── Enemy collisions ────────────────────────────────
|
||||||
// we're mid-fall (fall_vy > 0), count the stomp and
|
// Both enemies get the full stomp-or-die check; the helper
|
||||||
// bounce. Otherwise just ignore — the enemy slides past.
|
// mutates player state directly. If `alive` flips to 0
|
||||||
if e1_sx >= 72 and e1_sx < 96 {
|
// we fall through the rest of the frame (drawing is
|
||||||
if fall_vy > 0 {
|
// guarded below) and transition to GameOver at the end.
|
||||||
if player_y + 16 >= ENEMY_Y {
|
resolve_enemy_hit(e1_sx)
|
||||||
play hit
|
resolve_enemy_hit(e2_sx)
|
||||||
rise_count = 6
|
|
||||||
fall_vy = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Drawing ──────────────────────────────────────────
|
// ── Drawing ──────────────────────────────────────────
|
||||||
draw_player()
|
// Enemies and coins are always drawn so the scene stays
|
||||||
|
// coherent even on the fatal-contact frame.
|
||||||
// Enemies — walking on the grass line. anim_tick drives
|
|
||||||
// a small vertical bob so they visibly move on the golden.
|
|
||||||
var ey: u8 = ENEMY_Y
|
var ey: u8 = ENEMY_Y
|
||||||
if (anim_tick & 16) != 0 {
|
if (anim_tick & 16) != 0 {
|
||||||
ey = ENEMY_Y - 2
|
ey = ENEMY_Y - 2
|
||||||
|
|
@ -554,9 +618,6 @@ state Playing {
|
||||||
draw Tileset at: (e1_sx, ey) frame: TILE_ENEMY
|
draw Tileset at: (e1_sx, ey) frame: TILE_ENEMY
|
||||||
draw Tileset at: (e2_sx, ENEMY_Y) frame: TILE_ENEMY
|
draw Tileset at: (e2_sx, ENEMY_Y) frame: TILE_ENEMY
|
||||||
|
|
||||||
// Coins — alternate two vertical positions to fake a
|
|
||||||
// spin via position change (OAM attr flip isn't wired up
|
|
||||||
// in the current runtime).
|
|
||||||
var cy: u8 = COIN_Y
|
var cy: u8 = COIN_Y
|
||||||
if (anim_tick & 8) != 0 {
|
if (anim_tick & 8) != 0 {
|
||||||
cy = COIN_Y - 1
|
cy = COIN_Y - 1
|
||||||
|
|
@ -564,11 +625,85 @@ state Playing {
|
||||||
draw Tileset at: (c1_sx, cy) frame: TILE_COIN
|
draw Tileset at: (c1_sx, cy) frame: TILE_COIN
|
||||||
draw Tileset at: (c2_sx, COIN_Y) frame: TILE_COIN
|
draw Tileset at: (c2_sx, COIN_Y) frame: TILE_COIN
|
||||||
|
|
||||||
|
// Live HUD: draw one coin per stomp in the top-left. The
|
||||||
|
// background palette-1 region covers this strip so the
|
||||||
|
// sprite coins read correctly against the brick band.
|
||||||
|
if stomp_count >= 1 {
|
||||||
|
draw Tileset at: (16, 24) frame: TILE_COIN
|
||||||
|
}
|
||||||
|
if stomp_count >= 2 {
|
||||||
|
draw Tileset at: (28, 24) frame: TILE_COIN
|
||||||
|
}
|
||||||
|
if stomp_count >= 3 {
|
||||||
|
draw Tileset at: (40, 24) frame: TILE_COIN
|
||||||
|
}
|
||||||
|
if stomp_count >= 4 {
|
||||||
|
draw Tileset at: (52, 24) frame: TILE_COIN
|
||||||
|
}
|
||||||
|
|
||||||
|
// Player is only drawn while alive — on the dying frame
|
||||||
|
// we show the scene without the hero on top of the enemy
|
||||||
|
// that killed them, which reads as "player got got".
|
||||||
|
if alive == 1 {
|
||||||
|
draw_player()
|
||||||
|
}
|
||||||
|
|
||||||
// ── PPU scroll latch ─────────────────────────────────
|
// ── PPU scroll latch ─────────────────────────────────
|
||||||
// Write the scroll at the very end of the frame so it's
|
// Write the scroll at the very end of the frame so it's
|
||||||
// the last $2005 pair before the implicit wait_frame /
|
// the last $2005 pair before the implicit wait_frame /
|
||||||
// NMI handshake, minimizing mid-frame artifacts.
|
// NMI handshake, minimizing mid-frame artifacts.
|
||||||
scroll(camera_x, 0)
|
scroll(camera_x, 0)
|
||||||
|
|
||||||
|
// Fatal contact this frame → kick the state machine over
|
||||||
|
// to GameOver on the next frame.
|
||||||
|
if alive == 0 {
|
||||||
|
transition GameOver
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state GameOver {
|
||||||
|
var linger: u8 = 0
|
||||||
|
|
||||||
|
on enter {
|
||||||
|
linger = 0
|
||||||
|
stop_music
|
||||||
|
}
|
||||||
|
|
||||||
|
on frame {
|
||||||
|
linger += 1
|
||||||
|
|
||||||
|
// "GAME OVER" marker: a row of enemy tiles across the
|
||||||
|
// middle of the screen, plus a live readout of how many
|
||||||
|
// stomps the player racked up before dying (drawn as
|
||||||
|
// coins right underneath). The death-screen backdrop is
|
||||||
|
// whatever Playing last scrolled to — we don't clear it.
|
||||||
|
draw Tileset at: (96, 112) frame: TILE_ENEMY
|
||||||
|
draw Tileset at: (112, 112) frame: TILE_ENEMY
|
||||||
|
draw Tileset at: (128, 112) frame: TILE_ENEMY
|
||||||
|
draw Tileset at: (144, 112) frame: TILE_ENEMY
|
||||||
|
|
||||||
|
if stomp_count >= 1 {
|
||||||
|
draw Tileset at: (96, 128) frame: TILE_COIN
|
||||||
|
}
|
||||||
|
if stomp_count >= 2 {
|
||||||
|
draw Tileset at: (112, 128) frame: TILE_COIN
|
||||||
|
}
|
||||||
|
if stomp_count >= 3 {
|
||||||
|
draw Tileset at: (128, 128) frame: TILE_COIN
|
||||||
|
}
|
||||||
|
if stomp_count >= 4 {
|
||||||
|
draw Tileset at: (144, 128) frame: TILE_COIN
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-retry after ~1 s so the headless harness sees the
|
||||||
|
// full loop. A human can hit Start to retry immediately.
|
||||||
|
if linger >= 60 {
|
||||||
|
transition Playing
|
||||||
|
}
|
||||||
|
if button.start {
|
||||||
|
transition Playing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1 +1 @@
|
||||||
a00949db 132084
|
e8b3ea0b 132084
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.6 KiB |
Loading…
Add table
Add a link
Reference in a new issue