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:
Brian Fertig 2026-03-25 20:47:18 -06:00
parent 12adbe60bf
commit 4eb4d7f278
5 changed files with 93 additions and 9 deletions

BIN
assets/video/intro.mp4 Normal file

Binary file not shown.

View File

@ -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,

View File

@ -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');
}
}

81
src/scenes/IntroScene.js Normal file
View File

@ -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');
}
}

View File

@ -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');