Add IntroScene with video playback and skip functionality
- Introduce `IntroScene` to play `assets/video/intro.mp4` before the main menu - Implement dynamic video scaling to fit screen dimensions - Add skip prompt (Space/Click) that appears after first interaction - Move main menu music initialization to `MainMenuScene` for proper lifecycle management - Update scene flow: `BootScene` → `IntroScene` → `MainMenuScene` - Include fallback handling for video errors and metadata loading
This commit is contained in:
parent
12adbe60bf
commit
4eb4d7f278
Binary file not shown.
|
|
@ -1,4 +1,5 @@
|
|||
import { BootScene } from './scenes/BootScene.js';
|
||||
import { IntroScene } from './scenes/IntroScene.js';
|
||||
import { MainMenuScene } from './scenes/MainMenuScene.js';
|
||||
import { CampaignSelectScene } from './scenes/CampaignSelectScene.js';
|
||||
import { CampaignScene } from './scenes/CampaignScene.js';
|
||||
|
|
@ -21,6 +22,7 @@ const config = {
|
|||
},
|
||||
scene: [
|
||||
BootScene,
|
||||
IntroScene,
|
||||
MainMenuScene,
|
||||
CampaignSelectScene,
|
||||
CampaignScene,
|
||||
|
|
|
|||
|
|
@ -127,11 +127,6 @@ export class BootScene extends Phaser.Scene {
|
|||
this.registry.set('campaigns', campaignsData);
|
||||
this.registry.set('save', save);
|
||||
|
||||
// Start main menu music looping; store reference so other scenes can stop/resume it
|
||||
const menuMusic = this.sound.add('music_main_menu', { loop: true, volume: 0.5 });
|
||||
menuMusic.play();
|
||||
this.registry.set('music_main_menu', menuMusic);
|
||||
|
||||
this.scene.start('MainMenuScene');
|
||||
this.scene.start('IntroScene');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
export class IntroScene extends Phaser.Scene {
|
||||
constructor() { super('IntroScene'); }
|
||||
|
||||
preload() {
|
||||
this.load.video('intro', 'assets/video/intro.mp4');
|
||||
}
|
||||
|
||||
create() {
|
||||
this._skipPromptShown = false;
|
||||
this._transitioning = false;
|
||||
this._hasPlayed = false;
|
||||
|
||||
const video = this.add.video(960, 540, 'intro');
|
||||
this._video = video;
|
||||
|
||||
const vid = video.video; // underlying HTMLVideoElement
|
||||
|
||||
// Fix zoom: compute fit-scale from natural video dimensions
|
||||
const applyFit = () => {
|
||||
const vw = vid.videoWidth;
|
||||
const vh = vid.videoHeight;
|
||||
if (vw > 0 && vh > 0) {
|
||||
video.setScale(Math.min(1920 / vw, 1080 / vh));
|
||||
}
|
||||
};
|
||||
if (vid.readyState >= 1) {
|
||||
applyFit();
|
||||
} else {
|
||||
vid.addEventListener('loadedmetadata', applyFit, { once: true });
|
||||
}
|
||||
|
||||
// Guard: only allow complete → menu when video has genuinely played
|
||||
vid.addEventListener('playing', () => { this._hasPlayed = true; }, { once: true });
|
||||
|
||||
video.play();
|
||||
|
||||
video.on('complete', () => {
|
||||
if (this._hasPlayed) this._goToMenu();
|
||||
});
|
||||
|
||||
// Fallback if video errors out entirely
|
||||
vid.addEventListener('error', () => {
|
||||
this.time.delayedCall(300, () => this._goToMenu());
|
||||
}, { once: true });
|
||||
|
||||
// Skip prompt label (hidden until first interaction)
|
||||
this._skipLabel = this.add.text(960, 980, 'Press Space or Click to Skip...', {
|
||||
fontFamily: 'Audiowide',
|
||||
fontSize: '28px',
|
||||
color: '#ffffff',
|
||||
stroke: '#000000',
|
||||
strokeThickness: 4,
|
||||
}).setOrigin(0.5).setDepth(10).setAlpha(0);
|
||||
|
||||
// Delay input registration to ignore carry-over events from BootScene
|
||||
this.time.delayedCall(600, () => {
|
||||
if (this._transitioning) return;
|
||||
this.input.on('pointerdown', () => this._onInput());
|
||||
this.input.keyboard.on('keydown', (event) => {
|
||||
if (event.code === 'Space') this._onInput();
|
||||
else if (!this._skipPromptShown) this._onInput();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_onInput() {
|
||||
if (this._transitioning) return;
|
||||
if (this._skipPromptShown) {
|
||||
this._goToMenu();
|
||||
} else {
|
||||
this._skipPromptShown = true;
|
||||
this._skipLabel.setAlpha(1);
|
||||
}
|
||||
}
|
||||
|
||||
_goToMenu() {
|
||||
if (this._transitioning) return;
|
||||
this._transitioning = true;
|
||||
this.scene.start('MainMenuScene');
|
||||
}
|
||||
}
|
||||
|
|
@ -11,9 +11,15 @@ export class MainMenuScene extends Phaser.Scene {
|
|||
const save = this.registry.get('save');
|
||||
const { width, height } = this.scale;
|
||||
|
||||
// Resume main menu music if it was stopped by a battle
|
||||
const menuMusic = this.registry.get('music_main_menu');
|
||||
if (menuMusic && !menuMusic.isPlaying) menuMusic.play();
|
||||
// Start music on first visit; resume it on return from other scenes
|
||||
let menuMusic = this.registry.get('music_main_menu');
|
||||
if (!menuMusic) {
|
||||
menuMusic = this.sound.add('music_main_menu', { loop: true, volume: 0.5 });
|
||||
menuMusic.play();
|
||||
this.registry.set('music_main_menu', menuMusic);
|
||||
} else if (!menuMusic.isPlaying) {
|
||||
menuMusic.play();
|
||||
}
|
||||
|
||||
// ── Video background ──────────────────────────────────────────────────────
|
||||
const video = this.add.video(width / 2, height / 2, 'menu_bg');
|
||||
|
|
|
|||
Loading…
Reference in New Issue