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 { BootScene } from './scenes/BootScene.js';
|
||||||
|
import { IntroScene } from './scenes/IntroScene.js';
|
||||||
import { MainMenuScene } from './scenes/MainMenuScene.js';
|
import { MainMenuScene } from './scenes/MainMenuScene.js';
|
||||||
import { CampaignSelectScene } from './scenes/CampaignSelectScene.js';
|
import { CampaignSelectScene } from './scenes/CampaignSelectScene.js';
|
||||||
import { CampaignScene } from './scenes/CampaignScene.js';
|
import { CampaignScene } from './scenes/CampaignScene.js';
|
||||||
|
|
@ -21,6 +22,7 @@ const config = {
|
||||||
},
|
},
|
||||||
scene: [
|
scene: [
|
||||||
BootScene,
|
BootScene,
|
||||||
|
IntroScene,
|
||||||
MainMenuScene,
|
MainMenuScene,
|
||||||
CampaignSelectScene,
|
CampaignSelectScene,
|
||||||
CampaignScene,
|
CampaignScene,
|
||||||
|
|
|
||||||
|
|
@ -127,11 +127,6 @@ export class BootScene extends Phaser.Scene {
|
||||||
this.registry.set('campaigns', campaignsData);
|
this.registry.set('campaigns', campaignsData);
|
||||||
this.registry.set('save', save);
|
this.registry.set('save', save);
|
||||||
|
|
||||||
// Start main menu music looping; store reference so other scenes can stop/resume it
|
this.scene.start('IntroScene');
|
||||||
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');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 save = this.registry.get('save');
|
||||||
const { width, height } = this.scale;
|
const { width, height } = this.scale;
|
||||||
|
|
||||||
// Resume main menu music if it was stopped by a battle
|
// Start music on first visit; resume it on return from other scenes
|
||||||
const menuMusic = this.registry.get('music_main_menu');
|
let menuMusic = this.registry.get('music_main_menu');
|
||||||
if (menuMusic && !menuMusic.isPlaying) menuMusic.play();
|
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 ──────────────────────────────────────────────────────
|
// ── Video background ──────────────────────────────────────────────────────
|
||||||
const video = this.add.video(width / 2, height / 2, 'menu_bg');
|
const video = this.add.video(width / 2, height / 2, 'menu_bg');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue