export default class GrassSceneClean extends Phaser.Scene { constructor() { super({ key: 'GrassSceneClean' }); } preload() { this.load.path = 'assets/'; // --- ASSETS --- // 1. Podlaga (Foundation) this.load.image('ground_base', 'DEMO_FAZA1/Ground/ground_dirt_patch.png'); // 2. Vodni kanali (Water) this.load.image('river_tile_seamless', 'DEMO_FAZA1/Environment/river_tile_seamless.png'); // 3. Foliage // "Divja trava" - samo visoka trava this.load.image('grass_wild', 'DEMO_FAZA1/Vegetation/visoka_trava.png'); this.load.image('grass_wild_v2', 'DEMO_FAZA1/Vegetation/visoka_trava_v2.png'); // 4. Items & Charts this.load.image('hay', 'DEMO_FAZA1/Items/hay_drop_0.png'); this.load.image('trnje', 'DEMO_FAZA1/Obstacles/trnje.png'); this.load.image('toxic_fog', 'DEMO_FAZA1/VFX/toxic_fog.png'); this.load.image('amnesia_fog', 'DEMO_FAZA1/VFX/megla_ozadje.png'); // Generated Assets (Slices) // Trees for (let i = 0; i <= 5; i++) this.load.image(`tree_adult_${i}`, `DEMO_FAZA1/Trees/tree_adult_${i}.png`); // Environment (Dead Nature, Fence, Water) for (let i = 0; i <= 8; i++) this.load.image(`dead_nature_${i}`, `DEMO_FAZA1/Environment/dead_nature_${i}.png`); for (let i = 0; i <= 2; i++) this.load.image(`fence_sign_${i}`, `DEMO_FAZA1/Environment/fence_sign_${i}.png`); for (let i = 0; i < 16; i++) this.load.image(`water_tile_${i}`, `DEMO_FAZA1/Environment/water_tile_${i}.png`); // Misc Env this.load.image('sotor', 'DEMO_FAZA1/Environment/sotor.png'); this.load.image('campfire', 'DEMO_FAZA1/Environment/taborni_ogenj.png'); this.load.image('sign_danger', 'DEMO_FAZA1/Environment/sign_danger.png'); // Vegetation const veg = ['bush_hiding_spot', 'drevo_faza_1', 'drevo_faza_2', 'drevo_srednje', 'drevo_veliko', 'grass_cluster_dense', 'grass_cluster_flowery', 'trava_sop']; veg.forEach(k => this.load.image(k, `DEMO_FAZA1/Vegetation/${k}.png`)); // Ground (Path) for (let i = 0; i < 16; i++) { this.load.image(`path_tile_${i}`, `DEMO_FAZA1/Ground/path_tile_${i}.png`); } // REPLACED STATIC KAI WITH SPRITE SHEET // Frame size 256x256 based on 1024x1024 sheet this.load.spritesheet('kai', 'DEMO_FAZA1/Characters/kai_walk_sheet.png', { frameWidth: 256, frameHeight: 256 }); // Loading as 'kai' to keep existing references working, but now it has frames. // 5. UI Assets (Loaded in UIScene now to prevent duplicates) // REMOVED // 6. Camp Assets this.load.image('campfire', 'DEMO_FAZA1/Environment/taborni_ogenj.png'); this.load.image('tent', 'DEMO_FAZA1/Environment/sotor.png'); this.load.image('sleeping_bag', 'DEMO_FAZA1/Items/spalna_vreca.png'); // 7. NEW: Gronk & Structures this.load.spritesheet('gronk', 'DEMO_FAZA1/Characters/gronk_walk_sheet.png', { frameWidth: 256, frameHeight: 256 }); this.load.image('rain_catcher', 'DEMO_FAZA1/Structures/rain_catcher.png'); } create() { // --- WORLD CONFIGURATION --- const TILE_SIZE = 128; // Standard grid size const MAP_WIDTH_TILES = 250; const MAP_HEIGHT_TILES = 250; const WORLD_W = MAP_WIDTH_TILES * TILE_SIZE; // 32000 px const WORLD_H = MAP_HEIGHT_TILES * TILE_SIZE; // 32000 px this.physics.world.setBounds(0, 0, WORLD_W, WORLD_H); this.cameras.main.setBounds(0, 0, WORLD_W, WORLD_H); this.cameras.main.setBackgroundColor('#3a5f0b'); // Grass Green Background // --- ZOOM CONTROL --- this.cameras.main.setZoom(1.5); // Default start zoom this.input.on('wheel', (pointer, gameObjects, deltaX, deltaY, deltaZ) => { // MACBOOK OPTIMIZATION: Prevent Zoom when hovering over Editor Sidebar if (this.editorEnabled) { const SIDEBAR_W = 320; const PALETTE_X = this.scale.width - SIDEBAR_W; if (pointer.x > PALETTE_X) { return; // Stop processing zoom if over sidebar } } // Zoom In/Out based on wheel delta // deltaY > 0 means scroll down (zoom out), deltaY < 0 means scroll up (zoom in) const zoomFactor = -0.001; const newZoom = this.cameras.main.zoom + deltaY * zoomFactor; // Clamp zoom to reasonable limits (e.g., 0.5x to 3x) this.cameras.main.setZoom(Phaser.Math.Clamp(newZoom, 0.5, 3.0)); }); // --- 1. PODLAGA (The Foundation) --- // Preprosta rešitev: Rjava barva ozadja + tekstura čez // 1. Nastavimo barvo ozadja na rjavo (barva zemlje) this.cameras.main.setBackgroundColor('#4e342e'); // Dark Brown const BG_W = this.scale.width * 2.5; const BG_H = this.scale.height * 2.5; // 2. Uporabimo originalno sliko za tileSprite (Clean Slate) this.ground = this.add.tileSprite(this.scale.width / 2, this.scale.height / 2, BG_W, BG_H, 'ground_base'); this.ground.setScrollFactor(0); // Sticks to camera this.ground.setDepth(-100); // Note: We need to update tilePosition in update() loop to match camera scroll! // --- 2. RIVER SYSTEM (Infinite Scrolling - Horizontal) --- const riverHeight = 200; // Fixed height per user request const riverY = WORLD_H / 2 + 300; // Horizontal River using tileSprite // 1. ROTATION FIX: // The texture 'river_tile_seamless' is vertical (banks on Left/Right). // We want horizontal flow (banks on Top/Bottom). // So we rotate the sprite by 90 degrees. // When rotated 90 degrees: // - The sprite's "height" (local Y) becomes the screen width. // - The sprite's "width" (local X) becomes the screen height (river thickness). // So we initialize with swapped dimensions: // Width = riverHeight (which becomes vertical thickness after rotation) // Height = WORLD_W (which becomes horizontal length after rotation) this.river = this.add.tileSprite(WORLD_W / 2, riverY, riverHeight, WORLD_W, 'river_tile_seamless'); this.river.setAngle(90); // Rotate to horizontal this.river.setDepth(-50); // Above ground, below player // Physics for River (Obstacle) this.physics.add.existing(this.river, true); // Static body // 2. PHYSICS FIX for Rotated Sprite: // Arcade Physics bodies are AABB (Axis Aligned) and do NOT rotate with the sprite. // We must manually set the size to match the VISUAL shape on screen (Horizontal strip). this.river.body.setSize(WORLD_W, riverHeight * 0.8); // Offset is relative to the Top-Left of the UNROTATED sprite (which is confusing). // Or simpler: Center the body on the sprite. this.river.body.center.set(this.river.x, this.river.y); // However, static bodies are tricky with offsets after creation. // Best approach: Resize body, then re-center it. // But since we use existing(), it might be offset. // Let's rely on manual offset if needed, or use a separate Zone if it fails. // Trying standard offset correction: // Visual Width = WORLD_W, Visual Height = riverHeight. // Unrotated Width = riverHeight, Unrotated Height = WORLD_W. // This mismatch often causes physics debug to look wrong. // Alternative: Use an invisible Zone for physics. // Let's use an invisible rectangle for physics to be 100% safe and simple. this.river.body.enable = false; // Disable sprite body this.riverCollider = this.add.rectangle(WORLD_W / 2, riverY, WORLD_W, riverHeight * 0.8, 0x000000, 0); this.physics.add.existing(this.riverCollider, true); // --- RIVER EDGES INTEGRATION (Soft Blend) --- // Create gradients to blend river edges with ground const edgeHeight = 32; // Narrower blend for tighter fit const riverGraphics = this.add.graphics(); // Top Edge: Solid Brown -> Transparent (Downwards) // 0x4e342e is the background brown riverGraphics.fillGradientStyle(0x4e342e, 0x4e342e, 0x4e342e, 0x4e342e, 1, 1, 0, 0); riverGraphics.fillRect(0, riverY - (riverHeight/2) - (edgeHeight/2) + 10, WORLD_W, edgeHeight); // +10 nudge in // Bottom Edge: Transparent -> Solid Brown (Downwards) riverGraphics.fillGradientStyle(0x4e342e, 0x4e342e, 0x4e342e, 0x4e342e, 0, 0, 1, 1); riverGraphics.fillRect(0, riverY + (riverHeight/2) - (edgeHeight/2) - 10, WORLD_W, edgeHeight); // -10 nudge in riverGraphics.setDepth(-49); // Above river // --- 2.1 Prejšnji Stream System (Removed) --- /* // Center the whole construction const startX = WORLD_W / 2; const startY = WORLD_H / 2 - 300; // Start higher up to leave room for extensions // Main Head (Pipe + Splash) // Showing V7 Asset (Aggressive Clean + Transparent Green) this.stream = this.physics.add.staticImage(startX, startY, 'stream_final_v7'); this.stream.setOrigin(0.5, 0.5); this.stream.setDepth(-50); this.stream.setInteractive({ draggable: true }); // Enable Dragging // Physics Body for Main this.stream.body.setSize(this.stream.width * 0.8, this.stream.height * 0.2); this.stream.body.setOffset(this.stream.width * 0.1, this.stream.height * 0.4); // Extensions removed for reset // for (let i = 1; i <= 3; i++) { ... } // Collider added later after Kai creation // --- 2.1 STREAM BURYING (Dirt Patches) --- // "Zakopaj" potok v zemljo (masking edges) const dirtOffsets = [ { x: -350, y: 150 }, // Left side { x: 380, y: 180 }, // Right side (The "other" side?) { x: 0, y: 300 } // Bottom center ]; dirtOffsets.forEach((off, i) => { let dirt = this.add.image(startX + off.x, startY + off.y, 'ground_dirt_patch'); dirt.setScale(0.8 + Math.random() * 0.4); // Random size dirt.setAngle(Math.random() * 360); // Random rotation dirt.setDepth(-45); // Above stream (-50), below Kai dirt.setTint(0xdddddd); // Slightly darker to match mud dirt.setInteractive({ draggable: true }); // Allow user to adjust! }); */ // --- 3. FOLIAGE (Trava) --- this.grassGroup = this.physics.add.group({ immovable: true, allowGravity: false }); // INVENTAR this.inventory = { grass: 0 }; this.inventoryText = this.add.text(20, 20, 'Trava: 0', { fontSize: '32px', fill: '#ffffff', stroke: '#000000', strokeThickness: 4 }).setScrollFactor(0).setDepth(1000); // UI always on top // --- INTRO SEQUENCE STATE --- this.introStarted = false; const GRASS_COUNT = 0; // TEMPORARILY DISABLED PER USER REQUEST const SPREAD = 4000; // 4000px radius okoli centra // Parametri reke za preverjanje (da ne sadimo trave v vodo) // Reka je na riverY, visoka je riverHeight // riverY je sredina reke const riverSafeZone = riverHeight / 2 + 50; // Polovica višine + malo rezerve for (let i = 0; i < GRASS_COUNT; i++) { let x = (WORLD_W / 2) + (Math.random() * SPREAD * 2 - SPREAD); let y = (WORLD_H / 2) + (Math.random() * SPREAD * 2 - SPREAD); // PREVERJANJE: Če je trava v reki, preskoči if (Math.abs(y - riverY) < riverSafeZone) { continue; } // Randomizacija - samo divja trava let key = Math.random() > 0.5 ? 'grass_wild' : 'grass_wild_v2'; // Ustvari travo in jo dodaj v grupo let grass = this.grassGroup.create(x, y, key); // POMEMBNO: Origin spodaj na sredini, da raste iz tal! grass.setOrigin(0.5, 1.0); // Shranimo targetScale v objekt, da ga uporabimo v tweenu grass.targetScale = 0.5 + Math.random() * 0.5; // Začetno stanje: skrita grass.setScale(0); grass.setAngle(Math.random() * 20 - 10); grass.setAlpha(0.8 + Math.random() * 0.2); grass.setDepth(y); // Y-sort // Physics body (circle for better feel) if (grass.body) { grass.body.setCircle(grass.width / 4); grass.body.setOffset(grass.width / 4, grass.height / 2); // Adjusted for bottom origin } } // --- INTRO LISTENER --- // Klik na podlago sproži rast trave in čiščenje amnezije this.input.on('pointerdown', () => { if (!this.introStarted) { this.introStarted = true; this.startIntroSequence(); } }); // --- 4. ITEMS & OBSTACLES --- // REMOVED PER USER REQUEST /* // Trnje (Thorns) - Draggable this.trnje = this.add.image(startX - 200, startY + 100, 'trnje'); this.trnje.setScale(0.5); // Adjust scale if needed this.trnje.setInteractive({ draggable: true }); // Trigger Amnesia Clear on interaction this.trnje.on('pointerdown', () => { this.clearAmnesia(); }); // --- NEW: RAIN CATCHER --- this.rainCatcher = this.physics.add.image(startX + 150, startY + 50, 'rain_catcher'); this.rainCatcher.setScale(0.8); this.rainCatcher.setInteractive({ draggable: true }); this.rainCatcher.setDepth(startY + 50); // Y-sort this.rainCatcher.body.setImmovable(true); // Collider added later with Kai */ // General Drag Event // General Drag Event this.input.on('drag', function (pointer, gameObject, dragX, dragY) { gameObject.x = dragX; gameObject.y = dragY; }); // --- EDITOR MODE SYSTEM --- this.editorEnabled = true; // Enabled by default per user request this.selectedTile = 'path_tile_0'; this.editorGroup = this.add.group(); // Saved tiles // Initialize Default State this.selectedTile = 'path_tile_0'; // this.editorEnabled = false; // Duplicate init removed // UI Palette (Hidden by default) // FIX: Use viewport dimensions for UI const VIEW_W = this.scale.width; const VIEW_H = this.scale.height; // --- EDITOR UI SETUP (Clean Sidebar) --- const SIDEBAR_W = 320; const PALETTE_X = VIEW_W - SIDEBAR_W; // UI Container Group (for toggling visibility) this.editorUI = this.add.group(); // 1. Sidebar Background (Clean Glass Look) let sidebarBg = this.add.rectangle(PALETTE_X + SIDEBAR_W / 2, VIEW_H / 2, SIDEBAR_W, VIEW_H, 0x222222, 0.95) .setScrollFactor(0).setDepth(2000).setStrokeStyle(2, 0x444444); this.editorUI.add(sidebarBg); // Title let sidebarTitle = this.add.text(PALETTE_X + 20, 20, "TILES PALETTE", { fontSize: '24px', fontFamily: 'Arial', color: '#ffffff', fontStyle: 'bold' }).setScrollFactor(0).setDepth(2002); this.editorUI.add(sidebarTitle); // 2. Layer Switcher (Tabs) this.currentLayer = 'ground'; const layerBtns = []; const tabsY = 60; const createLayerBtn = (label, mode, index) => { let x = PALETTE_X + 20 + (index * 90); let btnBg = this.add.rectangle(x + 40, tabsY + 15, 80, 30, 0x333333).setScrollFactor(0).setDepth(2002).setInteractive({ useHandCursor: true }); let txt = this.add.text(x + 10, tabsY + 5, label, { fontSize: '14px', color: '#888', fontStyle: 'bold' }) .setScrollFactor(0).setDepth(2003); // Hit area on bg btnBg.on('pointerdown', () => { this.currentLayer = mode; layerBtns.forEach(b => { b.txt.setColor('#888'); b.bg.setFillStyle(0x333333); }); txt.setColor('#ffffff'); btnBg.setFillStyle(0x0077ff); }); this.editorUI.add(btnBg); this.editorUI.add(txt); return { txt, bg: btnBg }; }; layerBtns.push(createLayerBtn("Ground", 'ground', 0)); layerBtns.push(createLayerBtn("Deco", 'deco', 1)); layerBtns.push(createLayerBtn("Build", 'building', 2)); // Select first default layerBtns[0].txt.setColor('#ffffff'); layerBtns[0].bg.setFillStyle(0x0077ff); // 3. Palette Content (Scrollable) const contentY = 120; // Below tabs const itemContainer = this.add.container(PALETTE_X, contentY).setScrollFactor(0).setDepth(2001); // Mask for scrolling const maskShape = this.make.graphics(); maskShape.fillStyle(0xffffff); maskShape.fillRect(PALETTE_X, contentY, SIDEBAR_W, VIEW_H - contentY); const mask = maskShape.createGeometryMask(); itemContainer.setMask(mask); // Prepare Palette Items const paletteItems = []; for (let i = 0; i < 16; i++) paletteItems.push(`path_tile_${i}`); for (let i = 0; i < 16; i++) paletteItems.push(`water_tile_${i}`); for (let i = 0; i <= 2; i++) paletteItems.push(`fence_sign_${i}`); paletteItems.push('sign_danger'); for (let i = 0; i <= 5; i++) paletteItems.push(`tree_adult_${i}`); for (let i = 0; i <= 8; i++) paletteItems.push(`dead_nature_${i}`); ['bush_hiding_spot', 'drevo_faza_1', 'drevo_faza_2', 'drevo_srednje', 'drevo_veliko', 'grass_cluster_dense', 'grass_cluster_flowery', 'trava_sop'].forEach(k => paletteItems.push(k)); paletteItems.push('sotor', 'campfire', 'eraser_icon'); // Grid Layout let col = 0, row = 0; const CELL_SZ = 90; // Selector Highlight (Clean Blue Border) let selector = this.add.rectangle(0, 0, 80, 80).setStrokeStyle(4, 0x00aaff).setVisible(false); itemContainer.add(selector); paletteItems.forEach((key) => { let ix = 50 + (col * CELL_SZ); let iy = 50 + (row * CELL_SZ); let icon = this.add.image(ix, iy, key).setScale(0.3).setInteractive({ useHandCursor: true }); itemContainer.add(icon); icon.on('pointerdown', () => { this.selectedTile = key; selector.setPosition(ix, iy).setVisible(true); // Update Ghost if (key === 'eraser_icon') { this.ghostSprite.setVisible(false); } else { this.ghostSprite.setTexture(key); this.ghostSprite.setScale(key.includes('path') || key.includes('water') ? 0.5 : 1.0); // Simple scaling logic } }); col++; if (col >= 3) { col = 0; row++; } }); // Scroll Logic let scrollY = 0; const MAX_SCROLL = Math.max(0, (row * CELL_SZ) + 150 - (VIEW_H - contentY)); this.input.on('wheel', (ptr, gameObjs, dx, dy, dz) => { if (this.editorEnabled && ptr.x > PALETTE_X) { scrollY -= dy; if (scrollY > 0) scrollY = 0; if (scrollY < -MAX_SCROLL) scrollY = -MAX_SCROLL; itemContainer.y = contentY + scrollY; } }); // 4. Ghost Cursor this.ghostSprite = this.add.image(0, 0, this.selectedTile) .setAlpha(0.6).setDepth(3000).setVisible(false); // Topmost // Toggle Visibility Helpers const toggleEditor = (state) => { this.editorEnabled = state; this.editorUI.setVisible(state); itemContainer.setVisible(state); this.ghostSprite.setVisible(state && this.selectedTile !== 'eraser_icon'); console.log("Editor:", state); }; toggleEditor(true); // Start visible per user request // Toggle Key this.input.keyboard.on('keydown-E', () => { toggleEditor(!this.editorEnabled); }); this.input.on('pointermove', (pointer) => { if (!this.editorEnabled) return; // Hide Ghost if over UI if (pointer.x > PALETTE_X) { this.ghostSprite.setVisible(false); return; } else if (this.selectedTile !== 'eraser_icon') { this.ghostSprite.setVisible(true); } // Snap calculation const SNAP = 128; const sx = Math.floor(pointer.worldX / SNAP) * SNAP + (SNAP / 2); const sy = Math.floor(pointer.worldY / SNAP) * SNAP + (SNAP / 2); this.ghostSprite.setPosition(sx, sy); }); // Painting Logic this.input.on('pointerdown', (pointer) => { if (!this.editorEnabled) return; // Ignore UI clicks if (pointer.x > PALETTE_X) return; // ERASER if (this.selectedTile === 'eraser_icon') return; // Handled by object click // Snap const SNAP = 128; const sx = Math.floor(pointer.worldX / SNAP) * SNAP + (SNAP / 2); const sy = Math.floor(pointer.worldY / SNAP) * SNAP + (SNAP / 2); // Remove existing tile at this spot to avoid stacking let existing = this.editorGroup.getChildren().find(c => Math.abs(c.x - sx) < 10 && Math.abs(c.y - sy) < 10); if (existing) existing.destroy(); let placedStub = this.add.image(sx, sy, this.selectedTile); placedStub.setInteractive(); placedStub.on('pointerdown', () => { if (this.editorEnabled && this.selectedTile === 'eraser_icon') { const isWater = placedStub.texture.key.startsWith('water_tile_'); placedStub.destroy(); // If we deleted water, update neighbors if (isWater) { const neighbors = [ { dx: 0, dy: -SNAP }, { dx: SNAP, dy: 0 }, { dx: 0, dy: SNAP }, { dx: -SNAP, dy: 0 } ]; neighbors.forEach(n => this.updateAutotile(sx + n.dx, sy + n.dy)); } } }); // Layer Logic if (this.currentLayer === 'ground') { placedStub.setDepth(-40); placedStub.setScale(0.5); // Grid tiles } else { placedStub.setDepth(sy); // Y-sort } this.editorGroup.add(placedStub); // AUTO-TILE UPDATE if (this.selectedTile.startsWith('water_tile_')) { this.updateAutotile(sx, sy); } }); // --- PREVIOUSLY GENERATED PROPS (Draggable Example) --- // Commented out per request "samo blatno potko" /* const propAssets = ['tree_adult_0', 'tree_adult_1', 'dead_nature_0']; for (let i = 0; i < propAssets.length; i++) { let item = this.add.image(startX + 200 + (i * 100), startY + 200, propAssets[i]); item.setInteractive({ draggable: true }); } */ // --- RECONSTRUCT PATH (4x4 GRID) --- // REMOVED PER USER REQUEST "samo blatno potko" /* // Image was 1024x1024, sliced into 256x256 (4 cols, 4 rows) // Indices 0..15 let pIndex = 0; const GRID_SZ = 256; for (let r = 0; r < 4; r++) { for (let c = 0; c < 4; c++) { // Determine Tile Key let key = `path_tile_${pIndex}`; // Place it // Center offset: -1.5 * size to center the 4x4 block let px = startX + (c * GRID_SZ) + 200; let py = startY + (r * GRID_SZ) + 200; let tile = this.add.image(px, py, key); tile.setDepth(-40); // Ground level // Optional: make draggable? User said "naredi", maybe fixed? // tile.setInteractive({ draggable: true }); pIndex++; } } */ // --- 5. CHAR (Kai) --- this.kai = this.physics.add.sprite(WORLD_W / 2, WORLD_H / 2, 'kai'); // Povečava na polno velikost (256px) this.kai.setScale(1); this.kai.setCollideWorldBounds(true); this.kai.setOrigin(0.5, 0.9); // Adjust Physics Body for larger size // Width ~40, Height ~30 (relative to scaled sprite) this.kai.body.setSize(50, 40); this.kai.body.setOffset(256 / 2 - 25, 256 - 40); // Pivot offset based on 256 frame // Collider Stream <-> Kai // this.physics.add.collider(this.kai, this.stream); // --- NEW: GRONK (NPC) --- // REMOVED PER USER REQUEST /* this.gronk = this.physics.add.sprite(startX - 150, startY - 100, 'gronk'); this.gronk.setScale(0.8); // Gronk is big! this.gronk.setDepth(startY - 100); this.gronk.setImmovable(true); this.gronk.body.setSize(80, 60); this.gronk.body.setOffset(88, 190); // Adjusted for 256x256 frame // Gronk Animations this.anims.create({ key: 'gronk-idle', frames: this.anims.generateFrameNumbers('gronk', { start: 0, end: 3 }), // Using Down walk as idle for now frameRate: 4, repeat: -1 }); this.gronk.play('gronk-idle'); // Interaction (Say Hello) this.gronk.setInteractive(); this.gronk.on('pointerdown', () => { console.log("Gronk: 'Ej stari, kje si hodil? Si pozabil, da imava vajo s bendom?'"); // Future: Show Dialog Box }); */ // Colliders // this.physics.add.collider(this.kai, this.rainCatcher); // this.physics.add.collider(this.kai, this.gronk); // --- ANIMATIONS --- // 0-3: Down, 4-7: Left, 8-11: Right, 12-15: Up this.anims.create({ key: 'walk-down', frames: this.anims.generateFrameNumbers('kai', { start: 0, end: 3 }), frameRate: 8, repeat: -1 }); this.anims.create({ key: 'walk-left', frames: this.anims.generateFrameNumbers('kai', { start: 4, end: 7 }), frameRate: 8, repeat: -1 }); this.anims.create({ key: 'walk-right', frames: this.anims.generateFrameNumbers('kai', { start: 8, end: 11 }), frameRate: 8, repeat: -1 }); this.anims.create({ key: 'walk-up', frames: this.anims.generateFrameNumbers('kai', { start: 12, end: 15 }), frameRate: 8, repeat: -1 }); // Idle this.kai.play('walk-down'); this.kai.stop(); // Camera setup logic this.cameras.main.startFollow(this.kai, true, 0.1, 0.1); this.cursors = this.input.keyboard.createCursorKeys(); // Add WASD keys this.keys = this.input.keyboard.addKeys({ up: Phaser.Input.Keyboard.KeyCodes.W, down: Phaser.Input.Keyboard.KeyCodes.S, left: Phaser.Input.Keyboard.KeyCodes.A, right: Phaser.Input.Keyboard.KeyCodes.D }); // Collider added later after Kai creation // Collider added later after Kai creation // --- 3. FOLIAGE (Trava - Šopi) --- // Removed as requested // --- 4. ITEMS (Seno) --- // Removed as requested // 3. COLLIDERS if (this.stream) this.physics.add.collider(this.kai, this.stream); // Collider with riverCollider (invisible zone) instead of rotated sprite if (this.riverCollider) this.physics.add.collider(this.kai, this.riverCollider); // this.physics.add.collider(this.kai, this.obstaclesGroup); // --- ANIMATIONS --- // 0-3: Down, 4-7: Left, 8-11: Right, 12-15: Up // This is a duplicate animation creation block, removing it. // this.anims.create({ // key: 'walk-down', // frames: this.anims.generateFrameNumbers('kai', { start: 0, end: 3 }), // frameRate: 8, // repeat: -1 // }); // this.anims.create({ // key: 'walk-left', // frames: this.anims.generateFrameNumbers('kai', { start: 4, end: 7 }), // frameRate: 8, // repeat: -1 // }); // this.anims.create({ // key: 'walk-right', // frames: this.anims.generateFrameNumbers('kai', { start: 8, end: 11 }), // frameRate: 8, // repeat: -1 // }); // this.anims.create({ // key: 'walk-up', // frames: this.anims.generateFrameNumbers('kai', { start: 12, end: 15 }), // frameRate: 8, // repeat: -1 // }); // Launch UI Scene if (!this.scene.get('UIScene').scene.settings.active) { this.scene.launch('UIScene'); } // --- AMNESIA INIT --- // 1. PostFX Blur (Keep it for extra effect) this.amnesiaBlur = this.cameras.main.postFX.addBlur(0, 2, 2, 1); // 2. Overlay Texture (Megla Pozabe) /* this.amnesiaOverlay = this.add.image(WORLD_W / 2, WORLD_H / 2, 'amnesia_fog'); this.amnesiaOverlay.setScrollFactor(0); // Stuck to camera this.amnesiaOverlay.setDepth(6000); // Topmost this.amnesiaOverlay.setAlpha(0.8); this.amnesiaOverlay.setScale(Math.max(VIEW_W / this.amnesiaOverlay.width, VIEW_H / this.amnesiaOverlay.height)); // Cover screen */ // --- TOXIC FOG (Visuals) --- // Layer 1: Pinkish (The "Toxic" Part) /* this.fog1 = this.add.tileSprite(0, 0, WORLD_W, WORLD_H, 'toxic_fog'); this.fog1.setOrigin(0, 0); this.fog1.setAlpha(0.3); this.fog1.setDepth(5000); this.fog1.setBlendMode(Phaser.BlendModes.SCREEN); // Screen for glowing effect this.fog1.setTint(0xff00cc); // PINK */ // Layer 2: Greenish (The "Radiation" Part) /* this.fog2 = this.add.tileSprite(0, 0, WORLD_W, WORLD_H, 'toxic_fog'); this.fog2.setOrigin(0, 0); this.fog2.setAlpha(0.2); this.fog2.setDepth(5001); this.fog2.setScale(1.5); this.fog2.setBlendMode(Phaser.BlendModes.SCREEN); this.fog2.setTint(0x00ff00); // GREEN */ } // --- INTRO SEQUENCE --- startIntroSequence() { console.log("Starting Intro Sequence: Grass Growth + Amnesia Clear"); // 1. Grass Growth Animation this.grassGroup.getChildren().forEach((grass, index) => { this.tweens.add({ targets: grass, scaleX: grass.targetScale, // Use stored target scale scaleY: grass.targetScale, duration: 800 + Math.random() * 800, delay: Math.random() * 1500, // Staggered start ease: 'Back.out', }); }); // 2. Clear Amnesia (Blur Fade) this.clearAmnesia(); } // --- AMNESIA SYSTEM --- clearAmnesia() { if (this.amnesiaBlur) { this.tweens.add({ targets: [this.amnesiaBlur, this.amnesiaOverlay], // Fade both strength: 0, // For blur alpha: 0, // For overlay duration: 2000, onComplete: () => { this.cameras.main.postFX.remove(this.amnesiaBlur); this.amnesiaBlur = null; if (this.amnesiaOverlay) this.amnesiaOverlay.destroy(); } }); } } // --- AUTO-TILING SYSTEM --- getWaterBitmask(x, y) { // Check 4 neighbors: Top (1), Right (2), Bottom (4), Left (8) // Grid size is 128 (SNAP) const SNAP = 128; let mask = 0; // Helper to check if a tile at (tx, ty) is water const isWater = (tx, ty) => { // Check existing tiles in editorGroup // This is O(N), for optimization use a Map in production let found = this.editorGroup.getChildren().find(c => Math.abs(c.x - tx) < 10 && Math.abs(c.y - ty) < 10 && c.texture.key.startsWith('water_tile_') ); return !!found; }; if (isWater(x, y - SNAP)) mask += 1; // Top if (isWater(x + SNAP, y)) mask += 2; // Right if (isWater(x, y + SNAP)) mask += 4; // Bottom if (isWater(x - SNAP, y)) mask += 8; // Left return mask; } updateAutotile(x, y) { const SNAP = 128; // 1. Find the tile at x,y let tile = this.editorGroup.getChildren().find(c => Math.abs(c.x - x) < 10 && Math.abs(c.y - y) < 10 ); if (!tile || !tile.texture.key.startsWith('water_tile_')) return; // 2. Calculate new mask let mask = this.getWaterBitmask(x, y); tile.setTexture(`water_tile_${mask}`); // 3. Update neighbors const neighbors = [ { dx: 0, dy: -SNAP }, // Top { dx: SNAP, dy: 0 }, // Right { dx: 0, dy: SNAP }, // Bottom { dx: -SNAP, dy: 0 } // Left ]; neighbors.forEach(n => { let nx = x + n.dx; let ny = y + n.dy; let neighbor = this.editorGroup.getChildren().find(c => Math.abs(c.x - nx) < 10 && Math.abs(c.y - ny) < 10 ); if (neighbor && neighbor.texture.key.startsWith('water_tile_')) { let nMask = this.getWaterBitmask(nx, ny); neighbor.setTexture(`water_tile_${nMask}`); } }); } update(time, delta) { // --- PARALLAX & SCROLLING --- // 1. Ground Infinite Scroll if (this.ground) { this.ground.tilePositionX = this.cameras.main.scrollX; this.ground.tilePositionY = this.cameras.main.scrollY; } // 2. River Flow Animation (Horizontal) if (this.river) { this.river.tilePositionX += 2.0; // Flow to the left (texture moves right) } // --- PLAYER MOVEMENT --- const speed = 250; this.kai.setVelocity(0); let moving = false; // Input helpers const left = this.cursors.left.isDown || this.keys.left.isDown; const right = this.cursors.right.isDown || this.keys.right.isDown; const up = this.cursors.up.isDown || this.keys.up.isDown; const down = this.cursors.down.isDown || this.keys.down.isDown; const space = this.cursors.space.isDown; // --- GRASS PLUCKING MECHANIC (Trganje trave) --- if (space) { this.physics.overlap(this.kai, this.grassGroup, (player, grass) => { // 1. Uniči travo grass.destroy(); // 2. Dodaj v inventar this.inventory.grass++; this.inventoryText.setText('Trava: ' + this.inventory.grass); }); } if (left) { this.kai.setVelocityX(-speed); this.kai.play('walk-left', true); moving = true; } else if (right) { this.kai.setVelocityX(speed); this.kai.play('walk-right', true); moving = true; } if (up) { this.kai.setVelocityY(-speed); if (!left && !right) { this.kai.play('walk-up', true); } moving = true; } else if (down) { this.kai.setVelocityY(speed); if (!left && !right) { this.kai.play('walk-down', true); } moving = true; } if (this.kai.body.velocity.length() > 0) { this.kai.body.velocity.normalize().scale(speed); } else { this.kai.stop(); // Optional: reset to idle frame? } // --- Z-SORTING SYSTEM --- // Player this.kai.setDepth(this.kai.y); } }