monsterplex/src/config.js

169 lines
7.8 KiB
JavaScript

// Every spatial (pixel) constant in this file and in src/data/levels/*.js is
// multiplied by WORLD_SCALE, so the whole game - canvas, physics bodies,
// terrain, UI - resizes together. Change this one number to rescale it again.
export const WORLD_SCALE = 2; // 960x540 base design -> 1920x1080
export const GAME_WIDTH = 960 * WORLD_SCALE;
export const GAME_HEIGHT = 540 * WORLD_SCALE;
// Set true to show live g-force / debug readouts during PlayScene.
export const DEBUG = false;
export const STORAGE_KEY = 'monsterplex.progress.v1';
// Matter runs Phaser's default fixed 60Hz step.
export const PHYSICS_TIMESTEP_SECONDS = 1 / 60;
export const BUS = {
chassisWidth: 170 * WORLD_SCALE,
chassisHeight: 64 * WORLD_SCALE,
// Measured directly from assets/sprites/bus_chassis.png's baked-in wheel
// wells (it's a real photo bus - long rear overhang, short front hood, so
// the axles are NOT symmetric around the chassis center). Offsets are from
// the chassis center; re-measure these if bus_chassis.png is replaced.
wheelRadius: 17 * WORLD_SCALE,
rearWheelOffsetX: -35 * WORLD_SCALE,
frontWheelOffsetX: 64 * WORLD_SCALE,
// Where the wheel's CENTER rests at equilibrium. Raised from the
// wheel-well-measured 22 to 34 to lift the chassis and drop the tires
// further down for a bouncier stance - this now visibly departs from
// bus_chassis.png's painted wheel wells (a gap opens above the tire),
// a deliberate look/feel tradeoff rather than a measurement.
wheelRestOffsetY: 34 * WORLD_SCALE,
// Horizontal spread between the two suspension links each wheel hangs
// 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
// by two constraints spread by this much, which resists side-to-side
// wander while still allowing the wheel to compress/extend vertically.
axleSpread: 6 * WORLD_SCALE,
maxSeats: 5,
// The invisible box kids ride in, local to the chassis (0,0 = chassis
// center, y down): solid floor + left/right walls, but deliberately no
// ceiling fixture at all - see Bus.js's _buildCompartment. A kid whose
// own local Y (rotation-corrected relative to the chassis) rises above
// compartmentTopY has nothing left to collide with and is airborne; the
// same check runs both directions each tick (see KidManager), so falling
// back below it while inside compartmentHalfWidth re-contains the kid.
// Tune by playtesting - not physically derived.
compartmentHalfWidth: 75 * WORLD_SCALE,
compartmentWallThickness: 10 * WORLD_SCALE,
compartmentFloorThickness: 10 * WORLD_SCALE,
compartmentFloorY: 12 * WORLD_SCALE,
compartmentTopY: -60 * WORLD_SCALE,
// Density/friction/stiffness/damping are dimensionless ratios, not pixel
// distances - they don't scale with WORLD_SCALE.
chassisDensity: 0.0015,
chassisFriction: 0.4,
chassisFrictionAir: 0.01,
wheelDensity: 0.004,
wheelFriction: 0.9,
wheelFrictionStatic: 1.4,
// 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,
// so 0.02 was burning off roughly a third of angular velocity per second)
// and was the main reason the bus felt sluggish rather than Trials-fast.
wheelFrictionAir: 0.008,
// Some bounce on landing, not just suspension absorbing everything.
wheelRestitution: 0.45,
// Softer and less damped than before - more travel, and lets it actually
// oscillate (bounce) instead of settling immediately - "more shocks."
suspensionStiffness: 0.35,
suspensionDamping: 0.035,
// 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
// the tradeoff for real suspension travel/bounce instead of a stiff ride.
suspensionTravel: 30 * WORLD_SCALE,
// Angular velocities/torque steps are rotational (radians), not linear
// pixel distances, so they're left unscaled - wheel radius already scaled,
// so linear surface speed (angularVelocity * radius) scales automatically.
// Pushed way up from the original values for Trials/Snuggle-Truck-style
// speed - the old numbers made the bus crawl.
maxWheelAngularVelocity: 5,
driveTorque: 0.6,
brakeTorque: 0.4,
maxBrakeAngularVelocity: 3.5,
// Measured via headless testing: spinning the wheel faster alone barely
// moved the chassis - past a point the wheel just slips (friction force
// is capped by frictionCoefficient * normalForce regardless of how fast
// the wheel spins beyond matching ground speed), so throttle applies a
// direct force to the chassis itself, along its current facing/tilt, on
// top of the wheel spin. This is the standard fix in arcade 2D driving
// games for exactly this problem. Expressed as target acceleration
// (Bus.js computes force = chassis mass * this) rather than a raw force,
// so it stays correct if chassisDensity/size ever changes - tune these
// to change top speed feel.
// Cut by ~60% (0.011 -> 0.0045) for a much longer runway to top speed.
// Note this also lowers the top speed itself, not just the time to reach
// it - top speed is where this force balances chassisFrictionAir drag, so
// a smaller force settles at a lower equilibrium speed too.
driveAcceleration: 0.0045,
brakeAcceleration: 0.007,
// Cut by more than half (both the cap and the per-tick torque step) for a
// much slower, more gradual lean - easier to fine-tune mid-air at the
// cost of snappiness.
maxLeanAngularVelocity: 0.06,
leanTorqueStep: 0.004,
};
// Kid ejection is no longer impact-triggered (see KidManager/Bus's
// compartment box) - GFORCE now only drives the DEBUG g-force readout in
// PlayScene, kept because it's still a useful "how hard did that landing
// hit" diagnostic on its own.
export const GFORCE = {
// Scaled with WORLD_SCALE so "meters" keeps the same real-world meaning
// relative to the bus.
pixelsPerMeter: 50 * WORLD_SCALE,
gracePeriodMs: 500,
};
export const KID = {
radius: 14 * WORLD_SCALE,
density: 0.001,
friction: 0.6,
// While outside the compartment (past the open top - see
// BUS.compartmentTopY), a kid ignores the world's real gravity entirely
// and KidManager applies this fraction of it instead (see
// KidManager._onBeforeUpdate) - horizontal velocity is untouched either
// way (this game's gravity is purely vertical), so a lighter fall is all
// it takes for a kid to hang in the air and arc much further than the
// bus does, Snuggle-Truck style. Primary tuning knob for "how floaty,"
// not physically derived.
ejectedGravityScale: 0.35,
};
export const CAMERA = {
lerpX: 0.1,
lerpY: 0.1,
// Horizontal position is speed-driven (see CameraRig.update - it steers
// via followOffset.x) rather than a dead zone, so this is kept tiny
// rather than removed, just to dodge exact-equality edge cases in
// Phaser's deadzone math. Vertical still uses a real dead zone below.
deadzoneWidth: 2 * WORLD_SCALE,
deadzoneHeight: 140 * WORLD_SCALE,
// Where the bus sits on screen, as a fraction of screen width from the
// left edge - primary tuning knobs, not physically derived, tune by
// playtesting. restScreenFraction leaves enough room for the chassis
// (half its width is ~0.09 of the screen) not to clip off the left edge
// while still reading as "almost touching."
restScreenFraction: 0.1,
maxSpeedScreenFraction: 1 / 3,
// Horizontal chassis speed (px/s) at which framing reaches
// maxSpeedScreenFraction; clamped beyond it. There's no hard speed cap in
// the drive physics (chassisFrictionAir just balances driveAcceleration
// out asymptotically), so this is a starting estimate - tune by
// playtesting alongside the fractions above.
lookaheadMaxSpeed: 110 * WORLD_SCALE,
};
export const TERRAIN_DEPTH = 400 * WORLD_SCALE;