Add campaign 2 bus wheel variant (bigger wheels via theme)
- Introduce `bus_wheel_2` asset and register it in the asset manifest - Add `BUS.wheelRadius2` config for the larger wheel radius - Extend themes with a `bus` block to select wheel texture and radius per campaign; campaign02 uses the new bigger wheels while keeping the same axle center - Make `Bus` accept a bus theme parameter and use provided wheel texture/radius when creating wheels - Update `PlayScene` to pass the active theme's `bus` settings into the Bus constructor - Document the new per-campaign bus art/sizing behavior in sprites.md
This commit is contained in:
parent
3033c1d62d
commit
eaeebf1980
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
|
|
@ -44,6 +44,12 @@ with it (same ratios, just re-derive from that one constant).
|
||||||
no separate left/right variant needed. It's a real physics body, so it
|
no separate left/right variant needed. It's a real physics body, so it
|
||||||
visually spins as the bus drives; a plain hubcap/circle reads better at
|
visually spins as the bus drives; a plain hubcap/circle reads better at
|
||||||
speed than fine detail.
|
speed than fine detail.
|
||||||
|
- 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
|
||||||
|
`bus` block in `src/data/themes.js`), and its own chassis image,
|
||||||
|
`bus_chassis_2.png` (same 340x128 design box, physics body unchanged -
|
||||||
|
only the texture is swapped per campaign).
|
||||||
|
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
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,10 @@ export const BUS = {
|
||||||
// the axles are NOT symmetric around the chassis center). Offsets are from
|
// the axles are NOT symmetric around the chassis center). Offsets are from
|
||||||
// the chassis center; re-measure these if bus_chassis.png is replaced.
|
// the chassis center; re-measure these if bus_chassis.png is replaced.
|
||||||
wheelRadius: 17 * WORLD_SCALE,
|
wheelRadius: 17 * WORLD_SCALE,
|
||||||
|
// Campaign 2's bus uses the bigger bus_wheel_2.png (125px vs bus_wheel.png's
|
||||||
|
// 104px, ~1.2x) around the SAME wheel center - 17 * 1.2 ≈ 20. See
|
||||||
|
// `bus` in data/themes.js, which selects it for campaign02 only.
|
||||||
|
wheelRadius2: 20 * WORLD_SCALE,
|
||||||
rearWheelOffsetX: -35 * WORLD_SCALE,
|
rearWheelOffsetX: -35 * WORLD_SCALE,
|
||||||
frontWheelOffsetX: 64 * WORLD_SCALE,
|
frontWheelOffsetX: 64 * WORLD_SCALE,
|
||||||
// Where the wheel's CENTER rests at equilibrium. Raised from the
|
// Where the wheel's CENTER rests at equilibrium. Raised from the
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
|
import { BUS } from '../config.js';
|
||||||
|
|
||||||
// Per-campaign themes for the IN-LEVEL (play) presentation - the parallax
|
// Per-campaign themes for the IN-LEVEL (play) presentation - the parallax
|
||||||
// background stack, base camera color, and terrain look shown while actually
|
// background stack, base camera color, terrain look, and the bus's own
|
||||||
|
// art/sizing (e.g. campaign 2's bigger wheels) shown while actually
|
||||||
// driving a level. This is distinct from a campaign's *map* art (bgKey) and
|
// driving a level. This is distinct from a campaign's *map* art (bgKey) and
|
||||||
// menu music (musicKey), which live on the campaign object in
|
// menu music (musicKey), which live on the campaign object in
|
||||||
// data/levels/index.js.
|
// data/levels/index.js.
|
||||||
|
|
@ -75,6 +78,18 @@ export const DEFAULT_THEME = {
|
||||||
// visible through gaps. A hex string, the same format the scenes pass to
|
// visible through gaps. A hex string, the same format the scenes pass to
|
||||||
// Phaser's setBackgroundColor().
|
// Phaser's setBackgroundColor().
|
||||||
bgColor: '#8fc7e8',
|
bgColor: '#8fc7e8',
|
||||||
|
// The bus's own look (see Bus.js's constructor and BUS in config.js).
|
||||||
|
// `chassisKey` is the chassis texture (purely visual - the physics body
|
||||||
|
// stays the same BUS.chassisWidth/Height rectangle either way);
|
||||||
|
// `wheelTextureKey` is the texture both wheels render with; `wheelRadius`
|
||||||
|
// is the physics/display radius in world pixels. Changing the radius
|
||||||
|
// keeps the wheel centered on the same spot (the wishbone anchors and
|
||||||
|
// wheelRestOffsetY are radius-agnostic) - only the size changes.
|
||||||
|
bus: {
|
||||||
|
chassisKey: 'bus_chassis',
|
||||||
|
wheelTextureKey: 'bus_wheel',
|
||||||
|
wheelRadius: BUS.wheelRadius,
|
||||||
|
},
|
||||||
// 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
|
||||||
// _buildParallax). Keys are texture keys, not file paths.
|
// _buildParallax). Keys are texture keys, not file paths.
|
||||||
|
|
@ -92,8 +107,9 @@ export const THEMES = {
|
||||||
campaign01: DEFAULT_THEME,
|
campaign01: DEFAULT_THEME,
|
||||||
|
|
||||||
// Campaign 2 ("Underprivileged Community") - the urban _2 background set,
|
// Campaign 2 ("Underprivileged Community") - the urban _2 background set,
|
||||||
// a dusk base color matched to bg_far_2's sky, and the asphalt-terrain
|
// a dusk base color matched to bg_far_2's sky, the asphalt-terrain
|
||||||
// look above.
|
// look above, and the bigger bus_wheel_2.png wheels (same center, ~1.2x
|
||||||
|
// radius - see BUS.wheelRadius2 in config.js).
|
||||||
campaign02: {
|
campaign02: {
|
||||||
bgColor: '#92656d',
|
bgColor: '#92656d',
|
||||||
parallax: {
|
parallax: {
|
||||||
|
|
@ -102,12 +118,19 @@ export const THEMES = {
|
||||||
near: 'bg_near_2',
|
near: 'bg_near_2',
|
||||||
},
|
},
|
||||||
terrain: URBAN_TERRAIN,
|
terrain: URBAN_TERRAIN,
|
||||||
|
bus: {
|
||||||
|
chassisKey: 'bus_chassis_2',
|
||||||
|
wheelTextureKey: 'bus_wheel_2',
|
||||||
|
wheelRadius: BUS.wheelRadius2,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// Add campaign03: { ... } here when it's ready.
|
// Add campaign03: { ... } here when it's ready.
|
||||||
};
|
};
|
||||||
|
|
||||||
// Resolves the theme for a campaign id, always returning a complete theme
|
// Resolves the theme for a campaign id, always returning a complete theme
|
||||||
// (falls back to the default for unknown/absent campaigns).
|
// (falls back to the default for unknown/absent campaigns). Nested objects
|
||||||
|
// like `bus` are whole-value overrides, so a campaign entry that sets them
|
||||||
|
// must set every field it wants (they're small and stable - that's fine).
|
||||||
export function getTheme(campaignId) {
|
export function getTheme(campaignId) {
|
||||||
const theme = THEMES[campaignId];
|
const theme = THEMES[campaignId];
|
||||||
return theme ? { ...DEFAULT_THEME, ...theme } : DEFAULT_THEME;
|
return theme ? { ...DEFAULT_THEME, ...theme } : DEFAULT_THEME;
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,23 @@ import { BUS } from '../config.js';
|
||||||
// with no vertical give, which is too harsh for a bus that needs real
|
// with no vertical give, which is too harsh for a bus that needs real
|
||||||
// suspension travel to absorb bumps.
|
// suspension travel to absorb bumps.
|
||||||
export default class Bus {
|
export default class Bus {
|
||||||
constructor(scene, x, y, angle = 0) {
|
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
|
||||||
|
// texture and wheel texture to render and the physics/display wheel
|
||||||
|
// radius. Defaults to campaign 1's values, so existing callers (and
|
||||||
|
// any bus built outside a themed scene) are unchanged. The chassis
|
||||||
|
// texture is visual only - the physics body stays the same
|
||||||
|
// BUS.chassisWidth/Height rectangle either way. NOTE: only the wheel
|
||||||
|
// size varies - the wheel's CENTER is anchored to the chassis via the
|
||||||
|
// wishbone below (anchorY/wheelRestOffsetY are radius-agnostic), so a
|
||||||
|
// bigger radius grows the wheel around the same center, exactly as
|
||||||
|
// intended.
|
||||||
|
const { chassisKey = 'bus_chassis', wheelTextureKey = 'bus_wheel', wheelRadius = BUS.wheelRadius } = busTheme || {};
|
||||||
|
this.chassisKey = chassisKey;
|
||||||
|
this.wheelTextureKey = wheelTextureKey;
|
||||||
|
this.wheelRadius = wheelRadius;
|
||||||
|
|
||||||
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
|
||||||
|
|
@ -44,7 +58,7 @@ export default class Bus {
|
||||||
// of hanging level, snapping into place on the first physics step.
|
// of hanging level, snapping into place on the first physics step.
|
||||||
const constraintLength = Math.hypot(BUS.axleSpread, BUS.suspensionTravel);
|
const constraintLength = Math.hypot(BUS.axleSpread, BUS.suspensionTravel);
|
||||||
|
|
||||||
this.chassis = matter.add.image(x, y, 'bus_chassis', null, {
|
this.chassis = matter.add.image(x, y, this.chassisKey, null, {
|
||||||
shape: { type: 'rectangle', width: BUS.chassisWidth, height: BUS.chassisHeight },
|
shape: { type: 'rectangle', width: BUS.chassisWidth, height: BUS.chassisHeight },
|
||||||
chamfer: { radius: BUS.chassisHeight * 0.35 },
|
chamfer: { radius: BUS.chassisHeight * 0.35 },
|
||||||
density: BUS.chassisDensity,
|
density: BUS.chassisDensity,
|
||||||
|
|
@ -61,8 +75,8 @@ export default class Bus {
|
||||||
this.chassis.setDepth(2);
|
this.chassis.setDepth(2);
|
||||||
|
|
||||||
const wheelY = y + BUS.wheelRestOffsetY;
|
const wheelY = y + BUS.wheelRestOffsetY;
|
||||||
this.wheelRear = this._createWheel(x + BUS.rearWheelOffsetX, wheelY);
|
this.wheelRear = this._createWheel(x + BUS.rearWheelOffsetX, wheelY, this.wheelTextureKey, this.wheelRadius);
|
||||||
this.wheelFront = this._createWheel(x + BUS.frontWheelOffsetX, wheelY);
|
this.wheelFront = this._createWheel(x + BUS.frontWheelOffsetX, wheelY, this.wheelTextureKey, this.wheelRadius);
|
||||||
|
|
||||||
this.wheelRearLinks = this._attachWishbone(this.wheelRear, BUS.rearWheelOffsetX, anchorY, constraintLength);
|
this.wheelRearLinks = this._attachWishbone(this.wheelRear, BUS.rearWheelOffsetX, anchorY, constraintLength);
|
||||||
this.wheelFrontLinks = this._attachWishbone(this.wheelFront, BUS.frontWheelOffsetX, anchorY, constraintLength);
|
this.wheelFrontLinks = this._attachWishbone(this.wheelFront, BUS.frontWheelOffsetX, anchorY, constraintLength);
|
||||||
|
|
@ -225,9 +239,9 @@ export default class Bus {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
_createWheel(x, y) {
|
_createWheel(x, y, textureKey, radius) {
|
||||||
const wheel = this.scene.matter.add.image(x, y, 'bus_wheel', null, {
|
const wheel = this.scene.matter.add.image(x, y, textureKey, null, {
|
||||||
shape: { type: 'circle', radius: BUS.wheelRadius },
|
shape: { type: 'circle', radius },
|
||||||
density: BUS.wheelDensity,
|
density: BUS.wheelDensity,
|
||||||
friction: BUS.wheelFriction,
|
friction: BUS.wheelFriction,
|
||||||
frictionStatic: BUS.wheelFrictionStatic,
|
frictionStatic: BUS.wheelFrictionStatic,
|
||||||
|
|
@ -235,7 +249,7 @@ export default class Bus {
|
||||||
restitution: BUS.wheelRestitution,
|
restitution: BUS.wheelRestitution,
|
||||||
collisionFilter: { group: this.group, category: this.busCategory },
|
collisionFilter: { group: this.group, category: this.busCategory },
|
||||||
});
|
});
|
||||||
wheel.setDisplaySize(BUS.wheelRadius * 2, BUS.wheelRadius * 2);
|
wheel.setDisplaySize(radius * 2, radius * 2);
|
||||||
wheel.setDepth(2); // matches the chassis - see its setDepth comment
|
wheel.setDepth(2); // matches the chassis - see its setDepth comment
|
||||||
return wheel;
|
return wheel;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,15 @@ export default class PlayScene extends Phaser.Scene {
|
||||||
this.terrain = new Terrain(this, this.level, this.theme.terrain);
|
this.terrain = new Terrain(this, this.level, this.theme.terrain);
|
||||||
this.finishLine = new FinishLine(this, this.level);
|
this.finishLine = new FinishLine(this, this.level);
|
||||||
|
|
||||||
this.bus = new Bus(this, this.level.startPosition.x, this.level.startPosition.y, this.level.startAngle || 0);
|
this.bus = new Bus(
|
||||||
|
this,
|
||||||
|
this.level.startPosition.x,
|
||||||
|
this.level.startPosition.y,
|
||||||
|
this.level.startAngle || 0,
|
||||||
|
// Campaign 2 gets its own wheel art + size from here (see the `bus`
|
||||||
|
// block in data/themes.js); campaign 1 uses the defaults in Bus.js.
|
||||||
|
this.theme.bus
|
||||||
|
);
|
||||||
|
|
||||||
this.kidManager = new KidManager(this, this.bus, this.level.kidsAboard);
|
this.kidManager = new KidManager(this, this.bus, this.level.kidsAboard);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,14 @@ import { WORLD_SCALE } from '../config.js';
|
||||||
// nothing else in the codebase needs to change (see placeholderTextures.js).
|
// nothing else in the codebase needs to change (see placeholderTextures.js).
|
||||||
export const ASSET_MANIFEST = [
|
export const ASSET_MANIFEST = [
|
||||||
{ key: 'bus_chassis', path: 'assets/sprites/bus_chassis.png', width: 170 * WORLD_SCALE, height: 64 * WORLD_SCALE, kind: 'rect', color: 0x2255aa },
|
{ key: 'bus_chassis', path: 'assets/sprites/bus_chassis.png', width: 170 * WORLD_SCALE, height: 64 * WORLD_SCALE, kind: 'rect', color: 0x2255aa },
|
||||||
|
// Campaign 2's chassis art - same 340x128 design box, different look.
|
||||||
|
// Picked per campaign via the `bus` theme entry in data/themes.js; the
|
||||||
|
// physics body is unchanged (see Bus.js's constructor).
|
||||||
|
{ key: 'bus_chassis_2', path: 'assets/sprites/bus_chassis_2.png', width: 170 * WORLD_SCALE, height: 64 * WORLD_SCALE, kind: 'rect', color: 0x2255aa },
|
||||||
{ key: 'bus_wheel', path: 'assets/sprites/bus_wheel.png', width: 34 * WORLD_SCALE, height: 34 * WORLD_SCALE, kind: 'circle', color: 0x222222 },
|
{ key: 'bus_wheel', path: 'assets/sprites/bus_wheel.png', width: 34 * WORLD_SCALE, height: 34 * WORLD_SCALE, kind: 'circle', color: 0x222222 },
|
||||||
|
// Campaign 2's bigger wheels (125px source vs bus_wheel.png's 104px) -
|
||||||
|
// picked per campaign via the `bus` theme entry in data/themes.js.
|
||||||
|
{ key: 'bus_wheel_2', path: 'assets/sprites/bus_wheel_2.png', width: 34 * WORLD_SCALE, height: 34 * WORLD_SCALE, kind: 'circle', color: 0x222222 },
|
||||||
// Real art is a 5-frame sheet (one distinct kid per frame, 56x56px each,
|
// Real art is a 5-frame sheet (one distinct kid per frame, 56x56px each,
|
||||||
// matching BUS.maxSeats) - frameWidth/frameHeight (raw source pixels, NOT
|
// matching BUS.maxSeats) - frameWidth/frameHeight (raw source pixels, NOT
|
||||||
// WORLD_SCALE-relative) make PreloadScene load it as a spritesheet
|
// WORLD_SCALE-relative) make PreloadScene load it as a spritesheet
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue