✨ Glavne Features: - Main Demo Scene (main_demo_scene.html) s Kaijem, campfire, trees, tent - Kai Camp Showcase (kai_camp_showcase.html) - isoliran showcase - Asset cleanup & transparency fix za vse PNG assete - Nova struktura: 'assets/slike/nova mapa faza 0-1/' - Glavne reference v 'assets/slike/glavna_referenca/' 🔧 Technical: - Python scripts: complete_visual_cleanup.py, remove_green_bg.py - Transparency fix za vse karakterje, trees, props (brez zelenih ozadij) - Dokumentacija: TENT_FOUND_REPORT, TRANSPARENCY_FIX_REPORT, VISUAL_CLEANUP_REPORT 🎨 Assets: - Kai z dreadi (standalone sprite) - Animated campfire (frame1 + frame2) - Dead trees (10 variants) - Tent, grass texture, wood logs - Full Dark-Chibi Noir aesthetic 🚀 Demo Ready: HTML5 Canvas 2D Scene with WASD movement controls
529 lines
16 KiB
HTML
529 lines
16 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="sl">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Kaijev Svet - Dark-Chibi Noir Demo</title>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
background: linear-gradient(180deg, #0a0a0a 0%, #1a1a1a 50%, #2a2a2a 100%);
|
|
font-family: 'Courier New', monospace;
|
|
color: #e0e0e0;
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.header {
|
|
text-align: center;
|
|
padding: 20px;
|
|
background: rgba(0, 0, 0, 0.8);
|
|
border-bottom: 3px solid #ff6b6b;
|
|
width: 100%;
|
|
box-shadow: 0 4px 20px rgba(255, 107, 107, 0.4);
|
|
position: fixed;
|
|
top: 0;
|
|
z-index: 1000;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 2.5em;
|
|
color: #ff6b6b;
|
|
text-shadow: 0 0 15px rgba(255, 107, 107, 0.8),
|
|
0 0 30px rgba(255, 107, 107, 0.5);
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.subtitle {
|
|
font-size: 1em;
|
|
color: #a0a0a0;
|
|
font-style: italic;
|
|
}
|
|
|
|
#gameCanvas {
|
|
border: 4px solid #ff6b6b;
|
|
border-radius: 10px;
|
|
box-shadow: 0 0 50px rgba(255, 107, 107, 0.5),
|
|
inset 0 0 30px rgba(0, 0, 0, 0.5);
|
|
margin-top: 120px;
|
|
image-rendering: pixelated;
|
|
image-rendering: -moz-crisp-edges;
|
|
image-rendering: crisp-edges;
|
|
background: #2a3a2a;
|
|
}
|
|
|
|
.controls {
|
|
position: fixed;
|
|
bottom: 20px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
background: rgba(0, 0, 0, 0.9);
|
|
border: 2px solid #ff6b6b;
|
|
border-radius: 10px;
|
|
padding: 20px;
|
|
box-shadow: 0 0 30px rgba(255, 107, 107, 0.4);
|
|
display: flex;
|
|
gap: 15px;
|
|
align-items: center;
|
|
z-index: 1000;
|
|
}
|
|
|
|
.controls button {
|
|
background: #ff6b6b;
|
|
color: white;
|
|
border: none;
|
|
padding: 12px 24px;
|
|
border-radius: 5px;
|
|
cursor: pointer;
|
|
font-family: 'Courier New', monospace;
|
|
font-size: 0.95em;
|
|
transition: all 0.3s ease;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.controls button:hover {
|
|
background: #ff8787;
|
|
box-shadow: 0 0 20px rgba(255, 107, 107, 0.7);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.controls button:active {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.info {
|
|
background: rgba(255, 107, 107, 0.1);
|
|
padding: 8px 15px;
|
|
border-radius: 5px;
|
|
border-left: 4px solid #ff6b6b;
|
|
font-size: 0.9em;
|
|
}
|
|
|
|
.loading {
|
|
position: fixed;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
background: rgba(0, 0, 0, 0.95);
|
|
border: 3px solid #ff6b6b;
|
|
border-radius: 10px;
|
|
padding: 40px;
|
|
text-align: center;
|
|
z-index: 2000;
|
|
box-shadow: 0 0 50px rgba(255, 107, 107, 0.6);
|
|
}
|
|
|
|
.loading h2 {
|
|
color: #ff6b6b;
|
|
margin-bottom: 20px;
|
|
font-size: 1.8em;
|
|
}
|
|
|
|
.loading-bar {
|
|
width: 300px;
|
|
height: 20px;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border-radius: 10px;
|
|
overflow: hidden;
|
|
border: 2px solid #ff6b6b;
|
|
}
|
|
|
|
.loading-fill {
|
|
height: 100%;
|
|
background: linear-gradient(90deg, #ff6b6b, #ff8787);
|
|
width: 0%;
|
|
transition: width 0.3s ease;
|
|
box-shadow: 0 0 10px rgba(255, 107, 107, 0.8);
|
|
}
|
|
|
|
.hidden {
|
|
display: none;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div class="header">
|
|
<h1>🏕️ KAIJEV SVET</h1>
|
|
<p class="subtitle">Dark-Chibi Noir | Postapokaliptična Farma v Mrtvi Dolini</p>
|
|
</div>
|
|
|
|
<div class="loading" id="loadingScreen">
|
|
<h2>⚙️ Nalagam Demo Sceno...</h2>
|
|
<div class="loading-bar">
|
|
<div class="loading-fill" id="loadingFill"></div>
|
|
</div>
|
|
<p style="margin-top: 15px; color: #a0a0a0;" id="loadingText">Pripravljam assete...</p>
|
|
</div>
|
|
|
|
<canvas id="gameCanvas" width="1280" height="720"></canvas>
|
|
|
|
<div class="controls">
|
|
<div class="info">🎮 WASD = Gibanje</div>
|
|
<button onclick="toggleFire()">🔥 Toggle Fire</button>
|
|
<button onclick="resetScene()">🔄 Reset</button>
|
|
<div class="info" id="fpsCounter">FPS: 60</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Canvas setup
|
|
const canvas = document.getElementById('gameCanvas');
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
// Game state
|
|
const game = {
|
|
width: canvas.width,
|
|
height: canvas.height,
|
|
loaded: false,
|
|
assetsToLoad: 0,
|
|
assetsLoaded: 0,
|
|
fireActive: true,
|
|
campfireFrame: 0,
|
|
campfireTimer: 0,
|
|
keys: {},
|
|
time: 0
|
|
};
|
|
|
|
// Player (Kai)
|
|
const player = {
|
|
x: canvas.width / 2 - 50,
|
|
y: canvas.height / 2 - 50,
|
|
width: 120,
|
|
height: 120,
|
|
speed: 3,
|
|
sprite: null
|
|
};
|
|
|
|
// Assets
|
|
const assets = {
|
|
grass: null,
|
|
kai: null,
|
|
campfire: [],
|
|
trees: [],
|
|
tent: null,
|
|
woodLog: null
|
|
};
|
|
|
|
// Scene objects
|
|
const sceneObjects = [];
|
|
|
|
// Load image helper
|
|
function loadImage(src) {
|
|
game.assetsToLoad++;
|
|
const img = new Image();
|
|
img.onload = () => {
|
|
game.assetsLoaded++;
|
|
updateLoadingBar();
|
|
};
|
|
img.onerror = () => {
|
|
console.error(`Failed to load: ${src}`);
|
|
game.assetsLoaded++;
|
|
updateLoadingBar();
|
|
};
|
|
img.src = src;
|
|
return img;
|
|
}
|
|
|
|
// Update loading bar
|
|
function updateLoadingBar() {
|
|
const progress = (game.assetsLoaded / game.assetsToLoad) * 100;
|
|
document.getElementById('loadingFill').style.width = progress + '%';
|
|
document.getElementById('loadingText').textContent =
|
|
`Nalagam... ${game.assetsLoaded}/${game.assetsToLoad} assetov`;
|
|
|
|
if (game.assetsLoaded >= game.assetsToLoad) {
|
|
setTimeout(() => {
|
|
document.getElementById('loadingScreen').classList.add('hidden');
|
|
game.loaded = true;
|
|
gameLoop();
|
|
}, 500);
|
|
}
|
|
}
|
|
|
|
// Load all assets
|
|
function loadAssets() {
|
|
const basePath = 'assets/slike/nova mapa faza 0-1/';
|
|
|
|
// Grass texture
|
|
assets.grass = loadImage(basePath + 'Environment/podlaga/MOJE_SLIKE_KONCNA_teren_grass.png');
|
|
|
|
// Kai character
|
|
assets.kai = loadImage(basePath + 'Characters/kaj/Izdelek brez naslova (2).png');
|
|
player.sprite = assets.kai;
|
|
|
|
// Campfire animation
|
|
assets.campfire[0] = loadImage(basePath + 'Environment/props/MOJE_SLIKE_KONCNA_ostalo_campfire_frame1.png');
|
|
assets.campfire[1] = loadImage(basePath + 'Environment/props/MOJE_SLIKE_KONCNA_ostalo_campfire_frame2.png');
|
|
|
|
// Dead trees (load 10 variants)
|
|
for (let i = 0; i < 8; i++) {
|
|
assets.trees.push(loadImage(basePath + `Environment/narava/unnamed-${i + 1}.png`));
|
|
}
|
|
assets.trees.push(loadImage(basePath + 'Environment/narava/unnamed.png'));
|
|
assets.trees.push(loadImage(basePath + 'Environment/narava/unnamed Kopie.png'));
|
|
|
|
// Tent
|
|
assets.tent = loadImage(basePath + 'Environment/stavbe/MOJE_SLIKE_KONCNA_ostalo_tent_basic_style32.png');
|
|
|
|
// Wood log
|
|
assets.woodLog = loadImage(basePath + 'Environment/props/wood_log.png');
|
|
}
|
|
|
|
// Initialize scene
|
|
function initScene() {
|
|
// Background trees (far layer)
|
|
sceneObjects.push({
|
|
type: 'tree',
|
|
sprite: assets.trees[0],
|
|
x: 100,
|
|
y: 80,
|
|
width: 200,
|
|
height: 200,
|
|
layer: 1
|
|
});
|
|
|
|
sceneObjects.push({
|
|
type: 'tree',
|
|
sprite: assets.trees[1],
|
|
x: 900,
|
|
y: 100,
|
|
width: 220,
|
|
height: 220,
|
|
layer: 1
|
|
});
|
|
|
|
sceneObjects.push({
|
|
type: 'tree',
|
|
sprite: assets.trees[2],
|
|
x: 1050,
|
|
y: 150,
|
|
width: 180,
|
|
height: 180,
|
|
layer: 1
|
|
});
|
|
|
|
// Tent (mid layer)
|
|
sceneObjects.push({
|
|
type: 'tent',
|
|
sprite: assets.tent,
|
|
x: 200,
|
|
y: 320,
|
|
width: 180,
|
|
height: 180,
|
|
layer: 2
|
|
});
|
|
|
|
// Campfire (center, mid-to-front layer)
|
|
sceneObjects.push({
|
|
type: 'campfire',
|
|
sprite: assets.campfire[0],
|
|
x: canvas.width / 2 + 80,
|
|
y: canvas.height / 2 - 20,
|
|
width: 120,
|
|
height: 120,
|
|
layer: 3
|
|
});
|
|
|
|
// Wood log (foreground)
|
|
sceneObjects.push({
|
|
type: 'log',
|
|
sprite: assets.woodLog,
|
|
x: canvas.width / 2 - 100,
|
|
y: canvas.height / 2 + 100,
|
|
width: 100,
|
|
height: 100,
|
|
layer: 4
|
|
});
|
|
|
|
// Foreground trees (close layer)
|
|
sceneObjects.push({
|
|
type: 'tree',
|
|
sprite: assets.trees[3],
|
|
x: 50,
|
|
y: 450,
|
|
width: 240,
|
|
height: 240,
|
|
layer: 5
|
|
});
|
|
|
|
sceneObjects.push({
|
|
type: 'tree',
|
|
sprite: assets.trees[4],
|
|
x: 950,
|
|
y: 400,
|
|
width: 260,
|
|
height: 260,
|
|
layer: 5
|
|
});
|
|
|
|
// Sort objects by layer for proper rendering
|
|
sceneObjects.sort((a, b) => a.layer - b.layer);
|
|
}
|
|
|
|
// Draw grass background (tiled)
|
|
function drawGrass() {
|
|
if (!assets.grass || !assets.grass.complete) return;
|
|
|
|
const tileSize = 64; // Grass tile size
|
|
for (let x = 0; x < canvas.width; x += tileSize) {
|
|
for (let y = 0; y < canvas.height; y += tileSize) {
|
|
ctx.drawImage(assets.grass, x, y, tileSize, tileSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw scene objects
|
|
function drawSceneObjects() {
|
|
sceneObjects.forEach(obj => {
|
|
if (obj.type === 'campfire' && game.fireActive) {
|
|
// Draw animated campfire
|
|
const frameIndex = Math.floor(game.campfireFrame);
|
|
const sprite = assets.campfire[frameIndex % 2];
|
|
if (sprite && sprite.complete) {
|
|
ctx.drawImage(sprite, obj.x, obj.y, obj.width, obj.height);
|
|
}
|
|
} else if (obj.sprite && obj.sprite.complete) {
|
|
ctx.drawImage(obj.sprite, obj.x, obj.y, obj.width, obj.height);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Draw player (Kai)
|
|
function drawPlayer() {
|
|
if (player.sprite && player.sprite.complete) {
|
|
ctx.drawImage(player.sprite, player.x, player.y, player.width, player.height);
|
|
}
|
|
}
|
|
|
|
// Update player movement
|
|
function updatePlayer() {
|
|
if (game.keys['w'] || game.keys['W']) player.y -= player.speed;
|
|
if (game.keys['s'] || game.keys['S']) player.y += player.speed;
|
|
if (game.keys['a'] || game.keys['A']) player.x -= player.speed;
|
|
if (game.keys['d'] || game.keys['D']) player.x += player.speed;
|
|
|
|
// Keep player in bounds
|
|
player.x = Math.max(0, Math.min(canvas.width - player.width, player.x));
|
|
player.y = Math.max(0, Math.min(canvas.height - player.height, player.y));
|
|
}
|
|
|
|
// Update game state
|
|
function update(deltaTime) {
|
|
game.time += deltaTime;
|
|
|
|
// Update player
|
|
updatePlayer();
|
|
|
|
// Update campfire animation
|
|
if (game.fireActive) {
|
|
game.campfireTimer += deltaTime;
|
|
if (game.campfireTimer > 0.5) {
|
|
game.campfireFrame = (game.campfireFrame + 1) % 2;
|
|
game.campfireTimer = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Render everything
|
|
function render() {
|
|
// Clear canvas
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
|
// Draw grass background
|
|
drawGrass();
|
|
|
|
// Draw scene objects (background to mid-ground)
|
|
sceneObjects.forEach(obj => {
|
|
if (obj.layer < 4 || obj.y < player.y) {
|
|
if (obj.type === 'campfire' && game.fireActive) {
|
|
const frameIndex = Math.floor(game.campfireFrame);
|
|
const sprite = assets.campfire[frameIndex % 2];
|
|
if (sprite && sprite.complete) {
|
|
ctx.drawImage(sprite, obj.x, obj.y, obj.width, obj.height);
|
|
}
|
|
} else if (obj.sprite && obj.sprite.complete) {
|
|
ctx.drawImage(obj.sprite, obj.x, obj.y, obj.width, obj.height);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Draw player
|
|
drawPlayer();
|
|
|
|
// Draw remaining scene objects (foreground, in front of player)
|
|
sceneObjects.forEach(obj => {
|
|
if (obj.layer >= 4 && obj.y >= player.y) {
|
|
if (obj.sprite && obj.sprite.complete) {
|
|
ctx.drawImage(obj.sprite, obj.x, obj.y, obj.width, obj.height);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Game loop
|
|
let lastTime = 0;
|
|
let fpsTime = 0;
|
|
let frameCount = 0;
|
|
|
|
function gameLoop(timestamp = 0) {
|
|
if (!game.loaded) return;
|
|
|
|
const deltaTime = (timestamp - lastTime) / 1000;
|
|
lastTime = timestamp;
|
|
|
|
// Update
|
|
update(deltaTime);
|
|
|
|
// Render
|
|
render();
|
|
|
|
// FPS counter
|
|
fpsTime += deltaTime;
|
|
frameCount++;
|
|
if (fpsTime >= 1) {
|
|
document.getElementById('fpsCounter').textContent = `FPS: ${frameCount}`;
|
|
frameCount = 0;
|
|
fpsTime = 0;
|
|
}
|
|
|
|
requestAnimationFrame(gameLoop);
|
|
}
|
|
|
|
// Controls
|
|
function toggleFire() {
|
|
game.fireActive = !game.fireActive;
|
|
}
|
|
|
|
function resetScene() {
|
|
player.x = canvas.width / 2 - 50;
|
|
player.y = canvas.height / 2 - 50;
|
|
game.campfireFrame = 0;
|
|
game.fireActive = true;
|
|
}
|
|
|
|
// Keyboard input
|
|
window.addEventListener('keydown', (e) => {
|
|
game.keys[e.key] = true;
|
|
});
|
|
|
|
window.addEventListener('keyup', (e) => {
|
|
game.keys[e.key] = false;
|
|
});
|
|
|
|
// Initialize
|
|
loadAssets();
|
|
initScene();
|
|
</script>
|
|
</body>
|
|
|
|
</html> |