Add flour bonus item type with custom firework variant
- Extend bonus items spritesheet from 9 to 12 frames (add flour + 2 firework objects) - Add `flour` type to BONUS_ITEM config with warm off-white/tan color palette and firework variant (2-3x scale, half opacity for dust cloud effect) - Implement per-type firework variants in BonusItemManager._explode() - particles can now have varied sizes and alpha while maintaining standard physics body size - Add flour SFX asset (bonus-flour.mp3) and preload it - Update editor to support placing flour bonus items with appropriate icon, color, and sprite frame - Update all documentation (README, sprites.md) to reflect the new bonus item type Additionally: - Add campaign 2 bus physics customization: grippier tires (higher friction coefficients) and stiffer/less-damped suspension for bouncier ride - Thread per-campaign wheel friction and suspension parameters through Bus entity from themes.js - Redesign level11 with shorter course, fewer kids (4 vs 3), tighter time limit (42s vs 119s), and flour bonus items replacing most cash/CTC/guitar
This commit is contained in:
parent
8cba629232
commit
1eea919c8b
21
README.md
21
README.md
|
|
@ -54,7 +54,7 @@ no code changes needed.**
|
||||||
| `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 6 frames | 1x6 spritesheet: intact/smashed TV, chair, cone (see Smashables below) |
|
||||||
| `bonus_items` | `assets/sprites/bonus-items.png` | 64x64 x 9 frames | 1x9 spritesheet: CTC box + 2 firework objects, guitar + 2, cash + 2 (see Bonus Items 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) |
|
||||||
| `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 |
|
||||||
|
|
@ -108,7 +108,7 @@ 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, float or gravity mode) and
|
||||||
**Bonus items** (CTC box / guitar / cash, float-only). Pick the kind + type,
|
**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 +123,11 @@ 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)
|
## Bonus Items (CTC box / guitar / cash / flour)
|
||||||
|
|
||||||
`assets/sprites/bonus-items.png` is a 1x9 spritesheet (CTC box, 2 firework
|
`assets/sprites/bonus-items.png` is a 1x12 spritesheet (CTC box, 2 firework
|
||||||
frames, guitar, 2 firework frames, cash, 2 firework frames - 64x64 per
|
frames, guitar, 2 firework frames, cash, 2 firework frames, flour, 2 firework
|
||||||
frame), loaded as the `bonus_items` texture. Any level can carry an optional
|
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,12 +155,17 @@ 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.
|
= bright/deep green, cash = muted sage/olive-grey, flour = warm off-white/tan.
|
||||||
|
Some types override the burst's look entirely via
|
||||||
|
`BONUS_ITEM.fireworkVariants`: flour's puffs launch 2-3x the standard
|
||||||
|
particle size at half opacity (a soft cloud of dust, still with the
|
||||||
|
standard-sized physics bodies underneath).
|
||||||
|
|
||||||
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.
|
||||||
`BONUS_ITEM` in `config.js` holds all the tuning knobs (particle count,
|
`BONUS_ITEM` in `config.js` holds all the tuning knobs (particle count,
|
||||||
speed range, spin, lifetime, tint palette, pop scale/duration).
|
speed range, spin, lifetime, tint palette, firework variants, pop
|
||||||
|
scale/duration).
|
||||||
|
|
||||||
## Phaser 4 loading
|
## Phaser 4 loading
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 86 KiB |
Binary file not shown.
Binary file not shown.
10
sprites.md
10
sprites.md
|
|
@ -51,8 +51,14 @@ with it (same ratios, just re-derive from that one constant).
|
||||||
only the texture is swapped per campaign), and a "lift kit": the wheel
|
only the texture is swapped per campaign), and a "lift kit": the wheel
|
||||||
mount point sits ~8 design units lower under the chassis (see
|
mount point sits ~8 design units lower under the chassis (see
|
||||||
`wheelRestOffsetY2` in `src/config.js` and the `bus` block in
|
`wheelRestOffsetY2` in `src/config.js` and the `bus` block in
|
||||||
`src/data/themes.js`), so it reads as a lifted bus. Both wheels still
|
`src/data/themes.js`), so it reads as a lifted bus. And its tires grip
|
||||||
share one image per campaign.
|
harder: the per-campaign `bus` block also sets the wheel friction
|
||||||
|
coefficients (`wheelFriction2` / `wheelFrictionStatic2` in
|
||||||
|
`src/config.js`) - that's a physics feel change, not an art one. Its
|
||||||
|
shocks are different too - stiffer springs with less damping
|
||||||
|
(`suspensionStiffness2` / `suspensionDamping2`), so the wheel snaps
|
||||||
|
back to its rest position faster and bounces more vertically. Both
|
||||||
|
wheels still share one image per campaign.
|
||||||
- If you want a bus with different proportions (longer, taller, etc.) than
|
- If you want a bus with different proportions (longer, taller, etc.) than
|
||||||
340x128, tell me the size you want art at - that number is also the physics
|
340x128, tell me the size you want art at - that number is also the physics
|
||||||
body size in `src/config.js` (`BUS.chassisWidth`/`chassisHeight`), so it
|
body size in `src/config.js` (`BUS.chassisWidth`/`chassisHeight`), so it
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,12 @@ export const BUS = {
|
||||||
wheelDensity: 0.004,
|
wheelDensity: 0.004,
|
||||||
wheelFriction: 0.9,
|
wheelFriction: 0.9,
|
||||||
wheelFrictionStatic: 1.4,
|
wheelFrictionStatic: 1.4,
|
||||||
|
// Campaign 2's grippier tires (see `bus` in data/themes.js): higher
|
||||||
|
// friction coefficients for the bus_wheel_2 rubber - less spin/slip
|
||||||
|
// under throttle and brake, and the wheel grips the terrain harder.
|
||||||
|
// Dimensionless like the values above, not WORLD_SCALE-scaled.
|
||||||
|
wheelFriction2: 1.3,
|
||||||
|
wheelFrictionStatic2: 2.0,
|
||||||
// Lowered a lot from the original 0.02 - that was quietly eating most of
|
// Lowered a lot from the original 0.02 - that was quietly eating most of
|
||||||
// the wheel's spin-up each second (it's a per-tick multiplicative decay,
|
// the wheel's spin-up each second (it's a per-tick multiplicative decay,
|
||||||
// so 0.02 was burning off roughly a third of angular velocity per second)
|
// so 0.02 was burning off roughly a third of angular velocity per second)
|
||||||
|
|
@ -103,6 +109,13 @@ export const BUS = {
|
||||||
// oscillate (bounce) instead of settling immediately - "more shocks."
|
// oscillate (bounce) instead of settling immediately - "more shocks."
|
||||||
suspensionStiffness: 0.35,
|
suspensionStiffness: 0.35,
|
||||||
suspensionDamping: 0.035,
|
suspensionDamping: 0.035,
|
||||||
|
// Campaign 2's shocks (see `bus` in data/themes.js): stiffer springs that
|
||||||
|
// snap the wheel back to its rest position faster, paired with LESS
|
||||||
|
// damping so the extra spring energy survives as visible vertical bounce
|
||||||
|
// instead of being absorbed - a quicker, bouncier ride than campaign 1's
|
||||||
|
// soft settle.
|
||||||
|
suspensionStiffness2: 0.4,
|
||||||
|
suspensionDamping2: 0.012,
|
||||||
// How far the wheel can move up/down from wheelRestOffsetY. This will
|
// How far the wheel can move up/down from wheelRestOffsetY. This will
|
||||||
// visibly pop the wheel outside its wheel well on a hard landing - that's
|
// visibly pop the wheel outside its wheel well on a hard landing - that's
|
||||||
// the tradeoff for real suspension travel/bounce instead of a stiff ride.
|
// the tradeoff for real suspension travel/bounce instead of a stiff ride.
|
||||||
|
|
@ -260,7 +273,7 @@ export const SMASH_ITEM = {
|
||||||
floatHeight: 90 * WORLD_SCALE,
|
floatHeight: 90 * WORLD_SCALE,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Bonus items (assets/sprites/bonus-items.png - 9 frames of 64x64: a bonus
|
// Bonus items (assets/sprites/bonus-items.png - 12 frames of 64x64: a bonus
|
||||||
// item followed by its two firework-object frames - see
|
// item followed by its two firework-object frames - see
|
||||||
// src/entities/BonusItemManager.js for the type/frame/color table). Placed
|
// src/entities/BonusItemManager.js for the type/frame/color table). Placed
|
||||||
// per-level via the editor's "Placeable kind" switch ("Bonus items") and
|
// per-level via the editor's "Placeable kind" switch ("Bonus items") and
|
||||||
|
|
@ -303,6 +316,18 @@ export const BONUS_ITEM = {
|
||||||
lifeMs: 1300, // how long a particle stays visible; the fade-out is the last 40% of this
|
lifeMs: 1300, // how long a particle stays visible; the fade-out is the last 40% of this
|
||||||
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,
|
||||||
|
// full opacity). Flour's dust puffs are the only type that uses one: each
|
||||||
|
// firework sprite is shown 2-3x the normal particle size at ~half opacity,
|
||||||
|
// so a hit reads as a soft cloud of dust billowing over the bus instead of
|
||||||
|
// a confetti pop. Read per item in BonusItemManager._explode (variant =
|
||||||
|
// BONUS_ITEM.fireworkVariants[item.spec.type] || {}). NOTE: this is purely
|
||||||
|
// visual - the particles' physics bodies stay at the standard size (below),
|
||||||
|
// so the bigger sprites don't change how they settle on the terrain.
|
||||||
|
fireworkVariants: {
|
||||||
|
flour: { scaleMin: 2, scaleMax: 3, 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
|
||||||
// (which are drawn in their own colors) get a PHASER.MULTIPLY tint of
|
// (which are drawn in their own colors) get a PHASER.MULTIPLY tint of
|
||||||
// these so the burst reads as an echo of the item's own color scheme
|
// these so the burst reads as an echo of the item's own color scheme
|
||||||
|
|
@ -313,6 +338,7 @@ export const BONUS_ITEM = {
|
||||||
ctc: { main: 0xe0a040, accent: 0x703818 }, // warm toast-brown / cinnamon
|
ctc: { main: 0xe0a040, accent: 0x703818 }, // warm toast-brown / cinnamon
|
||||||
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)
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -84,14 +84,21 @@ export const DEFAULT_THEME = {
|
||||||
// `wheelTextureKey` is the texture both wheels render with; `wheelRadius`
|
// `wheelTextureKey` is the texture both wheels render with; `wheelRadius`
|
||||||
// is the physics/display radius in world pixels; `wheelRestOffsetY` is
|
// is the physics/display radius in world pixels; `wheelRestOffsetY` is
|
||||||
// the wheel mount point, local to the chassis (y down) - raising it is
|
// the wheel mount point, local to the chassis (y down) - raising it is
|
||||||
// the "lift kit" knob (campaign 2). A bigger radius keeps the wheel
|
// the "lift kit" knob (campaign 2); `wheelFriction`/`wheelFrictionStatic`
|
||||||
// centered on its axle (the wishbone anchors derive from these
|
// are the tire grip coefficients (campaign 2's tires grip harder);
|
||||||
// offsets, so they stay put) - only the size changes.
|
// `suspensionStiffness`/`suspensionDamping` are the shock settings
|
||||||
|
// (campaign 2's shocks snap back faster and bounce more). A bigger radius
|
||||||
|
// keeps the wheel centered on its axle (the wishbone anchors derive from
|
||||||
|
// these offsets, so they stay put) - only the size changes.
|
||||||
bus: {
|
bus: {
|
||||||
chassisKey: 'bus_chassis',
|
chassisKey: 'bus_chassis',
|
||||||
wheelTextureKey: 'bus_wheel',
|
wheelTextureKey: 'bus_wheel',
|
||||||
wheelRadius: BUS.wheelRadius,
|
wheelRadius: BUS.wheelRadius,
|
||||||
wheelRestOffsetY: BUS.wheelRestOffsetY,
|
wheelRestOffsetY: BUS.wheelRestOffsetY,
|
||||||
|
wheelFriction: BUS.wheelFriction,
|
||||||
|
wheelFrictionStatic: BUS.wheelFrictionStatic,
|
||||||
|
suspensionStiffness: BUS.suspensionStiffness,
|
||||||
|
suspensionDamping: BUS.suspensionDamping,
|
||||||
},
|
},
|
||||||
// The three parallax layers, back to front. `far` is the full-viewport
|
// The three parallax layers, back to front. `far` is the full-viewport
|
||||||
// sky; `mid`/`near` are bottom-anchored ground tiles (see PlayScene's
|
// sky; `mid`/`near` are bottom-anchored ground tiles (see PlayScene's
|
||||||
|
|
@ -129,6 +136,16 @@ export const THEMES = {
|
||||||
// campaign 1's (see BUS.wheelRestOffsetY2) - the bus sits taller and
|
// campaign 1's (see BUS.wheelRestOffsetY2) - the bus sits taller and
|
||||||
// the bigger wheels hang lower under the body.
|
// the bigger wheels hang lower under the body.
|
||||||
wheelRestOffsetY: BUS.wheelRestOffsetY2,
|
wheelRestOffsetY: BUS.wheelRestOffsetY2,
|
||||||
|
// Grippier tires (see BUS.wheelFriction2 / wheelFrictionStatic2 in
|
||||||
|
// config.js) - less slip under throttle/brake, harder grip on the
|
||||||
|
// road.
|
||||||
|
wheelFriction: BUS.wheelFriction2,
|
||||||
|
wheelFrictionStatic: BUS.wheelFrictionStatic2,
|
||||||
|
// Stiffer, less-damped shocks (see BUS.suspensionStiffness2 /
|
||||||
|
// suspensionDamping2) - the wheel returns to its rest position
|
||||||
|
// faster and the extra spring energy shows up as vertical bounce.
|
||||||
|
suspensionStiffness: BUS.suspensionStiffness2,
|
||||||
|
suspensionDamping: BUS.suspensionDamping2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Add campaign03: { ... } here when it's ready.
|
// Add campaign03: { ... } here when it's ready.
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ const state = {
|
||||||
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 - can float or fall, smash off on hit)
|
||||||
// or 'bonus' (CTC/guitar/cash - always float, pop into fireworks on hit).
|
// or 'bonus' (CTC/guitar/cash/flour - 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).
|
||||||
|
|
@ -204,19 +204,20 @@ const ITEM_SPRITE_FRAMES = { tv: 0, chair: 2, cone: 4 };
|
||||||
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. See BonusItemManager.js in the game for
|
// prizes - CTC box / guitar / cash / flour. See BonusItemManager.js in the
|
||||||
// the type/frame/color table; frames here are the BONUS frame of each pair
|
// game for the type/frame/color table; frames here are the BONUS frame of
|
||||||
// (0 CTC, 3 guitar, 6 cash).
|
// each pair (0 CTC, 3 guitar, 6 cash, 9 flour).
|
||||||
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' },
|
||||||
];
|
];
|
||||||
const BONUS_COLORS = { ctc: '#e0a040', guitar: '#4b9a45', cash: '#b3ad9c' };
|
const BONUS_COLORS = { ctc: '#e0a040', guitar: '#4b9a45', cash: '#b3ad9c', flour: '#cbb58e' };
|
||||||
const BONUS_ICONS = { ctc: 'CT', guitar: 'GT', cash: '$' };
|
const BONUS_ICONS = { ctc: 'CT', guitar: 'GT', cash: '$', flour: 'FL' };
|
||||||
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 };
|
const BONUS_SPRITE_FRAMES = { ctc: 0, guitar: 3, cash: 6, flour: 9 };
|
||||||
|
|
||||||
// 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
|
||||||
|
|
@ -249,7 +250,7 @@ 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). Only the relevant type+mode controls show below,
|
// (CTC/guitar/cash/flour). Only the relevant type+mode controls show below,
|
||||||
// and clicks on the preview place whichever kind is selected.
|
// 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']) {
|
||||||
|
|
@ -265,7 +266,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)');
|
label.append(radio, kind === 'smashable' ? 'Smashables (TV / Chair / Cone)' : 'Bonus items (CTC / Guitar / Cash / Flour)');
|
||||||
kindRadios.push(label);
|
kindRadios.push(label);
|
||||||
}
|
}
|
||||||
const kindWrap = document.createElement('div');
|
const kindWrap = document.createElement('div');
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
import { BONUS_ITEM } from '../config.js';
|
import { BONUS_ITEM } from '../config.js';
|
||||||
|
|
||||||
// bonus-items.png is 9 frames of 64x64, in order:
|
// bonus-items.png is 12 frames of 64x64, 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.
|
||||||
// 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 = {
|
||||||
ctc: { frame: 0, fireworks: [1, 2], label: 'CTC Box', soundKey: 'bonus_ctc' },
|
ctc: { frame: 0, fireworks: [1, 2], label: 'CTC Box', soundKey: 'bonus_ctc' },
|
||||||
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' },
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class BonusItemManager {
|
export default class BonusItemManager {
|
||||||
|
|
@ -121,7 +123,7 @@ export default class BonusItemManager {
|
||||||
const { scene } = this;
|
const { scene } = this;
|
||||||
|
|
||||||
// SFX: the item's own bonus clip (bonus-ctc.mp3 / bonus-guitar.mp3 /
|
// SFX: the item's own bonus clip (bonus-ctc.mp3 / bonus-guitar.mp3 /
|
||||||
// bonus-cash.mp3, loaded in PreloadScene). cache.audio.exists guards
|
// bonus-cash.mp3 / bonus-flour.mp3, loaded in PreloadScene). cache.audio.exists guards
|
||||||
// against a failed load - same load-tolerant pattern as voiceLine.js /
|
// against a failed load - same load-tolerant pattern as voiceLine.js /
|
||||||
// kidFallSound.js, so a missing clip degrades to silent instead of
|
// kidFallSound.js, so a missing clip degrades to silent instead of
|
||||||
// throwing. Each item collects at most once, so no overlap concern.
|
// throwing. Each item collects at most once, so no overlap concern.
|
||||||
|
|
@ -145,6 +147,12 @@ export default class BonusItemManager {
|
||||||
const palette = BONUS_ITEM.colors[item.spec.type] || BONUS_ITEM.colors.ctc;
|
const palette = BONUS_ITEM.colors[item.spec.type] || BONUS_ITEM.colors.ctc;
|
||||||
const [mainTint, accentTint] = [palette.main, palette.accent];
|
const [mainTint, accentTint] = [palette.main, palette.accent];
|
||||||
|
|
||||||
|
// Optional per-type firework override (BONUS_ITEM.fireworkVariants):
|
||||||
|
// flour's dust puffs launch 2-3x bigger and semi-transparent (the soft
|
||||||
|
// "cloud of dust" read) - every other type falls back to the standard
|
||||||
|
// size/full-opacity show. scaleRange is applied per particle below.
|
||||||
|
const variant = BONUS_ITEM.fireworkVariants[item.spec.type] || {};
|
||||||
|
|
||||||
const count = BONUS_ITEM.particleCount;
|
const count = BONUS_ITEM.particleCount;
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
// Alternate the two firework-object frames around the ring (and the
|
// Alternate the two firework-object frames around the ring (and the
|
||||||
|
|
@ -167,7 +175,10 @@ export default class BonusItemManager {
|
||||||
const particle = scene.matter.add.image(x, y, 'bonus_items', frame, {
|
const particle = scene.matter.add.image(x, y, 'bonus_items', frame, {
|
||||||
// Circle hitbox, same displaySize/4 convention as the item's own
|
// Circle hitbox, same displaySize/4 convention as the item's own
|
||||||
// hitbox above (and SMASH_ITEM.radius) - keeps every circle body in
|
// hitbox above (and SMASH_ITEM.radius) - keeps every circle body in
|
||||||
// the game on one consistent sizing rule.
|
// the game on one consistent sizing rule. NOTE: this always uses the
|
||||||
|
// STANDARD particleDisplaySize, even when the sprite below is shown
|
||||||
|
// bigger (flour's dust variant) - the physics footprint stays the
|
||||||
|
// same size for every item type, only the visible sprite grows.
|
||||||
shape: { type: 'circle', radius: BONUS_ITEM.particleDisplaySize / 4 },
|
shape: { type: 'circle', radius: BONUS_ITEM.particleDisplaySize / 4 },
|
||||||
density: 0.001,
|
density: 0.001,
|
||||||
friction: 0.4,
|
friction: 0.4,
|
||||||
|
|
@ -175,9 +186,22 @@ export default class BonusItemManager {
|
||||||
restitution: 0.25, // small bounce when they settle on the ground
|
restitution: 0.25, // small bounce when they settle on the ground
|
||||||
collisionFilter: { mask: 0xffffffff & ~this.bus.busCategory & ~this.bus.kidCategory },
|
collisionFilter: { mask: 0xffffffff & ~this.bus.busCategory & ~this.bus.kidCategory },
|
||||||
});
|
});
|
||||||
particle.setDisplaySize(BONUS_ITEM.particleDisplaySize, BONUS_ITEM.particleDisplaySize);
|
// Per-particle size: every type defaults to the standard particle size;
|
||||||
|
// a type with a firework variant (flour) varies each particle's scale
|
||||||
|
// between scaleMin and scaleMax of it, so the burst reads as soft,
|
||||||
|
// differently-sized puffs rather than one uniform ring.
|
||||||
|
const scaleRange = variant.scaleMax - variant.scaleMin;
|
||||||
|
// 1 (no variant) or scaleMin..scaleMax when a variant defines one.
|
||||||
|
const particleScale = scaleRange > 0
|
||||||
|
? variant.scaleMin + Math.random() * scaleRange
|
||||||
|
: 1;
|
||||||
|
particle.setDisplaySize(
|
||||||
|
BONUS_ITEM.particleDisplaySize * particleScale,
|
||||||
|
BONUS_ITEM.particleDisplaySize * particleScale,
|
||||||
|
);
|
||||||
particle.setDepth(5); // above the bus, under the HUD - this is the show, let it read on top
|
particle.setDepth(5); // above the bus, under the HUD - this is the show, let it read on top
|
||||||
particle.setTint(tint);
|
particle.setTint(tint);
|
||||||
|
if (variant.alpha !== undefined) particle.setAlpha(variant.alpha); // e.g. flour's semi-transparent dust
|
||||||
|
|
||||||
// Launch outward. setVelocity is the Phaser Matter component (px per
|
// Launch outward. setVelocity is the Phaser Matter component (px per
|
||||||
// 60Hz physics step, matching every other velocity in this codebase -
|
// 60Hz physics step, matching every other velocity in this codebase -
|
||||||
|
|
|
||||||
|
|
@ -12,25 +12,37 @@ export default class Bus {
|
||||||
this.scene = scene;
|
this.scene = scene;
|
||||||
const matter = scene.matter;
|
const matter = scene.matter;
|
||||||
// Per-campaign bus look (see `bus` in data/themes.js): chassis and
|
// Per-campaign bus look (see `bus` in data/themes.js): chassis and
|
||||||
// wheel texture, the physics/display wheel radius, and the wheel
|
// wheel texture, the physics/display wheel radius, the wheel mount
|
||||||
// mount point (wheelRestOffsetY - campaign 2's "lift kit", which drops
|
// point (wheelRestOffsetY - campaign 2's "lift kit", which drops the
|
||||||
// the wheels further under the chassis). Defaults to campaign 1's
|
// wheels further under the chassis), the tire grip (the
|
||||||
// values, so existing callers (and any bus built outside a themed
|
// wheelFriction/wheelFrictionStatic coefficients - campaign 2's tires
|
||||||
// scene) are unchanged. The chassis texture is visual only - the
|
// grip the road harder), and the shocks (suspensionStiffness /
|
||||||
// physics body stays the same BUS.chassisWidth/Height rectangle
|
// suspensionDamping - campaign 2's return faster and bounce more).
|
||||||
// either way. (The wishbone anchors below are DERIVED from these
|
// Defaults to campaign 1's values, so existing callers (and any bus
|
||||||
// values, so a bigger radius and/or a lower mount point both stay
|
// built outside a themed scene) are unchanged.
|
||||||
// correctly centered on the same axle line.)
|
// The chassis texture is visual only - the physics body stays the same
|
||||||
|
// BUS.chassisWidth/Height rectangle either way. (The wishbone anchors
|
||||||
|
// below are DERIVED from these values, so a bigger radius and/or a
|
||||||
|
// lower mount point both stay correctly centered on the same axle
|
||||||
|
// line.)
|
||||||
const {
|
const {
|
||||||
chassisKey = 'bus_chassis',
|
chassisKey = 'bus_chassis',
|
||||||
wheelTextureKey = 'bus_wheel',
|
wheelTextureKey = 'bus_wheel',
|
||||||
wheelRadius = BUS.wheelRadius,
|
wheelRadius = BUS.wheelRadius,
|
||||||
wheelRestOffsetY = BUS.wheelRestOffsetY,
|
wheelRestOffsetY = BUS.wheelRestOffsetY,
|
||||||
|
wheelFriction = BUS.wheelFriction,
|
||||||
|
wheelFrictionStatic = BUS.wheelFrictionStatic,
|
||||||
|
suspensionStiffness = BUS.suspensionStiffness,
|
||||||
|
suspensionDamping = BUS.suspensionDamping,
|
||||||
} = busTheme || {};
|
} = busTheme || {};
|
||||||
this.chassisKey = chassisKey;
|
this.chassisKey = chassisKey;
|
||||||
this.wheelTextureKey = wheelTextureKey;
|
this.wheelTextureKey = wheelTextureKey;
|
||||||
this.wheelRadius = wheelRadius;
|
this.wheelRadius = wheelRadius;
|
||||||
this._wheelRestOffsetY = wheelRestOffsetY;
|
this._wheelRestOffsetY = wheelRestOffsetY;
|
||||||
|
this._wheelFriction = wheelFriction;
|
||||||
|
this._wheelFrictionStatic = wheelFrictionStatic;
|
||||||
|
this._suspensionStiffness = suspensionStiffness;
|
||||||
|
this._suspensionDamping = suspensionDamping;
|
||||||
|
|
||||||
this.group = matter.world.nextGroup(true);
|
this.group = matter.world.nextGroup(true);
|
||||||
// Dedicated category so the compartment fixtures below can target kids
|
// Dedicated category so the compartment fixtures below can target kids
|
||||||
|
|
@ -239,12 +251,13 @@ export default class Bus {
|
||||||
const options = (anchorX) => ({
|
const options = (anchorX) => ({
|
||||||
pointA: { x: anchorX, y: anchorY },
|
pointA: { x: anchorX, y: anchorY },
|
||||||
pointB: { x: 0, y: 0 },
|
pointB: { x: 0, y: 0 },
|
||||||
damping: BUS.suspensionDamping,
|
// Per-campaign shocks (see the constructor's busTheme comment).
|
||||||
|
damping: this._suspensionDamping,
|
||||||
});
|
});
|
||||||
|
|
||||||
return [
|
return [
|
||||||
matter.add.constraint(this.chassis, wheel, length, BUS.suspensionStiffness, options(offsetX - BUS.axleSpread)),
|
matter.add.constraint(this.chassis, wheel, length, this._suspensionStiffness, options(offsetX - BUS.axleSpread)),
|
||||||
matter.add.constraint(this.chassis, wheel, length, BUS.suspensionStiffness, options(offsetX + BUS.axleSpread)),
|
matter.add.constraint(this.chassis, wheel, length, this._suspensionStiffness, options(offsetX + BUS.axleSpread)),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -252,8 +265,11 @@ export default class Bus {
|
||||||
const wheel = this.scene.matter.add.image(x, y, textureKey, null, {
|
const wheel = this.scene.matter.add.image(x, y, textureKey, null, {
|
||||||
shape: { type: 'circle', radius },
|
shape: { type: 'circle', radius },
|
||||||
density: BUS.wheelDensity,
|
density: BUS.wheelDensity,
|
||||||
friction: BUS.wheelFriction,
|
// Per-campaign tire grip (campaign 2's bus_wheel_2 rubber is
|
||||||
frictionStatic: BUS.wheelFrictionStatic,
|
// stickier - see the busTheme block in the constructor and
|
||||||
|
// BUS.wheelFriction2 in config.js).
|
||||||
|
friction: this._wheelFriction,
|
||||||
|
frictionStatic: this._wheelFrictionStatic,
|
||||||
frictionAir: BUS.wheelFrictionAir,
|
frictionAir: BUS.wheelFrictionAir,
|
||||||
restitution: BUS.wheelRestitution,
|
restitution: BUS.wheelRestitution,
|
||||||
collisionFilter: { group: this.group, category: this.busCategory },
|
collisionFilter: { group: this.group, category: this.busCategory },
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ export default class PreloadScene extends Phaser.Scene {
|
||||||
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');
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,8 @@ export const ASSET_MANIFEST = [
|
||||||
// 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.
|
||||||
{ 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 },
|
||||||
// 9 frames of 64x64 bonus items (CTC box + 2 firework frames, guitar + 2,
|
// 12 frames of 64x64 bonus items (CTC box + 2 firework frames, guitar + 2,
|
||||||
// cash + 2 - see BONUS_ITEM in config.js and BonusItemManager.js).
|
// cash + 2, flour + 2 - 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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue