Add trash can/tent/cat smashables, spray paint/backpack bonus items, and

- Expand smash-items spritesheet from 6 to 12 frames (8x2 grid) with new trash can, tent, and cat props plus their smash sound effects
- Expand bonus-items spritesheet from 12 to 18 frames (9x2 grid) with new spray paint and backpack collectibles plus their firework sounds
- Add level12 ("Curb Check") as a designed level and levels 13-20 as placeholder stubs to fill campaign 2's 10-level map
- Give campaign 2 a custom 3-row serpentine layout on the level select screen to differentiate it from campaign 1's two-lane design
- Update editor preview rendering to handle multi-row spritesheet grids and expose the new item types in the placeables panel
This commit is contained in:
Brian Fertig 2026-08-26 19:40:26 -06:00
parent 1eea919c8b
commit e6a37c1b3a
26 changed files with 1144 additions and 52 deletions

View File

@ -53,8 +53,8 @@ no code changes needed.**
| `bus_wheel` | `assets/sprites/bus_wheel.png` | 52x52 | used for both wheels | | `bus_wheel` | `assets/sprites/bus_wheel.png` | 52x52 | used for both wheels |
| `kid_idle` | `assets/sprites/kid_idle.png` | 28x28 | passenger while aboard | | `kid_idle` | `assets/sprites/kid_idle.png` | 28x28 | passenger while aboard |
| `kid_ejected` | `assets/sprites/kid_ejected.png` | 28x28 | passenger after being thrown off | | `kid_ejected` | `assets/sprites/kid_ejected.png` | 28x28 | passenger after being thrown off |
| `smash_items` | `assets/sprites/smash-items.png` | 64x64 x 6 frames | 1x6 spritesheet: intact/smashed TV, chair, cone (see Smashables below) | | `smash_items` | `assets/sprites/smash-items.png` | 64x64 x 12 frames | 8x2 spritesheet: intact/smashed TV, chair, cone, trash can, tent, cat (see Smashables below) |
| `bonus_items` | `assets/sprites/bonus-items.png` | 64x64 x 12 frames | 1x12 spritesheet: CTC box + 2 firework objects, guitar + 2, cash + 2, flour + 2 (see Bonus Items below) | | `bonus_items` | `assets/sprites/bonus-items.png` | 64x64 x 18 frames | 9x2 spritesheet: CTC box + 2 firework objects, guitar + 2, cash + 2, flour + 2, spray paint + 2, backpack + 2 (see Bonus Items below) |
| `bg_far` | `assets/backgrounds/bg_far.png` | 256x540 | tiled, slowest parallax layer | | `bg_far` | `assets/backgrounds/bg_far.png` | 256x540 | tiled, slowest parallax layer |
| `bg_mid` | `assets/backgrounds/bg_mid.png` | 256x540 | tiled, mid parallax layer | | `bg_mid` | `assets/backgrounds/bg_mid.png` | 256x540 | tiled, mid parallax layer |
| `bg_near` | `assets/backgrounds/bg_near.png` | 256x540 | tiled, fastest parallax layer | | `bg_near` | `assets/backgrounds/bg_near.png` | 256x540 | tiled, fastest parallax layer |
@ -80,17 +80,21 @@ Everything worth tweaking lives in `src/config.js`, notably:
Set `DEBUG = true` in `config.js` to show a live g-force readout and Matter's Set `DEBUG = true` in `config.js` to show a live g-force readout and Matter's
physics debug overlay in `PlayScene`. physics debug overlay in `PlayScene`.
## Smashables (TV / chair / cone) ## Smashables (TV / chair / cone / trash can / tent / cat)
`assets/sprites/smash-items.png` is a 1x6 spritesheet (TV, smashed TV, chair, `assets/sprites/smash-items.png` is an 8x2 spritesheet (TV, smashed TV, chair,
smashed chair, cone, smashed cone), loaded as the `smash_items` texture smashed chair, cone, smashed cone, trash can, smashed trash can, tent, smashed
tent, cat, smashed cat), loaded as the `smash_items` texture
(64x64 per frame). Any level can carry an optional `items` array (64x64 per frame). Any level can carry an optional `items` array
(see the editor's export for the exact shape): (see the editor's export for the exact shape):
```js ```js
items: [ items: [
{ type: 'tv', mode: 'float', x: 2500, y: 660 }, { type: 'tv', mode: 'float', x: 2500, y: 660 },
{ type: 'chair', mode: 'gravity', x: 3500, y: 760 }, { type: 'chair', mode: 'gravity', x: 3500, y: 760 },
{ type: 'trashcan', mode: 'float', x: 4500, y: 640 },
{ type: 'tent', mode: 'float', x: 5500, y: 620 },
{ type: 'cat', mode: 'gravity', x: 6500, y: 780 },
] ]
``` ```
@ -107,8 +111,8 @@ Smashed items also tally as +`SCORE.perSmashItem` (50) each on the
end-of-level score screen. end-of-level score screen.
The **level editor** (`editor.html`) has a "Placeables" section with a kind The **level editor** (`editor.html`) has a "Placeables" section with a kind
toggle: **Smashables** (TV / chair / cone, float or gravity mode) and toggle: **Smashables** (TV / chair / cone / trash can / tent / cat, float or
**Bonus items** (CTC box / guitar / cash / flour, float-only). Pick the kind + type, gravity mode) and **Bonus items** (CTC box / guitar / cash / flour, float-only). Pick the kind + type,
then click anywhere on the preview map to drop one there; click an existing then click anywhere on the preview map to drop one there; click an existing
item (on the map or in the list) to remove it. Exporting the level writes item (on the map or in the list) to remove it. Exporting the level writes
both the `items` and `bonusItems` arrays into the level file automatically. both the `items` and `bonusItems` arrays into the level file automatically.
@ -123,11 +127,12 @@ description / kids) and both placeable types stay editable, so you can tweak
them and re-export. "Start a new level" clears the import and unlocks the them and re-export. "Start a new level" clears the import and unlocks the
section controls again. section controls again.
## Bonus Items (CTC box / guitar / cash / flour) ## Bonus Items (CTC box / guitar / cash / flour / spray paint / backpack)
`assets/sprites/bonus-items.png` is a 1x12 spritesheet (CTC box, 2 firework `assets/sprites/bonus-items.png` is a 9x2 spritesheet (CTC box, 2 firework
frames, guitar, 2 firework frames, cash, 2 firework frames, flour, 2 firework frames, guitar, 2 firework frames, cash, 2 firework frames, flour, 2 firework
frames - 64x64 per frame), loaded as the `bonus_items` texture. Any level can carry an optional frames, spray paint, 2 firework frames, backpack, 2 firework frames - 64x64 per
frame), loaded as the `bonus_items` texture. Any level can carry an optional
`bonusItems` array (see the editor's export for the exact shape): `bonusItems` array (see the editor's export for the exact shape):
```js ```js
@ -155,11 +160,13 @@ kids (same trick as `SmashItemManager._smash`) so it can never bump the bus
or flick a kid out of the compartment. It does still collide with terrain or flick a kid out of the compartment. It does still collide with terrain
so it lands where it lands. Particles are tinted per the bonus type's own so it lands where it lands. Particles are tinted per the bonus type's own
color scheme (`BONUS_ITEM.colors`): CTC = warm toast-brown/cinnamon, guitar color scheme (`BONUS_ITEM.colors`): CTC = warm toast-brown/cinnamon, guitar
= bright/deep green, cash = muted sage/olive-grey, flour = warm off-white/tan. = bright/deep green, cash = muted sage/olive-grey, flour = warm off-white/tan,
spray paint = pale red/blue, backpack = tan/brown.
Some types override the burst's look entirely via Some types override the burst's look entirely via
`BONUS_ITEM.fireworkVariants`: flour's puffs launch 2-3x the standard `BONUS_ITEM.fireworkVariants`: flour's puffs launch 2-3x the standard
particle size at half opacity (a soft cloud of dust, still with the particle size at half opacity (a soft cloud of dust, still with the
standard-sized physics bodies underneath). standard-sized physics bodies underneath), and spray paint's puffs keep the
standard size but run at half opacity (a semi-transparent spray mist).
Collected bonus items tally as `SCORE.perBonusItem` (100) each on the Collected bonus items tally as `SCORE.perBonusItem` (100) each on the
end-of-level score screen, revealed right after the smashed-props line. end-of-level score screen, revealed right after the smashed-props line.

Binary file not shown.

Binary file not shown.

BIN
assets/fx/smash-cat.mp3 Normal file

Binary file not shown.

BIN
assets/fx/smash-tent.mp3 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

View File

