-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsurvive.html
More file actions
280 lines (243 loc) · 27.7 KB
/
survive.html
File metadata and controls
280 lines (243 loc) · 27.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Xzone Survivor</title>
<style>
/* Styly zůstávají stejné */
body { display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background-color: #1a1a1a; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; color: #eee; overflow: hidden; }
canvas { border: 2px solid #888; background-color: #282c34; cursor: crosshair; }
#ui-container { margin-top: 10px; width: 800px; display: flex; justify-content: space-between; align-items: center; font-size: 1.1em; flex-wrap: wrap; }
.ui-item { margin: 5px 10px; }
#xp-bar-container { width: 200px; height: 20px; background-color: #444; border: 1px solid #666; border-radius: 5px; overflow: hidden; }
#xp-bar { width: 0%; height: 100%; background-color: #4caf50; transition: width 0.2s ease-in-out; }
#level-up-screen, #game-over-screen { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: rgba(20, 20, 20, 0.95); padding: 30px; border-radius: 10px; text-align: center; display: none; z-index: 10; border: 2px solid #aaa; color: #eee; min-width: 300px; box-shadow: 0 0 20px rgba(0,0,0,0.5); }
#level-up-screen h2, #game-over-screen h2 { margin-top: 0; margin-bottom: 15px; }
#game-over-screen { background-color: rgba(50, 0, 0, 0.95); border-color: #f44336; color: #fdd; }
.upgrade-button, #restart-button { display: block; background-color: #4CAF50; color: white; padding: 12px 20px; margin: 10px auto; border: none; border-radius: 5px; cursor: pointer; font-size: 1em; min-width: 250px; transition: background-color 0.2s; text-align: left; line-height: 1.4; }
.upgrade-button small { display: block; font-size: 0.85em; color: #ccc; margin-top: 3px; }
.upgrade-button:hover { background-color: #45a049; }
#restart-button { background-color: #f44336; font-size: 1.1em; min-width: 150px; text-align: center; }
#restart-button:hover { background-color: #d32f2f;}
#timer { font-size: 1.2em; font-weight: bold; }
</style>
</head>
<body>
<h1>Xzone Survivor</h1>
<canvas id="gameCanvas" width="800" height="600"></canvas>
<div id="ui-container">
<span class="ui-item">Zdraví: <span id="health">100</span></span>
<span class="ui-item">Úroveň: <span id="level">1</span></span>
<div class="ui-item" id="xp-bar-container"><div id="xp-bar"></div></div>
<span class="ui-item">XP: <span id="xp-value">0</span> / <span id="xp-needed">10</span></span>
<span class="ui-item">Čas: <span id="timer">0:00</span></span>
</div>
<div id="level-up-screen">
<h2>Postup na další úroveň!</h2>
<p>Vyber si vylepšení:</p>
<div id="upgrade-options"></div>
</div>
<div id="game-over-screen">
<h2>Konec hry!</h2>
<p>Přežil jsi <span id="final-time">0:00</span></p>
<p>Dosáhl jsi úrovně <span id="final-level">1</span></p>
<button id="restart-button">Hrát znovu</button>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
// --- UI Prvky ---
const healthDisplay=document.getElementById('health'); const levelDisplay=document.getElementById('level'); const xpBar=document.getElementById('xp-bar'); const xpValueDisplay=document.getElementById('xp-value'); const xpNeededDisplay=document.getElementById('xp-needed'); const levelUpScreen=document.getElementById('level-up-screen'); const upgradeOptionsContainer=document.getElementById('upgrade-options'); const gameOverScreen=document.getElementById('game-over-screen'); const restartButton=document.getElementById('restart-button'); const timerDisplay=document.getElementById('timer'); const finalTimeDisplay=document.getElementById('final-time'); const finalLevelDisplay=document.getElementById('final-level');
// --- Herní Konstanty ---
const PROJECTILE_SIZE = 8; const XP_ORB_SIZE = 6; const PLAYER_BASE_SPEED = 2.5; const ENEMY_BASE_SPEED = 1; const PROJECTILE_SPEED = 6; const PROJECTILE_MAX_BOUNCES = 1; const ENEMY_SPAWN_RATE_START = 80; const ENEMY_SPAWN_RATE_MIN = 25; const ENEMY_HEALTH_START = 10; const ENEMY_DAMAGE = 10; const XP_PER_ENEMY = 3; const LEVEL_XP_BASE = 10; const LEVEL_XP_FACTOR = 1.5; const PLAYER_BASE_HEALTH = 100; const INVINCIBILITY_DURATION = 30;
const DEFAULT_ENEMY_SIZE = 30;
const BOSS_SPAWN_TIME = 60; const BOSS_SIZE = 80; const BOSS_HEALTH_BASE = 500; const BOSS_SPEED = 0.5; const BOSS_DAMAGE = 25; const BOSS_XP_VALUE = 100;
// --- Načítání obrázků ---
let imagesToLoad = {
player: 'player_x_logo.png',
enemy_originalky: 'originalky_logo.png',
enemy_jrc: 'jrc_logo.png',
enemy_smarty: 'smarty_logo.png',
enemy_swirl: 'green_swirl.png',
boss_alza: 'alza.png'
};
let loadedImages = {};
let imagesLoadedCount = 0;
let totalImages = Object.keys(imagesToLoad).length;
let allImagesLoaded = false; // Tento flag použijeme pro start
let imageLoadErrors = false;
function loadImage(key, src) { const img = new Image(); img.onload = () => { imagesLoadedCount++; console.log(`Obrázek načten: ${key} (${src})`); checkAllImagesLoaded(); }; img.onerror = () => { console.error(`CHYBA při načítání obrázku: ${key} (${src})`); imagesLoadedCount++; imageLoadErrors = true; checkAllImagesLoaded(); }; img.src = src; loadedImages[key] = img; }
function checkAllImagesLoaded() { if (!allImagesLoaded && imagesLoadedCount === totalImages) { allImagesLoaded = true; if (imageLoadErrors) { console.warn("Některé obrázky se nepodařilo načíst. Hra použije záložní grafiku."); } else { console.log("Všechny obrázky úspěšně načteny."); } } }
console.log("Zahajuji načítání obrázků..."); for (const key in imagesToLoad) { loadImage(key, imagesToLoad[key]); }
// --- Konec načítání obrázků ---
// --- Typy Nepřátel ---
const enemyTypes = [
{ id: 'originalky', imageKey: 'enemy_originalky', displaySize: 35, healthMultiplier: 1.0, speedMultiplier: 1.0, xpMultiplier: 1.0 },
{ id: 'jrc', imageKey: 'enemy_jrc', displaySize: 45, healthMultiplier: 2.5, speedMultiplier: 0.7, xpMultiplier: 1.5 },
{ id: 'smarty', imageKey: 'enemy_smarty', displaySize: 30, healthMultiplier: 0.6, speedMultiplier: 1.4, xpMultiplier: 0.8 },
{ id: 'swirl', imageKey: 'enemy_swirl', displaySize: 30, healthMultiplier: 0.8, speedMultiplier: 1.2, xpMultiplier: 0.9 },
];
// --- Herní Stav ---
let player = {}; let enemies = []; let projectiles = []; let xpOrbs = []; let keysPressed = {}; let mousePos = { x: 0, y: 0 }; let enemySpawnCooldown = ENEMY_SPAWN_RATE_START; let currentEnemySpawnRate = ENEMY_SPAWN_RATE_START; let currentEnemyHealthBase = ENEMY_HEALTH_START; let gameTime = 0; let gameTimerInterval; let isPaused = false; let isGameOver = false; let animationFrameId = null; let playerInvincibleTimer = 0; let currentUpgradeChoices = [];
let bossSpawned = false;
// --- Pomocné funkce ---
function distance(x1, y1, x2, y2) { const dx = x1 - x2; const dy = y1 - y2; return Math.sqrt(dx * dx + dy * dy); }
function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }
function formatTime(seconds) { const min = Math.floor(seconds / 60); const sec = Math.floor(seconds % 60); return `${min}:${sec.toString().padStart(2, '0')}`; }
function getMousePos(canvas, evt) { const rect = canvas.getBoundingClientRect(); return { x: evt.clientX - rect.left, y: evt.clientY - rect.top }; }
// --- Inicializace a Restart ---
function resetPlayerState() {
const playerDisplaySize = 35;
player = {
x: canvas.width/2-playerDisplaySize/2, y: canvas.height/2-playerDisplaySize/2,
image: loadedImages['player'], // Použij přednačtený obrázek
size: playerDisplaySize,
speed: PLAYER_BASE_SPEED,
health: PLAYER_BASE_HEALTH, maxHealth: PLAYER_BASE_HEALTH,
level: 1, xp: 0, xpToNextLevel: LEVEL_XP_BASE,
weapons: [ { id: 'pistol', level: 1, cooldown: 0, baseFireRate: 40, baseDamage: 10, projectilesPerShot: 1 } ],
statModifiers: { fireRateMultiplier: 1.0, damageMultiplier: 1.0, areaMultiplier: 1.0 }
};
}
function resetGameState() {
resetPlayerState(); enemies = []; projectiles = []; xpOrbs = []; keysPressed = {};
mousePos = { x: player.x + player.size / 2, y: player.y + player.size / 2 };
enemySpawnCooldown = ENEMY_SPAWN_RATE_START; currentEnemySpawnRate = ENEMY_SPAWN_RATE_START; currentEnemyHealthBase = ENEMY_HEALTH_START;
gameTime = 0; isPaused = false; isGameOver = false; playerInvincibleTimer = 0; currentUpgradeChoices = [];
bossSpawned = false; // Reset bosse
healthDisplay.textContent=player.health; levelDisplay.textContent=player.level; xpValueDisplay.textContent=player.xp; xpNeededDisplay.textContent=player.xpToNextLevel; xpBar.style.width='0%'; timerDisplay.textContent=formatTime(gameTime); levelUpScreen.style.display='none'; gameOverScreen.style.display='none';
}
function startGame() { resetGameState(); if (gameTimerInterval) clearInterval(gameTimerInterval); gameTimerInterval = setInterval(() => { if (!isPaused && !isGameOver) { gameTime++; timerDisplay.textContent = formatTime(gameTime); currentEnemySpawnRate = Math.max(ENEMY_SPAWN_RATE_MIN, ENEMY_SPAWN_RATE_START - Math.floor(gameTime / 4)); currentEnemyHealthBase = ENEMY_HEALTH_START + Math.floor(gameTime / 8); } }, 1000); if (animationFrameId) cancelAnimationFrame(animationFrameId); gameLoop(); }
// --- Herní Smyčka ---
function gameLoop() { animationFrameId = requestAnimationFrame(gameLoop); if (isGameOver) return; if (isPaused) return; update(); draw(); }
// --- Aktualizační Funkce ---
function update() { updatePlayer(); handleWeaponFiring(); updateProjectiles(); updateEnemies(); updateXpOrbs(); handleEnemySpawning(); handleBossSpawning(); handleCollisions(); if (playerInvincibleTimer > 0) playerInvincibleTimer--; if (player.health <= 0 && !isGameOver) { isGameOver = true; showGameOver(); } }
function updatePlayer() { let dx = 0, dy = 0; if (keysPressed['arrowup'] || keysPressed['w']) dy -= 1; if (keysPressed['arrowdown'] || keysPressed['s']) dy += 1; if (keysPressed['arrowleft'] || keysPressed['a']) dx -= 1; if (keysPressed['arrowright'] || keysPressed['d']) dx += 1; const len = Math.sqrt(dx * dx + dy * dy); if (len > 0) { dx = (dx / len) * player.speed; dy = (dy / len) * player.speed; } player.x += dx; player.y += dy; player.x = Math.max(0, Math.min(canvas.width - player.size, player.x)); player.y = Math.max(0, Math.min(canvas.height - player.size, player.y)); }
function handleWeaponFiring() { if(isPaused) return; const playerCenterX=player.x+player.size/2; const playerCenterY=player.y+player.size/2; player.weapons.forEach(weapon => { weapon.cooldown=(weapon.cooldown||0)-1; const fireRate=Math.max(5,(weapon.baseFireRate||60)/player.statModifiers.fireRateMultiplier); const damage=(weapon.baseDamage||0)*player.statModifiers.damageMultiplier; const area=player.statModifiers.areaMultiplier; if(weapon.cooldown<=0){ weapon.cooldown=fireRate; switch(weapon.id){ case 'pistol': { const numProjectiles=weapon.projectilesPerShot||1; const angleSpread=numProjectiles>1?Math.PI/18:0; const baseDx=mousePos.x-playerCenterX; const baseDy=mousePos.y-playerCenterY; const baseDist=Math.sqrt(baseDx*baseDx+baseDy*baseDy)||1; const baseAngle=Math.atan2(baseDy,baseDx); for(let i=0; i<numProjectiles; i++){ const currentAngle=baseAngle+(i-(numProjectiles-1)/2)*angleSpread; const vx=Math.cos(currentAngle)*PROJECTILE_SPEED; const vy=Math.sin(currentAngle)*PROJECTILE_SPEED; projectiles.push({ x:playerCenterX-PROJECTILE_SIZE/2, y:playerCenterY-PROJECTILE_SIZE/2, vx:vx, vy:vy, size:PROJECTILE_SIZE, damage:damage, color:'#ffeb3b', bounces:0, maxBounces:PROJECTILE_MAX_BOUNCES }); } break; } case 'aura': { const auraRadius=(weapon.baseRadius||60)*area; enemies.forEach(enemy => { if(distance(playerCenterX,playerCenterY,enemy.x+enemy.size/2,enemy.y+enemy.size/2)<auraRadius){ enemy.health-=damage; } }); break; } case 'back_shot': { const dx=playerCenterX-mousePos.x; const dy=playerCenterY-mousePos.y; const dist=Math.sqrt(dx*dx+dy*dy)||1; const vx=(dx/dist)*PROJECTILE_SPEED; const vy=(dy/dist)*PROJECTILE_SPEED; projectiles.push({ x:playerCenterX-PROJECTILE_SIZE/2, y:playerCenterY-PROJECTILE_SIZE/2, vx:vx, vy:vy, size:PROJECTILE_SIZE, damage:damage, color:'#f06292', bounces:0, maxBounces:0 }); break; } } } }); }
function updateProjectiles() { for(let i=projectiles.length-1; i>=0; i--){ const p=projectiles[i]; p.x+=p.vx; p.y+=p.vy; let bounced=false; if(p.x<=0&&p.vx<0){ p.vx*=-1; p.x=0; p.bounces++; bounced=true; }else if(p.x+p.size>=canvas.width&&p.vx>0){ p.vx*=-1; p.x=canvas.width-p.size; p.bounces++; bounced=true; } if(p.y<=0&&p.vy<0){ p.vy*=-1; p.y=0; p.bounces++; bounced=true; }else if(p.y+p.size>=canvas.height&&p.vy>0){ p.vy*=-1; p.y=canvas.height-p.size; p.bounces++; bounced=true; } if(p.bounces>p.maxBounces){ projectiles.splice(i,1); } } }
// --- Spawn běžných nepřátel (už nevytváří Image) ---
function handleEnemySpawning() {
enemySpawnCooldown--;
if (enemySpawnCooldown <= 0) {
const typeTemplate = enemyTypes[getRandomInt(0, enemyTypes.length - 1)];
const enemyImage = loadedImages[typeTemplate.imageKey]; // Použij přednačtený obrázek
const enemySizeOnCanvas = typeTemplate.displaySize || DEFAULT_ENEMY_SIZE;
const side = getRandomInt(0, 3); let spawnX, spawnY; const margin = 30;
if (side === 0) { spawnX = Math.random() * canvas.width; spawnY = -enemySizeOnCanvas - margin; }
else if (side === 1) { spawnX = canvas.width + margin; spawnY = Math.random() * canvas.height; }
else if (side === 2) { spawnX = Math.random() * canvas.width; spawnY = canvas.height + margin; }
else { spawnX = -enemySizeOnCanvas - margin; spawnY = Math.random() * canvas.height; }
enemies.push({
x: spawnX, y: spawnY,
image: enemyImage, // Přiřaď přednačtený obrázek
imageKey: typeTemplate.imageKey, // Stále se hodí pro debug
size: enemySizeOnCanvas,
speed: (ENEMY_BASE_SPEED + Math.random() * 0.4) * typeTemplate.speedMultiplier,
maxHealth: Math.max(1, Math.floor(currentEnemyHealthBase * typeTemplate.healthMultiplier)),
health: Math.max(1, Math.floor(currentEnemyHealthBase * typeTemplate.healthMultiplier)),
damage: ENEMY_DAMAGE,
xpValue: Math.max(1, Math.floor(XP_PER_ENEMY * typeTemplate.xpMultiplier)),
isBoss: false
});
enemySpawnCooldown = currentEnemySpawnRate;
}
}
// Spawn bosse (používá přednačtený obrázek)
function handleBossSpawning() {
if (!bossSpawned && gameTime >= BOSS_SPAWN_TIME) {
bossSpawned = true;
console.log("BOSS SPAWN!");
const spawnX = canvas.width / 2 - BOSS_SIZE / 2; const spawnY = -BOSS_SIZE - 30;
const bossImage = loadedImages['boss_alza']; // Použij přednačtený
const bossHealth = BOSS_HEALTH_BASE + Math.floor(gameTime / 10) * 50;
enemies.push({
x: spawnX, y: spawnY,
image: bossImage, // Přiřaď přednačtený
imageKey: 'boss_alza',
size: BOSS_SIZE,
speed: BOSS_SPEED,
maxHealth: bossHealth, health: bossHealth,
damage: BOSS_DAMAGE, xpValue: BOSS_XP_VALUE,
isBoss: true
});
}
}
function updateEnemies() { enemies.forEach(enemy => { const playerCenterX=player.x+player.size/2; const playerCenterY=player.y+player.size/2; const enemyCenterX=enemy.x+enemy.size/2; const enemyCenterY=enemy.y+enemy.size/2; const dx=playerCenterX-enemyCenterX; const dy=playerCenterY-enemyCenterY; const dist=Math.sqrt(dx*dx+dy*dy); if(dist>0){ enemy.x+=(dx/dist)*enemy.speed; enemy.y+=(dy/dist)*enemy.speed; } }); }
function updateXpOrbs() {}
function checkCollision(obj1, obj2) { return obj1.x < obj2.x + obj2.size && obj1.x + obj1.size > obj2.x && obj1.y < obj2.y + obj2.size && obj1.y + obj1.size > obj2.y; }
function handleCollisions() { for(let i=projectiles.length-1; i>=0; i--){ const proj=projectiles[i]; if(!proj) continue; for(let j=enemies.length-1; j>=0; j--){ const enemy=enemies[j]; if(!enemy) continue; if(checkCollision(proj,enemy)){ enemy.health-=proj.damage; projectiles.splice(i,1); if(enemy.health<=0){ xpOrbs.push({ x:enemy.x+enemy.size/2-XP_ORB_SIZE/2, y:enemy.y+enemy.size/2-XP_ORB_SIZE/2, size:XP_ORB_SIZE, value:enemy.xpValue, color:'#4caf50' }); enemies.splice(j,1); } break; } } } if(playerInvincibleTimer<=0){ for(let j=enemies.length-1; j>=0; j--){ const enemy=enemies[j]; if(!enemy) continue; if(checkCollision(player,enemy)){ player.health-=enemy.damage; playerInvincibleTimer=INVINCIBILITY_DURATION; healthDisplay.textContent=Math.max(0,player.health); const knockbackStrength= enemy.isBoss ? 5 : 15; const dx=player.x-enemy.x; const dy=player.y-enemy.y; const dist=Math.sqrt(dx*dx+dy*dy)||1; enemy.x-=(dx/dist)*knockbackStrength; enemy.y-=(dy/dist)*knockbackStrength; break; } } } for(let k=xpOrbs.length-1; k>=0; k--){ const orb=xpOrbs[k]; if(!orb) continue; const pickupRadius=player.size/2+orb.size*2; if(distance(player.x+player.size/2,player.y+player.size/2,orb.x+orb.size/2,orb.y+orb.size/2)<pickupRadius){ gainXP(orb.value); xpOrbs.splice(k,1); } } }
// --- XP a Level Up ---
function gainXP(amount) { if(isGameOver) return; player.xp+=amount; if(player.xp>=player.xpToNextLevel){ levelUp(); } updateXPUI(); }
function levelUp() { player.level++; const leftoverXp=player.xp-player.xpToNextLevel; player.xp=Math.max(0,leftoverXp); player.xpToNextLevel=Math.floor(LEVEL_XP_BASE*Math.pow(LEVEL_XP_FACTOR,player.level-1)); levelDisplay.textContent=player.level; updateXPUI(); isPaused=true; showLevelUpOptions(); }
function updateXPUI() { xpValueDisplay.textContent=player.xp; xpNeededDisplay.textContent=player.xpToNextLevel; const xpPercentage=Math.max(0,Math.min(100,(player.xp/player.xpToNextLevel)*100)); xpBar.style.width=`${xpPercentage}%`; }
// --- Vylepšení ---
const BASE_UPGRADES=[ { id:"health", name:"Více zdraví", description:"Zvýší max zdraví a plně vyléčí.", type:"stat", apply:()=>{ player.maxHealth+=25; player.health=player.maxHealth; healthDisplay.textContent=player.health;} }, { id:"speed", name:"Rychlejší pohyb", description:"Zvýší rychlost pohybu hráče.", type:"stat", apply:()=>{ player.speed+=0.4 } }, { id:"damage_mult", name:"Globální poškození +15%", description:"Zvýší veškeré udělené poškození.", type:"stat", apply:()=>{ player.statModifiers.damageMultiplier+=0.15 } }, { id:"firerate_mult", name:"Globální kadence +15%", description:"Zvýší rychlost všech útoků.", type:"stat", apply:()=>{ player.statModifiers.fireRateMultiplier+=0.15 } }, { id:"area_mult", name:"Globální dosah +20%", description:"Zvýší dosah/oblast některých útoků.", type:"stat", apply:()=>{ player.statModifiers.areaMultiplier+=0.2 } }, ];
const WEAPON_DEFINITIONS={ 'pistol':{ name:"Pistole", baseFireRate:40, baseDamage:10, projectilesPerShot:1, maxLevel:5, upgradeDesc:"Střel/Pošk./Kadence" }, 'aura':{ name:"Aura", baseFireRate:70, baseDamage:5, baseRadius:60, maxLevel:5, upgradeDesc:"Pošk./Dosah/Interval" }, 'back_shot':{ name:"Zadní výstřel", baseFireRate:55, baseDamage:8, maxLevel:5, upgradeDesc:"Pošk./Kadence" }, };
function getWeaponUpgradeOptions() { let options=[]; const ownedWeaponIds=player.weapons.map(w => w.id); for(const weaponId in WEAPON_DEFINITIONS){ if(!ownedWeaponIds.includes(weaponId)){ options.push({ id:`add_${weaponId}`, name:`Nová zbraň: ${WEAPON_DEFINITIONS[weaponId].name}`, description:`Začneš používat tuto zbraň.`, type:'weapon_add', weaponId:weaponId }); } } player.weapons.forEach(weapon => { const def=WEAPON_DEFINITIONS[weapon.id]; if(weapon.level<def.maxLevel){ options.push({ id:`upgrade_${weapon.id}`, name:`Vylepšit: ${def.name} (Lvl ${weapon.level+1})`, description:`Zlepšuje: ${def.upgradeDesc}`, type:'weapon_upgrade', weaponId:weapon.id }); } }); return options; }
function showLevelUpOptions() { upgradeOptionsContainer.innerHTML=''; currentUpgradeChoices=[]; const allPossible=[...BASE_UPGRADES, ...getWeaponUpgradeOptions()]; allPossible.sort(() => Math.random()-0.5); const numOptions=Math.min(3,allPossible.length); for(let i=0; i<numOptions; i++){ const upgrade=allPossible[i]; currentUpgradeChoices.push(upgrade); const button=document.createElement('button'); button.classList.add('upgrade-button'); button.innerHTML=`${upgrade.name} <small>${upgrade.description}</small>`; button.onclick=() => selectUpgrade(i); upgradeOptionsContainer.appendChild(button); } levelUpScreen.style.display='block'; }
function selectUpgrade(choiceIndex) { const chosenUpgrade=currentUpgradeChoices[choiceIndex]; if(!chosenUpgrade) return; if(chosenUpgrade.type==='stat'){ chosenUpgrade.apply(); }else if(chosenUpgrade.type==='weapon_add'){ const def=WEAPON_DEFINITIONS[chosenUpgrade.weaponId]; player.weapons.push({ id:chosenUpgrade.weaponId, level:1, cooldown:0, baseFireRate:def.baseFireRate, baseDamage:def.baseDamage, projectilesPerShot:def.projectilesPerShot, baseRadius:def.baseRadius }); }else if(chosenUpgrade.type==='weapon_upgrade'){ const weaponToUpgrade=player.weapons.find(w => w.id===chosenUpgrade.weaponId); if(weaponToUpgrade){ weaponToUpgrade.level++; applyWeaponLevelBonuses(weaponToUpgrade); } } levelUpScreen.style.display='none'; isPaused=false; }
function applyWeaponLevelBonuses(weapon) { switch(weapon.id){ case 'pistol': weapon.projectilesPerShot=1+Math.floor((weapon.level-1)/2); weapon.baseDamage=10+(weapon.level-1)*3; weapon.baseFireRate=40-(weapon.level-1)*3; break; case 'aura': weapon.baseDamage=5+(weapon.level-1)*2; weapon.baseRadius=60+(weapon.level-1)*10; weapon.baseFireRate=70-(weapon.level-1)*5; break; case 'back_shot': weapon.baseDamage=8+(weapon.level-1)*4; weapon.baseFireRate=55-(weapon.level-1)*5; break; } weapon.baseFireRate=Math.max(5,weapon.baseFireRate); }
// --- Kreslící Funkce ---
// Funkce drawPixelArt není potřeba
// function drawPixelArt(sprite, x, y) { ... }
function draw() { ctx.fillStyle='#282c34'; ctx.fillRect(0,0,canvas.width,canvas.height); xpOrbs.forEach(orb => drawCircle(orb.x,orb.y,orb.size/2,orb.color)); projectiles.forEach(p => drawRect(p.x,p.y,p.size,p.size,p.color)); drawEnemies(); drawPlayer(); const auraWeapon=player.weapons.find(w=>w.id==='aura'); if(auraWeapon&&!isPaused){ const auraRadius=(auraWeapon.baseRadius||60)*player.statModifiers.areaMultiplier; ctx.beginPath(); ctx.arc(player.x+player.size/2,player.y+player.size/2,auraRadius,0,Math.PI*2); ctx.fillStyle='rgba(150,200,255,0.08)'; ctx.fill(); ctx.strokeStyle='rgba(200,220,255,0.15)'; ctx.lineWidth=2; ctx.stroke(); } }
function drawPlayer() {
if (playerInvincibleTimer > 0 && Math.floor(playerInvincibleTimer / 4) % 2 === 0) return; // Blikání
if (player.image && player.image.complete && player.image.naturalHeight !== 0) {
ctx.drawImage(player.image, player.x, player.y, player.size, player.size);
} else {
drawRect(player.x, player.y, player.size, player.size, '#61dafb'); // Záloha
}
}
// --- UPRAVENO: drawEnemies (používá přednačtené obrázky) ---
function drawEnemies() {
enemies.forEach(enemy => {
// Kresli obrázek, pokud je k dispozici a platný
if (enemy.image && enemy.image.complete && enemy.image.naturalHeight !== 0) {
try {
ctx.drawImage(enemy.image, enemy.x, enemy.y, enemy.size, enemy.size);
} catch (e) {
console.error(`Chyba při kreslení obrázku ${enemy.imageKey}:`, e);
drawRect(enemy.x, enemy.y, enemy.size, enemy.size, '#f44336'); // Záloha
}
} else {
// Pokud obrázek není připraven (nebo se nenačetl), kresli zálohu
drawRect(enemy.x, enemy.y, enemy.size, enemy.size, '#f44336');
}
// Ukazatel zdraví
if (enemy.health < enemy.maxHealth) {
const healthBarYOffset = enemy.isBoss ? 12 : 8; const healthBarWidth = enemy.size; const healthBarHeight = enemy.isBoss ? 6 : 4; const healthPercentage=Math.max(0,enemy.health/enemy.maxHealth); ctx.fillStyle= enemy.isBoss ? '#440000' : '#555'; ctx.fillRect(enemy.x,enemy.y-healthBarYOffset,healthBarWidth,healthBarHeight); ctx.fillStyle='#f44336'; ctx.fillRect(enemy.x,enemy.y-healthBarYOffset,healthBarWidth*healthPercentage,healthBarHeight);
}
});
}
// --- Konec úpravy drawEnemies ---
function drawRect(x, y, width, height, color) { ctx.fillStyle=color; ctx.fillRect(x,y,width,height); }
function drawCircle(x, y, radius, color) { ctx.fillStyle=color; ctx.beginPath(); ctx.arc(x+radius,y+radius,radius,0,Math.PI*2); ctx.fill(); ctx.closePath(); }
// --- Konec Hry ---
function showGameOver() { if(gameTimerInterval) clearInterval(gameTimerInterval); finalTimeDisplay.textContent=formatTime(gameTime); finalLevelDisplay.textContent=player.level; gameOverScreen.style.display='block'; }
// --- Ovládání ---
document.addEventListener('keydown',(e)=>{ if(["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","w","a","s","d"].includes(e.key)) e.preventDefault(); keysPressed[e.key.toLowerCase()]=true; });
document.addEventListener('keyup',(e)=>{ keysPressed[e.key.toLowerCase()]=false; });
canvas.addEventListener('mousemove',(evt)=>{ if(!isPaused&&!isGameOver) mousePos=getMousePos(canvas,evt); });
// --- Restart ---
restartButton.addEventListener('click', startGame);
// --- Spuštění Hry (Čeká na VŠECHNY obrázky pomocí flagu) ---
window.onload = () => {
console.log("DOM načten, čekám na dokončení načítání obrázků...");
function attemptStartGame() {
if (allImagesLoaded) { // Použij globální příznak
console.log("Všechny obrázky připraveny (nebo nastala chyba), spouštím hru...");
startGame();
} else {
// Pokud ještě nejsou načteny, zkus to znovu za chvíli
// console.log("Čekám na načtení všech obrázků..."); // Odkomentuj pro detailní logování
setTimeout(attemptStartGame, 100); // Zkus znovu za 100ms
}
}
attemptStartGame(); // Začni proces čekání/spuštění
};
// --- Konec Spuštění Hry ---
</script>
</body>
</html>