feat: add level time limits with countdown timer
- Add `timeLimit` configuration to levels 1-4 - Implement countdown timer that starts when throttle is pressed - Display remaining time in HUD, turning red when under 10s - Add 'time-up' failure condition and message in LevelFailedScene
This commit is contained in:
parent
0c80e4774e
commit
17fe6efd10
|
|
@ -4,6 +4,7 @@ export default {
|
|||
name: "GoFundMe",
|
||||
description: "Get the Money!",
|
||||
kidsAboard: 4,
|
||||
timeLimit: 60,
|
||||
startPosition: { x: 200, y: 700 },
|
||||
startAngle: 0,
|
||||
terrain: [
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export default {
|
|||
name: 'Big Air',
|
||||
description: 'Clear the gap and land level, or the kids pay for it.',
|
||||
kidsAboard: 3,
|
||||
timeLimit: 20,
|
||||
startPosition: { x: 100 * WORLD_SCALE, y: 350 * WORLD_SCALE },
|
||||
startAngle: 0,
|
||||
terrain: [{ points: runway }, { points: landing }],
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ export default {
|
|||
name: 'Tight Squeeze',
|
||||
description: 'A steep valley and a long run of sharp bumps - lean early, lean often.',
|
||||
kidsAboard: 4,
|
||||
timeLimit: 20,
|
||||
startPosition: { x: 100 * WORLD_SCALE, y: 350 * WORLD_SCALE },
|
||||
startAngle: 0,
|
||||
terrain: [{ points: buildPoints() }],
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ export default {
|
|||
name: "Mega Drop",
|
||||
description: "",
|
||||
kidsAboard: 5,
|
||||
timeLimit: 76,
|
||||
startPosition: { x: 200, y: 700 },
|
||||
startAngle: 0,
|
||||
terrain: [
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { createButton } from '../util/ui.js';
|
|||
const REASON_TEXT = {
|
||||
'all-kids-lost': 'Every kid got thrown off the bus!',
|
||||
'bus-fell': 'The bus fell into the gap!',
|
||||
'time-up': 'Ran out of time before reaching the finish line!',
|
||||
};
|
||||
|
||||
export default class LevelFailedScene extends Phaser.Scene {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ export default class PlayScene extends Phaser.Scene {
|
|||
this.levelId = data.levelId;
|
||||
this.level = getLevelById(this.levelId);
|
||||
this._levelEnded = false;
|
||||
this._timerStarted = false;
|
||||
this._timeRemaining = this.level.timeLimit;
|
||||
}
|
||||
|
||||
create() {
|
||||
|
|
@ -62,11 +64,14 @@ export default class PlayScene extends Phaser.Scene {
|
|||
if (this._levelEnded) return;
|
||||
|
||||
const intent = this.inputController.getIntent();
|
||||
if (!this._timerStarted && intent.throttle > 0) this._timerStarted = true;
|
||||
|
||||
this.bus.applyIntent(intent);
|
||||
this.engineSound.update();
|
||||
this.finishLine.update(time);
|
||||
this.cameraRig.update();
|
||||
this._updateParallax();
|
||||
this._updateTimer(delta);
|
||||
this._checkBusFell();
|
||||
this._checkAllKidsLost();
|
||||
|
||||
|
|
@ -168,6 +173,28 @@ export default class PlayScene extends Phaser.Scene {
|
|||
}
|
||||
}
|
||||
|
||||
// Counts down only once the player has actually hit the gas (throttle >
|
||||
// 0) for the first time - sitting idle at the start line costs nothing,
|
||||
// matching "the clock is your speedrun, not your reaction time" framing.
|
||||
_updateTimer(delta) {
|
||||
if (!this._timerStarted || this._timeRemaining <= 0) return;
|
||||
|
||||
this._timeRemaining = Math.max(0, this._timeRemaining - delta / 1000);
|
||||
this.timerText.setText(this._formatTime(this._timeRemaining));
|
||||
this.timerText.setColor(this._timeRemaining <= 10 ? '#c0392b' : '#1a1f29');
|
||||
|
||||
if (this._timeRemaining <= 0) {
|
||||
this.events.emit('level-failed', { reason: 'time-up' });
|
||||
}
|
||||
}
|
||||
|
||||
_formatTime(seconds) {
|
||||
const whole = Math.max(0, Math.ceil(seconds));
|
||||
const m = Math.floor(whole / 60);
|
||||
const s = whole % 60;
|
||||
return `${m}:${String(s).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
_buildGoal() {
|
||||
const goal = this.level.goal;
|
||||
this.goalBody = this.matter.add.rectangle(goal.x, goal.y, goal.width, goal.height, {
|
||||
|
|
@ -196,6 +223,14 @@ export default class PlayScene extends Phaser.Scene {
|
|||
backgroundColor: '#ffffffaa',
|
||||
}).setScrollFactor(0).setDepth(10);
|
||||
|
||||
this.timerText = this.add.text(GAME_WIDTH - startX, y, this._formatTime(this._timeRemaining), {
|
||||
fontFamily: 'monospace',
|
||||
fontSize: `${32 * WORLD_SCALE}px`,
|
||||
fontStyle: 'bold',
|
||||
color: '#1a1f29',
|
||||
backgroundColor: '#ffffffaa',
|
||||
}).setOrigin(1, 0).setScrollFactor(0).setDepth(10);
|
||||
|
||||
if (DEBUG) {
|
||||
this.debugText = this.add.text(startX, y + 52 * WORLD_SCALE, 'g-force: 0.00', {
|
||||
fontFamily: 'monospace',
|
||||
|
|
|
|||
Loading…
Reference in New Issue