mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 08:55:38 +00:00
205 lines
7.4 KiB
Text
205 lines
7.4 KiB
Text
|
|
// Friendly Assets Demo — showcases NEScript's pleasant-to-author
|
|||
|
|
// asset syntax. Every one of the "raw byte" art forms (palettes,
|
|||
|
|
// CHR tiles, nametables, attribute tables, sfx envelopes, music
|
|||
|
|
// note indices) has a friendlier alternative so you don't have to
|
|||
|
|
// reach for a hex editor to make your game look and sound good.
|
|||
|
|
//
|
|||
|
|
// This demo uses every one of them at once:
|
|||
|
|
//
|
|||
|
|
// 1. Named NES colours (`black`, `sky_blue`, `dk_red`, …) inside
|
|||
|
|
// a grouped `palette` declaration with per-slot fields and a
|
|||
|
|
// shared `universal:` colour — auto-fixes the $3F10 mirror.
|
|||
|
|
// 2. A `sprite` declared with ASCII pixel art rather than 16
|
|||
|
|
// bytes of 2-bitplane CHR.
|
|||
|
|
// 3. A `background` laid out with a `legend` + `map:` tilemap
|
|||
|
|
// and a `palette_map:` grid that auto-packs the 64-byte
|
|||
|
|
// attribute table.
|
|||
|
|
// 4. An `sfx` with a scalar `pitch:` (matching the v1 driver's
|
|||
|
|
// latch-once behaviour) and the friendlier `envelope:` alias.
|
|||
|
|
// 5. A `music` track written with note names (`C4, E4 40, rest 10`)
|
|||
|
|
// plus a default `tempo:` so the common case stays concise.
|
|||
|
|
//
|
|||
|
|
// Build: cargo run --release -- build examples/friendly_assets.ne
|
|||
|
|
// Output: examples/friendly_assets.nes
|
|||
|
|
|
|||
|
|
game "Friendly Assets" {
|
|||
|
|
mapper: NROM
|
|||
|
|
mirroring: horizontal
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── Palette ────────────────────────────────────────────────
|
|||
|
|
//
|
|||
|
|
// Grouped form: one `universal:` field feeds every sub-palette's
|
|||
|
|
// shared index-0 byte (fixing the PPU mirror trap where the last
|
|||
|
|
// four bytes of the 32-byte blob would otherwise clobber the
|
|||
|
|
// background universal colour). Each `bgN` / `spN` field only
|
|||
|
|
// needs three colours — `universal` supplies the fourth.
|
|||
|
|
//
|
|||
|
|
// Every colour here is a named constant; see docs/language-guide.md
|
|||
|
|
// for the full list. Hex byte literals (`0x0F`, `0x21`, …) still
|
|||
|
|
// work if you prefer them.
|
|||
|
|
|
|||
|
|
palette Sunset {
|
|||
|
|
universal: black // shared background colour
|
|||
|
|
bg0: [dk_blue, blue, sky_blue] // horizon
|
|||
|
|
bg1: [dk_red, red, peach] // clouds
|
|||
|
|
bg2: [dk_olive, olive, cream] // ground highlights
|
|||
|
|
bg3: [dk_gray, lt_gray, white] // rocks
|
|||
|
|
sp0: [dk_blue, blue, sky_blue] // player body uses bg0 tones
|
|||
|
|
sp1: [dk_red, red, peach] // enemies
|
|||
|
|
sp2: [dk_green, green, mint] // pickups
|
|||
|
|
sp3: [dk_gray, lt_gray, white] // UI / text
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── Sprite ─────────────────────────────────────────────────
|
|||
|
|
//
|
|||
|
|
// ASCII pixel art. Characters map to 2-bit palette indices:
|
|||
|
|
//
|
|||
|
|
// `.` or ` ` → 0 (transparent)
|
|||
|
|
// `#` or `1` → 1 (darker shade)
|
|||
|
|
// `%` or `2` → 2 (mid shade)
|
|||
|
|
// `@` or `3` → 3 (highlight)
|
|||
|
|
//
|
|||
|
|
// 8x8 for a plain tile; multiples of 8 in either dimension for
|
|||
|
|
// multi-tile sprites (emitted in row-major reading order).
|
|||
|
|
|
|||
|
|
sprite Star {
|
|||
|
|
pixels: [
|
|||
|
|
"...@@...",
|
|||
|
|
"..@##@..",
|
|||
|
|
".@####@.",
|
|||
|
|
"@######@",
|
|||
|
|
".@####@.",
|
|||
|
|
"..@##@..",
|
|||
|
|
"...@@...",
|
|||
|
|
"........"
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── Background ─────────────────────────────────────────────
|
|||
|
|
//
|
|||
|
|
// `legend { ... }` names each tile index with a single character;
|
|||
|
|
// `map:` is the 32×30 nametable authored directly as one string
|
|||
|
|
// per row. Short rows are right-padded with tile 0; fewer than 30
|
|||
|
|
// rows pads the bottom with tile 0 as well.
|
|||
|
|
//
|
|||
|
|
// `palette_map:` is a 16×15 grid of sub-palette digits (`0`-`3`)
|
|||
|
|
// where each cell covers one 16×16 metatile. The parser packs it
|
|||
|
|
// into the awkward 8×8 attribute table automatically — no more
|
|||
|
|
// hand-computing `(br<<6)|(bl<<4)|(tr<<2)|tl` by eye.
|
|||
|
|
|
|||
|
|
background Horizon {
|
|||
|
|
legend {
|
|||
|
|
".": 0 // sky (built-in smiley tile as a stand-in)
|
|||
|
|
"S": 0 // star placeholder — same tile
|
|||
|
|
}
|
|||
|
|
// Sparse map: every cell in the first three rows is tile 0.
|
|||
|
|
// This is enough to exercise the tile + attribute pipeline
|
|||
|
|
// without depending on sprite CHR we haven't declared.
|
|||
|
|
map: [
|
|||
|
|
"................................",
|
|||
|
|
"................................",
|
|||
|
|
"................................"
|
|||
|
|
]
|
|||
|
|
// Paint the top two metatile rows (rows 0-1) with sub-palette 1
|
|||
|
|
// so the sky uses the warm cloud colours. The next 13 rows use
|
|||
|
|
// sub-palette 0 (cool blues).
|
|||
|
|
palette_map: [
|
|||
|
|
"1111111111111111",
|
|||
|
|
"1111111111111111",
|
|||
|
|
"0000000000000000",
|
|||
|
|
"0000000000000000",
|
|||
|
|
"0000000000000000",
|
|||
|
|
"0000000000000000",
|
|||
|
|
"0000000000000000",
|
|||
|
|
"0000000000000000",
|
|||
|
|
"0000000000000000",
|
|||
|
|
"0000000000000000",
|
|||
|
|
"0000000000000000",
|
|||
|
|
"0000000000000000",
|
|||
|
|
"0000000000000000",
|
|||
|
|
"0000000000000000",
|
|||
|
|
"0000000000000000"
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── SFX ────────────────────────────────────────────────────
|
|||
|
|
//
|
|||
|
|
// Scalar `pitch:` + `envelope:` alias. The v1 audio driver only
|
|||
|
|
// reads the first `pitch` byte (it latches the pulse period on
|
|||
|
|
// trigger and never updates it) so a per-frame array was always
|
|||
|
|
// redundant — a single byte makes the intent obvious.
|
|||
|
|
|
|||
|
|
sfx Chime {
|
|||
|
|
duty: 2
|
|||
|
|
pitch: 0x40 // latched period byte
|
|||
|
|
envelope: [15, 14, 13, 12, 10, 8, 6, 4, 2, 1]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── Music ──────────────────────────────────────────────────
|
|||
|
|
//
|
|||
|
|
// `tempo:` sets the default frames-per-note; individual notes can
|
|||
|
|
// override it by trailing the name with a frame count. Note names
|
|||
|
|
// are C1..B5 with `Cs4`/`Db4` style accidentals (`#` and `♭` are
|
|||
|
|
// not valid identifier characters so sharp/flat use a letter).
|
|||
|
|
|
|||
|
|
music Waltz {
|
|||
|
|
duty: 2
|
|||
|
|
volume: 10
|
|||
|
|
repeat: true
|
|||
|
|
tempo: 20
|
|||
|
|
notes: [
|
|||
|
|
C4, E4, G4, C5, // rising C major chord
|
|||
|
|
G4 40, // held chord tone
|
|||
|
|
rest 10, // brief pause
|
|||
|
|
E4, C4, // descent
|
|||
|
|
B4, D5, Fs5, B5, // up a fifth with a sharp
|
|||
|
|
A4 30, // landing note
|
|||
|
|
rest 20 // bar break
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── Game state ─────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
var px: u8 = 120
|
|||
|
|
var py: u8 = 112
|
|||
|
|
var tick: u8 = 0
|
|||
|
|
var music_on: bool = false
|
|||
|
|
|
|||
|
|
on frame {
|
|||
|
|
tick += 1
|
|||
|
|
|
|||
|
|
// Let the d-pad nudge the star around the screen.
|
|||
|
|
if button.right { px += 1 }
|
|||
|
|
if button.left { px -= 1 }
|
|||
|
|
if button.down { py += 1 }
|
|||
|
|
if button.up { py -= 1 }
|
|||
|
|
|
|||
|
|
// A / B ping the sfx so the envelope is audible under Mesen.
|
|||
|
|
if button.a { play Chime }
|
|||
|
|
if button.b { play Chime }
|
|||
|
|
|
|||
|
|
// Auto-start the waltz once on the first frame so the jsnes
|
|||
|
|
// golden-capture run (which doesn't simulate input) still
|
|||
|
|
// hits the music driver's start path.
|
|||
|
|
if tick == 10 {
|
|||
|
|
if music_on {
|
|||
|
|
// Already playing — nothing to do.
|
|||
|
|
} else {
|
|||
|
|
start_music Waltz
|
|||
|
|
music_on = true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Keep a baseline sfx chirp so the audio golden is non-silent
|
|||
|
|
// even when the music is between notes.
|
|||
|
|
if tick == 120 {
|
|||
|
|
tick = 0
|
|||
|
|
play Chime
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
draw Star at: (px, py)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
start Main
|