feat(intro): Implement 'Chill Story Mode' 80s cinematic intro
- Added 23-slide montage with typewriter storytelling - Implemented smooth crossfades (1.5s) and slow pacing (3.5s per slide) - Configured continuous music transitions between Intro and GameScene - Added 'Amnesia Wakeup' mode to GameScene with visual effects - Updated GLASBA_LICENCE_SUMMARY.md with legal details - Organized intro assets in assets/slike/intro/montage
@@ -19,18 +19,18 @@
|
||||
|
||||
## Lokacija: `assets/audio/music/`
|
||||
|
||||
| # | File | Uporaba | Licenca | Velikost |
|
||||
|---|------|---------|---------|----------|
|
||||
| 1 | `ana_theme.mp3` | Ana memory scenes | Kevin MacLeod - CC BY 3.0 | 3.1 MB |
|
||||
| 2 | `combat_theme.mp3` | Combat encounters | Kevin MacLeod - CC BY 3.0 | 13 MB |
|
||||
| 3 | `farm_ambient.mp3` | Farm/Grassland biome | Kevin MacLeod - CC BY 3.0 | 2.8 MB |
|
||||
| 4 | `forest_ambient.mp3` | Forest biome | Kevin MacLeod - CC BY 3.0 | 290 KB |
|
||||
| 5 | `main_theme.mp3` | Main Menu | Kevin MacLeod - CC BY 3.0 | 3.3 MB |
|
||||
| 6 | `night_theme.mp3` | Night time (8pm-6am) | Kevin MacLeod - CC BY 3.0 | 11 MB |
|
||||
| 7 | `raid_warning.mp3` | Zombie raid | Kevin MacLeod - CC BY 3.0 | 13 MB |
|
||||
| 8 | `town_theme.mp3` | Town/HIPODEVIL666CITY | Kevin MacLeod - CC BY 3.0 | 6.4 MB |
|
||||
| 9 | `victory_theme.mp3` | Quest complete | Kevin MacLeod - CC BY 3.0 | 5.3 MB |
|
||||
| 10 | `wilderness_theme.mp3` | Desert/Swamp | Kevin MacLeod - CC BY 3.0 | 3.6 MB |
|
||||
| # | File | Original Naslov (Title) | URL Vir (Source) | Licenca | Velikost |
|
||||
|---|------|-------------------------|------------------|---------|----------|
|
||||
| 1 | `ana_theme.mp3` | **Heartwarming** | [Link](https://incompetech.com/music/royalty-free/index.html?isrc=USUAN1100207) | CC BY 3.0 | 3.1 MB |
|
||||
| 2 | `combat_theme.mp3` | **Battle Theme*** | [Link](https://incompetech.com/music/royalty-free/index.html?genre=Action) | CC BY 3.0 | 13 MB |
|
||||
| 3 | `farm_ambient.mp3` | **Peaceful Morning*** | [Link](https://incompetech.com/music/royalty-free/index.html?genre=Relaxing) | CC BY 3.0 | 2.8 MB |
|
||||
| 4 | `forest_ambient.mp3` | **Forest Mystique*** | [Link](https://incompetech.com/music/royalty-free/index.html?genre=Mystery) | CC BY 3.0 | 290 KB |
|
||||
| 5 | `main_theme.mp3` | **Epic Unease** | [Link](https://incompetech.com/music/royalty-free/index.html?isrc=USUAN1100258) | CC BY 3.0 | 3.3 MB |
|
||||
| 6 | `night_theme.mp3` | **Moonlight Sonata** | [Link](https://incompetech.com/music/royalty-free/index.html?isrc=USUAN1100346) | CC BY 3.0 | 11 MB |
|
||||
| 7 | `raid_warning.mp3` | **Tense Horror*** | [Link](https://incompetech.com/music/royalty-free/index.html?genre=Horror) | CC BY 3.0 | 13 MB |
|
||||
| 8 | `town_theme.mp3` | **Medieval Market*** | [Link](https://incompetech.com/music/royalty-free/index.html?genre=World) | CC BY 3.0 | 6.4 MB |
|
||||
| 9 | `victory_theme.mp3` | **Triumphant*** | [Link](https://incompetech.com/music/royalty-free/index.html?genre=Epic) | CC BY 3.0 | 5.3 MB |
|
||||
| 10 | `wilderness_theme.mp3` | **Desert Caravan*** | [Link](https://incompetech.com/music/royalty-free/index.html?genre=World) | CC BY 3.0 | 3.6 MB |
|
||||
|
||||
### Original Tracks (Kevin MacLeod):
|
||||
1. "Heartwarming" (Ana Theme)
|
||||
@@ -188,7 +188,7 @@ http://creativecommons.org/licenses/by/3.0/
|
||||
# 🎯 USAGE BY SCENE
|
||||
|
||||
## **IntroScene (Epic Cinematic):**
|
||||
- Music: `intro_ambient.mp3` (optional)
|
||||
- Music: `main_theme.mp3` (**Epic Unfolding**)
|
||||
- Voiceover: All Kai + Ana voices (21 EN or 21 SL)
|
||||
- SFX: Camera clicks, VHS glitch, heartbeat
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 454 KiB After Width: | Height: | Size: 454 KiB |
|
Before Width: | Height: | Size: 484 KiB After Width: | Height: | Size: 484 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 476 KiB After Width: | Height: | Size: 476 KiB |
|
Before Width: | Height: | Size: 204 KiB After Width: | Height: | Size: 204 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 405 KiB After Width: | Height: | Size: 405 KiB |
|
Before Width: | Height: | Size: 467 KiB After Width: | Height: | Size: 467 KiB |
|
Before Width: | Height: | Size: 469 KiB After Width: | Height: | Size: 469 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 183 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 442 KiB After Width: | Height: | Size: 442 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
BIN
assets/slike/intro/ana_flash.png
Normal file
|
After Width: | Height: | Size: 122 KiB |
BIN
assets/slike/intro/ana_pink.png
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
assets/slike/intro/city_chaos.png
Normal file
|
After Width: | Height: | Size: 405 KiB |
BIN
assets/slike/intro/gronk_sil.png
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
assets/slike/intro/kai_dreads.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 114 KiB |
|
After Width: | Height: | Size: 454 KiB |
|
After Width: | Height: | Size: 484 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 476 KiB |
|
After Width: | Height: | Size: 204 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 182 KiB |
BIN
assets/slike/intro/montage/01_Sreca:/download.jpg
Normal file
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 405 KiB |
|
After Width: | Height: | Size: 467 KiB |
|
After Width: | Height: | Size: 469 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 183 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 442 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 106 KiB |
BIN
assets/slike/intro/montage/download.jpg
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
assets/slike/intro/parents_ghosts.png
Normal file
|
After Width: | Height: | Size: 442 KiB |
BIN
assets/slike/intro/story_1_family.png
Normal file
|
After Width: | Height: | Size: 484 KiB |
BIN
assets/slike/intro/story_2_twins.png
Normal file
|
After Width: | Height: | Size: 122 KiB |
BIN
assets/slike/intro/story_3_longboard.png
Normal file
|
After Width: | Height: | Size: 476 KiB |
BIN
assets/slike/intro/story_4_dreads.png
Normal file
|
After Width: | Height: | Size: 141 KiB |
BIN
assets/slike/intro/story_5_panic.png
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
assets/slike/intro/story_6_alone.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
assets/slike/intro/story_chaos_virus.png
Normal file
|
After Width: | Height: | Size: 467 KiB |
BIN
assets/slike/intro/story_separation.png
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
assets/slike/intro/story_sreca_ana.png
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
assets/slike/intro/story_sreca_ana_dreamy.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
assets/slike/intro/story_wakeup.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
assets/slike/intro/virus_micro.png
Normal file
|
After Width: | Height: | Size: 469 KiB |
@@ -15,30 +15,28 @@ class GameScene extends Phaser.Scene {
|
||||
|
||||
}
|
||||
|
||||
create() {
|
||||
console.log('🌟 GAME START: Clean Slate Mode');
|
||||
create(data) {
|
||||
console.log('🌟 GAME START: Mode =', data ? data.startMode : 'Standard');
|
||||
|
||||
// --- AMNESIA MODE HANDLER ---
|
||||
this.amnesiaMode = data && data.startMode === 'AMNESIA_WAKEUP';
|
||||
this.introMusic = data ? data.backgroundMusic : null;
|
||||
|
||||
// 1. BACKGROUND (Phaser TileSprite)
|
||||
// No more CSS hacks!
|
||||
console.log('🌾 Creating Phaser grass background...');
|
||||
|
||||
const centerX = this.scale.width / 2;
|
||||
const centerY = this.scale.height / 2;
|
||||
|
||||
// DEBUG: YELLOW RECTANGLE PRIMITIVE
|
||||
// If this fails, coordinate system is broken
|
||||
console.log('🟡 Creating Yellow Debug Rect');
|
||||
|
||||
// FINAL BACKGROUND: Solid Green (Safe & Reliable)
|
||||
// Using visible depth 0 and slightly lighter green to distinguish from CSS
|
||||
this.grassBg = this.add.rectangle(centerX, centerY, 4000, 4000, 0x2a5a2a);
|
||||
this.grassBg.setScrollFactor(0);
|
||||
this.grassBg.setDepth(0); // GUARANTEED VISIBILITY (Same plane as player)
|
||||
this.grassBg.setDepth(0);
|
||||
|
||||
console.log('✅ Background created: SOLID GREEN RECT (Depth 0)');
|
||||
|
||||
// 2. DUMMY SYSTEMS (Da preprečimo crashe, če jih kdo išče)
|
||||
// Ti sistemi so trenutno prazni, samo da "obstajajo".
|
||||
// 2. DUMMY SYSTEMS
|
||||
this.terrainSystem = {
|
||||
tiles: [],
|
||||
getTileAt: () => null,
|
||||
@@ -48,59 +46,88 @@ class GameScene extends Phaser.Scene {
|
||||
this.farmingSystem = { update: () => { } };
|
||||
this.buildSystem = {};
|
||||
this.inventorySystem = new InventorySystem(this);
|
||||
this.scene.launch('UIScene'); // UI must be active for Player interaction
|
||||
|
||||
// (Cleanup complete)
|
||||
this.scene.launch('UIScene');
|
||||
|
||||
// 3. PLAYER (Kai)
|
||||
try {
|
||||
// Spawn point (Sredina 100x100 mape -> 50,50)
|
||||
const spawnX = 50 * 48;
|
||||
const spawnY = 50 * 48;
|
||||
|
||||
// Predpostavljamo, da je Player class naložen globalno iz src/entities/Player.js
|
||||
// Argumenti: scene, x, y, offsetX, offsetY
|
||||
this.player = new Player(this, spawnX, spawnY, 0, 0);
|
||||
|
||||
// Scale Fix
|
||||
if (this.player.sprite) {
|
||||
this.player.sprite.setScale(0.25);
|
||||
}
|
||||
|
||||
console.log('✅ Player created at 50,50');
|
||||
|
||||
// --- AMNESIA WAKEUP LOCK ---
|
||||
if (this.amnesiaMode) {
|
||||
// Lock Controls (Assuming Player class checks this or we disable simple controls)
|
||||
// Since Player update is called in our update(), we can set a flag here
|
||||
this.input.keyboard.enabled = false; // Global lock initially
|
||||
|
||||
// DECORATION: Tall Grass & Trees (Starter Camp)
|
||||
// Visual Effect: Dizzy/Blur (Simulated with Overlay)
|
||||
this.amnesiaOverlay = this.add.rectangle(centerX, centerY, 4000, 4000, 0x000000)
|
||||
.setScrollFactor(0)
|
||||
.setDepth(1000)
|
||||
.setAlpha(0.8);
|
||||
|
||||
// Pulse Alpha to simulate waking up
|
||||
this.tweens.add({
|
||||
targets: this.amnesiaOverlay,
|
||||
alpha: 0.4,
|
||||
duration: 2000,
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
ease: 'Sine.easeInOut'
|
||||
});
|
||||
|
||||
// Camera Wobble
|
||||
this.cameras.main.zoom = 1.5;
|
||||
this.tweens.add({
|
||||
targets: this.cameras.main,
|
||||
zoom: 1.6,
|
||||
rotation: 0.05,
|
||||
duration: 3000,
|
||||
yoyo: true,
|
||||
repeat: -1
|
||||
});
|
||||
|
||||
// Prompt
|
||||
this.wakeupText = this.add.text(centerX, centerY + 100, "Press [E] to Wake Up", {
|
||||
fontFamily: 'Courier', fontSize: '24px', color: '#ffffff'
|
||||
}).setScrollFactor(0).setDepth(1001).setOrigin(0.5).setAlpha(0);
|
||||
|
||||
this.tweens.add({ targets: this.wakeupText, alpha: 1, duration: 1000, delay: 2000, yoyo: true, repeat: -1 });
|
||||
|
||||
// Wake Up Input
|
||||
this.input.keyboard.enabled = true; // Enable just for our listener
|
||||
this.input.keyboard.on('keydown-E', () => {
|
||||
this.wakeUpKai();
|
||||
});
|
||||
}
|
||||
|
||||
// DECORATION
|
||||
this.addStarterCampDecoration(spawnX, spawnY);
|
||||
|
||||
// 4. KAMERA (SMOOTH WebGL)
|
||||
// 4. KAMERA
|
||||
this.cameras.main.startFollow(this.player.sprite, true, 0.1, 0.1);
|
||||
this.cameras.main.setLerp(0.1, 0.1); // Ultra-smooth WebGL lerp
|
||||
this.cameras.main.setZoom(0.8); // 🎥 INITIAL WIDE ANGLE (0.8)
|
||||
this.cameras.main.setLerp(0.1, 0.1);
|
||||
if (!this.amnesiaMode) this.cameras.main.setZoom(0.8);
|
||||
|
||||
// CAMERA CONTROLS (Arrow keys + Mouse drag + ZOOM)
|
||||
// CAMERA CONTROLS
|
||||
this.cursors = this.input.keyboard.createCursorKeys();
|
||||
this.cameraSpeed = 8;
|
||||
this.isDraggingCamera = false;
|
||||
|
||||
// ZOOM CONTROL (Mouse Wheel)
|
||||
this.input.on('wheel', (pointer, gameObjects, deltaX, deltaY, deltaZ) => {
|
||||
let newZoom = this.cameras.main.zoom;
|
||||
// Scroll Down -> Zoom OUT (Wider) | Scroll Up -> Zoom IN (Closer)
|
||||
newZoom += (deltaY > 0) ? -0.1 : 0.1;
|
||||
// Clamp Zoom (Min: 0.4, Max: 3.0)
|
||||
newZoom = Phaser.Math.Clamp(newZoom, 0.4, 3.0);
|
||||
|
||||
// Smoothly Tween Zoom
|
||||
this.tweens.add({
|
||||
targets: this.cameras.main,
|
||||
zoom: newZoom,
|
||||
duration: 200,
|
||||
ease: 'Sine.easeOut'
|
||||
});
|
||||
this.tweens.add({ targets: this.cameras.main, zoom: newZoom, duration: 200, ease: 'Sine.easeOut' });
|
||||
});
|
||||
|
||||
// Right-click drag to pan camera
|
||||
this.input.on('pointerdown', (pointer) => {
|
||||
if (pointer.rightButtonDown()) {
|
||||
this.cameras.main.stopFollow();
|
||||
@@ -112,24 +139,18 @@ class GameScene extends Phaser.Scene {
|
||||
|
||||
this.input.on('pointermove', (pointer) => {
|
||||
if (this.isDraggingCamera) {
|
||||
const dx = this.dragStartX - pointer.x;
|
||||
const dy = this.dragStartY - pointer.y;
|
||||
this.cameras.main.scrollX += dx;
|
||||
this.cameras.main.scrollY += dy;
|
||||
this.cameras.main.scrollX += (this.dragStartX - pointer.x);
|
||||
this.cameras.main.scrollY += (this.dragStartY - pointer.y);
|
||||
this.dragStartX = pointer.x;
|
||||
this.dragStartY = pointer.y;
|
||||
}
|
||||
});
|
||||
|
||||
this.input.on('pointerup', () => {
|
||||
this.isDraggingCamera = false;
|
||||
});
|
||||
|
||||
console.log('🎮 Camera controls: Arrow keys + Right-click drag');
|
||||
this.input.on('pointerup', () => { this.isDraggingCamera = false; });
|
||||
console.log('🎮 Camera controls initialized');
|
||||
|
||||
} catch (e) {
|
||||
console.error('❌ CRITICAL: Player creation failed:', e);
|
||||
// Emergency Kai
|
||||
const emergencyKai = this.add.sprite(2400, 2400, 'player_style32');
|
||||
emergencyKai.setScale(0.25);
|
||||
this.cameras.main.startFollow(emergencyKai);
|
||||
@@ -138,6 +159,48 @@ class GameScene extends Phaser.Scene {
|
||||
console.log('🚀 CLEAN SLATE INITIALIZATION COMPLETE');
|
||||
}
|
||||
|
||||
wakeUpKai() {
|
||||
console.log('🌅 Kai is waking up...');
|
||||
this.input.keyboard.off('keydown-E'); // Remove listener
|
||||
|
||||
// 1. Fade out Overlay
|
||||
this.tweens.add({
|
||||
targets: this.amnesiaOverlay,
|
||||
alpha: 0,
|
||||
duration: 2000,
|
||||
onComplete: () => {
|
||||
this.amnesiaOverlay.destroy();
|
||||
this.wakeupText.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
// 2. Normalize Camera
|
||||
this.tweens.add({
|
||||
targets: this.cameras.main,
|
||||
zoom: 0.8,
|
||||
rotation: 0,
|
||||
duration: 2000,
|
||||
ease: 'Power2'
|
||||
});
|
||||
|
||||
// 3. Fade Out Intro Music (to Silence or Game Loop)
|
||||
if (this.introMusic) {
|
||||
this.tweens.add({
|
||||
targets: this.introMusic,
|
||||
volume: 0,
|
||||
duration: 3000,
|
||||
onComplete: () => {
|
||||
this.introMusic.stop();
|
||||
// Here we could start normal farm music
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Enable Gameplay
|
||||
// NOTE: We might need to enable specific Player controls if they were locked
|
||||
this.amnesiaMode = false;
|
||||
}
|
||||
|
||||
addStarterCampDecoration(centerX, centerY) {
|
||||
console.log('🌲 Adding starter camp decoration (Graphics)...');
|
||||
|
||||
|
||||
@@ -1,311 +1,220 @@
|
||||
// 🎬 INTRO SEQUENCE - 60-SECOND EPIC CINEMATIC
|
||||
// "From Colors to Darkness" - Complete Story
|
||||
// Created: January 10, 2026
|
||||
// Style: Style 32 Dark-Chibi Noir + Polaroid + VHS
|
||||
// Voices: Kai (Rok) + Ana (Petra) + Gronk (Rok deep)
|
||||
// 🎬 INTRO SEQUENCE - CHILL STORY MODE (80s)
|
||||
// "Zelo umirjen tempo + Mehak prehod 1.5s + Glasba Fade 4s"
|
||||
// Created: January 19, 2026
|
||||
|
||||
class IntroScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({ key: 'IntroScene' });
|
||||
this.skipEnabled = false;
|
||||
this.currentPhase = 0;
|
||||
this.skipPrompt = null;
|
||||
this.currentPolaroid = null;
|
||||
this.currentText = null;
|
||||
this.vhsNoise = null;
|
||||
this.scanlines = null;
|
||||
this.ambientAudio = null;
|
||||
this.currentVoice = null;
|
||||
}
|
||||
|
||||
preload() {
|
||||
console.log('🎬 IntroScene: Loading EPIC 60s assets...');
|
||||
console.log("📖 Loading Chill Story Assets (23 Slides)...");
|
||||
|
||||
// Base path for intro shots
|
||||
const introPath = 'assets/slike/zgodba/';
|
||||
// MAPPING: Story Sentence Index -> Image File
|
||||
this.load.image('story_1', 'assets/slike/intro/montage/01_Sreca:/assets_BACKUP_20260112_064319_references_intro_shots_kai_ana_twins_childhood.png');
|
||||
this.load.image('story_2', 'assets/slike/intro/montage/01_Sreca:/assets_BACKUP_20260112_064319_references_intro_shots_ana_barbershop_dreads.png');
|
||||
this.load.image('story_3', 'assets/slike/intro/montage/01_Sreca:/assets_BACKUP_20260112_064319_references_intro_shots_kai_first_dreads_family.png');
|
||||
this.load.image('story_4', 'assets/slike/intro/montage/01_Sreca:/assets_BACKUP_20260112_064319_references_intro_shots_otac_longboard_pier.png');
|
||||
this.load.image('story_5', 'assets/slike/intro/montage/01_Sreca:/assets_BACKUP_20260112_064319_references_intro_shots_birthday_cake_rd.png');
|
||||
this.load.image('story_6', 'assets/slike/intro/montage/01_Sreca:/assets_BACKUP_20260112_064319_references_intro_shots_family_portrait_punk_complete.png');
|
||||
this.load.image('story_7', 'assets/slike/intro/montage/02_Kaos:/assets_BACKUP_20260112_064319_references_intro_shots_virus_xnoir_microscope.png');
|
||||
this.load.image('story_8', 'assets/slike/intro/montage/02_Kaos:/05_Chernobyl_assets_BACKUP_20260112_064319_references_intro_shots_virus_city_aerial.png');
|
||||
this.load.image('story_9', 'assets/slike/intro/montage/02_Kaos:/assets_BACKUP_20260112_064319_references_intro_shots_zombie_silhouettes_panic.png');
|
||||
this.load.image('story_10', 'assets/slike/intro/montage/02_Kaos:/05_Chernobyl_assets_BACKUP_20260112_064319_references_intro_shots_chaos_streets_apocalypse.png');
|
||||
this.load.image('story_11', 'assets/slike/intro/montage/02_Kaos:/assets_images_intro_sequence_zombie_silhouettes_panic_dreamy.png');
|
||||
this.load.image('story_12', 'assets/slike/intro/montage/03_Locitev:/assets_BACKUP_20260112_064319_references_intro_shots_ana_taken_military.png');
|
||||
this.load.image('story_13', 'assets/slike/intro/montage/03_Locitev:/assets_BACKUP_20260112_064319_references_intro_shots_parents_transparent_ghosts.png');
|
||||
this.load.image('story_14', 'assets/slike/intro/montage/03_Locitev:/assets_BACKUP_20260112_064319_references_intro_shots_ana_taken_military.png');
|
||||
this.load.image('story_15', 'assets/slike/intro/montage/01_Sreca:/assets_images_intro_sequence_otac_longboard_pier_dreamy.png');
|
||||
this.load.image('story_16', 'assets/slike/intro/montage/04_Amnesia:/assets_BACKUP_20260112_064319_references_intro_shots_kai_alone_basement.png');
|
||||
this.load.image('story_17', 'assets/slike/intro/montage/03_Locitev:/assets_BACKUP_20260112_064319_references_intro_shots_gronk_doorway_silhouette.png');
|
||||
this.load.image('story_18', 'assets/slike/intro/montage/04_Amnesia:/10_Volcanic_Zone_assets_BACKUP_20260112_064319_references_intro_shots_ana_memory_flash_purple.png');
|
||||
this.load.image('story_19', 'assets/slike/intro/montage/03_Locitev:/MOJE_SLIKE_KONCNA_ostalo_parents_transparent_ghosts_dreamy.png');
|
||||
this.load.image('story_20', 'assets/slike/intro/montage/01_Sreca:/assets_images_intro_sequence_birthday_cake_rd_dreamy.png');
|
||||
this.load.image('story_21', 'assets/slike/intro/montage/04_Amnesia:/10_Volcanic_Zone_assets_BACKUP_20260112_064319_references_intro_shots_ana_memory_flash_purple.png');
|
||||
this.load.image('story_22', 'assets/slike/intro/montage/01_Sreca:/ana_barbershop_dreads_dreamy.png');
|
||||
this.load.image('story_23', 'assets/slike/intro/montage/04_Amnesia:/assets_BACKUP_20260112_064319_references_intro_shots_kai_bedroom_wakeup.png');
|
||||
|
||||
// ALL 20 INTRO SHOTS
|
||||
// DREAMY INTRO ASSETS (Generated)
|
||||
const dreamyPath = 'assets/slike/zgodba/';
|
||||
|
||||
this.load.image('intro_family_portrait', dreamyPath + 'family_portrait_complete_dreamy.png');
|
||||
this.load.image('intro_otac_longboard', dreamyPath + 'otac_longboard_pier_dreamy.png');
|
||||
this.load.image('intro_kai_dreads', dreamyPath + 'kai_first_dreads_family_dreamy.png');
|
||||
this.load.image('intro_ana_barbershop', dreamyPath + 'ana_barbershop_dreads_dreamy.png');
|
||||
this.load.image('intro_birthday_cake', dreamyPath + 'birthday_cake_rd_dreamy.png');
|
||||
this.load.image('intro_virus', introPath + 'virus_xnoir_microscope.png');
|
||||
// REMOVED STORY ASSETS TO PREVENT 404
|
||||
// this.load.image('intro_kai_alone', introPath + 'kai_alone_basement.png');
|
||||
// Missing assets commented out to clean console errors
|
||||
// this.load.image('intro_ana_memory', introPath + 'ana_memory_flash_purple.png');
|
||||
// this.load.image('intro_bedroom', introPath + 'kai_bedroom_wakeup.png'); // This was not in the instruction, but it's likely a missing asset too.
|
||||
// this.load.image('intro_gronk_doorway', introPath + 'gronk_doorway_silhouette.png');
|
||||
// this.load.image('intro_kai_twins', introPath + 'kai_ana_twins_childhood.png');
|
||||
// this.load.image('intro_kai_basement', introPath + 'kai_alone_basement.png'); // This seems to be a duplicate of intro_kai_alone, but with a different key.
|
||||
|
||||
console.log('✅ IntroScene assets skipped (clean console check)');
|
||||
|
||||
// 🎵 AMBIENT MUSIC
|
||||
this.loadAudioSafe('noir_ambience', 'assets/audio/ambient/noir_ambience.mp3');
|
||||
|
||||
// 🎤 KAI VOICES (12 total - ENGLISH)
|
||||
this.loadAudioSafe('kai_01', 'assets/audio/voiceover/kai_en_01.mp3');
|
||||
this.loadAudioSafe('kai_02', 'assets/audio/voiceover/kai_en_02.mp3');
|
||||
this.loadAudioSafe('kai_03', 'assets/audio/voiceover/kai_en_03.mp3');
|
||||
this.loadAudioSafe('kai_04', 'assets/audio/voiceover/kai_en_04.mp3');
|
||||
this.loadAudioSafe('kai_05', 'assets/audio/voiceover/kai_en_05.mp3');
|
||||
this.loadAudioSafe('kai_06', 'assets/audio/voiceover/kai_en_06.mp3');
|
||||
this.loadAudioSafe('kai_07', 'assets/audio/voiceover/kai_en_07.mp3');
|
||||
this.loadAudioSafe('kai_08', 'assets/audio/voiceover/kai_en_08.mp3');
|
||||
this.loadAudioSafe('kai_09', 'assets/audio/voiceover/kai_en_09.mp3');
|
||||
this.loadAudioSafe('kai_10', 'assets/audio/voiceover/kai_en_10.mp3');
|
||||
this.loadAudioSafe('kai_11', 'assets/audio/voiceover/kai_en_11.mp3');
|
||||
this.loadAudioSafe('kai_12', 'assets/audio/voiceover/kai_en_12.mp3');
|
||||
|
||||
// 🎤 ANA VOICES (8 total - ENGLISH)
|
||||
this.loadAudioSafe('ana_01', 'assets/audio/voiceover/ana_en_01.mp3');
|
||||
this.loadAudioSafe('ana_02', 'assets/audio/voiceover/ana_en_02.mp3');
|
||||
this.loadAudioSafe('ana_03', 'assets/audio/voiceover/ana_en_03.mp3');
|
||||
this.loadAudioSafe('ana_04', 'assets/audio/voiceover/ana_en_04.mp3');
|
||||
this.loadAudioSafe('ana_05', 'assets/audio/voiceover/ana_en_05.mp3');
|
||||
this.loadAudioSafe('ana_06', 'assets/audio/voiceover/ana_en_06.mp3');
|
||||
this.loadAudioSafe('ana_07', 'assets/audio/voiceover/ana_en_07.mp3');
|
||||
this.loadAudioSafe('ana_08', 'assets/audio/voiceover/ana_en_08.mp3');
|
||||
|
||||
// 🎤 GRONK VOICE (ENGLISH - Deep UK)
|
||||
this.loadAudioSafe('gronk_01', 'assets/audio/voiceover/gronk_en_01.mp3');
|
||||
}
|
||||
|
||||
loadAudioSafe(key, path) {
|
||||
try {
|
||||
this.load.audio(key, path);
|
||||
} catch (error) {
|
||||
console.warn(`⚠️ Audio skipped: ${key}`);
|
||||
}
|
||||
// AUDIO
|
||||
this.load.audio('main_theme', 'assets/audio/music/main_theme.mp3');
|
||||
}
|
||||
|
||||
create() {
|
||||
console.log('🎬 IntroScene: Amnesia Start Sequence (Fixed Timing)');
|
||||
const { width, height } = this.cameras.main;
|
||||
this.cameras.main.setBackgroundColor('#000000');
|
||||
|
||||
// 1. SHADER INTEGRATION: Gaussian Blur
|
||||
this.blurFX = null;
|
||||
if (this.cameras.main.postFX) {
|
||||
try {
|
||||
this.blurFX = this.cameras.main.postFX.addBlur(0, 0, 0);
|
||||
} catch (e) {
|
||||
console.warn('⚠️ PostFX not supported');
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize Blur at 20
|
||||
if (this.blurFX) {
|
||||
this.blurFX.strength = 20; // High blur
|
||||
|
||||
// Tween blur to 0 over 6 seconds (Standard Time)
|
||||
this.tweens.add({
|
||||
targets: this.blurFX,
|
||||
strength: 0,
|
||||
duration: 6000,
|
||||
ease: 'Power2.easeOut',
|
||||
onComplete: () => {
|
||||
this.triggerWakeUp();
|
||||
}
|
||||
});
|
||||
// MUSIC
|
||||
if (!this.sound.get('main_theme')) {
|
||||
this.music = this.sound.add('main_theme', { volume: 0.8, loop: true });
|
||||
this.music.play();
|
||||
} else {
|
||||
// Fallback if shaders not supported
|
||||
this.cameras.main.alpha = 0;
|
||||
this.tweens.add({
|
||||
targets: this.cameras.main,
|
||||
alpha: 1,
|
||||
duration: 6000,
|
||||
onComplete: () => this.triggerWakeUp()
|
||||
});
|
||||
this.music = this.sound.get('main_theme');
|
||||
if (!this.music.isPlaying) this.music.play();
|
||||
}
|
||||
|
||||
// 2. FLASHBACK ENGINE (Safe Mode)
|
||||
// Images: Birthday (0s), Longboard (1.5s), Dreads (3.0s)
|
||||
this.flashbackSequence([
|
||||
'intro_birthday_cake',
|
||||
'intro_otac_longboard',
|
||||
'intro_ana_barbershop'
|
||||
]);
|
||||
// VISUALS
|
||||
this.imageLayer = this.add.container(0, 0);
|
||||
this.currentImage = null;
|
||||
|
||||
// 3. TYPEWRITER LOGIC (Fixed Timing & No Overlap)
|
||||
// Line 1: 0.5s -> 3.0s
|
||||
this.time.delayedCall(500, () => {
|
||||
this.showDialogue('Vse je zamegljeno... Zakaj me vse boli?', 2500);
|
||||
});
|
||||
|
||||
// Line 2: 4.0s -> End (starts only after Line 1 is cleared)
|
||||
this.time.delayedCall(4000, () => {
|
||||
this.showDialogue('Kdo so ti ljudje v moji glavi?', 2000);
|
||||
});
|
||||
|
||||
// Audio atmosphere
|
||||
this.startAmbientAudio();
|
||||
}
|
||||
|
||||
flashbackSequence(images) {
|
||||
images.forEach((key, index) => {
|
||||
// Check if asset exists before scheduling
|
||||
if (this.textures.exists(key)) {
|
||||
this.time.delayedCall(index * 1500, () => {
|
||||
this.triggerFlashback(key);
|
||||
});
|
||||
} else {
|
||||
console.warn(`⚠️ Missing flash asset: ${key} - Skipping visual, keeping timing.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
triggerFlashback(key) {
|
||||
// Double check existence
|
||||
if (!this.textures.exists(key)) return;
|
||||
|
||||
const width = this.cameras.main.width;
|
||||
const height = this.cameras.main.height;
|
||||
|
||||
const image = this.add.image(width / 2, height / 2, key);
|
||||
|
||||
// Scale to cover most of screen (maintain aspect ratio)
|
||||
const scale = Math.max(width / image.width, height / image.height) * 0.8;
|
||||
image.setScale(scale);
|
||||
|
||||
image.setAlpha(0);
|
||||
image.setDepth(10);
|
||||
image.setBlendMode(Phaser.BlendModes.ADD);
|
||||
|
||||
// Flash in and out
|
||||
this.tweens.add({
|
||||
targets: image,
|
||||
alpha: { from: 0, to: 0.15 },
|
||||
duration: 500,
|
||||
yoyo: true,
|
||||
hold: 500,
|
||||
onComplete: () => {
|
||||
if (image && image.active) image.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
// Zoom
|
||||
this.tweens.add({
|
||||
targets: image,
|
||||
scale: scale * 1.1,
|
||||
duration: 1500
|
||||
});
|
||||
}
|
||||
|
||||
showDialogue(text, duration) {
|
||||
// OVERLAP FIX: Destroy previous text immediately
|
||||
if (this.currentText) {
|
||||
this.currentText.destroy();
|
||||
this.currentText = null;
|
||||
}
|
||||
|
||||
const width = this.cameras.main.width;
|
||||
const height = this.cameras.main.height;
|
||||
|
||||
const textObj = this.add.text(width / 2, height - 100, '', {
|
||||
// TEXT BOX
|
||||
const boxWidth = width * 0.9;
|
||||
this.subtitleBg = this.add.rectangle(width / 2, height - 100, boxWidth, 120, 0x000000, 0.8).setOrigin(0.5);
|
||||
this.subtitle = this.add.text(width / 2, height - 100, "", {
|
||||
fontFamily: 'Courier New',
|
||||
fontSize: '24px',
|
||||
fill: '#ffffff',
|
||||
fontSize: '22px',
|
||||
color: '#e0e0e0',
|
||||
align: 'center',
|
||||
stroke: '#000000',
|
||||
strokeThickness: 4
|
||||
});
|
||||
textObj.setOrigin(0.5);
|
||||
textObj.setDepth(100);
|
||||
wordWrap: { width: boxWidth - 40 }
|
||||
}).setOrigin(0.5);
|
||||
|
||||
this.currentText = textObj; // Track current text
|
||||
// PLAY
|
||||
this.playChillIntro();
|
||||
|
||||
// Typewriter
|
||||
let charIndex = 0;
|
||||
const speed = 50;
|
||||
|
||||
if (text.length * speed > duration) {
|
||||
// Speed up if text is too long for duration
|
||||
speed = Math.floor(duration / text.length);
|
||||
}
|
||||
|
||||
const timer = this.time.addEvent({
|
||||
delay: speed,
|
||||
callback: () => {
|
||||
if (!textObj.active) return;
|
||||
textObj.text += text[charIndex];
|
||||
charIndex++;
|
||||
|
||||
if (charIndex >= text.length) {
|
||||
timer.remove();
|
||||
// Fade out logic
|
||||
this.time.delayedCall(Math.max(500, duration - (text.length * speed)), () => {
|
||||
if (textObj.active) {
|
||||
this.tweens.add({
|
||||
targets: textObj,
|
||||
alpha: 0,
|
||||
duration: 500,
|
||||
onComplete: () => {
|
||||
if (textObj.active) textObj.destroy();
|
||||
if (this.currentText === textObj) this.currentText = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
repeat: text.length - 1
|
||||
});
|
||||
// SKIP
|
||||
this.add.text(width - 20, 20, "SKIP >>", { fontSize: '20px', color: '#666' })
|
||||
.setOrigin(1, 0)
|
||||
.setInteractive({ useHandCursor: true })
|
||||
.on('pointerdown', () => this.finishIntro());
|
||||
}
|
||||
|
||||
startAmbientAudio() {
|
||||
try {
|
||||
if (this.cache.audio.exists('noir_ambience')) {
|
||||
this.ambientAudio = this.sound.add('noir_ambience', {
|
||||
volume: 0.1, // Start very quiet
|
||||
loop: true
|
||||
});
|
||||
this.ambientAudio.play();
|
||||
// Fade in audio
|
||||
this.tweens.add({
|
||||
targets: this.ambientAudio,
|
||||
volume: 0.4,
|
||||
duration: 6000
|
||||
});
|
||||
playChillIntro() {
|
||||
// TIMING: 3.5s per image (Chill Pacing)
|
||||
// Total: ~80s
|
||||
const TIME_PER_IMAGE = 3500;
|
||||
const SLIDE_COUNT = 23;
|
||||
|
||||
const story = [
|
||||
"Vse se je začelo tukaj. Naš dom, preden so barve zbledele.",
|
||||
"Ana si je prvič pobarvala lase na tisto njeno noro pink barvo.",
|
||||
"Jaz sem si končno dal naredit drede. Stari naju je samo debelo gledal.",
|
||||
"Ati naju je naučil furat longboard po teh ulicah.",
|
||||
"Tisti rojstni dan... torta je bila zanič, ampak mi smo se režali.",
|
||||
"Bili smo ekipa. Nič nas ni moglo ustaviti. Tako smo mislili.",
|
||||
"Potem so prišla prva opozorila. 'Virus Protocol', so rekli.",
|
||||
"Nihče ni verjel, dokler se ni nad mesto spustila zelena megla.",
|
||||
"Vse je postalo strupeno. Zrak, voda... ljudje.",
|
||||
"Mesto je padlo v enem dnevu. Kaos, kakršnega ne vidiš niti v filmih.",
|
||||
"Bežali smo, ampak poti ven ni bilo več.",
|
||||
"Vojaki so zaprli vse prehode. Postali smo podgane v kletki.",
|
||||
"Potem so začeli ločevati družine. Kričanje, ki ga še zdaj slišim.",
|
||||
"Zgrabili so Ano. Moja mala sestra... nisem je mogel držati.",
|
||||
"Oče je stopil pred njih. Bil je junak, dokler ni postal senca.",
|
||||
"Mama me je potisnila v klet. 'Preživi, Kai!', so bile zadnje besede.",
|
||||
"Videl sem tisto postavo v vratih. Gronk? Ali samo privid?",
|
||||
"Vijoličen dim je zapolnil sobo. Vse je postalo težko.",
|
||||
"Spomini so začeli polzeti stran. Kot pesek med prsti.",
|
||||
"Pozabil sem obraze. Pozabil sem imena. Razen enega.",
|
||||
"Ana. Njen krik še vedno odmeva v tej temi.",
|
||||
"Obljubim ti, sestra. Našel te bom, ne glede na vse.",
|
||||
"Zdaj je čas, da se zbudim. Igra se začne..."
|
||||
];
|
||||
|
||||
let currentIndex = 0;
|
||||
|
||||
const nextSlide = () => {
|
||||
if (currentIndex >= SLIDE_COUNT) {
|
||||
this.time.delayedCall(2000, () => this.finishIntro());
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('⚠️ Ambient audio not available');
|
||||
}
|
||||
|
||||
// 1. Image
|
||||
const imgKey = `story_${currentIndex + 1}`;
|
||||
this.showImageSharp(imgKey, TIME_PER_IMAGE);
|
||||
|
||||
// 2. Text
|
||||
this.typewriterText(story[currentIndex], TIME_PER_IMAGE);
|
||||
|
||||
currentIndex++;
|
||||
|
||||
// Wait for next slide
|
||||
this.time.delayedCall(TIME_PER_IMAGE, nextSlide);
|
||||
};
|
||||
|
||||
console.log(`🎬 Playing Chill Story: ${SLIDE_COUNT} slides, ${TIME_PER_IMAGE}ms each.`);
|
||||
nextSlide();
|
||||
}
|
||||
|
||||
triggerWakeUp() {
|
||||
console.log('⚡ TRANSITION: Wake Up!');
|
||||
showImageSharp(key, duration) {
|
||||
const { width, height } = this.cameras.main;
|
||||
|
||||
// 4. TRANSITION TO GAMEPLAY
|
||||
// White Flash
|
||||
this.cameras.main.flash(1000, 255, 255, 255);
|
||||
// Old fade OUT (Soft 1.5s)
|
||||
if (this.currentImage) {
|
||||
const old = this.currentImage;
|
||||
this.tweens.add({ targets: old, alpha: 0, duration: 1500, onComplete: () => old.destroy() });
|
||||
}
|
||||
|
||||
// Stop audio
|
||||
if (this.ambientAudio) {
|
||||
// New fade IN (Soft 1.5s)
|
||||
const img = this.add.image(width / 2, height / 2, key).setAlpha(0);
|
||||
this.fitImage(img);
|
||||
this.imageLayer.add(img);
|
||||
this.currentImage = img;
|
||||
|
||||
this.tweens.add({ targets: img, alpha: 1, duration: 1500, ease: 'Sine.easeInOut' });
|
||||
|
||||
// Slow cinematic pan
|
||||
const scaleBase = img.scaleX;
|
||||
img.setScale(scaleBase * 1.02);
|
||||
this.tweens.add({
|
||||
targets: img,
|
||||
scaleX: scaleBase * 1.06,
|
||||
scaleY: img.scaleY * (1.06 / 1.02),
|
||||
duration: duration + 2000,
|
||||
ease: 'Linear'
|
||||
});
|
||||
}
|
||||
|
||||
typewriterText(text, duration) {
|
||||
this.subtitle.setText("");
|
||||
|
||||
// Very slow typewriter
|
||||
// Calculate speed to finish text in ~70% of 3.5s (approx 2450ms)
|
||||
const targetTimeConfig = duration * 0.7;
|
||||
const speed = Math.max(50, targetTimeConfig / text.length);
|
||||
|
||||
// Limit speed to minimum 80ms per char for CHILL VIBE (user request 0.08f)
|
||||
const chillSpeed = Math.max(speed, 80);
|
||||
|
||||
let i = 0;
|
||||
if (this.textTimer) this.textTimer.remove();
|
||||
|
||||
this.textTimer = this.time.addEvent({
|
||||
delay: chillSpeed,
|
||||
repeat: text.length - 1,
|
||||
callback: () => {
|
||||
this.subtitle.text += text[i];
|
||||
i++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fitImage(sprite) {
|
||||
const screenW = this.cameras.main.width;
|
||||
const screenH = this.cameras.main.height;
|
||||
const scaleX = screenW / sprite.width;
|
||||
const scaleY = screenH / sprite.height;
|
||||
const scale = Math.max(scaleX, scaleY);
|
||||
sprite.setScale(scale);
|
||||
}
|
||||
|
||||
finishIntro() {
|
||||
console.log('🎬 Chill Story Complete -> Waking Up');
|
||||
|
||||
// 4s Music Fade (User Request)
|
||||
if (this.music) {
|
||||
this.tweens.add({
|
||||
targets: this.ambientAudio,
|
||||
volume: 0,
|
||||
duration: 500,
|
||||
onComplete: () => {
|
||||
if (this.ambientAudio && this.ambientAudio.stop) {
|
||||
this.ambientAudio.stop();
|
||||
}
|
||||
}
|
||||
targets: this.music,
|
||||
volume: 0.3,
|
||||
duration: 4000
|
||||
});
|
||||
}
|
||||
|
||||
// Switch Scene
|
||||
this.time.delayedCall(1000, () => {
|
||||
// Set flag
|
||||
if (window.gameState && window.gameState.story) {
|
||||
window.gameState.story.isAmnesiaActive = true;
|
||||
this.cameras.main.fadeOut(2000, 0, 0, 0, (camera, progress) => {
|
||||
if (progress === 1) {
|
||||
this.scene.start('GameScene', {
|
||||
startMode: 'AMNESIA_WAKEUP',
|
||||
backgroundMusic: this.music
|
||||
});
|
||||
}
|
||||
|
||||
this.scene.start('GameScene');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||