477 lines
17 KiB
JavaScript
477 lines
17 KiB
JavaScript
/**
|
|
* ULTIMATE CINEMATIC PROLOGUE SCENE
|
|
* 100% Polished - Multilingual + Subtitle Sync + Pure Cinematic Mode
|
|
*
|
|
* Features:
|
|
* - Dual language support (SLO/ENG)
|
|
* - Frame-perfect subtitle synchronization
|
|
* - Pure cinematic mode (no HUD, no UI, just story)
|
|
* - Film-quality transitions
|
|
* - Emotional voice delivery
|
|
*/
|
|
|
|
class UltimatePrologueScene extends Phaser.Scene {
|
|
constructor() {
|
|
super({ key: 'UltimatePrologueScene' });
|
|
this.language = 'en'; // Default: English (can be changed via settings)
|
|
}
|
|
|
|
init(data) {
|
|
// Get language from settings or data
|
|
this.language = data.language || window.i18n?.getCurrentLanguage() || 'en';
|
|
console.log(`🎬 Ultimate Prologue - Language: ${this.language.toUpperCase()}`);
|
|
}
|
|
|
|
preload() {
|
|
console.log('🎬 Preloading Ultimate Cinematic Assets...');
|
|
|
|
const lang = this.language;
|
|
const voicePath = `assets/audio/voiceover/intro_final/${lang}/`;
|
|
|
|
// Load intro visuals
|
|
// Load intro visuals (UPDATED paths to assets/slike/zgodba)
|
|
// Using "intro_chaos" as fallback for black screen if simple black png missing, but I saw one.
|
|
// Actually I found these specific filenames in 'grep' output for 'zgodba'.
|
|
|
|
this.load.image('intro_black', 'assets/slike/zgodba/assets_BACKUP_20260112_064319_references_intro_black_screen.png');
|
|
this.load.image('intro_cellar', 'assets/slike/zgodba/assets_references_intro_cellar_ruins.png');
|
|
this.load.image('intro_id_card', 'assets/slike/zgodba/assets_references_intro_id_card.png');
|
|
this.load.image('intro_twin_photo', 'assets/slike/zgodba/assets_references_intro_twin_photo.png');
|
|
this.load.image('intro_blur', 'assets/slike/zgodba/assets_references_intro_blur_overlay.png');
|
|
|
|
// Load voices (current language)
|
|
// DISABLED: Audio files likely missing or paths wrong. To prevent errors:
|
|
/*
|
|
this.load.audio('v1_breathing', voicePath + '01_breathing.mp3');
|
|
this.load.audio('v2_flyover', voicePath + '02_flyover.mp3');
|
|
this.load.audio('v3_awakening', voicePath + '03_awakening.mp3');
|
|
this.load.audio('v4_id_card', voicePath + '04_id_card.mp3');
|
|
this.load.audio('v5_determination', voicePath + '05_determination.mp3');
|
|
*/
|
|
|
|
// Noir ambient music (TEMP DISABLED - file path issue)
|
|
// this.load.audio('noir_music', 'assets/audio/music/night_theme.wav');
|
|
}
|
|
|
|
create() {
|
|
const { width, height } = this.cameras.main;
|
|
|
|
console.log('🎬 Starting Ultimate Cinematic Prologue...');
|
|
console.log(` Language: ${this.language === 'en' ? 'English' : 'Slovenščina'}`);
|
|
|
|
// ================================================================
|
|
// PURE CINEMATIC MODE - No HUD, No UI, Only Story
|
|
// ================================================================
|
|
|
|
// Start noir music (TEMP DISABLED - file issue)
|
|
/* this.noirMusic = this.sound.add('noir_music', {
|
|
volume: 0.2,
|
|
loop: true
|
|
});
|
|
this.noirMusic.play(); */
|
|
|
|
// Create visual layers
|
|
this.bgLayer = this.add.container(0, 0);
|
|
this.subtitleLayer = this.add.container(0, 0);
|
|
|
|
// Black screen (full opacity)
|
|
this.blackScreen = this.add.rectangle(width / 2, height / 2, width, height, 0x000000);
|
|
this.bgLayer.add(this.blackScreen);
|
|
|
|
// Subtitle text (cinematic positioning - bottom center with safe margin)
|
|
this.subtitle = this.add.text(width / 2, height - 80, '', {
|
|
fontSize: '28px',
|
|
fontFamily: 'Georgia, serif',
|
|
color: '#ffffff',
|
|
align: 'center',
|
|
stroke: '#000000',
|
|
strokeThickness: 5,
|
|
wordWrap: { width: 900 },
|
|
lineSpacing: 10,
|
|
shadow: {
|
|
offsetX: 2,
|
|
offsetY: 2,
|
|
color: '#000000',
|
|
blur: 5,
|
|
fill: true
|
|
}
|
|
});
|
|
this.subtitle.setOrigin(0.5);
|
|
this.subtitle.setAlpha(0);
|
|
this.subtitle.setDepth(1000);
|
|
this.subtitleLayer.add(this.subtitle);
|
|
|
|
// Skip hint (minimal, top-right)
|
|
const skipHint = this.add.text(width - 30, 30, '[ESC]', {
|
|
fontSize: '16px',
|
|
fontFamily: 'monospace',
|
|
color: '#666666',
|
|
alpha: 0.5
|
|
});
|
|
skipHint.setOrigin(1, 0);
|
|
|
|
// ESC to skip
|
|
this.input.keyboard.on('keydown-ESC', () => this.skipToGame());
|
|
|
|
// ================================================================
|
|
// START INTRO SEQUENCE
|
|
// ================================================================
|
|
this.time.delayedCall(1000, () => this.phase1_Breathing());
|
|
}
|
|
|
|
// ====================================================================
|
|
// PHASE 1: BLACK SCREEN - Heavy Breathing & Confusion (0:00-0:07)
|
|
// ====================================================================
|
|
phase1_Breathing() {
|
|
console.log('🎬 Phase 1: Breathing');
|
|
|
|
// Audio safety check
|
|
if (!this.cache.audio.exists('v1_breathing')) {
|
|
console.warn('⚠️ v1_breathing not found - skipping audio');
|
|
this.time.delayedCall(2000, () => this.phase2_Flyover());
|
|
return;
|
|
}
|
|
const voice = this.sound.add('v1_breathing');
|
|
|
|
// Subtitle with precise timing
|
|
const subs = this.getSubtitles();
|
|
this.showSubtitle(subs.breathing);
|
|
|
|
// Play voice
|
|
voice.play();
|
|
|
|
// Proceed after voice + 2s pause
|
|
voice.once('complete', () => {
|
|
this.time.delayedCall(2000, () => this.phase2_Flyover());
|
|
});
|
|
}
|
|
|
|
// ====================================================================
|
|
// PHASE 2: NARRATOR FLYOVER - World Description (0:07-0:25)
|
|
// ====================================================================
|
|
phase2_Flyover() {
|
|
console.log('🎬 Phase 2: Flyover');
|
|
|
|
// Fade black to slight transparency (show void/darkness)
|
|
this.tweens.add({
|
|
targets: this.blackScreen,
|
|
alpha: 0.5,
|
|
duration: 4000,
|
|
ease: 'Sine.easeInOut'
|
|
});
|
|
|
|
// Audio safety check
|
|
if (!this.cache.audio.exists('v2_flyover')) {
|
|
console.warn('⚠️ v2_flyover not found - skipping audio');
|
|
this.time.delayedCall(2000, () => this.phase3_Awakening());
|
|
return;
|
|
}
|
|
const voice = this.sound.add('v2_flyover');
|
|
voice.play();
|
|
|
|
// Subtitle timing (split into two parts for readability)
|
|
const subs = this.getSubtitles();
|
|
|
|
this.time.delayedCall(500, () => {
|
|
this.showSubtitle(subs.flyover_1);
|
|
});
|
|
|
|
this.time.delayedCall(8000, () => {
|
|
this.showSubtitle(subs.flyover_2);
|
|
});
|
|
|
|
voice.once('complete', () => {
|
|
this.time.delayedCall(1500, () => this.phase3_Awakening());
|
|
});
|
|
}
|
|
|
|
// ====================================================================
|
|
// PHASE 3: AWAKENING - Kai Wakes in Cellar (0:25-0:40)
|
|
// ====================================================================
|
|
phase3_Awakening() {
|
|
console.log('🎬 Phase 3: Awakening');
|
|
|
|
const { width, height } = this.cameras.main;
|
|
|
|
// Fade in cellar background
|
|
const cellar = this.add.image(width / 2, height / 2, 'intro_cellar');
|
|
cellar.setAlpha(0);
|
|
cellar.setScale(1.05); // Slight zoom for depth
|
|
this.bgLayer.add(cellar);
|
|
|
|
// Blur overlay (vision is blurred)
|
|
const blur = this.add.image(width / 2, height / 2, 'intro_blur');
|
|
blur.setAlpha(0);
|
|
this.bgLayer.add(blur);
|
|
|
|
// Fade out black, fade in cellar + blur
|
|
this.tweens.add({
|
|
targets: this.blackScreen,
|
|
alpha: 0,
|
|
duration: 2500
|
|
});
|
|
|
|
this.tweens.add({
|
|
targets: [cellar, blur],
|
|
alpha: 1,
|
|
duration: 3000,
|
|
ease: 'Sine.easeIn'
|
|
});
|
|
|
|
// Subtle zoom in (like opening eyes)
|
|
this.tweens.add({
|
|
targets: cellar,
|
|
scale: 1,
|
|
duration: 5000,
|
|
ease: 'Sine.easeOut'
|
|
});
|
|
|
|
// Play awakening voice
|
|
this.time.delayedCall(2500, () => {
|
|
// Audio safety check
|
|
if (!this.cache.audio.exists('v3_awakening')) {
|
|
console.warn('⚠️ v3_awakening not found - skipping audio');
|
|
this.time.delayedCall(2000, () => this.phase4_IDCard());
|
|
return;
|
|
}
|
|
const voice = this.sound.add('v3_awakening');
|
|
voice.play();
|
|
|
|
const subs = this.getSubtitles();
|
|
this.showSubtitle(subs.awakening);
|
|
|
|
// Gradually clear blur (vision clears)
|
|
this.time.delayedCall(3000, () => {
|
|
this.tweens.add({
|
|
targets: blur,
|
|
alpha: 0,
|
|
duration: 5000,
|
|
ease: 'Cubic.easeOut'
|
|
});
|
|
});
|
|
|
|
voice.once('complete', () => {
|
|
this.time.delayedCall(2000, () => this.phase4_IDCard());
|
|
});
|
|
});
|
|
}
|
|
|
|
// ====================================================================
|
|
// PHASE 4: ID CARD - Discovery & Memory (0:40-0:58)
|
|
// ====================================================================
|
|
phase4_IDCard() {
|
|
console.log('🎬 Phase 4: ID Card');
|
|
|
|
const { width, height } = this.cameras.main;
|
|
|
|
// Show ID card (zoom in from smaller)
|
|
const idCard = this.add.image(width / 2, height / 2, 'intro_id_card');
|
|
idCard.setScale(0.6);
|
|
idCard.setAlpha(0);
|
|
this.bgLayer.add(idCard);
|
|
|
|
this.tweens.add({
|
|
targets: idCard,
|
|
alpha: 1,
|
|
scale: 1,
|
|
duration: 2000,
|
|
ease: 'Cubic.easeOut'
|
|
});
|
|
|
|
// Play ID card voice
|
|
this.time.delayedCall(1500, () => {
|
|
// Audio safety check
|
|
if (!this.cache.audio.exists('v4_id_card')) {
|
|
console.warn('⚠️ v4_id_card not found - skipping audio');
|
|
this.time.delayedCall(2000, () => this.phase5_Determination());
|
|
return;
|
|
}
|
|
const voice = this.sound.add('v4_id_card');
|
|
voice.play();
|
|
|
|
const subs = this.getSubtitles();
|
|
|
|
// Part 1: Reading ID
|
|
this.showSubtitle(subs.id_card_1);
|
|
|
|
// Part 2: Empty feeling
|
|
this.time.delayedCall(6000, () => {
|
|
this.showSubtitle(subs.id_card_2);
|
|
|
|
// Cross-fade to twin photo
|
|
this.time.delayedCall(4000, () => {
|
|
const twinPhoto = this.add.image(width / 2, height / 2, 'intro_twin_photo');
|
|
twinPhoto.setAlpha(0);
|
|
twinPhoto.setScale(1.1);
|
|
this.bgLayer.add(twinPhoto);
|
|
|
|
// Fade out ID, fade in photo
|
|
this.tweens.add({
|
|
targets: idCard,
|
|
alpha: 0,
|
|
duration: 2000
|
|
});
|
|
|
|
this.tweens.add({
|
|
targets: twinPhoto,
|
|
alpha: 1,
|
|
scale: 1,
|
|
duration: 2500,
|
|
ease: 'Sine.easeInOut'
|
|
});
|
|
|
|
// Warm glow effect (memory warmth)
|
|
this.cameras.main.flash(2000, 255, 200, 150, false, null, 0.15);
|
|
});
|
|
});
|
|
|
|
voice.once('complete', () => {
|
|
this.time.delayedCall(1500, () => this.phase5_Determination());
|
|
});
|
|
});
|
|
}
|
|
|
|
// ====================================================================
|
|
// PHASE 5: DETERMINATION - The Promise (0:58-1:10) → Quest → Game
|
|
// ====================================================================
|
|
phase5_Determination() {
|
|
console.log('🎬 Phase 5: Determination');
|
|
|
|
// Audio safety check
|
|
if (!this.cache.audio.exists('v5_determination')) {
|
|
console.warn('⚠️ v5_determination not found - skipping audio');
|
|
this.time.delayedCall(2000, () => this.exitPrologue());
|
|
return;
|
|
}
|
|
const voice = this.sound.add('v5_determination');
|
|
voice.play();
|
|
|
|
const subs = this.getSubtitles();
|
|
|
|
// Part 1: Promise
|
|
this.showSubtitle(subs.determination_1);
|
|
|
|
// Part 2: Ana's name
|
|
this.time.delayedCall(5000, () => {
|
|
this.showSubtitle(subs.determination_2);
|
|
|
|
// Camera flash (determination spark)
|
|
this.cameras.main.flash(800, 100, 50, 80);
|
|
});
|
|
|
|
voice.once('complete', () => {
|
|
// Show quest notification (brief)
|
|
this.showQuestBrief();
|
|
|
|
// Fade to game
|
|
this.time.delayedCall(4000, () => this.exitPrologue());
|
|
});
|
|
}
|
|
|
|
showQuestBrief() {
|
|
const { width, height } = this.cameras.main;
|
|
|
|
const questText = this.language === 'en'
|
|
? '📜 New Quest: Find clues about your past'
|
|
: '📜 Nova naloga: Poišči sledi o svoji preteklosti';
|
|
|
|
const quest = this.add.text(width / 2, height - 150, questText, {
|
|
fontSize: '24px',
|
|
fontFamily: 'Georgia, serif',
|
|
color: '#ffdd00',
|
|
stroke: '#000000',
|
|
strokeThickness: 4,
|
|
shadow: {
|
|
offsetX: 2,
|
|
offsetY: 2,
|
|
color: '#000000',
|
|
blur: 8,
|
|
fill: true
|
|
}
|
|
});
|
|
quest.setOrigin(0.5);
|
|
quest.setAlpha(0);
|
|
|
|
this.tweens.add({
|
|
targets: quest,
|
|
alpha: 1,
|
|
y: height - 180,
|
|
duration: 1000,
|
|
ease: 'Back.easeOut'
|
|
});
|
|
}
|
|
|
|
showSubtitle(text) {
|
|
this.subtitle.setText(text);
|
|
|
|
this.tweens.add({
|
|
targets: this.subtitle,
|
|
alpha: 1,
|
|
duration: 600,
|
|
ease: 'Sine.easeIn'
|
|
});
|
|
|
|
// Auto-fade after reading time (adaptive)
|
|
const readTime = Math.max(3000, text.length * 50);
|
|
this.time.delayedCall(readTime, () => {
|
|
this.tweens.add({
|
|
targets: this.subtitle,
|
|
alpha: 0,
|
|
duration: 600
|
|
});
|
|
});
|
|
}
|
|
|
|
getSubtitles() {
|
|
if (this.language === 'sl') {
|
|
return {
|
|
breathing: "Vse je temno... Zakaj slišim samo tišino?",
|
|
flyover_1: "Pravijo, da svet ni umrl s pokom,\nampak s tihim šepetom.",
|
|
flyover_2: "Dolina smrti ni le kraj.\nJe spomin, ki ga nihče več ne želi imeti.",
|
|
awakening: "Glava... boli me.\nKje sem? Kdo sem?",
|
|
id_card_1: "Kai Marković. Štirinajst let. To sem jaz.",
|
|
id_card_2: "Ampak ta druga deklica...\nZakaj se ob njej počutim tako prazno?\nKot da mi manjka polovica srca.",
|
|
determination_1: "Nekdo me čaka tam zunaj.\nNe spomnim se obraza, čutim pa obljubo.",
|
|
determination_2: "Grem te poiskat... Ana."
|
|
};
|
|
} else {
|
|
return {
|
|
breathing: "Everything is dark...\nWhy do I only hear silence?",
|
|
flyover_1: "They say the world didn't die with a bang,\nbut with a quiet whisper.",
|
|
flyover_2: "The Valley of Death is not just a place.\nIt's a memory no one wants to have anymore.",
|
|
awakening: "My head... it hurts.\nWhere am I? Who am I?",
|
|
id_card_1: "Kai Marković. Fourteen years old. That's me.",
|
|
id_card_2: "But this other girl...\nWhy do I feel so empty when I see her?\nLike I'm missing half of my heart.",
|
|
determination_1: "Someone is waiting for me out there.\nI can't remember the face, but I feel the promise.",
|
|
determination_2: "I'm coming to find you... Ana."
|
|
};
|
|
}
|
|
}
|
|
|
|
skipToGame() {
|
|
console.log('⏭️ Skipping to game...');
|
|
this.exitPrologue();
|
|
}
|
|
|
|
exitPrologue() {
|
|
console.log('🎬 Intro complete! Starting game...');
|
|
|
|
// Fade out music (if exists)
|
|
if (this.noirMusic) {
|
|
this.tweens.add({
|
|
targets: this.noirMusic,
|
|
volume: 0,
|
|
duration: 2000,
|
|
onComplete: () => this.noirMusic.stop()
|
|
});
|
|
}
|
|
|
|
// Fade to black
|
|
this.cameras.main.fadeOut(2000, 0, 0, 0);
|
|
|
|
this.cameras.main.once('camerafadeoutcomplete', () => {
|
|
this.scene.start('GameScene');
|
|
});
|
|
}
|
|
}
|