111 lines
3.4 KiB
JavaScript
111 lines
3.4 KiB
JavaScript
import Phaser from '../vendor/phaser.js';
|
|
import { config } from '../config/Config.js';
|
|
import { toColor } from '../utils/Color.js';
|
|
import { Ship } from '../entities/Ship.js';
|
|
import { Starfield } from '../visuals/Starfield.js';
|
|
|
|
const FONT_FALLBACK = "'Segoe UI', 'Helvetica Neue', Arial, sans-serif";
|
|
|
|
/**
|
|
* The game world (v0.1: one ship in open space).
|
|
* Click anywhere to fly there.
|
|
*/
|
|
export class GameScene extends Phaser.Scene {
|
|
constructor() {
|
|
super({ key: 'GameScene' });
|
|
}
|
|
|
|
create() {
|
|
// Camera: smoothly follows the ship (updateCamera below). This motion
|
|
// is what drives the parallax starfield — ship flies, view trails.
|
|
this.cameraFollowShip = config.get('game.camera.followShip', true);
|
|
this.cameraFollowRate = config.get('game.camera.followRate', 3.0); // 1/s
|
|
|
|
// The ship — spawns at the world origin in open, unbounded space.
|
|
this.ship = new Ship(this, 0, 0);
|
|
this.ship.setDepth(10);
|
|
|
|
// Center the camera on the ship from the very first frame.
|
|
this.cameras.main.setScroll(-this.scale.width / 2, -this.scale.height / 2);
|
|
|
|
// Background (stars are placed around the current camera view).
|
|
this.starfield = new Starfield(this);
|
|
this.starfield.create();
|
|
|
|
// Hint
|
|
this.hint = this.add
|
|
.text(this.scale.width / 2, this.scale.height - 26, config.get('game.hintText', ''), {
|
|
fontFamily: FONT_FALLBACK,
|
|
fontSize: '14px',
|
|
color: '#54608a',
|
|
})
|
|
.setOrigin(0.5)
|
|
.setScrollFactor(0); // UI: pinned to the screen, not the world
|
|
|
|
// Input: click = fly there
|
|
this.input.on('pointerdown', (pointer) => {
|
|
this.showTargetMarker(pointer.worldX, pointer.worldY);
|
|
this.ship.setTarget(pointer.worldX, pointer.worldY);
|
|
this.hideHint();
|
|
});
|
|
}
|
|
|
|
update(_time, delta) {
|
|
this.ship.update(_time, delta);
|
|
this.updateCamera(delta);
|
|
this.starfield.update(); // after the camera, so it sees this frame's motion
|
|
}
|
|
|
|
/**
|
|
* The camera chases the ship with a frame-rate-independent ease:
|
|
* scroll += (shipCenter - scroll) * (1 - e^(-rate·dt))
|
|
* While the ship flies, the camera trails it and the starfield streams
|
|
* past in the opposite direction (real parallax). When the ship stops,
|
|
* the camera keeps easing until it is centered on the ship again — a
|
|
* slow recenter (≈1.5 s at followRate 3) with the stars parallaxing
|
|
* along for the ride.
|
|
*/
|
|
updateCamera(delta) {
|
|
if (!this.cameraFollowShip || !this.ship) return;
|
|
const rate = this.cameraFollowRate;
|
|
if (!(rate > 0)) return;
|
|
|
|
const cam = this.cameras.main;
|
|
const k = 1 - Math.exp(-rate * (Math.min(delta, 64) / 1000));
|
|
cam.setScroll(
|
|
Phaser.Math.Linear(cam.scrollX, this.ship.x - this.scale.width / 2, k),
|
|
Phaser.Math.Linear(cam.scrollY, this.ship.y - this.scale.height / 2, k),
|
|
);
|
|
}
|
|
|
|
showTargetMarker(x, y) {
|
|
const color = toColor(config.get('game.markerColor', '#41c7ff'));
|
|
const marker = this.add.circle(x, y, 10, color, 0.8).setDepth(5);
|
|
this.tweens.add({
|
|
targets: marker,
|
|
scale: 2.4,
|
|
alpha: 0,
|
|
duration: 450,
|
|
ease: 'Sine.easeOut',
|
|
onComplete: () => marker.destroy(),
|
|
});
|
|
}
|
|
|
|
hideHint() {
|
|
if (!this.hint || !this.hint.active) return;
|
|
this.tweens.add({
|
|
targets: this.hint,
|
|
alpha: 0,
|
|
duration: 400,
|
|
onComplete: () => {
|
|
this.hint.destroy();
|
|
this.hint = null;
|
|
},
|
|
});
|
|
}
|
|
|
|
shutdown() {
|
|
this.starfield?.destroy();
|
|
}
|
|
}
|