927 lines
36 KiB
JavaScript
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);
|
|
}
|
|
}
|