Add "lift kit" for Campaign 2 bus

Campaign 2's larger wheels now hang further under the chassis via a new `wheelRestOffsetY` theme knob (42 vs 34 design units), making the bus visibly sit taller. The wishbone anchor and initial wheel position derive from this per-campaign offset, so both size and mount point stay correctly centered on the axle line.

- Add `BUS.wheelRestOffsetY2` in config
- Expose `wheelRestOffsetY` in theme `bus` blocks (default = campaign 1)
- Use themed offset in Bus constructor for anchor and wheel placement
- Update docs/comments to explain the lift-kit behavior
This commit is contained in:
Brian Fertig 2026-08-23 18:39:07 -06:00
parent eaeebf1980
commit 8cba629232
4 changed files with 42 additions and 19 deletions

View File

@ -46,10 +46,13 @@ with it (same ratios, just re-derive from that one constant).
speed than fine detail. speed than fine detail.
- Campaign 2 uses its own wheel image, `bus_wheel_2.png` (bigger - it's - Campaign 2 uses its own wheel image, `bus_wheel_2.png` (bigger - it's
displayed at a ~1.2x radius around the same axle center, selected via the displayed at a ~1.2x radius around the same axle center, selected via the
`bus` block in `src/data/themes.js`), and its own chassis image, `bus` block in `src/data/themes.js`), its own chassis image,
`bus_chassis_2.png` (same 340x128 design box, physics body unchanged - `bus_chassis_2.png` (same 340x128 design box, physics body unchanged -
only the texture is swapped per campaign). only the texture is swapped per campaign), and a "lift kit": the wheel
Both wheels still share one image per campaign. mount point sits ~8 design units lower under the chassis (see
`wheelRestOffsetY2` in `src/config.js` and the `bus` block in
`src/data/themes.js`), so it reads as a lifted bus. 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

View File

@ -41,6 +41,10 @@ export const BUS = {
// bus_chassis.png's painted wheel wells (a gap opens above the tire), // bus_chassis.png's painted wheel wells (a gap opens above the tire),
// a deliberate look/feel tradeoff rather than a measurement. // a deliberate look/feel tradeoff rather than a measurement.
wheelRestOffsetY: 34 * WORLD_SCALE, wheelRestOffsetY: 34 * WORLD_SCALE,
// Campaign 2's "lift kit": the same mount point 8 design units lower
// (see `bus` in data/themes.js), so the bigger wheels hang further under
// the chassis and the bus visibly sits taller.
wheelRestOffsetY2: 42 * WORLD_SCALE,
// Horizontal spread between the two suspension links each wheel hangs // Horizontal spread between the two suspension links each wheel hangs
// from (a "wishbone") - a single point-to-point constraint can't resist // from (a "wishbone") - a single point-to-point constraint can't resist
// sideways drift at all (it's radially symmetric), so each wheel is held // sideways drift at all (it's radially symmetric), so each wheel is held

View File

@ -82,13 +82,16 @@ export const DEFAULT_THEME = {
// `chassisKey` is the chassis texture (purely visual - the physics body // `chassisKey` is the chassis texture (purely visual - the physics body
// stays the same BUS.chassisWidth/Height rectangle either way); // stays the same BUS.chassisWidth/Height rectangle either way);
// `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. Changing the radius // is the physics/display radius in world pixels; `wheelRestOffsetY` is
// keeps the wheel centered on the same spot (the wishbone anchors and // the wheel mount point, local to the chassis (y down) - raising it is
// wheelRestOffsetY are radius-agnostic) - only the size changes. // the "lift kit" knob (campaign 2). 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,
}, },
// 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
@ -122,6 +125,10 @@ export const THEMES = {
chassisKey: 'bus_chassis_2', chassisKey: 'bus_chassis_2',
wheelTextureKey: 'bus_wheel_2', wheelTextureKey: 'bus_wheel_2',
wheelRadius: BUS.wheelRadius2, wheelRadius: BUS.wheelRadius2,
// "Lift kit": drop the wheel mount point 8 design units below
// campaign 1's (see BUS.wheelRestOffsetY2) - the bus sits taller and
// the bigger wheels hang lower under the body.
wheelRestOffsetY: BUS.wheelRestOffsetY2,
}, },
}, },
// Add campaign03: { ... } here when it's ready. // Add campaign03: { ... } here when it's ready.