@ -216,16 +216,18 @@ export const SCORE = {
perKidSaved: 100, perKidSaved: 100,
perKidLost: -50, perKidLost: -50,
perSecondRemaining: 10, perSecondRemaining: 10,
// Per smashable item (TV/chair/cone, see SMASH_ITEM) knocked off its // Per smashable item (TV/chair/cone/trash can/tent/cat, see SMASH_ITEM)
// perch by the bus - revealed on the tally between the kids and the clock. // knocked off its perch by the bus - revealed on the tally between the
// kids and the clock.
perSmashItem: 50, perSmashItem: 50,
// Per bonus item (CTC box / guitar / cash - see BONUS_ITEM) the bus plows // Per bonus item (CTC box / guitar / cash - see BONUS_ITEM) the bus plows
// through - revealed on the tally right after the smashed-props line. // through - revealed on the tally right after the smashed-props line.
perBonusItem: 100, perBonusItem: 100,
}; };
// Smashable items (assets/sprites/smash-items.png - 6 frames of 64x64, one // Smashable items (assets/sprites/smash-items.png - 12 frames of 64x64 in
// intact + one smashed variant per prop, see src/entities/SmashItemManager.js // an 8-wide x 2-row grid, one intact + one smashed variant per prop:
// TV, chair, cone, trash can, tent, cat - see src/entities/SmashItemManager.js
// for the type/frame table). Placed per-level via the editor ("Smashables" // for the type/frame table). Placed per-level via the editor ("Smashables"
// section) and exported into the level's `items` array. // section) and exported into the level's `items` array.
// //
@ -317,15 +319,17 @@ export const BONUS_ITEM = {
particleDisplaySize: 40 * WORLD_SCALE, // each firework particle is shown square at this size (80 world px, half the bonus item's own 128) particleDisplaySize: 40 * WORLD_SCALE, // each firework particle is shown square at this size (80 world px, half the bonus item's own 128)
// Optional per-type firework overrides (defaults: plain particleDisplaySize, // Optional per-type firework overrides (defaults: plain particleDisplaySize,
// full opacity). Flour's dust puffs are the only type that uses one: each // full opacity). Two types use one: flour's dust puffs are shown 2-3x the
// firework sprite is shown 2-3x the normal particle size at ~half opacity, // normal particle size at ~half opacity, so a hit reads as a soft cloud of
// so a hit reads as a soft cloud of dust billowing over the bus instead of // dust billowing over the bus instead of a confetti pop; spraypaint's puffs
// a confetti pop. Read per item in BonusItemManager._explode (variant = // keep the standard size but go half-opacity, reading as a semi-transparent
// spray mist. Read per item in BonusItemManager._explode (variant =
// BONUS_ITEM.fireworkVariants[item.spec.type] || {}). NOTE: this is purely // BONUS_ITEM.fireworkVariants[item.spec.type] || {}). NOTE: this is purely
// visual - the particles' physics bodies stay at the standard size (below), // visual - the particles' physics bodies stay at the standard size (below),
// so the bigger sprites don't change how they settle on the terrain. // so the bigger/transparent sprites don't change how they settle on the terrain.
fireworkVariants: { fireworkVariants: {
flour: { scaleMin: 2, scaleMax: 3, alpha: 0.5 }, flour: { scaleMin: 2, scaleMax: 3, alpha: 0.5 },
spraypaint: { alpha: 0.5 },
}, },
// Tint per bonus type, matched to each item's art: the firework sprites // Tint per bonus type, matched to each item's art: the firework sprites
@ -339,6 +343,8 @@ export const BONUS_ITEM = {
guitar: { main: 0x50c050, accent: 0x105018 }, // bright green / deep forest guitar: { main: 0x50c050, accent: 0x105018 }, // bright green / deep forest
cash: { main: 0xaab888, accent: 0x404030 }, // muted sage / olive-grey (cash's own scheme is near-white grey-green) cash: { main: 0xaab888, accent: 0x404030 }, // muted sage / olive-grey (cash's own scheme is near-white grey-green)
flour: { main: 0xf4f1e8, accent: 0xcbb58e }, // warm off-white / tan (flour's own scheme is pale beige) flour: { main: 0xf4f1e8, accent: 0xcbb58e }, // warm off-white / tan (flour's own scheme is pale beige)
spraypaint: { main: 0xe8b0a0, accent: 0x4058b8 }, // pale red / blue (its two firework frames are red and blue)
backpack: { main: 0xd8c0a0, accent: 0x705030 }, // pale tan / dark brown (backpack's own scheme is warm brown)
}, },
}; };

View File

@ -9,6 +9,15 @@ import level08 from './level08.js';
import level09 from './level09.js'; import level09 from './level09.js';
import level10 from './level10.js'; import level10 from './level10.js';
import level11 from './level11.js'; import level11 from './level11.js';
import level12 from './level12.js';
import level13 from './level13.js';
import level14 from './level14.js';
import level15 from './level15.js';
import level16 from './level16.js';
import level17 from './level17.js';
import level18 from './level18.js';
import level19 from './level19.js';
import level20 from './level20.js';
// Campaigns group levels into themed "maps" for the level select screen. // Campaigns group levels into themed "maps" for the level select screen.
// Each campaign gets its own background art (bgKey), menu theme music // Each campaign gets its own background art (bgKey), menu theme music
@ -37,10 +46,34 @@ export const CAMPAIGNS = [
// Menu music for this campaign's map in LevelSelect (util/music.js // Menu music for this campaign's map in LevelSelect (util/music.js
// playMenuTrack); undefined = the main title theme. // playMenuTrack); undefined = the main title theme.
musicKey: 'urban_trap_theme', musicKey: 'urban_trap_theme',
// Its old levels (level04-06) moved into campaign01 while it grows toward // Its old levels (level04-06) moved into campaign01 while it grew toward
// 10; add campaign02's own levels here as they're built. An empty // 10; now full at 10 (level11-level20). level12 is a real designed level
// campaign renders as a bare map with "0/0 cleared" - fine as scaffolding. // (built via editor/levelBuilder.js's section pipeline, not hand-drawn in
levels: [level11], // the browser editor - review/playtest before shipping); level13-level20
// are placeholder stubs (flat filler terrain, "Coming Soon" name/desc) to
// reserve their slots on the map - replace each in editor.html as it gets
// designed for real.
levels: [level11, level12, level13, level14, level15, level16, level17, level18, level19, level20],
// Custom layout (see positioning.js / the module comment above) so this
// campaign's map doesn't read as a reskin of campaign01's 2-lane loop: a
// 3-row serpentine (row1 left->right, row2 right->left, row3 left->right)
// instead of campaign01's top/bottom lane pair. Same 960x540 design-unit
// space; rows sit at y=220/320/420 (vs campaign01's 268/412 two-lane
// band) and x stays within 100-750 on every row so the flag's nudge past
// the last node (see LevelSelectScene._buildCampaign) lands in-bounds
// without needing to flip direction.
positions: [
{ x: 100, y: 220 }, // level11
{ x: 317, y: 220 }, // level12
{ x: 533, y: 220 }, // level13
{ x: 750, y: 220 }, // level14
{ x: 750, y: 320 }, // level15
{ x: 425, y: 320 }, // level16
{ x: 100, y: 320 }, // level17
{ x: 100, y: 420 }, // level18
{ x: 425, y: 420 }, // level19
{ x: 750, y: 420 }, // level20
],
}, },
]; ];

597
src/data/levels/level12.js Normal file
View File

@ -0,0 +1,597 @@
// Generated by editor.html - review before dropping into src/data/levels/.
export default {
id: "level12",
name: "Curb Check",
description: "Check yo self before you wreck yo self",
kidsAboard: 4,
timeLimit: 66,
startPosition: { x: 200, y: 700 },
startAngle: 0,
terrain: [
{ points: [
{ x: -400, y: 840 },
{ x: -330, y: 840 },
{ x: -260, y: 840 },
{ x: -190, y: 840 },
{ x: -120, y: 840 },
{ x: -50, y: 840 },
{ x: 20, y: 840 },
{ x: 90, y: 840 },
{ x: 160, y: 840 },
{ x: 230, y: 840 },
{ x: 300, y: 840 },
{ x: 370, y: 840 },
{ x: 440, y: 840 },
{ x: 510, y: 840 },
{ x: 580, y: 840 },
{ x: 650, y: 840 },
{ x: 720, y: 840 },
{ x: 790, y: 840 },
{ x: 860, y: 840 },
{ x: 930, y: 840 },
{ x: 1000, y: 840 },
{ x: 1070, y: 840 },
{ x: 1140, y: 838 },
{ x: 1210, y: 836 },
{ x: 1280, y: 834 },
{ x: 1350, y: 828 },
{ x: 1420, y: 822 },
{ x: 1490, y: 816 },
{ x: 1560, y: 808 },
{ x: 1630, y: 802 },
{ x: 1700, y: 794 },
{ x: 1770, y: 786 },
{ x: 1840, y: 776 },
{ x: 1910, y: 768 },
{ x: 1980, y: 758 },
{ x: 2050, y: 750 },
{ x: 2120, y: 742 },
{ x: 2190, y: 732 },
{ x: 2260, y: 724 },
{ x: 2330, y: 714 },
{ x: 2400, y: 706 },
{ x: 2470, y: 698 },
{ x: 2540, y: 692 },
{ x: 2610, y: 684 },
{ x: 2680, y: 678 },
{ x: 2750, y: 674 },
{ x: 2820, y: 668 },
{ x: 2890, y: 666 },
{ x: 2960, y: 662 },
{ x: 3030, y: 660 },
{ x: 3100, y: 660 },
{ x: 3170, y: 660 },
{ x: 3240, y: 656 },
{ x: 3310, y: 650 },
{ x: 3380, y: 640 },
{ x: 3450, y: 628 },
{ x: 3520, y: 612 },
{ x: 3590, y: 596 },
{ x: 3660, y: 580 },
{ x: 3730, y: 564 },
{ x: 3800, y: 548 },
{ x: 3870, y: 532 },
{ x: 3940, y: 516 },
{ x: 4010, y: 500 },
{ x: 4080, y: 484 },
{ x: 4150, y: 468 },
{ x: 4220, y: 452 },
{ x: 4290, y: 436 },
{ x: 4360, y: 420 },
{ x: 4430, y: 404 },
{ x: 4500, y: 388 },
{ x: 4570, y: 372 },
{ x: 4640, y: 354 },
{ x: 4710, y: 332 },
{ x: 4780, y: 308 },
{ x: 4850, y: 280 },
{ x: 4920, y: 250 },
{ x: 4990, y: 220 },
{ x: 5060, y: 192 },
{ x: 5130, y: 162 },
{ x: 5200, y: 132 },
{ x: 5270, y: 102 },
{ x: 5340, y: 72 },
{ x: 5410, y: 44 },
{ x: 5480, y: 14 },
{ x: 5550, y: -16 },
{ x: 5620, y: -46 },
{ x: 5690, y: -76 },
{ x: 5760, y: -104 },
{ x: 5830, y: -134 },
{ x: 5900, y: -164 },
{ x: 5970, y: -194 },
{ x: 6040, y: -226 },
{ x: 6110, y: -260 },
{ x: 6180, y: -296 },
{ x: 6250, y: -334 },
{ x: 6320, y: -374 },
{ x: 6390, y: -414 },
{ x: 6460, y: -454 },
{ x: 6530, y: -494 },
{ x: 6600, y: -534 },
{ x: 6670, y: -574 },
{ x: 6740, y: -614 },
{ x: 6810, y: -654 },
{ x: 6880, y: -694 },
{ x: 6950, y: -734 },
{ x: 7020, y: -774 },
{ x: 7090, y: -814 },
{ x: 7160, y: -854 },
{ x: 7230, y: -894 },
{ x: 7300, y: -934 },
{ x: 7370, y: -976 },
{ x: 7440, y: -1024 },
{ x: 7510, y: -1082 },
{ x: 7580, y: -1148 },
{ x: 7650, y: -1216 },
{ x: 7720, y: -1284 },
{ x: 7790, y: -1354 },
{ x: 7860, y: -1422 },
{ x: 7930, y: -1490 },
{ x: 8000, y: -1558 },
{ x: 8070, y: -1626 },
{ x: 8120, y: -1676 },
{ x: 8120, y: -1674 },
{ x: 8190, y: -1074 },
{ x: 8260, y: -1068 },
{ x: 8330, y: -1062 },
{ x: 8400, y: -1056 },
{ x: 8470, y: -1052 },
{ x: 8540, y: -1046 },
{ x: 8610, y: -1040 },
{ x: 8680, y: -1034 },
{ x: 8750, y: -1028 },
{ x: 8820, y: -1022 },
{ x: 8890, y: -1016 },
{ x: 8960, y: -1010 },
{ x: 9030, y: -1004 },
{ x: 9100, y: -1000 },
{ x: 9170, y: -994 },
{ x: 9240, y: -988 },
{ x: 9310, y: -982 },
{ x: 9380, y: -976 },
{ x: 9450, y: -970 },
{ x: 9520, y: -964 },
{ x: 9590, y: -958 },
{ x: 9660, y: -948 },
{ x: 9730, y: -934 },
{ x: 9800, y: -916 },
{ x: 9870, y: -892 },
{ x: 9940, y: -866 },
{ x: 10010, y: -838 },
{ x: 10080, y: -810 },
{ x: 10150, y: -784 },
{ x: 10220, y: -756 },
{ x: 10290, y: -730 },
{ x: 10360, y: -702 },
{ x: 10430, y: -674 },
{ x: 10500, y: -648 },
{ x: 10570, y: -620 },
{ x: 10640, y: -594 },
{ x: 10710, y: -566 },
{ x: 10780, y: -538 },
{ x: 10850, y: -512 },
{ x: 10920, y: -484 },
{ x: 10990, y: -458 },
{ x: 11060, y: -434 },
{ x: 11130, y: -418 },
{ x: 11200, y: -408 },
{ x: 11270, y: -402 },
{ x: 11340, y: -402 },
{ x: 11410, y: -402 },
{ x: 11480, y: -402 },
{ x: 11550, y: -402 },
{ x: 11620, y: -402 },
{ x: 11690, y: -402 },
{ x: 11760, y: -402 },
{ x: 11830, y: -402 },
{ x: 11900, y: -402 },
{ x: 11970, y: -402 },
{ x: 12040, y: -402 },
{ x: 12110, y: -402 },
{ x: 12180, y: -402 },
{ x: 12250, y: -402 },
{ x: 12320, y: -402 },
{ x: 12390, y: -402 },
{ x: 12460, y: -402 },
{ x: 12530, y: -402 },
{ x: 12600, y: -402 },
{ x: 12670, y: -402 },
{ x: 12740, y: -402 },
{ x: 12810, y: -402 },
{ x: 12880, y: -402 },
{ x: 12950, y: -402 },
{ x: 13020, y: -402 },
{ x: 13090, y: -402 },
{ x: 13160, y: -402 },
{ x: 13230, y: -402 },
{ x: 13300, y: -402 },
{ x: 13328, y: -402 },
{ x: 13398, y: -406 },
{ x: 13468, y: -420 },
{ x: 13538, y: -442 },
{ x: 13608, y: -474 },
{ x: 13678, y: -514 },
{ x: 13720, y: -542 },
{ x: 13790, y: -542 },
] },
{ points: [
{ x: 14280, y: -542 },
{ x: 14350, y: -542 },
{ x: 14420, y: -542 },
{ x: 14490, y: -542 },
{ x: 14560, y: -542 },
{ x: 14630, y: -542 },
{ x: 14700, y: -542 },
{ x: 14770, y: -542 },
{ x: 14840, y: -542 },
{ x: 14910, y: -542 },
{ x: 14980, y: -542 },
{ x: 15050, y: -542 },
{ x: 15120, y: -542 },
{ x: 15190, y: -542 },
] },
{ points: [
{ x: 15568, y: -62 },
{ x: 15638, y: -62 },
{ x: 15680, y: -62 },
{ x: 15750, y: -62 },
{ x: 15820, y: -62 },
{ x: 15890, y: -62 },
{ x: 15960, y: -62 },
{ x: 16030, y: -62 },
{ x: 16100, y: -62 },
{ x: 16170, y: -62 },
{ x: 16240, y: -62 },
{ x: 16310, y: -62 },
{ x: 16380, y: -62 },
{ x: 16450, y: -62 },
{ x: 16520, y: -62 },
{ x: 16590, y: -62 },
{ x: 16660, y: -62 },
{ x: 16730, y: -62 },
{ x: 16800, y: -62 },
{ x: 16870, y: -62 },
{ x: 16940, y: -62 },
{ x: 17010, y: -62 },
{ x: 17080, y: -62 },
{ x: 17150, y: -98 },
{ x: 17220, y: -132 },
{ x: 17290, y: -168 },
{ x: 17360, y: -202 },
{ x: 17430, y: -238 },
{ x: 17500, y: -272 },
{ x: 17570, y: -308 },
{ x: 17640, y: -342 },
{ x: 17710, y: -342 },
] },
{ points: [
{ x: 18060, y: -222 },
{ x: 18130, y: -210 },
{ x: 18200, y: -180 },
{ x: 18270, y: -142 },
{ x: 18340, y: -104 },
{ x: 18410, y: -74 },
{ x: 18480, y: -62 },
{ x: 18550, y: -62 },
{ x: 18620, y: -62 },
{ x: 18690, y: -62 },
{ x: 18760, y: -62 },
{ x: 18830, y: -62 },
{ x: 18900, y: -62 },
{ x: 18970, y: -62 },
{ x: 19040, y: -62 },
{ x: 19110, y: -62 },
{ x: 19180, y: -62 },
{ x: 19250, y: -62 },
{ x: 19320, y: -62 },
{ x: 19390, y: -62 },
{ x: 19460, y: -62 },
{ x: 19488, y: -62 },
{ x: 19558, y: -66 },
{ x: 19628, y: -80 },
{ x: 19698, y: -102 },
{ x: 19768, y: -134 },
{ x: 19838, y: -174 },
{ x: 19880, y: -202 },
{ x: 19897, y: -216 },
{ x: 19913, y: -228 },
{ x: 19930, y: -238 },
{ x: 19947, y: -246 },
{ x: 19963, y: -250 },
{ x: 19980, y: -250 },
{ x: 19997, y: -248 },
{ x: 20013, y: -240 },
{ x: 20030, y: -232 },
{ x: 20047, y: -218 },
{ x: 20063, y: -204 },
{ x: 20080, y: -188 },
{ x: 20097, y: -174 },
{ x: 20113, y: -158 },
{ x: 20130, y: -146 },
{ x: 20147, y: -134 },
{ x: 20163, y: -126 },
{ x: 20180, y: -122 },
{ x: 20197, y: -120 },
{ x: 20213, y: -122 },
{ x: 20230, y: -128 },
{ x: 20247, y: -136 },
{ x: 20263, y: -144 },
{ x: 20280, y: -154 },
{ x: 20297, y: -164 },
{ x: 20313, y: -174 },
{ x: 20330, y: -180 },
{ x: 20347, y: -184 },
{ x: 20363, y: -186 },
{ x: 20380, y: -184 },
{ x: 20397, y: -178 },
{ x: 20413, y: -170 },
{ x: 20430, y: -156 },
{ x: 20447, y: -142 },
{ x: 20463, y: -126 },
{ x: 20480, y: -108 },
{ x: 20497, y: -90 },
{ x: 20513, y: -74 },
{ x: 20530, y: -58 },
{ x: 20547, y: -46 },
{ x: 20563, y: -36 },
{ x: 20580, y: -30 },
{ x: 20597, y: -28 },
{ x: 20613, y: -28 },
{ x: 20630, y: -32 },
{ x: 20647, y: -38 },
{ x: 20663, y: -48 },
{ x: 20680, y: -56 },
{ x: 20697, y: -66 },
{ x: 20713, y: -74 },
{ x: 20730, y: -80 },
{ x: 20747, y: -84 },
{ x: 20763, y: -86 },
{ x: 20780, y: -84 },
{ x: 20797, y: -78 },
{ x: 20813, y: -70 },
{ x: 20830, y: -58 },
{ x: 20847, y: -42 },
{ x: 20863, y: -26 },
{ x: 20880, y: -10 },
{ x: 20897, y: 8 },
{ x: 20913, y: 24 },
{ x: 20930, y: 38 },
{ x: 20947, y: 48 },
{ x: 20963, y: 56 },
{ x: 20980, y: 62 },
{ x: 20997, y: 62 },
{ x: 21013, y: 60 },
{ x: 21030, y: 54 },
{ x: 21047, y: 46 },
{ x: 21063, y: 36 },
{ x: 21080, y: 24 },
{ x: 21097, y: 14 },
{ x: 21113, y: 2 },
{ x: 21130, y: -6 },
{ x: 21147, y: -14 },
{ x: 21163, y: -16 },
{ x: 21180, y: -18 },
{ x: 21197, y: -14 },
{ x: 21213, y: -8 },
{ x: 21230, y: 0 },
{ x: 21247, y: 12 },
{ x: 21263, y: 24 },
{ x: 21280, y: 38 },
{ x: 21297, y: 52 },
{ x: 21313, y: 66 },
{ x: 21330, y: 78 },
{ x: 21346, y: 92 },
{ x: 21363, y: 104 },
{ x: 21379, y: 114 },
{ x: 21396, y: 124 },
{ x: 21412, y: 134 },
{ x: 21429, y: 142 },
{ x: 21445, y: 150 },
{ x: 21462, y: 156 },
{ x: 21478, y: 162 },
{ x: 21495, y: 166 },
{ x: 21512, y: 168 },
{ x: 21528, y: 172 },
{ x: 21545, y: 172 },
{ x: 21561, y: 174 },
{ x: 21578, y: 174 },
{ x: 21594, y: 172 },
{ x: 21611, y: 170 },
{ x: 21627, y: 168 },
{ x: 21644, y: 166 },
{ x: 21660, y: 162 },
{ x: 21677, y: 158 },
{ x: 21693, y: 152 },
{ x: 21710, y: 148 },
{ x: 21727, y: 142 },
{ x: 21743, y: 138 },
{ x: 21760, y: 134 },
{ x: 21776, y: 134 },
{ x: 21793, y: 136 },
{ x: 21809, y: 140 },
{ x: 21826, y: 146 },
{ x: 21842, y: 154 },
{ x: 21859, y: 162 },
{ x: 21875, y: 172 },
{ x: 21892, y: 180 },
{ x: 21908, y: 190 },
{ x: 21925, y: 196 },
{ x: 21942, y: 202 },
{ x: 21958, y: 206 },
{ x: 21975, y: 208 },
{ x: 21991, y: 208 },
{ x: 22008, y: 206 },
{ x: 22024, y: 204 },
{ x: 22041, y: 198 },
{ x: 22057, y: 192 },
{ x: 22074, y: 184 },
{ x: 22090, y: 178 },
{ x: 22107, y: 172 },
{ x: 22123, y: 166 },
{ x: 22140, y: 162 },
{ x: 22157, y: 160 },
{ x: 22173, y: 160 },
{ x: 22190, y: 162 },
{ x: 22206, y: 168 },
{ x: 22223, y: 174 },
{ x: 22239, y: 182 },
{ x: 22256, y: 190 },
{ x: 22272, y: 200 },
{ x: 22289, y: 208 },
{ x: 22305, y: 218 },
{ x: 22322, y: 226 },
{ x: 22338, y: 232 },
{ x: 22355, y: 236 },
{ x: 22372, y: 238 },
{ x: 22388, y: 240 },
{ x: 22405, y: 238 },
{ x: 22421, y: 234 },
{ x: 22438, y: 228 },
{ x: 22454, y: 222 },
{ x: 22471, y: 216 },
{ x: 22487, y: 210 },
{ x: 22504, y: 202 },
{ x: 22520, y: 198 },
{ x: 22537, y: 194 },
{ x: 22553, y: 192 },
{ x: 22570, y: 192 },
{ x: 22587, y: 196 },
{ x: 22603, y: 200 },
{ x: 22620, y: 206 },
{ x: 22636, y: 214 },
{ x: 22653, y: 222 },
{ x: 22669, y: 232 },
{ x: 22686, y: 242 },
{ x: 22702, y: 250 },
{ x: 22719, y: 258 },
{ x: 22735, y: 264 },
{ x: 22752, y: 270 },
{ x: 22768, y: 272 },
{ x: 22785, y: 272 },
{ x: 22802, y: 270 },
{ x: 22818, y: 266 },
{ x: 22835, y: 262 },
{ x: 22851, y: 256 },
{ x: 22868, y: 248 },
{ x: 22884, y: 242 },
{ x: 22901, y: 236 },
{ x: 22917, y: 230 },
{ x: 22934, y: 226 },
{ x: 22950, y: 224 },
{ x: 22967, y: 224 },
{ x: 22983, y: 228 },
{ x: 23000, y: 232 },
{ x: 23017, y: 238 },
{ x: 23033, y: 246 },
{ x: 23050, y: 254 },
{ x: 23066, y: 264 },
{ x: 23083, y: 272 },
{ x: 23099, y: 282 },
{ x: 23116, y: 290 },
{ x: 23132, y: 296 },
{ x: 23149, y: 300 },
{ x: 23165, y: 302 },
{ x: 23182, y: 302 },
{ x: 23198, y: 300 },
{ x: 23215, y: 296 },
{ x: 23232, y: 290 },
{ x: 23248, y: 284 },
{ x: 23265, y: 276 },
{ x: 23281, y: 270 },
{ x: 23298, y: 262 },
{ x: 23314, y: 258 },
{ x: 23331, y: 254 },
{ x: 23347, y: 252 },
{ x: 23364, y: 250 },
{ x: 23380, y: 252 },
{ x: 23397, y: 256 },
{ x: 23413, y: 262 },
{ x: 23430, y: 270 },
{ x: 23447, y: 278 },
{ x: 23463, y: 286 },
{ x: 23480, y: 296 },
{ x: 23496, y: 304 },
{ x: 23513, y: 310 },
{ x: 23529, y: 316 },
{ x: 23546, y: 320 },
{ x: 23562, y: 322 },
{ x: 23579, y: 322 },
{ x: 23595, y: 318 },
{ x: 23612, y: 314 },
{ x: 23628, y: 308 },
{ x: 23645, y: 302 },
{ x: 23662, y: 294 },
{ x: 23678, y: 286 },
{ x: 23695, y: 278 },
{ x: 23711, y: 272 },
{ x: 23728, y: 268 },
{ x: 23744, y: 266 },
{ x: 23761, y: 264 },
{ x: 23777, y: 266 },
{ x: 23794, y: 268 },
{ x: 23810, y: 274 },
{ x: 23827, y: 280 },
{ x: 23843, y: 288 },
{ x: 23860, y: 296 },
{ x: 23930, y: 296 },
{ x: 24000, y: 296 },
{ x: 24070, y: 296 },
{ x: 24140, y: 296 },
{ x: 24210, y: 296 },
{ x: 24280, y: 296 },
{ x: 24350, y: 296 },
{ x: 24420, y: 296 },
{ x: 24490, y: 296 },
{ x: 24560, y: 296 },
{ x: 24630, y: 296 },
{ x: 24700, y: 296 },
{ x: 24770, y: 296 },
{ x: 24840, y: 296 },
{ x: 24910, y: 296 },
{ x: 24980, y: 296 },
{ x: 25050, y: 296 },
{ x: 25120, y: 296 },
{ x: 25190, y: 296 },
{ x: 25260, y: 296 },
{ x: 25330, y: 296 },
{ x: 25400, y: 296 },
{ x: 25470, y: 296 },
{ x: 25540, y: 296 },
{ x: 25610, y: 296 },
{ x: 25680, y: 296 },
{ x: 25750, y: 296 },
{ x: 25820, y: 296 },
{ x: 25890, y: 296 },
{ x: 25960, y: 296 },
{ x: 26030, y: 296 },
{ x: 26100, y: 296 },
{ x: 26170, y: 296 },
{ x: 26240, y: 296 },
{ x: 26310, y: 296 },
{ x: 26380, y: 296 },
{ x: 26450, y: 296 },
{ x: 26520, y: 296 },
{ x: 26590, y: 296 },
{ x: 26660, y: 296 },
] },
],
obstacles: [],
items: [
{ type: "trashcan", mode: "gravity", x: 16821, y: -468 },
{ type: "cat", mode: "gravity", x: 17154, y: -443 },
{ type: "cat", mode: "gravity", x: 11904, y: -826 },
{ type: "tent", mode: "gravity", x: 19496, y: -268 },
{ type: "trashcan", mode: "gravity", x: 26054, y: -9 },
{ type: "trashcan", mode: "gravity", x: 25796, y: 41 },
{ type: "trashcan", mode: "gravity", x: 25546, y: -134 },
],
bonusItems: [
{ type: "spraypaint", x: 8371, y: -1534 },
{ type: "backpack", x: 15579, y: -476 },
{ type: "flour", x: 20263, y: -659 },
],
goal: { x: 26420, y: 57, width: 280, height: 640 },
cameraBounds: { x: -600, y: -2476, width: 28060, height: 4116 },
};

