fertig-classic-games/src/scenes/LandingScene.js

144 lines
4.5 KiB
JavaScript

import * as Phaser from 'phaser';
import { GAME_HEIGHT, GAME_WIDTH, COLORS } from '../config.js';
import { auth } from '../services/auth.js';
import { Button } from '../ui/Button.js';
import { Plaque } from '../ui/Plaque.js';
import { addFullscreenButton } from '../ui/FullscreenButton.js';
import { playMenuMusic } from '../ui/MenuMusic.js';
export default class LandingScene extends Phaser.Scene {
constructor() { super('Landing'); }
create() {
playMenuMusic();
const cx = GAME_WIDTH / 2;
this.add.image(cx, GAME_HEIGHT / 2, 'bg-menu').setDisplaySize(GAME_WIDTH, GAME_HEIGHT);
const logo = this.add.image(cx, 290, 'main-title').setOrigin(0.5, 0.5).setAlpha(0);
logo.postFX.addShadow(3, 6, 0.005, 2, 0x000000, 10, 0.75);
this._logo = logo;
this._transitioning = false;
this.tweens.add({
targets: logo,
alpha: 1,
y: 260,
duration: 900,
ease: 'Power2',
onComplete: () => {
this.tweens.add({
targets: logo,
scaleX: 1.025,
scaleY: 1.025,
duration: 2200,
yoyo: true,
repeat: -1,
ease: 'Sine.easeInOut',
});
},
});
this.renderButtons();
addFullscreenButton(this);
if (this._authUnsub) this._authUnsub();
this._authUnsub = auth.subscribe(() => {
if (!this.scene.isActive('Landing')) return;
this.children.removeAll();
this.create();
});
this.events.once(Phaser.Scenes.Events.SHUTDOWN, () => {
if (this._authUnsub) { this._authUnsub(); this._authUnsub = null; }
});
}
renderButtons() {
const cx = GAME_WIDTH / 2;
const user = auth.user;
this._avatar = null;
const avatarR = 28;
const avatarGap = 18;
const pad = { x: 32, y: 14 };
const y = 630;
const name = user?.displayName ?? user?.username ?? 'Player';
const welcomeText = this.add.text(0, y, `Welcome, ${name}`, {
fontFamily: 'Righteous',
fontSize: '36px',
color: COLORS.accentHex,
}).setOrigin(0.5).setDepth(1);
this._welcomeText = welcomeText;
welcomeText.setShadow(0, 3, 'rgba(0,0,0,0.8)', 5);
const hasAvatar = !!user?.avatarPath;
const totalW = hasAvatar ? avatarR * 2 + avatarGap + welcomeText.width : welcomeText.width;
const groupLeft = cx - totalW / 2;
welcomeText.setX(hasAvatar ? groupLeft + avatarR * 2 + avatarGap + welcomeText.width / 2 : cx);
this._welcomeBg = new Plaque(this, totalW + pad.x * 2, welcomeText.height + pad.y * 2, { radius: 16 })
.setPosition(cx, y);
if (hasAvatar) {
const avatarCx = groupLeft + avatarR;
const key = `landing-avatar-${user.id}`;
(async () => {
try {
if (!this.textures.exists(key)) {
await new Promise((resolve) => {
this.load.image(key, user.avatarPath);
this.load.once('complete', resolve);
this.load.start();
});
}
if (this._transitioning || !this.scene.isActive('Landing')) return;
const maskG = this.make.graphics({ x: 0, y: 0, add: false });
maskG.fillStyle(0xffffff);
maskG.fillCircle(avatarCx, y, avatarR);
this._avatar = this.add.image(avatarCx, y, key)
.setDisplaySize(avatarR * 2, avatarR * 2)
.setMask(maskG.createGeometryMask())
.setDepth(1);
} catch { /* no avatar shown */ }
})();
}
this.playBtn = new Button(this, cx, 810, 'Play', () => this.goToGameMenu(), { width: 480 });
this.profileBtn = new Button(this, cx, 890, 'Profile', () => this.scene.start('Profile'), { width: 480 });
}
goToGameMenu() {
if (this._transitioning) return;
this._transitioning = true;
this.playBtn.disableInteractive();
this.profileBtn.disableInteractive();
// Fade the landing buttons out while the logo zooms past the camera.
const fadeOut = [this.playBtn, this.profileBtn, this._welcomeText, this._welcomeBg].filter(Boolean);
if (this._avatar) fadeOut.push(this._avatar);
this.tweens.add({
targets: fadeOut,
alpha: 0,
duration: 350,
ease: 'Power2.easeOut',
});
if (!this._logo) { this.scene.start('GameMenu'); return; }
this.tweens.killTweensOf(this._logo);
this.tweens.add({
targets: this._logo,
x: GAME_WIDTH / 2,
y: GAME_HEIGHT / 2,
scaleX: 2.4,
scaleY: 2.4,
alpha: 0,
duration: 700,
ease: 'Cubic.easeIn',
onComplete: () => this.scene.start('GameMenu'),
});
}
}