Add compartment debug overlay for kid ejection troubleshooting
Introduce `COMPARTMENT_DEBUG` config flag and a new `compartmentDebug.js` utility that renders the invisible bus compartment (floor, left/right walls, and open-top boundary) as colored rectangles tracking the chassis each frame. Includes per-kid position markers color-coded by state (aboard/ejected) and a live HUD readout showing local coordinates and ejection timing to diagnose why kids eject unexpectedly.
This commit is contained in:
parent
2e4b67db89
commit
499d82d4d4
|
|
@ -9,6 +9,12 @@ export const GAME_HEIGHT = 540 * WORLD_SCALE;
|
|||
// Set true to show live g-force / debug readouts during PlayScene.
|
||||
export const DEBUG = false;
|
||||
|
||||
// Set true to render the invisible kid compartment (floor + left/right
|
||||
// walls) as simple colored rectangles that track the chassis, plus a live
|
||||
// per-kid local-position/state readout in the PlayScene HUD. Troubleshooting
|
||||
// aid for ejection behavior - off during normal play.
|
||||
export const COMPARTMENT_DEBUG = true;
|
||||
|
||||
export const STORAGE_KEY = 'monsterplex.progress.v1';
|
||||
|
||||
// Matter runs Phaser's default fixed 60Hz step.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Phaser from 'phaser';
|
||||
import { GAME_WIDTH, GAME_HEIGHT, DEBUG, WORLD_SCALE } from '../config.js';
|
||||
import { GAME_WIDTH, GAME_HEIGHT, DEBUG, WORLD_SCALE, COMPARTMENT_DEBUG } from '../config.js';
|
||||
import { getLevelById } from '../data/levels/index.js';
|
||||
import Bus from '../entities/Bus.js';
|
||||
import Terrain from '../entities/Terrain.js';
|
||||
|
|
@ -13,6 +13,7 @@ import { stopMenuMusic } from '../util/music.js';
|
|||
import { playLevelVoiceLine } from '../util/voiceLine.js';
|
||||
import { formatTime } from '../util/formatTime.js';
|
||||
import { createPill } from '../util/pill.js';
|
||||
import buildCompartmentDebug from '../util/compartmentDebug.js';
|
||||
|
||||
export default class PlayScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
|
|
@ -41,6 +42,11 @@ export default class PlayScene extends Phaser.Scene {
|
|||
|
||||
this.kidManager = new KidManager(this, this.bus, this.level.kidsAboard);
|
||||
|
||||
// Troubleshooting overlay: renders the invisible compartment fixtures
|
||||
// (floor + walls) as simple rectangles tracking the bus, plus per-kid
|
||||
// state readout. Off by default - toggle COMPARTMENT_DEBUG in config.js.
|
||||
this.compartmentDebug = COMPARTMENT_DEBUG ? buildCompartmentDebug(this, this.bus, this.kidManager) : null;
|
||||
|
||||
this.inputController = new InputController(this);
|
||||
|
||||
this.engineSound = new EngineSound(this, this.bus);
|
||||
|
|
@ -320,6 +326,7 @@ export default class PlayScene extends Phaser.Scene {
|
|||
// Matter.World#shutdown() already calls removeAllListeners() on itself
|
||||
// internally (verified against the vendored source), so this was always
|
||||
// redundant, not just now-unsafe.
|
||||
if (this.compartmentDebug) this.compartmentDebug.destroy();
|
||||
if (this.gForceMonitor) this.gForceMonitor.destroy();
|
||||
if (this.kidManager) this.kidManager.destroy();
|
||||
if (this.engineSound) this.engineSound.destroy();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
import Phaser from 'phaser';
|
||||
import { BUS, KID, WORLD_SCALE } from '../config.js';
|
||||
|
||||
// Debug-only visualisation of the invisible kid compartment (see
|
||||
// Bus._buildCompartment): one simple colored rectangle per fixture
|
||||
// (floor + left wall + right wall) tracking the chassis every frame, plus
|
||||
// the open-top line and one marker per kid showing its state. Used for
|
||||
// troubleshooting ejection behavior (does the kid visually leave through
|
||||
// the open top before KidManager latches it? where does it sit relative to
|
||||
// the floor/walls while aboard?).
|
||||
//
|
||||
// Rectangles are plain Phaser.GameObjects.Rectangle (rendering only - they
|
||||
// are NOT physics bodies, deliberately, so they can't interfere with the
|
||||
// very collision behavior being observed).
|
||||
export default function buildCompartmentDebug(scene, bus, kidManager) {
|
||||
const depth = 5;
|
||||
|
||||
const mkRect = (w, h, color, alpha) =>
|
||||
scene.add.rectangle(0, 0, w, h, color, alpha).setOrigin(0.5).setDepth(depth);
|
||||
|
||||
const floor = mkRect(BUS.compartmentHalfWidth * 2, BUS.compartmentFloorThickness, 0x00e676, 0.75);
|
||||
const leftWall = mkRect(BUS.compartmentWallThickness, (BUS.compartmentFloorY - BUS.compartmentTopY) + BUS.compartmentFloorThickness, 0x40c4ff, 0.75);
|
||||
const rightWall = mkRect(BUS.compartmentWallThickness, (BUS.compartmentFloorY - BUS.compartmentTopY) + BUS.compartmentFloorThickness, 0xff5252, 0.75);
|
||||
|
||||
// The open-top boundary (BUS.compartmentTopY) is where the aboard/ejected
|
||||
// transition happens - drawn as a thin dashed-style line so "crossing
|
||||
// this line" is visible in gameplay.
|
||||
const topLine = mkRect(BUS.compartmentHalfWidth * 2 + BUS.compartmentWallThickness * 2, 2 * WORLD_SCALE, 0xffeb3b, 0.9);
|
||||
|
||||
// One marker per kid: small rectangle at the kid's position, color-coded
|
||||
// by state (green = aboard, red = ejected).
|
||||
const kidMarkers = kidManager.kids.map((kid) => mkRect(KID.radius * 2 + 4 * WORLD_SCALE, KID.radius * 2 + 4 * WORLD_SCALE, 0xffffff, 0.0));
|
||||
|
||||
// HUD readout, same style as the other pills.
|
||||
const label = scene.add.text(24 * WORLD_SCALE, 110 * WORLD_SCALE, '', {
|
||||
fontFamily: 'monospace',
|
||||
fontSize: `${13 * WORLD_SCALE}px`,
|
||||
color: '#1a1f29',
|
||||
}).setScrollFactor(0).setDepth(10);
|
||||
|
||||
const shapes = [floor, leftWall, rightWall, topLine];
|
||||
|
||||
function update() {
|
||||
const chassis = bus.chassis;
|
||||
const cos = Math.cos(chassis.rotation);
|
||||
const sin = Math.sin(chassis.rotation);
|
||||
const toWorld = (localX, localY) => ({
|
||||
x: chassis.x + localX * cos - localY * sin,
|
||||
y: chassis.y + localX * sin + localY * cos,
|
||||
});
|
||||
|
||||
const f = toWorld(0, BUS.compartmentFloorY);
|
||||
floor.setPosition(f.x, f.y);
|
||||
floor.setAngle(chassis.rotation);
|
||||
|
||||
const wallHeight = (BUS.compartmentFloorY - BUS.compartmentTopY) + BUS.compartmentFloorThickness;
|
||||
const wallCenterY = (BUS.compartmentFloorY + BUS.compartmentTopY) / 2;
|
||||
const l = toWorld(-BUS.compartmentHalfWidth, wallCenterY);
|
||||
leftWall.setPosition(l.x, l.y);
|
||||
leftWall.setAngle(chassis.rotation);
|
||||
const r = toWorld(BUS.compartmentHalfWidth, wallCenterY);
|
||||
rightWall.setPosition(r.x, r.y);
|
||||
rightWall.setAngle(chassis.rotation);
|
||||
|
||||
const t = toWorld(0, BUS.compartmentTopY);
|
||||
topLine.setPosition(t.x, t.y);
|
||||
topLine.setAngle(chassis.rotation);
|
||||
|
||||
let lines = [`floor: x${-BUS.compartmentHalfWidth}..${BUS.compartmentHalfWidth} y=${BUS.compartmentFloorY}`];
|
||||
lines.push(`walls: x=${-BUS.compartmentHalfWidth} / x=${BUS.compartmentHalfWidth} top(open): y=${BUS.compartmentTopY}`);
|
||||
for (let i = 0; i < kidManager.kids.length; i++) {
|
||||
const kid = kidManager.kids[i];
|
||||
const dx = kid.image.x - chassis.x;
|
||||
const dy = kid.image.y - chassis.y;
|
||||
const localX = dx * cos + dy * sin;
|
||||
const localY = -dx * sin + dy * cos;
|
||||
const marker = kidMarkers[i];
|
||||
marker.setPosition(kid.image.x, kid.image.y);
|
||||
marker.setFillStyle(kid.state === 'aboard' ? 0x00e676 : 0xff5252, 0.9);
|
||||
marker.setSize(KID.radius * 2 + 4 * WORLD_SCALE, KID.radius * 2 + 4 * WORLD_SCALE);
|
||||
lines.push(`kid${i}: ${kid.state} local=(${localX.toFixed(0)}, ${localY.toFixed(0)})${kid.state === 'ejected' && kid.ejectedAt !== null ? ` outFor=${((scene.time.now - kid.ejectedAt) / 1000).toFixed(1)}s` : ''}`);
|
||||
}
|
||||
label.setText(lines.join('\n'), { align: 'left' });
|
||||
}
|
||||
|
||||
scene.events.on('update', update);
|
||||
scene.events.once('shutdown', () => {
|
||||
scene.events.off('update', update);
|
||||
for (const s of shapes) s.destroy();
|
||||
for (const m of kidMarkers) m.destroy();
|
||||
label.destroy();
|
||||
});
|
||||
|
||||
return { shapes, kidMarkers, label, destroy: () => { shapes.concat(kidMarkers).concat([label]).forEach((s) => s.destroy()); } };
|
||||
}
|
||||
Loading…
Reference in New Issue