View File

@ -0,0 +1,51 @@
// Generated by editor.html - review before dropping into src/data/levels/.
export default {
id: "level13",
name: "Coming Soon",
description: "Level design in progress - stub terrain, replace in editor.html.",
kidsAboard: 3,
timeLimit: 15,
startPosition: { x: 200, y: 700 },
startAngle: 0,
terrain: [
{ points: [
{ x: -400, y: 840 },
{ x: -330, y: 840 },
{ x: -260, y: 840 },
{ x: -190, y: 840 },
{ x: -120, y: 840 },
{ x: -50, y: 840 },
{ x: 20, y: 840 },
{ x: 90, y: 840 },
{ x: 160, y: 840 },
{ x: 230, y: 840 },
{ x: 300, y: 840 },
{ x: 370, y: 840 },
{ x: 440, y: 840 },
{ x: 510, y: 840 },
{ x: 580, y: 840 },
{ x: 600, y: 840 },
{ x: 670, y: 836 },
{ x: 740, y: 828 },
{ x: 810, y: 814 },
{ x: 880, y: 800 },
{ x: 950, y: 784 },
{ x: 1020, y: 770 },
{ x: 1090, y: 758 },
{ x: 1160, y: 752 },
{ x: 1200, y: 750 },
{ x: 1270, y: 750 },
{ x: 1340, y: 750 },
{ x: 1410, y: 750 },
{ x: 1480, y: 750 },
{ x: 1550, y: 750 },
{ x: 1620, y: 750 },
{ x: 1690, y: 750 },
{ x: 1760, y: 750 },
{ x: 1800, y: 750 },
] },
],
obstacles: [],
goal: { x: 1560, y: 510, width: 280, height: 640 },
cameraBounds: { x: -600, y: -50, width: 3200, height: 2000 },
};