View File

@ -11,20 +11,26 @@ export default class Bus {
constructor(scene, x, y, angle = 0, busTheme = null) { constructor(scene, x, y, angle = 0, busTheme = null) {
this.scene = scene; this.scene = scene;
const matter = scene.matter; const matter = scene.matter;
// Per-campaign bus look (see `bus` in data/themes.js): which chassis // Per-campaign bus look (see `bus` in data/themes.js): chassis and
// texture and wheel texture to render and the physics/display wheel // wheel texture, the physics/display wheel radius, and the wheel
// radius. Defaults to campaign 1's values, so existing callers (and // mount point (wheelRestOffsetY - campaign 2's "lift kit", which drops
// any bus built outside a themed scene) are unchanged. The chassis // the wheels further under the chassis). Defaults to campaign 1's
// texture is visual only - the physics body stays the same // values, so existing callers (and any bus built outside a themed
// BUS.chassisWidth/Height rectangle either way. NOTE: only the wheel // scene) are unchanged. The chassis texture is visual only - the
// size varies - the wheel's CENTER is anchored to the chassis via the // physics body stays the same BUS.chassisWidth/Height rectangle
// wishbone below (anchorY/wheelRestOffsetY are radius-agnostic), so a // either way. (The wishbone anchors below are DERIVED from these
// bigger radius grows the wheel around the same center, exactly as // values, so a bigger radius and/or a lower mount point both stay
// intended. // correctly centered on the same axle line.)
const { chassisKey = 'bus_chassis', wheelTextureKey = 'bus_wheel', wheelRadius = BUS.wheelRadius } = busTheme || {}; const {
chassisKey = 'bus_chassis',
wheelTextureKey = 'bus_wheel',
wheelRadius = BUS.wheelRadius,
wheelRestOffsetY = BUS.wheelRestOffsetY,
} = busTheme || {};
this.chassisKey = chassisKey; this.chassisKey = chassisKey;
this.wheelTextureKey = wheelTextureKey; this.wheelTextureKey = wheelTextureKey;
this.wheelRadius = wheelRadius; this.wheelRadius = wheelRadius;
this._wheelRestOffsetY = wheelRestOffsetY;
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
@ -50,7 +56,10 @@ export default class Bus {
// so the spring pulling the wheel out to its full constraint length is // so the spring pulling the wheel out to its full constraint length is
// what lands it exactly on wheelRestOffsetY (anchorY + travel = restY), // what lands it exactly on wheelRestOffsetY (anchorY + travel = restY),
// while still leaving suspensionTravel of compress/droop room. // while still leaving suspensionTravel of compress/droop room.
const anchorY = BUS.wheelRestOffsetY - BUS.suspensionTravel; // this._wheelRestOffsetY is the campaign's mount point (the lift-kit
// knob) - raising it lowers the wheel under the chassis and, since
// the anchor rides with it, the bus visibly sits taller.
const anchorY = this._wheelRestOffsetY - BUS.suspensionTravel;
// The wheel doesn't hang straight down from a single point (that point // The wheel doesn't hang straight down from a single point (that point
// is split into two, spread by axleSpread), so the constraint's target // is split into two, spread by axleSpread), so the constraint's target
// length is the hypotenuse to the spread anchor, not just the vertical // length is the hypotenuse to the spread anchor, not just the vertical
@ -74,7 +83,7 @@ export default class Bus {
// alpha. // alpha.
this.chassis.setDepth(2); this.chassis.setDepth(2);
const wheelY = y + BUS.wheelRestOffsetY; const wheelY = y + this._wheelRestOffsetY;
this.wheelRear = this._createWheel(x + BUS.rearWheelOffsetX, wheelY, this.wheelTextureKey, this.wheelRadius); this.wheelRear = this._createWheel(x + BUS.rearWheelOffsetX, wheelY, this.wheelTextureKey, this.wheelRadius);
this.wheelFront = this._createWheel(x + BUS.frontWheelOffsetX, wheelY, this.wheelTextureKey, this.wheelRadius); this.wheelFront = this._createWheel(x + BUS.frontWheelOffsetX, wheelY, this.wheelTextureKey, this.wheelRadius);