86 lines
3.1 KiB
JavaScript
86 lines
3.1 KiB
JavaScript
import Phaser from 'phaser';
|
|
import { GFORCE, KID } from '../config.js';
|
|
|
|
export default class Kid {
|
|
constructor(scene, bus, seatIndex) {
|
|
this.scene = scene;
|
|
this.bus = bus;
|
|
this.seatIndex = seatIndex;
|
|
this.state = 'aboard'; // aboard | ejected | settled
|
|
|
|
const seat = bus.seatOffsets[seatIndex];
|
|
const worldPos = bus.getSeatWorldPosition(seatIndex);
|
|
|
|
this.image = scene.matter.add.image(worldPos.x, worldPos.y, 'kid_idle', null, {
|
|
shape: { type: 'circle', radius: KID.radius },
|
|
density: KID.density,
|
|
friction: KID.friction,
|
|
collisionFilter: { group: bus.group },
|
|
});
|
|
this.image.setDisplaySize(KID.radius * 2, KID.radius * 2);
|
|
// Bus parts are all left at Phaser's default depth (0) - render kids
|
|
// just behind that, so the chassis always draws over them (seated,
|
|
// ejected, or falling back down) instead of insertion order deciding
|
|
// it implicitly.
|
|
this.image.setDepth(-1);
|
|
|
|
this.seatConstraint = scene.matter.add.constraint(bus.chassis, this.image, KID.seatLength, KID.seatStiffness, {
|
|
pointA: seat,
|
|
pointB: { x: 0, y: 0 },
|
|
damping: KID.seatDamping,
|
|
});
|
|
|
|
this._settleAt = 0;
|
|
}
|
|
|
|
eject(gForce) {
|
|
if (this.state !== 'aboard') return;
|
|
this.state = 'ejected';
|
|
|
|
this.scene.matter.world.removeConstraint(this.seatConstraint, true);
|
|
this.seatConstraint = null;
|
|
|
|
// Neutral group re-enables normal category/mask collision (was sharing
|
|
// the bus's non-colliding group while seated).
|
|
this.image.body.collisionFilter.group = 0;
|
|
this.image.setTexture('kid_ejected');
|
|
|
|
// Opt out of the world's real gravity - KidManager applies a lighter,
|
|
// custom fraction of it each tick instead (see
|
|
// KidManager._onBeforeUpdate), so the kid floats through a much bigger
|
|
// arc than the bus's own fall would.
|
|
this.image.body.ignoreGravity = true;
|
|
|
|
const severity = Phaser.Math.Clamp(gForce / GFORCE.ejectThresholdG, 1, 3);
|
|
|
|
// Always pops straight up through the roof, never sideways or downward
|
|
// - deliberately ignores the chassis's actual velocity/tilt at impact,
|
|
// which previously could fling a kid out sideways or into the ground
|
|
// depending on which way the bus was moving. Horizontal speed still
|
|
// carries over on its own from the velocity the seat constraint was
|
|
// already imparting, so no sideways force is needed to "retain" it.
|
|
this.image.applyForce({
|
|
x: 0,
|
|
y: -GFORCE.ejectImpulseMagnitude * severity,
|
|
});
|
|
|
|
this._settleAt = this.scene.time.now + KID.settleTimeMs;
|
|
}
|
|
|
|
update(time) {
|
|
if (this.state === 'ejected' && time >= this._settleAt) {
|
|
this.state = 'settled';
|
|
}
|
|
}
|
|
|
|
destroy() {
|
|
// No removeConstraint() here (unlike eject(), which runs mid-gameplay
|
|
// and needs it): destroy() only ever runs from PlayScene's shutdown
|
|
// cleanup, by which point Phaser's Matter World plugin may have already
|
|
// torn down (nulled) this.scene.matter.world, and World#shutdown()
|
|
// already clears every constraint/body itself anyway.
|
|
this.seatConstraint = null;
|
|
this.image.destroy();
|
|
}
|
|
}
|