View File

@ -0,0 +1,51 @@
// Generated by editor.html - review before dropping into src/data/levels/.
export default {
id: "level14",
name: "Coming Soon",
description: "Level design in progress - stub terrain, replace in editor.html.",
kidsAboard: 3,
timeLimit: 15,
startPosition: { x: 200, y: 700 },
startAngle: 0,
terrain: [
{ points: [
{ x: -400, y: 840 },
{ x: -330, y: 840 },
{ x: -260, y: 840 },
{ x: -190, y: 840 },
{ x: -120, y: 840 },
{ x: -50, y: 840 },
{ x: 20, y: 840 },
{ x: 90, y: 840 },
{ x: 160, y: 840 },
{ x: 230, y: 840 },
{ x: 300, y: 840 },
{ x: 370, y: 840 },
{ x: 440, y: 840 },
{ x: 510, y: 840 },
{ x: 580, y: 840 },
{ x: 600, y: 840 },
{ x: 670, y: 836 },
{ x: 740, y: 828 },
{ x: 810, y: 814 },
{ x: 880, y: 800 },
{ x: 950, y: 784 },
{ x: 1020, y: 770 },
{ x: 1090, y: 758 },
{ x: 1160, y: 752 },
{ x: 1200, y: 750 },
{ x: 1270, y: 750 },
{ x: 1340, y: 750 },
{ x: 1410, y: 750 },
{ x: 1480, y: 750 },
{ x: 1550, y: 750 },
{ x: 1620, y: 750 },
{ x: 1690, y: 750 },
{ x: 1760, y: 750 },
{ x: 1800, y: 750 },
] },
],
obstacles: [],
goal: { x: 1560, y: 510, width: 280, height: 640 },
cameraBounds: { x: -600, y: -50, width: 3200, height: 2000 },
};

View File

@ -0,0 +1,51 @@
// Generated by editor.html - review before dropping into src/data/levels/.
export default {
id: "level15",
name: "Coming Soon",
description: "Level design in progress - stub terrain, replace in editor.html.",
kidsAboard: 3,
timeLimit: 15,
startPosition: { x: 200, y: 700 },
startAngle: 0,
terrain: [
{ points: [
{ x: -400, y: 840 },
{ x: -330, y: 840 },
{ x: -260, y: 840 },
{ x: -190, y: 840 },
{ x: -120, y: 840 },
{ x: -50, y: 840 },
{ x: 20, y: 840 },
{ x: 90, y: 840 },
{ x: 160, y: 840 },
{ x: 230, y: 840 },
{ x: 300, y: 840 },
{ x: 370, y: 840 },
{ x: 440, y: 840 },
{ x: 510, y: 840 },
{ x: 580, y: 840 },
{ x: 600, y: 840 },
{ x: 670, y: 836 },
{ x: 740, y: 828 },
{ x: 810, y: 814 },
{ x: 880, y: 800 },
{ x: 950, y: 784 },
{ x: 1020, y: 770 },
{ x: 1090, y: 758 },
{ x: 1160, y: 752 },
{ x: 1200, y: 750 },
{ x: 1270, y: 750 },
{ x: 1340, y: 750 },
{ x: 1410, y: 750 },
{ x: 1480, y: 750 },
{ x: 1550, y: 750 },
{ x: 1620, y: 750 },
{ x: 1690, y: 750 },
{ x: 1760, y: 750 },
{ x: 1800, y: 750 },
] },
],
obstacles: [],
goal: { x: 1560, y: 510, width: 280, height: 640 },
cameraBounds: { x: -600, y: -50, width: 3200, height: 2000 },
};

View File

@ -0,0 +1,51 @@
// Generated by editor.html - review before dropping into src/data/levels/.
export default {
id: "level16",
name: "Coming Soon",
description: "Level design in progress - stub terrain, replace in editor.html.",
kidsAboard: 3,
timeLimit: 15,
startPosition: { x: 200, y: 700 },
startAngle: 0,
terrain: [
{ points: [
{ x: -400, y: 840 },
{ x: -330, y: 840 },
{ x: -260, y: 840 },
{ x: -190, y: 840 },
{ x: -120, y: 840 },
{ x: -50, y: 840 },
{ x: 20, y: 840 },
{ x: 90, y: 840 },
{ x: 160, y: 840 },
{ x: 230, y: 840 },
{ x: 300, y: 840 },
{ x: 370, y: 840 },
{ x: 440, y: 840 },
{ x: 510, y: 840 },
{ x: 580, y: 840 },
{ x: 600, y: 840 },
{ x: 670, y: 836 },
{ x: 740, y: 828 },
{ x: 810, y: 814 },
{ x: 880, y: 800 },
{ x: 950, y: 784 },
{ x: 1020, y: 770 },
{ x: 1090, y: 758 },
{ x: 1160, y: 752 },
{ x: 1200, y: 750 },
{ x: 1270, y: 750 },
{ x: 1340, y: 750 },
{ x: 1410, y: 750 },
{ x: 1480, y: 750 },
{ x: 1550, y: 750 },
{ x: 1620, y: 750 },
{ x: 1690, y: 750 },
{ x: 1760, y: 750 },
{ x: 1800, y: 750 },
] },
],
obstacles: [],
goal: { x: 1560, y: 510, width: 280, height: 640 },
cameraBounds: { x: -600, y: -50, width: 3200, height: 2000 },
};

