Files
novafarma/nova farma TRAE/src/scenes/GrassScene_Clean.js

927 lines
36 KiB
JavaScript

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