VISUAL UPDATE: Demo Phase 1 setup (GrassScene with wind, fog, day/night). Pending path fix for assets.
11
DEV_LOG.md
@@ -41,3 +41,14 @@ Posodobili smo dokument `docs/GAME_DESIGN_NOTES.md` z naslednjimi koncepti:
|
||||
* 10-Fazna struktura izdajanja.
|
||||
* **Licence Preverjene:** Najdene so v (Apache, MIT, BSD), kar je pričakovano za Python knjižnice.
|
||||
* **Koda in Dokumentacija:** Vse posodobljeno in pripravljeno na vnos referenc.
|
||||
|
||||
### 5. Priprava Assetov za Fazo 1 (25.01.2026 13:36 CET)
|
||||
* **Referenčne slike:** Vse prekopirane v (132 datotek).
|
||||
* **Obdelava:**
|
||||
* Konvertirano v PNG.
|
||||
* *Intelligent Resize* (Gronk 512px, Kai 256px, Insekti 96px).
|
||||
* *Sharpening* (LANCZOS + Unsharp Mask).
|
||||
* Ohranjena originalna ozadja.
|
||||
* **Struktura:**
|
||||
* Ustvarjena mapa .
|
||||
* Izbrisane stare neurejene podmape.
|
||||
|
||||
BIN
assets/.DS_Store
vendored
BIN
assets/DEMO_FAZA1/drevo_faza_1.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
assets/DEMO_FAZA1/drevo_faza_2.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
assets/DEMO_FAZA1/drevo_majhno.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
assets/DEMO_FAZA1/drevo_srednje.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
assets/DEMO_FAZA1/drevo_veliko.png
Normal file
|
After Width: | Height: | Size: 146 KiB |
BIN
assets/DEMO_FAZA1/megla_ozadje.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
assets/DEMO_FAZA1/suho_drevo.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
assets/DEMO_FAZA1/tla_trava_tekstura.png
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
assets/DEMO_FAZA1/trava_sop.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
assets/DEMO_FAZA1/visoka_trava.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
@@ -1,267 +1,144 @@
|
||||
export default class GrassScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({ key: 'GrassScene' });
|
||||
this.currentZoom = 1.0;
|
||||
this.targetZoom = 1.0;
|
||||
this.baseTime = 12; // Start at 12:00 (Noon)
|
||||
this.timeSpeed = 0.5; // How fast time passes
|
||||
}
|
||||
|
||||
preload() {
|
||||
console.log("🌲 Loading Interactive Forest...");
|
||||
console.log("🌿 Loading Environment assets...");
|
||||
|
||||
// TERRAIN
|
||||
this.load.image('grass', 'assets/slike/environment/trava .png');
|
||||
this.load.image('dirt_path', 'assets/slike/nova mapa faza 0-1/Environment/podlaga/dirt_path.png');
|
||||
this.load.image('tall_grass', 'assets/slike/environment/tall_grass.png');
|
||||
// Load assets from DEMO_FAZA1
|
||||
this.load.image('ground_dead', 'assets/DEMO_FAZA1/tla_trava_tekstura.png');
|
||||
this.load.image('grass_tall', 'assets/DEMO_FAZA1/visoka_trava.png');
|
||||
this.load.image('grass_tuft', 'assets/DEMO_FAZA1/trava_sop.png');
|
||||
|
||||
// CAMP - Cleaned PNG files (256px)
|
||||
this.load.image('tent', 'assets/slike/nova mapa faza 0-1/Environment/stavbe/MOJE_SLIKE_KONCNA_ostalo_tent_basic_style32.png');
|
||||
this.load.image('campfire', 'assets/slike/nova mapa faza 0-1/Environment/props/MOJE_SLIKE_KONCNA_ostalo_campfire_frame1.png');
|
||||
// Trees (Growth Stages)
|
||||
this.load.image('tree_small', 'assets/DEMO_FAZA1/drevo_majhno.png');
|
||||
this.load.image('tree_medium', 'assets/DEMO_FAZA1/drevo_srednje.png');
|
||||
this.load.image('tree_large', 'assets/DEMO_FAZA1/drevo_veliko.png');
|
||||
this.load.image('tree_dead', 'assets/DEMO_FAZA1/suho_drevo.png');
|
||||
|
||||
// NATURE - Cleaned PNG trees (512px) from narava subfolder
|
||||
this.load.image('dead_tree', 'assets/slike/nova mapa faza 0-1/Environment/narava/unnamed.png');
|
||||
this.load.image('bush', 'assets/slike/nova mapa faza 0-1/Environment/narava/MOJE_SLIKE_KONCNA_okolje_bush_green.png');
|
||||
this.load.image('rock_small', 'assets/slike/nova mapa faza 0-1/Environment/narava/MOJE_SLIKE_KONCNA_okolje_biomi_gore_rock_small_style32.png');
|
||||
this.load.image('rock_medium', 'assets/slike/nova mapa faza 0-1/Environment/narava/MOJE_SLIKE_KONCNA_okolje_biomi_gore_rock_medium_style32.png');
|
||||
// Weather
|
||||
this.load.image('fog', 'assets/DEMO_FAZA1/megla_ozadje.png');
|
||||
|
||||
// PROPS - Cleaned PNG (256px)
|
||||
this.load.image('wood_log', 'assets/slike/nova mapa faza 0-1/Environment/props/wood_log.png');
|
||||
this.load.image('broken_board', 'assets/slike/nova mapa faza 0-1/UI/icon_longboard.png');
|
||||
|
||||
// CHARACTER
|
||||
this.load.image('kai', 'assets/slike/characters/kai_idle_west_2_styleA_1024x1024.png');
|
||||
// GLSL Wind Shader (Inline for simplicity)
|
||||
this.windPipeline = null; // Will create in create()
|
||||
}
|
||||
|
||||
create() {
|
||||
console.log("🏕️ Building Interactive Forest...");
|
||||
const { width, height } = this.cameras.main;
|
||||
const { width, height } = this.scale;
|
||||
|
||||
// Store all objects for DOF effect
|
||||
this.backgroundObjects = [];
|
||||
this.foregroundObjects = [];
|
||||
// 1. DEAD GROUND (Tiling background)
|
||||
// Uses the 'dead' texture as the base canvas
|
||||
this.ground = this.add.tileSprite(0, 0, width, height, 'ground_dead').setOrigin(0, 0);
|
||||
|
||||
// --- LAYER 0: GRASS BACKGROUND ---
|
||||
const grass = this.add.tileSprite(0, 0, width, height, 'grass').setOrigin(0, 0);
|
||||
grass.setDepth(0);
|
||||
// 2. VEGETATION GROUPS
|
||||
this.trees = [];
|
||||
this.grasses = [];
|
||||
|
||||
// --- LAYER 1: DIRT PATH with FEATHERING ---
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const pathX = width / 2 + (i * 120);
|
||||
const pathY = height / 2 + 100 + (i * 40);
|
||||
const path = this.add.image(pathX, pathY, 'dirt_path');
|
||||
path.setScale(0.5);
|
||||
path.setDepth(1);
|
||||
path.setAlpha(0.7 + (i * 0.025));
|
||||
}
|
||||
// 3. GENERATE WORLD (Procedural placement)
|
||||
this.generateVegetation(width, height);
|
||||
|
||||
// --- LAYER 2: BACKGROUND TREES ---
|
||||
const bgTree1 = this.add.image(width / 2 + 450, height / 2 - 120, 'dead_tree');
|
||||
bgTree1.setScale(0.4);
|
||||
bgTree1.setDepth(2);
|
||||
bgTree1.setAlpha(0.5);
|
||||
bgTree1.setTint(0x666666);
|
||||
this.backgroundObjects.push(bgTree1);
|
||||
// 4. WEATHER LAYERS (Fog)
|
||||
this.fog1 = this.add.tileSprite(0, 0, width, height, 'fog').setOrigin(0, 0).setAlpha(0.2).setBlendMode(Phaser.BlendModes.ADD);
|
||||
this.fog2 = this.add.tileSprite(0, 0, width, height, 'fog').setOrigin(0, 0).setAlpha(0.15).setBlendMode(Phaser.BlendModes.ADD);
|
||||
|
||||
const bgTree2 = this.add.image(width / 2 + 650, height / 2 + 20, 'dead_tree');
|
||||
bgTree2.setScale(0.38);
|
||||
bgTree2.setDepth(2);
|
||||
bgTree2.setAlpha(0.45);
|
||||
bgTree2.setTint(0x666666);
|
||||
this.backgroundObjects.push(bgTree2);
|
||||
// 5. LIGHTING / DAY-NIGHT OVERLAY
|
||||
this.dayNightOverlay = this.add.rectangle(0, 0, width, height, 0x000000, 0).setOrigin(0, 0).setDepth(1000);
|
||||
|
||||
// --- LAYER 3: VIRUS GLOW ---
|
||||
const virusGlow1 = this.add.circle(width / 2 + 280, height / 2 + 50, 80, 0x00ff00, 0.25);
|
||||
virusGlow1.setDepth(3);
|
||||
virusGlow1.setBlendMode(Phaser.BlendModes.ADD);
|
||||
|
||||
const virusGlow2 = this.add.circle(width / 2 + 530, height / 2 + 200, 70, 0x00ff00, 0.2);
|
||||
virusGlow2.setDepth(3);
|
||||
virusGlow2.setBlendMode(Phaser.BlendModes.ADD);
|
||||
|
||||
const virusGlow3 = this.add.circle(width / 2 + 650, height / 2 + 50, 60, 0x00ff00, 0.18);
|
||||
virusGlow3.setDepth(3);
|
||||
virusGlow3.setBlendMode(Phaser.BlendModes.ADD);
|
||||
|
||||
this.tweens.add({
|
||||
targets: [virusGlow1, virusGlow2, virusGlow3],
|
||||
alpha: { from: 0.15, to: 0.35 },
|
||||
scaleX: { from: 0.85, to: 1.15 },
|
||||
scaleY: { from: 0.85, to: 1.15 },
|
||||
duration: 2500,
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
ease: 'Sine.easeInOut'
|
||||
});
|
||||
|
||||
// --- LAYER 4: CAMP ---
|
||||
const tent = this.add.image(width / 2 - 300, height / 2 - 50, 'tent');
|
||||
tent.setScale(0.4);
|
||||
tent.setDepth(10);
|
||||
|
||||
const campfire = this.add.image(width / 2 - 100, height / 2 + 30, 'campfire');
|
||||
campfire.setScale(0.3);
|
||||
campfire.setDepth(11);
|
||||
|
||||
// --- LAYER 5: PATH DECORATIONS ---
|
||||
const rock1 = this.add.image(width / 2 + 150, height / 2 + 120, 'rock_small');
|
||||
rock1.setScale(0.28);
|
||||
rock1.setDepth(8);
|
||||
|
||||
const rock2 = this.add.image(width / 2 + 380, height / 2 + 210, 'rock_medium');
|
||||
rock2.setScale(0.32);
|
||||
rock2.setDepth(8);
|
||||
|
||||
const rock3 = this.add.image(width / 2 + 520, height / 2 + 260, 'rock_small');
|
||||
rock3.setScale(0.25);
|
||||
rock3.setDepth(8);
|
||||
|
||||
const log1 = this.add.image(width / 2 + 240, height / 2 + 150, 'wood_log');
|
||||
log1.setScale(0.22);
|
||||
log1.setDepth(9);
|
||||
log1.setAngle(35);
|
||||
|
||||
const brokenBoard = this.add.image(width / 2 + 460, height / 2 + 235, 'broken_board');
|
||||
brokenBoard.setScale(0.08);
|
||||
brokenBoard.setDepth(9);
|
||||
brokenBoard.setAngle(-20);
|
||||
brokenBoard.setTint(0x555555);
|
||||
|
||||
// --- LAYER 6: MID-GROUND TREES ---
|
||||
const tree1 = this.add.image(width / 2 + 280, height / 2 + 20, 'dead_tree');
|
||||
tree1.setScale(0.55);
|
||||
tree1.setDepth(13);
|
||||
tree1.setAngle(-12);
|
||||
this.backgroundObjects.push(tree1);
|
||||
|
||||
const tree2 = this.add.image(width / 2 + 530, height / 2 + 170, 'dead_tree');
|
||||
tree2.setScale(0.58);
|
||||
tree2.setDepth(13);
|
||||
tree2.setAngle(10);
|
||||
this.backgroundObjects.push(tree2);
|
||||
|
||||
// Bushes
|
||||
const bush1 = this.add.image(width / 2 + 330, height / 2 + 75, 'bush');
|
||||
bush1.setScale(0.32);
|
||||
bush1.setDepth(12);
|
||||
|
||||
const bush2 = this.add.image(width / 2 + 460, height / 2 + 165, 'bush');
|
||||
bush2.setScale(0.35);
|
||||
bush2.setDepth(12);
|
||||
|
||||
const bush3 = this.add.image(width / 2 + 590, height / 2 + 240, 'bush');
|
||||
bush3.setScale(0.3);
|
||||
bush3.setDepth(12);
|
||||
|
||||
// --- LAYER 7: DYNAMIC GRASS TUFTS (wind effect) ---
|
||||
this.grassTufts = [];
|
||||
const tuftsPositions = [
|
||||
{ x: width / 2 + 180, y: height / 2 + 110, delay: 0, duration: 3500 },
|
||||
{ x: width / 2 + 350, y: height / 2 + 60, delay: 0.5, duration: 4000 },
|
||||
{ x: width / 2 + 270, y: height / 2 + 180, delay: 1.0, duration: 3200 },
|
||||
{ x: width / 2 + 490, y: height / 2 + 150, delay: 1.5, duration: 3800 },
|
||||
{ x: width / 2 + 400, y: height / 2 + 240, delay: 0.8, duration: 3600 },
|
||||
{ x: width / 2 + 560, y: height / 2 + 220, delay: 1.2, duration: 4200 },
|
||||
];
|
||||
|
||||
tuftsPositions.forEach(pos => {
|
||||
const tuft = this.add.image(pos.x, pos.y, 'tall_grass');
|
||||
tuft.setScale(0.12);
|
||||
tuft.setDepth(16);
|
||||
tuft.setOrigin(0.5, 1); // Bottom center
|
||||
|
||||
// Wind sway animation
|
||||
this.tweens.add({
|
||||
targets: tuft,
|
||||
angle: { from: -3, to: 3 },
|
||||
duration: pos.duration,
|
||||
delay: pos.delay * 1000,
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
ease: 'Sine.easeInOut'
|
||||
});
|
||||
|
||||
this.grassTufts.push(tuft);
|
||||
});
|
||||
|
||||
// --- LAYER 8: FOREGROUND TREE ---
|
||||
const fgTree1 = this.add.image(width / 2 + 200, height / 2 + 180, 'dead_tree');
|
||||
fgTree1.setScale(0.7);
|
||||
fgTree1.setDepth(25);
|
||||
fgTree1.setAngle(-5);
|
||||
fgTree1.setAlpha(0.95);
|
||||
this.foregroundObjects.push(fgTree1);
|
||||
|
||||
// --- LAYER 9: KAI (focus point) ---
|
||||
this.kai = this.add.image(width / 2 + 200, height / 2 + 140, 'kai');
|
||||
this.kai.setScale(0.16);
|
||||
this.kai.setDepth(20);
|
||||
|
||||
// --- CAMERA SETUP ---
|
||||
this.cameras.main.setBounds(0, 0, width * 2, height * 2);
|
||||
this.cameras.main.startFollow(this.kai, false, 0.1, 0.1);
|
||||
|
||||
// --- MOUSE WHEEL ZOOM ---
|
||||
this.input.on('wheel', (pointer, gameObjects, deltaX, deltaY, deltaZ) => {
|
||||
if (deltaY > 0) {
|
||||
// Zoom Out
|
||||
this.targetZoom = Math.max(0.5, this.targetZoom - 0.15);
|
||||
} else {
|
||||
// Zoom In
|
||||
this.targetZoom = Math.min(2.5, this.targetZoom + 0.15);
|
||||
}
|
||||
});
|
||||
|
||||
// --- UI ---
|
||||
const uiText = this.add.text(20, 20, "🎮 INTERACTIVE FOREST", {
|
||||
fontFamily: 'Courier New, monospace',
|
||||
fontSize: '20px',
|
||||
color: '#d4af37',
|
||||
backgroundColor: '#000000',
|
||||
padding: { x: 10, y: 5 },
|
||||
fixedToCamera: true
|
||||
}).setScrollFactor(0).setDepth(100);
|
||||
|
||||
const controlsText = this.add.text(20, 55, "🖱️ Mouse Wheel: Zoom In/Out", {
|
||||
fontFamily: 'Courier New, monospace',
|
||||
fontSize: '14px',
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#000000',
|
||||
padding: { x: 10, y: 5 }
|
||||
}).setScrollFactor(0).setDepth(100);
|
||||
|
||||
this.zoomText = this.add.text(20, 90, `📷 Zoom: ${this.currentZoom.toFixed(2)}x`, {
|
||||
fontFamily: 'Courier New, monospace',
|
||||
fontSize: '14px',
|
||||
color: '#00ff88',
|
||||
backgroundColor: '#000000',
|
||||
padding: { x: 10, y: 5 }
|
||||
}).setScrollFactor(0).setDepth(100);
|
||||
|
||||
console.log("✅ Interactive forest ready! Use mouse wheel to zoom!");
|
||||
// UI Info
|
||||
this.infoText = this.add.text(10, 10, 'Time: 12:00', { font: '16px monospace', fill: '#fff' }).setDepth(2000);
|
||||
}
|
||||
|
||||
update() {
|
||||
// Smooth zoom transition
|
||||
if (Math.abs(this.currentZoom - this.targetZoom) > 0.01) {
|
||||
this.currentZoom = Phaser.Math.Linear(this.currentZoom, this.targetZoom, 0.1);
|
||||
this.cameras.main.setZoom(this.currentZoom);
|
||||
generateVegetation(w, h) {
|
||||
// Randomly place HIGH GRASS (for cutting/hiding)
|
||||
for (let i = 0; i < 50; i++) {
|
||||
let x = Phaser.Math.Between(50, w - 50);
|
||||
let y = Phaser.Math.Between(50, h - 50);
|
||||
|
||||
// Update zoom text
|
||||
if (this.zoomText) {
|
||||
this.zoomText.setText(`📷 Zoom: ${this.currentZoom.toFixed(2)}x`);
|
||||
}
|
||||
|
||||
// Depth of Field effect based on zoom
|
||||
const dofFactor = Phaser.Math.Clamp((this.currentZoom - 1) / 1.5, 0, 1);
|
||||
|
||||
// Blur background when zoomed in
|
||||
this.backgroundObjects.forEach(obj => {
|
||||
obj.setAlpha(0.5 - (dofFactor * 0.3)); // Fade out
|
||||
});
|
||||
|
||||
// Slight blur on foreground too
|
||||
this.foregroundObjects.forEach(obj => {
|
||||
obj.setAlpha(0.95 - (dofFactor * 0.2));
|
||||
});
|
||||
// Swaying tall grass
|
||||
let grass = this.add.image(x, y, 'grass_tall');
|
||||
grass.setScale(0.15 + (Math.random() * 0.1)); // Random size
|
||||
grass.setDepth(y); // Y-sort
|
||||
grass.setOrigin(0.5, 1); // Anchor at bottom
|
||||
grass.swaySpeed = 0.002 + (Math.random() * 0.002);
|
||||
grass.swayOffset = Math.random() * 100;
|
||||
this.grasses.push(grass);
|
||||
}
|
||||
|
||||
// Randomly place TREES
|
||||
for (let i = 0; i < 15; i++) {
|
||||
let x = Phaser.Math.Between(50, w - 50);
|
||||
let y = Phaser.Math.Between(50, h - 50);
|
||||
let type = Phaser.Math.RND.pick(['tree_small', 'tree_medium', 'tree_large', 'tree_dead']);
|
||||
|
||||
let tree = this.add.image(x, y, type);
|
||||
// Size adjustments based on type
|
||||
if (type === 'tree_large') tree.setScale(0.8);
|
||||
else if (type === 'tree_medium') tree.setScale(0.6);
|
||||
else tree.setScale(0.5);
|
||||
|
||||
tree.setDepth(y);
|
||||
tree.setOrigin(0.5, 0.95); // Anchor near bottom
|
||||
tree.swaySpeed = 0.001 + (Math.random() * 0.001); // Slower sway for trees
|
||||
tree.swayOffset = Math.random() * 100;
|
||||
this.trees.push(tree);
|
||||
}
|
||||
}
|
||||
|
||||
update(time, delta) {
|
||||
// --- 1. WIND ANIMATION (Manual Vertex Sway Simulation) ---
|
||||
// Since we aren't using a complex shader pipeline yet, we use rotation/skew
|
||||
|
||||
this.grasses.forEach(g => {
|
||||
// Simple sway using Sine wave
|
||||
g.rotation = Math.sin((time * g.swaySpeed) + g.swayOffset) * 0.1; // +/- 0.1 radians
|
||||
});
|
||||
|
||||
this.trees.forEach(t => {
|
||||
// Trees sway less
|
||||
t.rotation = Math.sin((time * t.swaySpeed) + t.swayOffset) * 0.03;
|
||||
});
|
||||
|
||||
// --- 2. FOG MOVEMENT ---
|
||||
this.fog1.tilePositionX += 0.5;
|
||||
this.fog2.tilePositionX += 0.2;
|
||||
this.fog2.tilePositionY += 0.1;
|
||||
|
||||
// --- 3. DAY/NIGHT CYCLE ---
|
||||
this.baseTime += (delta * 0.001 * this.timeSpeed); // Simulated hours
|
||||
if (this.baseTime >= 24) this.baseTime = 0;
|
||||
|
||||
this.updateLighting(this.baseTime);
|
||||
this.infoText.setText(`Time: ${Math.floor(this.baseTime)}:${Math.floor((this.baseTime % 1) * 60).toString().padStart(2, '0')}`);
|
||||
}
|
||||
|
||||
updateLighting(hour) {
|
||||
// Simple ambient light logic
|
||||
let alpha = 0;
|
||||
let color = 0x000000; // Darkness color
|
||||
|
||||
if (hour >= 6 && hour < 12) {
|
||||
// Sunrise -> Noon (Brightening)
|
||||
alpha = Phaser.Math.Interpolation.Linear([0.6, 0.0], (hour - 6) / 6);
|
||||
color = 0xffaa00; // Orange tint for sunrise
|
||||
}
|
||||
else if (hour >= 12 && hour < 18) {
|
||||
// Noon -> Sunset (Normal)
|
||||
alpha = 0;
|
||||
}
|
||||
else if (hour >= 18 && hour < 21) {
|
||||
// Sunset -> Night (Darkening)
|
||||
alpha = Phaser.Math.Interpolation.Linear([0.0, 0.5], (hour - 18) / 3);
|
||||
color = 0x330066; // Purple evening tint
|
||||
}
|
||||
else {
|
||||
// Night (Dark)
|
||||
alpha = 0.7; // 70% darkness
|
||||
color = 0x000022; // Deep blue night
|
||||
}
|
||||
|
||||
this.dayNightOverlay.setFillStyle(color, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
</head>
|
||||
<body>
|
||||
<!-- Phaser Library -->
|
||||
<script src="node_modules/phaser/dist/phaser.js"></script>
|
||||
<script src="../node_modules/phaser/dist/phaser.js"></script>
|
||||
<!-- Game Entry -->
|
||||
<script type="module" src="src/game.js"></script>
|
||||
<script type="module" src="../src/game.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||