View File

@ -0,0 +1,51 @@
// Generated by editor.html - review before dropping into src/data/levels/.
export default {
id: "level17",
name: "Coming Soon",
description: "Level design in progress - stub terrain, replace in editor.html.",
kidsAboard: 3,
timeLimit: 15,
startPosition: { x: 200, y: 700 },
startAngle: 0,
terrain: [
{ points: [
{ x: -400, y: 840 },
{ x: -330, y: 840 },
{ x: -260, y: 840 },
{ x: -190, y: 840 },
{ x: -120, y: 840 },
{ x: -50, y: 840 },
{ x: 20, y: 840 },
{ x: 90, y: 840 },
{ x: 160, y: 840 },
{ x: 230, y: 840 },
{ x: 300, y: 840 },
{ x: 370, y: 840 },
{ x: 440, y: 840 },
{ x: 510, y: 840 },
{ x: 580, y: 840 },
{ x: 600, y: 840 },
{ x: 670, y: 836 },
{ x: 740, y: 828 },
{ x: 810, y: 814 },
{ x: 880, y: 800 },
{ x: 950, y: 784 },
{ x: 1020, y: 770 },
{ x: 1090, y: 758 },
{ x: 1160, y: 752 },
{ x: 1200, y: 750 },
{ x: 1270, y: 750 },
{ x: 1340, y: 750 },
{ x: 1410, y: 750 },
{ x: 1480, y: 750 },
{ x: 1550, y: 750 },
{ x: 1620, y: 750 },
{ x: 1690, y: 750 },
{ x: 1760, y: 750 },
{ x: 1800, y: 750 },
] },
],
obstacles: [],
goal: { x: 1560, y: 510, width: 280, height: 640 },
cameraBounds: { x: -600, y: -50, width: 3200, height: 2000 },
};

View File

@ -0,0 +1,51 @@
// Generated by editor.html - review before dropping into src/data/levels/.
export default {
id: "level18",
name: "Coming Soon",
description: "Level design in progress - stub terrain, replace in editor.html.",
kidsAboard: 3,
timeLimit: 15,
startPosition: { x: 200, y: 700 },
startAngle: 0,
terrain: [
{ points: [
{ x: -400, y: 840 },
{ x: -330, y: 840 },
{ x: -260, y: 840 },
{ x: -190, y: 840 },
{ x: -120, y: 840 },
{ x: -50, y: 840 },
{ x: 20, y: 840 },
{ x: 90, y: 840 },
{ x: 160, y: 840 },
{ x: 230, y: 840 },
{ x: 300, y: 840 },
{ x: 370, y: 840 },
{ x: 440, y: 840 },
{ x: 510, y: 840 },
{ x: 580, y: 840 },
{ x: 600, y: 840 },
{ x: 670, y: 836 },
{ x: 740, y: 828 },
{ x: 810, y: 814 },
{ x: 880, y: 800 },
{ x: 950, y: 784 },
{ x: 1020, y: 770 },
{ x: 1090, y: 758 },
{ x: 1160, y: 752 },
{ x: 1200, y: 750 },
{ x: 1270, y: 750 },
{ x: 1340, y: 750 },
{ x: 1410, y: 750 },
{ x: 1480, y: 750 },
{ x: 1550, y: 750 },
{ x: 1620, y: 750 },
{ x: 1690, y: 750 },
{ x: 1760, y: 750 },
{ x: 1800, y: 750 },
] },
],
obstacles: [],
goal: { x: 1560, y: 510, width: 280, height: 640 },
cameraBounds: { x: -600, y: -50, width: 3200, height: 2000 },
};

View File

@ -0,0 +1,51 @@
// Generated by editor.html - review before dropping into src/data/levels/.
export default {
id: "level19",
name: "Coming Soon",
description: "Level design in progress - stub terrain, replace in editor.html.",
kidsAboard: 3,
timeLimit: 15,
startPosition: { x: 200, y: 700 },
startAngle: 0,
terrain: [
{ points: [
{ x: -400, y: 840 },
{ x: -330, y: 840 },
{ x: -260, y: 840 },
{ x: -190, y: 840 },
{ x: -120, y: 840 },
{ x: -50, y: 840 },
{ x: 20, y: 840 },
{ x: 90, y: 840 },
{ x: 160, y: 840 },
{ x: 230, y: 840 },
{ x: 300, y: 840 },
{ x: 370, y: 840 },
{ x: 440, y: 840 },
{ x: 510, y: 840 },
{ x: 580, y: 840 },
{ x: 600, y: 840 },
{ x: 670, y: 836 },
{ x: 740, y: 828 },
{ x: 810, y: 814 },
{ x: 880, y: 800 },
{ x: 950, y: 784 },
{ x: 1020, y: 770 },
{ x: 1090, y: 758 },
{ x: 1160, y: 752 },
{ x: 1200, y: 750 },
{ x: 1270, y: 750 },
{ x: 1340, y: 750 },
{ x: 1410, y: 750 },
{ x: 1480, y: 750 },
{ x: 1550, y: 750 },
{ x: 1620, y: 750 },
{ x: 1690, y: 750 },
{ x: 1760, y: 750 },
{ x: 1800, y: 750 },
] },
],
obstacles: [],
goal: { x: 1560, y: 510, width: 280, height: 640 },
cameraBounds: { x: -600, y: -50, width: 3200, height: 2000 },
};

View File

@ -0,0 +1,51 @@
// Generated by editor.html - review before dropping into src/data/levels/.
export default {
id: "level20",
name: "Coming Soon",
description: "Level design in progress - stub terrain, replace in editor.html.",
kidsAboard: 3,
timeLimit: 15,
startPosition: { x: 200, y: 700 },
startAngle: 0,
terrain: [
{ points: [
{ x: -400, y: 840 },
{ x: -330, y: 840 },
{ x: -260, y: 840 },
{ x: -190, y: 840 },
{ x: -120, y: 840 },
{ x: -50, y: 840 },
{ x: 20, y: 840 },
{ x: 90, y: 840 },
{ x: 160, y: 840 },
{ x: 230, y: 840 },
{ x: 300, y: 840 },
{ x: 370, y: 840 },
{ x: 440, y: 840 },
{ x: 510, y: 840 },
{ x: 580, y: 840 },
{ x: 600, y: 840 },
{ x: 670, y: 836 },
{ x: 740, y: 828 },
{ x: 810, y: 814 },
{ x: 880, y: 800 },
{ x: 950, y: 784 },
{ x: 1020, y: 770 },
{ x: 1090, y: 758 },
{ x: 1160, y: 752 },
{ x: 1200, y: 750 },
{ x: 1270, y: 750 },
{ x: 1340, y: 750 },
{ x: 1410, y: 750 },
{ x: 1480, y: 750 },
{ x: 1550, y: 750 },
{ x: 1620, y: 750 },
{ x: 1690, y: 750 },
{ x: 1760, y: 750 },
{ x: 1800, y: 750 },
] },
],
obstacles: [],
goal: { x: 1560, y: 510, width: 280, height: 640 },
cameraBounds: { x: -600, y: -50, width: 3200, height: 2000 },
};

View File

@ -28,8 +28,8 @@ const state = {
// items remain editable, and the level can be re-exported. // items remain editable, and the level can be re-exported.
imported: null, imported: null,
// Smashable props + bonus items placed on the course. Each entry has a // Smashable props + bonus items placed on the course. Each entry has a
// `kind`: 'smashable' (TV/chair/cone - can float or fall, smash off on hit) // `kind`: 'smashable' (TV/chair/cone/trash can/tent/cat - can float or fall, smash off on hit)
// or 'bonus' (CTC/guitar/cash/flour - always float, pop into fireworks on hit). // or 'bonus' (CTC/guitar/cash/flour/spray paint/backpack - always float, pop into fireworks on hit).
// x/y are in the SAME scaled world units the preview canvas plots (and that // x/y are in the SAME scaled world units the preview canvas plots (and that
// the exported level uses); refIndex is the track section it was added // the exported level uses); refIndex is the track section it was added
// under (rebased on reordering). // under (rebased on reordering).
@ -190,34 +190,43 @@ const ITEM_TYPES = [
{ id: 'tv', label: 'TV' }, { id: 'tv', label: 'TV' },
{ id: 'chair', label: 'Chair' }, { id: 'chair', label: 'Chair' },
{ id: 'cone', label: 'Cone' }, { id: 'cone', label: 'Cone' },
{ id: 'trashcan', label: 'Trash Can' },
{ id: 'tent', label: 'Tent' },
{ id: 'cat', label: 'Cat' },
]; ];
const ITEM_COLORS = { tv: '#4a90d9', chair: '#d9a04a', cone: '#d96a4a' }; const ITEM_COLORS = { tv: '#4a90d9', chair: '#d9a04a', cone: '#d96a4a', trashcan: '#6a9a5a', tent: '#d98a3a', cat: '#9a8a6a' };
const ITEM_ICONS = { tv: 'TV', chair: 'CH', cone: 'CO' }; const ITEM_ICONS = { tv: 'TV', chair: 'CH', cone: 'CO', trashcan: 'TR', tent: 'TN', cat: 'CA' };
// The real spritesheet, loaded directly as an <img> for the preview map // The real spritesheet, loaded directly as an <img> for the preview map
// (frames: 0 TV, 1 smashed TV, 2 chair, 3 smashed chair, 4 cone, 5 smashed // (frames: 0 TV, 1 smashed TV, 2 chair, 3 smashed chair, 4 cone, 5 smashed
// cone - 64x64 cells, same order as ITEM_TYPES pairs above). The circle // cone, 6 trash can, 7 smashed trash can, 8 tent, 9 smashed tent, 10 cat,
// 11 smashed cat - 64x64 cells in an 8-wide x 2-row grid, same order as the
// ITEM_TYPES pairs above; frames 6+ live on the second row). The circle
// fallback in drawPreview covers the brief moment before it finishes loading // fallback in drawPreview covers the brief moment before it finishes loading
// and any file:// context where the image is blocked. // and any file:// context where the image is blocked.
const ITEM_SPRITE = new Image(); const ITEM_SPRITE = new Image();
ITEM_SPRITE.src = 'assets/sprites/smash-items.png'; ITEM_SPRITE.src = 'assets/sprites/smash-items.png';
const ITEM_SPRITE_FRAMES = { tv: 0, chair: 2, cone: 4 }; const ITEM_SPRITE_FRAMES = { tv: 0, chair: 2, cone: 4, trashcan: 6, tent: 8, cat: 10 };
const ITEM_SPRITE_CELL = 64; const ITEM_SPRITE_CELL = 64;
// Bonus items ("Placeables" toggle -> "Bonus items"): float-only collectible // Bonus items ("Placeables" toggle -> "Bonus items"): float-only collectible
// prizes - CTC box / guitar / cash / flour. See BonusItemManager.js in the // prizes - CTC box / guitar / cash / flour / spray paint / backpack. See
// game for the type/frame/color table; frames here are the BONUS frame of // BonusItemManager.js in the game for the type/frame/color table; frames
// each pair (0 CTC, 3 guitar, 6 cash, 9 flour). // here are the BONUS frame of each pair (0 CTC, 3 guitar, 6 cash, 9 flour,
// 12 spray paint, 15 backpack - on the 9-wide x 2-row sheet, frames 9+ sit
// on the second row).
const BONUS_TYPES = [ const BONUS_TYPES = [
{ id: 'ctc', label: 'CTC Box' }, { id: 'ctc', label: 'CTC Box' },
{ id: 'guitar', label: 'Guitar' }, { id: 'guitar', label: 'Guitar' },
{ id: 'cash', label: 'Cash' }, { id: 'cash', label: 'Cash' },
{ id: 'flour', label: 'Flour' }, { id: 'flour', label: 'Flour' },
{ id: 'spraypaint', label: 'Spray Paint' },
{ id: 'backpack', label: 'Backpack' },
]; ];
const BONUS_COLORS = { ctc: '#e0a040', guitar: '#4b9a45', cash: '#b3ad9c', flour: '#cbb58e' }; const BONUS_COLORS = { ctc: '#e0a040', guitar: '#4b9a45', cash: '#b3ad9c', flour: '#cbb58e', spraypaint: '#c04838', backpack: '#8a6a48' };
const BONUS_ICONS = { ctc: 'CT', guitar: 'GT', cash: '$', flour: 'FL' }; const BONUS_ICONS = { ctc: 'CT', guitar: 'GT', cash: '$', flour: 'FL', spraypaint: 'SP', backpack: 'BP' };
const BONUS_SPRITE = new Image(); const BONUS_SPRITE = new Image();
BONUS_SPRITE.src = 'assets/sprites/bonus-items.png'; BONUS_SPRITE.src = 'assets/sprites/bonus-items.png';
const BONUS_SPRITE_FRAMES = { ctc: 0, guitar: 3, cash: 6, flour: 9 }; const BONUS_SPRITE_FRAMES = { ctc: 0, guitar: 3, cash: 6, flour: 9, spraypaint: 12, backpack: 15 };
// Samples the terrain surface Y (highest ground) at a given world X - used // Samples the terrain surface Y (highest ground) at a given world X - used
// to draw the "falls to here" drop line for gravity-mode items in the // to draw the "falls to here" drop line for gravity-mode items in the
@ -250,8 +259,9 @@ function buildItemsForm() {
itemsFormEl.appendChild(fieldset); itemsFormEl.appendChild(fieldset);
// --- Placeable kind toggle: smashables (TV/chair/cone) vs bonus items // --- Placeable kind toggle: smashables (TV/chair/cone) vs bonus items
// (CTC/guitar/cash/flour). Only the relevant type+mode controls show below, // (CTC/guitar/cash/flour/spray paint/backpack). Only the relevant type+mode
// and clicks on the preview place whichever kind is selected. // controls show below, and clicks on the preview place whichever kind is
// selected.
const kindRadios = []; const kindRadios = [];
for (const kind of ['smashable', 'bonus']) { for (const kind of ['smashable', 'bonus']) {
const radio = document.createElement('input'); const radio = document.createElement('input');
@ -266,7 +276,7 @@ function buildItemsForm() {
}); });
const label = document.createElement('label'); const label = document.createElement('label');
label.className = 'checkbox-row'; label.className = 'checkbox-row';
label.append(radio, kind === 'smashable' ? 'Smashables (TV / Chair / Cone)' : 'Bonus items (CTC / Guitar / Cash / Flour)'); label.append(radio, kind === 'smashable' ? 'Smashables (TV / Chair / Cone / Trash Can / Tent / Cat)' : 'Bonus items (CTC / Guitar / Cash / Flour / Spray Paint / Backpack)');
kindRadios.push(label); kindRadios.push(label);
} }
const kindWrap = document.createElement('div'); const kindWrap = document.createElement('div');
@ -752,9 +762,15 @@ function drawPreview(levelData) {
} }
// Both spritesheets share the same 64px cell size, so ITEM_SPRITE_CELL // Both spritesheets share the same 64px cell size, so ITEM_SPRITE_CELL
// works for bonus frames too (no separate cell constant needed). // works for bonus frames too (no separate cell constant needed).
// Each sheet is a multi-row grid (smash: 8 wide, bonus: 9 wide), so
// derive the cell's row from the frame index - frames on row 2+ are
// out of bounds with a single-row assumption.
const cols = Math.max(1, Math.floor(spriteTable.naturalWidth / ITEM_SPRITE_CELL));
const sx = (frame % cols) * ITEM_SPRITE_CELL;
const sy = Math.floor(frame / cols) * ITEM_SPRITE_CELL;
ctx.drawImage( ctx.drawImage(
spriteTable, spriteTable,
frame * ITEM_SPRITE_CELL, 0, ITEM_SPRITE_CELL, ITEM_SPRITE_CELL, sx, sy, ITEM_SPRITE_CELL, ITEM_SPRITE_CELL,
ix - half, iy - half, itemDrawScale, itemDrawScale, ix - half, iy - half, itemDrawScale, itemDrawScale,
); );
} else { } else {

View File

@ -1,10 +1,12 @@
import { BONUS_ITEM } from '../config.js'; import { BONUS_ITEM } from '../config.js';
// bonus-items.png is 12 frames of 64x64, in order: // bonus-items.png is 18 frames of 64x64 in a 9-wide x 2-row grid, in order:
// 0 CTC box, 1 CTC firework object 1, 2 CTC firework object 2, // 0 CTC box, 1 CTC firework object 1, 2 CTC firework object 2,
// 3 Guitar, 4 Guitar firework object 1, 5 Guitar firework object 2, // 3 Guitar, 4 Guitar firework object 1, 5 Guitar firework object 2,
// 6 Cash, 7 Cash firework object 1, 8 Cash firework object 2, // 6 Cash, 7 Cash firework object 1, 8 Cash firework object 2,
// 9 Flour, 10 Flour firework object 1, 11 Flour firework object 2. // 9 Flour, 10 Flour firework object 1, 11 Flour firework object 2,
// 12 Spray paint, 13 Spray paint firework object 1, 14 Spray paint firework object 2,
// 15 Backpack, 16 Backpack firework object 1, 17 Backpack firework object 2.
// Each bonus item is followed by its two firework-object frames, which are // Each bonus item is followed by its two firework-object frames, which are
// the sprites the explosion burst is made of (see _explode below). // the sprites the explosion burst is made of (see _explode below).
const BONUS_TYPES = { const BONUS_TYPES = {
@ -12,6 +14,8 @@ const BONUS_TYPES = {
guitar: { frame: 3, fireworks: [4, 5], label: 'Guitar', soundKey: 'bonus_guitar' }, guitar: { frame: 3, fireworks: [4, 5], label: 'Guitar', soundKey: 'bonus_guitar' },
cash: { frame: 6, fireworks: [7, 8], label: 'Cash', soundKey: 'bonus_cash' }, cash: { frame: 6, fireworks: [7, 8], label: 'Cash', soundKey: 'bonus_cash' },
flour: { frame: 9, fireworks: [10, 11], label: 'Flour', soundKey: 'bonus_flour' }, flour: { frame: 9, fireworks: [10, 11], label: 'Flour', soundKey: 'bonus_flour' },
spraypaint: { frame: 12, fireworks: [13, 14], label: 'Spray Paint', soundKey: 'bonus_spraypaint' },
backpack: { frame: 15, fireworks: [16, 17], label: 'Backpack', soundKey: 'bonus_backpack' },
}; };
export default class BonusItemManager { export default class BonusItemManager {

View File

@ -1,12 +1,17 @@
import { SMASH_ITEM } from '../config.js'; import { SMASH_ITEM } from '../config.js';
// smash-items.png is 6 frames of 64x64, in order: // smash-items.png is 12 frames of 64x64 in an 8-wide x 2-row grid
// (frame 6 onward wraps to row 2), in order:
// 0 intact TV, 1 smashed TV, 2 intact chair, 3 smashed chair, // 0 intact TV, 1 smashed TV, 2 intact chair, 3 smashed chair,
// 4 intact cone, 5 smashed cone. // 4 intact cone, 5 smashed cone, 6 intact trash can, 7 smashed trash can,
// 8 intact tent, 9 smashed tent, 10 intact cat, 11 smashed cat.
const ITEM_TYPES = { const ITEM_TYPES = {
tv: { baseFrame: 0, smashedFrame: 1, label: 'TV', soundKey: 'smash_tv' }, tv: { baseFrame: 0, smashedFrame: 1, label: 'TV', soundKey: 'smash_tv' },
chair: { baseFrame: 2, smashedFrame: 3, label: 'Chair', soundKey: 'smash_chair' }, chair: { baseFrame: 2, smashedFrame: 3, label: 'Chair', soundKey: 'smash_chair' },
cone: { baseFrame: 4, smashedFrame: 5, label: 'Cone', soundKey: 'smash_cone' }, cone: { baseFrame: 4, smashedFrame: 5, label: 'Cone', soundKey: 'smash_cone' },
trashcan: { baseFrame: 6, smashedFrame: 7, label: 'Trash Can', soundKey: 'smash_trashcan' },
tent: { baseFrame: 8, smashedFrame: 9, label: 'Tent', soundKey: 'smash_tent' },
cat: { baseFrame: 10, smashedFrame: 11, label: 'Cat', soundKey: 'smash_cat' },
}; };
export default class SmashItemManager { export default class SmashItemManager {

View File

@ -39,10 +39,23 @@ export default class PreloadScene extends Phaser.Scene {
this.load.audio('smash_tv', 'assets/fx/smash-tv.mp3'); this.load.audio('smash_tv', 'assets/fx/smash-tv.mp3');
this.load.audio('smash_chair', 'assets/fx/smash-chair.mp3'); this.load.audio('smash_chair', 'assets/fx/smash-chair.mp3');
this.load.audio('smash_cone', 'assets/fx/smash-cone.mp3'); this.load.audio('smash_cone', 'assets/fx/smash-cone.mp3');
// Newer smashable props (trash can / tent / cat). These files don't ship
// yet - a missing one just 404s into FILE_LOAD_ERROR like the voice lines,
// and SmashItemManager._smash checks cache.audio.exists before playing,
// so they degrade to silent. Drop a real MP3 at the path and it plays.
this.load.audio('smash_trashcan', 'assets/fx/smash-trashcan.mp3');
this.load.audio('smash_tent', 'assets/fx/smash-tent.mp3');
this.load.audio('smash_cat', 'assets/fx/smash-cat.mp3');
this.load.audio('bonus_cash', 'assets/fx/bonus-cash.mp3'); this.load.audio('bonus_cash', 'assets/fx/bonus-cash.mp3');
this.load.audio('bonus_ctc', 'assets/fx/bonus-ctc.mp3'); this.load.audio('bonus_ctc', 'assets/fx/bonus-ctc.mp3');
this.load.audio('bonus_guitar', 'assets/fx/bonus-guitar.mp3'); this.load.audio('bonus_guitar', 'assets/fx/bonus-guitar.mp3');
this.load.audio('bonus_flour', 'assets/fx/bonus-flour.mp3'); this.load.audio('bonus_flour', 'assets/fx/bonus-flour.mp3');
// Newer bonus props (spray paint / backpack). These files don't ship yet
// - a missing one just 404s into FILE_LOAD_ERROR like the voice lines,
// and BonusItemManager._explode checks cache.audio.exists before playing,
// so they degrade to silent. Drop a real MP3 at the path and it plays.
this.load.audio('bonus_spraypaint', 'assets/fx/bonus-spraypaint.mp3');
this.load.audio('bonus_backpack', 'assets/fx/bonus-backpack.mp3');
loadKidFallSounds(this.load); loadKidFallSounds(this.load);
// Not every level has a voice-over - this just attempts one per level // Not every level has a voice-over - this just attempts one per level

View File

@ -21,13 +21,16 @@ export const ASSET_MANIFEST = [
// as before - only used for the single-frame placeholder fallback. // as before - only used for the single-frame placeholder fallback.
{ key: 'kid_idle', path: 'assets/sprites/kid_idle.png', width: 28 * WORLD_SCALE, height: 28 * WORLD_SCALE, kind: 'circle', color: 0xf2c14e, frameWidth: 56, frameHeight: 56 }, { key: 'kid_idle', path: 'assets/sprites/kid_idle.png', width: 28 * WORLD_SCALE, height: 28 * WORLD_SCALE, kind: 'circle', color: 0xf2c14e, frameWidth: 56, frameHeight: 56 },
{ key: 'kid_ejected', path: 'assets/sprites/kid_ejected.png', width: 28 * WORLD_SCALE, height: 28 * WORLD_SCALE, kind: 'circle', color: 0xe0553b, frameWidth: 56, frameHeight: 56 }, { key: 'kid_ejected', path: 'assets/sprites/kid_ejected.png', width: 28 * WORLD_SCALE, height: 28 * WORLD_SCALE, kind: 'circle', color: 0xe0553b, frameWidth: 56, frameHeight: 56 },
// 6 frames of 64x64 smashable props (intact/smashed TV, chair, cone) - // 12 frames of 64x64 smashable props (intact/smashed TV, chair, cone,
// see SMASH_ITEM in config.js and src/entities/SmashItemManager.js. // trash can, tent, cat) in an 8-wide x 2-row grid - see SMASH_ITEM in
// config.js and src/entities/SmashItemManager.js.
// frameWidth/frameHeight are raw SOURCE pixels (one 64x64 cell each), // frameWidth/frameHeight are raw SOURCE pixels (one 64x64 cell each),
// same convention as the kid sheets above. // same convention as the kid sheets above; Phaser derives the 8x2 grid
// from the sheet's own dimensions, so adding rows is frame-safe.
{ key: 'smash_items', path: 'assets/sprites/smash-items.png', width: 64 * WORLD_SCALE, height: 64 * WORLD_SCALE, kind: 'rect', color: 0x4c8a5c, frameWidth: 64, frameHeight: 64 }, { key: 'smash_items', path: 'assets/sprites/smash-items.png', width: 64 * WORLD_SCALE, height: 64 * WORLD_SCALE, kind: 'rect', color: 0x4c8a5c, frameWidth: 64, frameHeight: 64 },
// 12 frames of 64x64 bonus items (CTC box + 2 firework frames, guitar + 2, // 18 frames of 64x64 bonus items (CTC box + 2 firework frames, guitar + 2,
// cash + 2, flour + 2 - see BONUS_ITEM in config.js and BonusItemManager.js). // cash + 2, flour + 2, spray paint + 2, backpack + 2) in a 9-wide x 2-row
// grid - see BONUS_ITEM in config.js and BonusItemManager.js.
{ key: 'bonus_items', path: 'assets/sprites/bonus-items.png', width: 64 * WORLD_SCALE, height: 64 * WORLD_SCALE, kind: 'rect', color: 0xe0a040, frameWidth: 64, frameHeight: 64 }, { key: 'bonus_items', path: 'assets/sprites/bonus-items.png', width: 64 * WORLD_SCALE, height: 64 * WORLD_SCALE, kind: 'rect', color: 0xe0a040, frameWidth: 64, frameHeight: 64 },
// Campaign 1 ("Evergreen Lake") parallax set - see DEFAULT_THEME in // Campaign 1 ("Evergreen Lake") parallax set - see DEFAULT_THEME in
// data/themes.js. The _2 set is campaign 2's; both are 3136x672 natively // data/themes.js. The _2 set is campaign 2's; both are 3136x672 natively