From 39a95e73372de08e6da259d17a3d397726d8e73b Mon Sep 17 00:00:00 2001 From: "m.kamzabaev" Date: Fri, 31 Jul 2026 11:03:17 +0000 Subject: [PATCH] fix: complete rewrite, simplified wave logic --- script.js | 385 +++++++++++++++++++++++++----------------------------- 1 file changed, 179 insertions(+), 206 deletions(-) diff --git a/script.js b/script.js index b109b68..cc41014 100644 --- a/script.js +++ b/script.js @@ -21,7 +21,6 @@ const player = { weaponLevel: 1, score: 0, invincible: 0, - level: 1, wave: 1 }; @@ -44,14 +43,14 @@ let keys = {}; let paused = false; let gameTime = 0; let waveTimer = 0; -let waveActive = false; -let enemiesInWave = 0; -let enemiesToSpawn = 5; -let bossActive = false; -let boss = null; +let lastShot = 0; let backgroundOffset = 0; let shake = 0; -let lastShot = 0; +let bossActive = false; +let boss = null; +let waveNum = 1; +let enemiesRemaining = 0; +let spawnTimer = 0; const enemyTypes = [ { name: 'soldier', width: 32, height: 48, hp: 50, speed: 1.5, color: '#8b0000', score: 100, weaponDrop: 0.25 }, @@ -61,27 +60,29 @@ const enemyTypes = [ { name: 'mech', width: 70, height: 55, hp: 250, speed: 0.6, color: '#004a6a', score: 500, weaponDrop: 0.7 } ]; -function startWave() { +function startNextWave() { if (bossActive) return; - waveActive = true; - enemiesToSpawn = 5 + player.wave * 2; - enemiesInWave = 0; - waveTimer = 0; + waveNum++; + enemiesRemaining = 5 + waveNum * 2; + spawnTimer = 0; + document.getElementById('wave-num').textContent = waveNum; } function spawnEnemy() { - const type = enemyTypes[Math.floor(Math.random() * Math.min(enemyTypes.length, 1 + player.wave))]; + const typeIdx = Math.min(Math.floor(waveNum / 2), enemyTypes.length - 1); + const availableTypes = enemyTypes.slice(0, Math.max(1, typeIdx + 1)); + const type = availableTypes[Math.floor(Math.random() * availableTypes.length)]; const isFly = type.fly; + enemies.push({ x: canvas.width + 50, y: isFly ? 100 + Math.random() * 150 : 452 - type.height, ...type, - maxHp: type.hp + player.wave * 15, - hp: type.hp + player.wave * 15, + maxHp: type.hp + waveNum * 15, + hp: type.hp + waveNum * 15, shootTimer: 0, animFrame: 0, - fly: isFly, - isBoss: false + fly: isFly }); } @@ -92,18 +93,16 @@ function spawnBoss() { y: 280, width: 120, height: 100, - hp: 3000 + player.wave * 500, - maxHp: 3000 + player.wave * 500, + hp: 3000, + maxHp: 3000, speed: 0.5, color: '#8b0000', score: 5000, animFrame: 0, - shootTimer: 0, - moveTimer: 0, - isBoss: true, - phase: 1 + moveTimer: 0 }; enemies = []; + enemiesRemaining = 0; } const powerupTypes = [ @@ -181,16 +180,12 @@ document.addEventListener('keydown', e => { player.weapon = (player.weapon + 1) % weapons.length; } if (e.code === 'KeyP' || e.code === 'Escape') { - togglePause(); + paused = !paused; + document.getElementById('pause-btn').textContent = paused ? '▶ Продолжить' : '⏸ Пауза'; } }); document.addEventListener('keyup', e => keys[e.code] = false); -function togglePause() { - paused = !paused; - document.getElementById('pause-btn').textContent = paused ? '▶ Продолжить' : '⏸ Пауза'; -} - function update() { if (paused) return; @@ -200,88 +195,31 @@ function update() { if (shake > 0) shake--; const wpn = weapons[player.weapon]; - const damageMultiplier = 1 + (player.weaponLevel - 1) * 0.15; + const damageMult = 1 + (player.weaponLevel - 1) * 0.15; - if (keys['ArrowLeft']) { player.vx = -player.speed; player.facing = -1; } - else if (keys['ArrowRight']) { player.vx = player.speed; player.facing = 1; } - else player.vx = 0; - - if (keys['ArrowUp'] && player.grounded) { - player.vy = player.jumpForce; - player.grounded = false; - } - - player.vy += 0.7; - player.x += player.vx; - player.y += player.vy; - - if (player.y > 452 - player.height) { - player.y = 452 - player.height; - player.vy = 0; - player.grounded = true; - } - - if (player.x < 0) player.x = 0; - if (player.x > canvas.width - player.width) player.x = canvas.width - player.width; - - if (keys['KeyZ'] && Date.now() - lastShot > wpn.fireRate) { - const count = wpn.count || 1; - for (let i = 0; i < count; i++) { - const angle = (count > 1) ? (i - (count-1)/2) * 0.15 : 0; - const spread = wpn.flame ? (Math.random() - 0.5) * 0.4 : 0; - const shootUp = keys['ArrowUp'] && !player.grounded ? -1 : 0; - bullets.push({ - x: player.x + player.width/2, - y: player.y + 14, - vx: (Math.cos(angle + spread) * wpn.bulletSpeed * player.facing) + (wpn.flame ? (Math.random() - 0.5) * 2 : 0), - vy: (shootUp !== 0 ? -wpn.bulletSpeed * 0.7 : 0) + Math.sin(angle + spread) * wpn.bulletSpeed + (wpn.flame ? (Math.random() - 0.5) * 2 : 0), - damage: wpn.damage * damageMultiplier, - color: wpn.color, - size: wpn.bulletSize, - glow: wpn.glow, - explosive: wpn.explosive, - flame: wpn.flame, - plasma: wpn.plasma, - laser: wpn.laser, - life: wpn.flame ? 20 : 100 - }); - } - lastShot = Date.now(); - } - - bullets = bullets.filter(b => { - b.x += b.vx; - b.y += b.vy; - if (b.laser) { - b.life = 10; - b.x += b.vx * 3; - b.y += b.vy * 3; - } - if (b.flame) b.life--; - return b.x > -50 && b.x < canvas.width + 50 && b.y > -50 && b.y < canvas.height + 50 && (!b.flame || b.life > 0); - }); - - if (!bossActive && waveActive) { - if (enemiesToSpawn > 0 && waveTimer % 40 === 0) { + // Спавн врагов + if (!bossActive && enemiesRemaining > 0) { + spawnTimer++; + if (spawnTimer > 50) { spawnEnemy(); - enemiesToSpawn--; - enemiesInWave++; - } - if (enemiesToSpawn <= 0 && enemies.length === 0) { - waveActive = false; - player.wave++; - document.getElementById('wave').textContent = player.wave; - if (player.score >= 10000 * player.wave && !bossActive) { - spawnBoss(); - } else { - setTimeout(startWave, 2000); - } + enemiesRemaining--; + spawnTimer = 0; + document.getElementById('wprogress').textContent = enemiesRemaining + '/' + (5 + waveNum * 2); } } + // Проверка завершения волны + if (!bossActive && enemiesRemaining === 0 && enemies.length === 0) { + if (player.score >= 10000 && waveNum >= 3) { + spawnBoss(); + } else { + startNextWave(); + } + } + + // Босс if (bossActive && boss) { boss.moveTimer++; - boss.shootTimer++; boss.animFrame++; boss.y += Math.sin(boss.moveTimer * 0.03) * 1.5; if (boss.x > 500) boss.x -= boss.speed; @@ -304,20 +242,76 @@ function update() { } boss = null; bossActive = false; - player.wave++; - document.getElementById('wave').textContent = player.wave; - setTimeout(startWave, 3000); + startNextWave(); } } + // Игрок + if (keys['ArrowLeft']) { player.vx = -player.speed; player.facing = -1; } + else if (keys['ArrowRight']) { player.vx = player.speed; player.facing = 1; } + else player.vx = 0; + + if (keys['ArrowUp'] && player.grounded) { + player.vy = player.jumpForce; + player.grounded = false; + } + + player.vy += 0.7; + player.x += player.vx; + player.y += player.vy; + + if (player.y > 452 - player.height) { + player.y = 452 - player.height; + player.vy = 0; + player.grounded = true; + } + + if (player.x < 0) player.x = 0; + if (player.x > canvas.width - player.width) player.x = canvas.width - player.width; + + // Стрельба + if (keys['KeyZ'] && Date.now() - lastShot > wpn.fireRate) { + const count = wpn.count || 1; + const shootUp = (keys['ArrowUp'] || (keys['ArrowUp'] && !player.grounded)) ? -1 : 0; + + for (let i = 0; i < count; i++) { + const angle = (count > 1) ? (i - (count-1)/2) * 0.15 : 0; + const spread = wpn.flame ? (Math.random() - 0.5) * 0.4 : 0; + bullets.push({ + x: player.x + player.width/2, + y: player.y + 14, + vx: Math.cos(angle + spread) * wpn.bulletSpeed * player.facing + (wpn.flame ? (Math.random() - 0.5) * 2 : 0), + vy: (shootUp ? -wpn.bulletSpeed * 0.7 : 0) + Math.sin(angle + spread) * wpn.bulletSpeed + (wpn.flame ? (Math.random() - 0.5) * 2 : 0), + damage: wpn.damage * damageMult, + color: wpn.color, + size: wpn.bulletSize, + glow: wpn.glow, + explosive: wpn.explosive, + flame: wpn.flame, + plasma: wpn.plasma, + laser: wpn.laser, + life: 100 + }); + } + lastShot = Date.now(); + } + + // Пули + bullets = bullets.filter(b => { + b.x += b.vx; + b.y += b.vy; + if (b.laser) b.life = 15; + if (b.flame) b.life--; + return b.life > 0 && b.x > -50 && b.x < canvas.width + 50 && b.y > -50 && b.y < canvas.height + 50; + }); + + // Враги enemies = enemies.filter(e => { e.x -= e.speed; e.shootTimer++; e.animFrame++; - if (e.fly) { - e.y += Math.sin(e.animFrame * 0.05) * 2; - } + if (e.fly) e.y += Math.sin(e.animFrame * 0.05) * 2; if (player.invincible <= 0 && player.x < e.x + e.width && player.x + player.width > e.x && @@ -331,50 +325,52 @@ function update() { return e.x > -100 && player.hp > 0; }); + // Попадания bullets = bullets.filter(b => { let hit = false; + // Босс if (bossActive && boss && b.x > boss.x && b.x < boss.x + boss.width && b.y > boss.y && b.y < boss.y + boss.height) { boss.hp -= b.damage; hit = true; - createExplosion(b.x, b.y, b.color, b.explosive ? 25 : b.laser ? 15 : 10); + createExplosion(b.x, b.y, b.color, b.laser ? 15 : 10); createSparks(b.x, b.y, 5); - if (boss.hp <= boss.maxHp * 0.5 && boss.phase === 1) { - boss.phase = 2; - boss.speed = 1.0; - } } + // Обычные враги enemies.forEach((e, ei) => { if (b.x > e.x && b.x < e.x + e.width && b.y > e.y && b.y < e.y + e.height) { e.hp -= b.damage; hit = true; - createExplosion(b.x, b.y, b.color, b.explosive ? 25 : b.laser ? 15 : 10); + createExplosion(b.x, b.y, b.color, 10); if (e.hp <= 0) { player.score += e.score; - createBlood(e.x + e.width/2, e.y + e.height/2, e.name === 'tank' || e.name === 'mech' ? 20 : 12); + createBlood(e.x + e.width/2, e.y + e.height/2, 12); if (Math.random() < e.weaponDrop) spawnPowerup(e.x, e.y); enemies.splice(ei, 1); } if (b.explosive) { - explosions.push({ x: b.x, y: b.y, radius: 0, maxRadius: 60, alpha: 1 }); + explosions.push({ x: b.x, y: b.y, radius: 0, alpha: 1 }); shake = 3; } } }); + return !hit; }); + // Взрывы explosions = explosions.filter(e => { e.radius += 4; e.alpha -= 0.05; return e.alpha > 0; }); + // Предметы powerups = powerups.filter(p => { p.vy += 0.5; p.y += p.vy; @@ -400,19 +396,8 @@ function update() { if (p.shield) player.invincible = 300; if (p.bomb) { enemies.forEach(e => { e.hp -= 150; createExplosion(e.x + e.width/2, e.y + e.height/2, '#ff6600', 10); }); - enemies = enemies.filter(e => { - if (e.hp <= 0) { - player.score += e.score; - createBlood(e.x + e.width/2, e.y + e.height/2, 15); - return false; - } - return true; - }); - if (bossActive && boss) { - boss.hp -= 500; - createExplosion(boss.x + boss.width/2, boss.y + boss.height/2, '#ff6600', 30); - shake = 15; - } + enemies = enemies.filter(e => e.hp > 0); + if (bossActive && boss) { boss.hp -= 500; shake = 15; } } player.score += 50; createExplosion(p.x + p.width/2, p.y + p.height/2, p.color, 20); @@ -421,6 +406,7 @@ function update() { return true; }); + // Частицы particles = particles.filter(p => { p.x += p.vx; p.y += p.vy; @@ -434,26 +420,26 @@ function update() { backgroundOffset += player.vx * 0.3; + // UI document.getElementById('hp').textContent = player.hp; document.getElementById('maxhp').textContent = player.maxHp; document.getElementById('wpn').textContent = weapons[player.weapon].name + (player.weaponLevel > 1 ? ' +' + (player.weaponLevel - 1) : ''); document.getElementById('scr').textContent = player.score; - document.getElementById('wave-num').textContent = player.wave; - document.getElementById('wprogress').textContent = (enemiesToSpawn > 0 ? enemiesToSpawn : 0) + '/' + enemiesInWave; if (player.hp <= 0) { - alert('GAME OVER! Счёт: ' + player.score + ', Волна: ' + player.wave); + alert('GAME OVER! Счёт: ' + player.score + ', Волна: ' + waveNum); resetGame(); } } function resetGame() { - player.x = 100; player.y = 350; player.hp = 100; player.maxHp = 100; player.score = 0; - player.weapon = 0; player.weaponLevel = 1; player.level = 1; player.wave = 1; - bullets = []; enemies = []; powerups = []; particles = []; explosions = []; boss = null; - bossActive = false; waveActive = false; + player.x = 100; player.y = 350; player.hp = 100; player.maxHp = 100; + player.score = 0; player.weapon = 0; player.weaponLevel = 1; + waveNum = 1; enemiesRemaining = 5; spawnTimer = 0; + bullets = []; enemies = []; powerups = []; particles = []; explosions = []; + boss = null; bossActive = false; document.getElementById('wave-num').textContent = '1'; - startWave(); + document.getElementById('wprogress').textContent = '5/5'; } function draw() { @@ -462,6 +448,7 @@ function draw() { ctx.translate((Math.random() - 0.5) * shake * 2, (Math.random() - 0.5) * shake * 2); } + // Небо const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height); gradient.addColorStop(0, '#0a0a1a'); gradient.addColorStop(0.5, '#16213e'); @@ -469,6 +456,7 @@ function draw() { ctx.fillStyle = gradient; ctx.fillRect(0, 0, canvas.width, canvas.height); + // Звёзды ctx.fillStyle = '#fff'; for (let i = 0; i < 80; i++) { const x = ((i * 73 + backgroundOffset * 0.1) % canvas.width + canvas.width) % canvas.width; @@ -479,6 +467,7 @@ function draw() { } ctx.globalAlpha = 1; + // Горы ctx.fillStyle = '#1a1a2e'; for (let i = 0; i < 8; i++) { const x = ((i * 120 - backgroundOffset * 0.2) % 1200) - 200; @@ -489,30 +478,31 @@ function draw() { ctx.fill(); } + // Город ctx.fillStyle = '#2a2a3e'; for (let i = 0; i < canvas.width; i += 60) { - const buildingH = 80 + Math.sin(i) * 30; - ctx.fillRect(i, 452 - buildingH, 50, buildingH); - const windows = Math.floor(buildingH / 20); - for (let w = 0; w < windows; w++) { - for (let col = 0; col < 3; col++) { - if (Math.sin(gameTime * 0.02 + i + w + col) > 0.3) { + const h = 80 + Math.sin(i) * 30; + ctx.fillRect(i, 452 - h, 50, h); + for (let w = 0; w < Math.floor(h / 20); w++) { + for (let c = 0; c < 3; c++) { + if (Math.sin(gameTime * 0.02 + i + w + c) > 0.3) { ctx.fillStyle = '#ffff88'; - ctx.fillRect(i + 8 + col * 14, 452 - buildingH + 10 + w * 20, 8, 12); + ctx.fillRect(i + 8 + c * 14, 452 - h + 10 + w * 20, 8, 12); ctx.fillStyle = '#2a2a3e'; } } } } + // Земля ctx.fillStyle = '#3a2e1a'; ctx.fillRect(0, 452, canvas.width, 48); ctx.fillStyle = '#4a3a2a'; ctx.fillRect(0, 452, canvas.width, 8); ctx.fillStyle = '#2d4a1e'; for (let i = 0; i < canvas.width; i += 20) { - const grassH = 4 + Math.sin(gameTime * 0.05 + i) * 2; - ctx.fillRect(i, 452 - grassH, 8, grassH); + const gh = 4 + Math.sin(gameTime * 0.05 + i) * 2; + ctx.fillRect(i, 452 - gh, 8, gh); } ctx.fillStyle = '#5a4a3a'; ctx.fillRect(120, 456, 16, 10); @@ -520,11 +510,12 @@ function draw() { ctx.fillRect(580, 454, 14, 12); ctx.fillRect(720, 458, 18, 8); + // Игрок if (player.invincible % 4 < 2) { - const legOffset = player.vx !== 0 ? Math.sin(Date.now() * 0.02) * 6 : 0; + const lo = player.vx !== 0 ? Math.sin(Date.now() * 0.02) * 6 : 0; ctx.fillStyle = '#2a3a5a'; - ctx.fillRect(player.x + 6, player.y + 36, 8, 12 + legOffset); - ctx.fillRect(player.x + 18, player.y + 36, 8, 12 - legOffset); + ctx.fillRect(player.x + 6, player.y + 36, 8, 12 + lo); + ctx.fillRect(player.x + 18, player.y + 36, 8, 12 - lo); ctx.fillStyle = '#1a1a2e'; ctx.fillRect(player.x + 4, player.y + 44, 10, 4); ctx.fillRect(player.x + 18, player.y + 44, 10, 4); @@ -541,10 +532,10 @@ function draw() { ctx.fillStyle = '#2a3a4a'; ctx.fillRect(player.x + 8, player.y - 6, 14, 6); ctx.fillStyle = '#fff'; - const eyeX = player.facing === 1 ? player.x + 18 : player.x + 10; - ctx.fillRect(eyeX, player.y + 2, 6, 4); + const ex = player.facing === 1 ? player.x + 18 : player.x + 10; + ctx.fillRect(ex, player.y + 2, 6, 4); ctx.fillStyle = '#000'; - ctx.fillRect(player.facing === 1 ? eyeX + 2 : eyeX, player.y + 3, 3, 2); + ctx.fillRect(player.facing === 1 ? ex + 2 : ex, player.y + 3, 3, 2); ctx.fillStyle = '#ffccaa'; ctx.fillRect(player.x + 14, player.y + 16, 10, 10); ctx.fillStyle = '#4a90d9'; @@ -562,6 +553,7 @@ function draw() { } } + // Пули bullets.forEach(b => { if (b.laser) { ctx.strokeStyle = b.glow; @@ -578,10 +570,10 @@ function draw() { ctx.arc(b.x, b.y, b.size, 0, Math.PI * 2); ctx.fill(); } else { - const glowGradient = ctx.createRadialGradient(b.x, b.y, 0, b.x, b.y, b.size * 3); - glowGradient.addColorStop(0, b.glow); - glowGradient.addColorStop(1, 'transparent'); - ctx.fillStyle = glowGradient; + const gg = ctx.createRadialGradient(b.x, b.y, 0, b.x, b.y, b.size * 3); + gg.addColorStop(0, b.glow); + gg.addColorStop(1, 'transparent'); + ctx.fillStyle = gg; ctx.beginPath(); ctx.arc(b.x, b.y, b.size * 3, 0, Math.PI * 2); ctx.fill(); @@ -589,28 +581,18 @@ function draw() { ctx.beginPath(); ctx.arc(b.x, b.y, b.size, 0, Math.PI * 2); ctx.fill(); - if (b.plasma) { - ctx.strokeStyle = b.glow; - ctx.lineWidth = b.size; - ctx.beginPath(); - ctx.moveTo(b.x, b.y); - ctx.lineTo(b.x - b.vx * 2, b.y - b.vy * 2); - ctx.stroke(); - } } }); + // Взрывы explosions.forEach(e => { ctx.fillStyle = `rgba(255, 100, 0, ${e.alpha})`; ctx.beginPath(); ctx.arc(e.x, e.y, e.radius, 0, Math.PI * 2); ctx.fill(); - ctx.fillStyle = `rgba(255, 200, 50, ${e.alpha * 0.7})`; - ctx.beginPath(); - ctx.arc(e.x, e.y, e.radius * 0.7, 0, Math.PI * 2); - ctx.fill(); }); + // Частицы particles.forEach(p => { ctx.globalAlpha = p.life / 50; ctx.fillStyle = p.color; @@ -620,6 +602,7 @@ function draw() { }); ctx.globalAlpha = 1; + // Босс if (bossActive && boss) { boss.animFrame++; ctx.fillStyle = '#3a1a1a'; @@ -638,6 +621,7 @@ function draw() { ctx.fillStyle = '#5a2a2a'; ctx.fillRect(boss.x, boss.y + 60, 20, 40); ctx.fillRect(boss.x + 100, boss.y + 60, 20, 40); + const hpPct = boss.hp / boss.maxHp; ctx.fillStyle = '#333'; ctx.fillRect(boss.x, boss.y - 25, boss.width, 12); @@ -650,42 +634,37 @@ function draw() { ctx.textAlign = 'left'; } + // Враги enemies.forEach(e => { e.animFrame++; if (e.name === 'soldier') { - const legOffset = Math.sin(e.animFrame * 0.3) * 6; + const lo = Math.sin(e.animFrame * 0.3) * 6; ctx.fillStyle = '#1a1a2e'; - ctx.fillRect(e.x + 6, e.y + 36, 8, 12 + legOffset); - ctx.fillRect(e.x + 18, e.y + 36, 8, 12 - legOffset); + ctx.fillRect(e.x + 6, e.y + 36, 8, 12 + lo); + ctx.fillRect(e.x + 18, e.y + 36, 8, 12 - lo); ctx.fillStyle = e.color; ctx.fillRect(e.x + 4, e.y + 14, 24, 24); ctx.fillStyle = '#ffccaa'; ctx.fillRect(e.x + 6, e.y, 20, 16); ctx.fillStyle = '#3a4a3a'; ctx.fillRect(e.x + 4, e.y - 4, 24, 8); - ctx.fillStyle = '#4a2a2a'; - ctx.fillRect(e.x + 16, e.y + 18, 14, 8); ctx.fillStyle = '#444'; ctx.fillRect(e.x + 28, e.y + 20, 12, 6); } else if (e.name === 'tank') { ctx.fillStyle = '#1a1a1a'; - const trackOffset = Math.sin(e.animFrame * 0.1) * 2; - ctx.fillRect(e.x, e.y + 24 + trackOffset, e.width, 11); + const to = Math.sin(e.animFrame * 0.1) * 2; + ctx.fillRect(e.x, e.y + 24 + to, e.width, 11); ctx.fillStyle = e.color; ctx.fillRect(e.x + 4, e.y + 8, e.width - 8, 20); ctx.fillStyle = '#3d6016'; ctx.fillRect(e.x + 18, e.y, 24, 14); ctx.fillStyle = '#2d4010'; ctx.fillRect(e.x + 36, e.y + 4, 28, 8); - ctx.fillStyle = '#1a1a1a'; - ctx.beginPath(); - ctx.arc(e.x + 30, e.y + 7, 5, 0, Math.PI * 2); - ctx.fill(); } else if (e.name === 'helicopter') { ctx.fillStyle = '#333'; - const rotorOffset = Math.sin(e.animFrame * 0.8) * 20; + const ro = Math.sin(e.animFrame * 0.8) * 20; ctx.fillRect(e.x - 15, e.y - 8, 85, 4); - ctx.fillRect(e.x + 25 + rotorOffset, e.y - 12, 6, 8); + ctx.fillRect(e.x + 25 + ro, e.y - 12, 6, 8); ctx.fillStyle = e.color; ctx.beginPath(); ctx.ellipse(e.x + 25, e.y + 8, 30, 15, 0, 0, Math.PI * 2); @@ -694,29 +673,24 @@ function draw() { ctx.beginPath(); ctx.arc(e.x + 40, e.y + 5, 12, 0, Math.PI * 2); ctx.fill(); - ctx.fillStyle = e.color; - ctx.fillRect(e.x - 20, e.y + 4, 20, 8); - ctx.fillRect(e.x - 25, e.y, 8, 16); } else if (e.name === 'commando') { - const legOffset = Math.sin(e.animFrame * 0.4) * 8; + const lo = Math.sin(e.animFrame * 0.4) * 8; ctx.fillStyle = '#2a1a2a'; - ctx.fillRect(e.x + 8, e.y + 38, 7, 12 + legOffset); - ctx.fillRect(e.x + 19, e.y + 38, 7, 12 - legOffset); + ctx.fillRect(e.x + 8, e.y + 38, 7, 12 + lo); + ctx.fillRect(e.x + 19, e.y + 38, 7, 12 - lo); ctx.fillStyle = e.color; ctx.fillRect(e.x + 6, e.y + 16, 22, 24); ctx.fillStyle = '#ffccaa'; ctx.fillRect(e.x + 8, e.y + 2, 18, 16); - ctx.fillStyle = '#4a1a4a'; - ctx.fillRect(e.x + 6, e.y - 2, 22, 8); ctx.fillStyle = '#6a2a6a'; ctx.fillRect(e.x + 18, e.y + 20, 16, 8); ctx.fillStyle = '#333'; ctx.fillRect(e.x + 32, e.y + 22, 14, 6); } else if (e.name === 'mech') { - const legOffset = Math.sin(e.animFrame * 0.15) * 4; + const lo = Math.sin(e.animFrame * 0.15) * 4; ctx.fillStyle = '#1a2a3a'; - ctx.fillRect(e.x + 10, e.y + 40, 14, 15 + legOffset); - ctx.fillRect(e.x + 46, e.y + 40, 14, 15 - legOffset); + ctx.fillRect(e.x + 10, e.y + 40, 14, 15 + lo); + ctx.fillRect(e.x + 46, e.y + 40, 14, 15 - lo); ctx.fillStyle = e.color; ctx.fillRect(e.x + 8, e.y + 20, 54, 28); ctx.fillRect(e.x + 20, e.y + 8, 30, 20); @@ -724,8 +698,6 @@ function draw() { ctx.beginPath(); ctx.arc(e.x + 35, e.y + 18, 10, 0, Math.PI * 2); ctx.fill(); - ctx.fillStyle = '#003344'; - ctx.fillRect(e.x + 50, e.y + 24, 24, 10); ctx.fillStyle = '#ff4400'; ctx.beginPath(); ctx.arc(e.x + 35, e.y + 18, 4, 0, Math.PI * 2); @@ -739,24 +711,25 @@ function draw() { ctx.fillRect(e.x + 1, e.y - 15, (e.width - 2) * hpPct, 6); }); + // Предметы powerups.forEach(p => { - const bobY = p.grounded ? Math.sin(p.bobOffset) * 4 : 0; - const glowGradient = ctx.createRadialGradient(p.x + p.width/2, p.y + p.height/2 + bobY, 0, p.x + p.width/2, p.y + p.height/2 + bobY, 20); - glowGradient.addColorStop(0, p.color); - glowGradient.addColorStop(1, 'transparent'); - ctx.fillStyle = glowGradient; + const by = p.grounded ? Math.sin(p.bobOffset) * 4 : 0; + const gg = ctx.createRadialGradient(p.x + p.width/2, p.y + p.height/2 + by, 0, p.x + p.width/2, p.y + p.height/2 + by, 20); + gg.addColorStop(0, p.color); + gg.addColorStop(1, 'transparent'); + ctx.fillStyle = gg; ctx.beginPath(); - ctx.arc(p.x + p.width/2, p.y + p.height/2 + bobY, 20, 0, Math.PI * 2); + ctx.arc(p.x + p.width/2, p.y + p.height/2 + by, 20, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = p.color; - ctx.fillRect(p.x + 2, p.y + 4 + bobY, p.width - 4, p.height - 4); + ctx.fillRect(p.x + 2, p.y + 4 + by, p.width - 4, p.height - 4); ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; - ctx.strokeRect(p.x + 2, p.y + 4 + bobY, p.width - 4, p.height - 4); + ctx.strokeRect(p.x + 2, p.y + 4 + by, p.width - 4, p.height - 4); ctx.fillStyle = '#fff'; ctx.font = 'bold 18px monospace'; ctx.textAlign = 'center'; - ctx.fillText(p.label, p.x + p.width/2, p.y + 20 + bobY); + ctx.fillText(p.label, p.x + p.width/2, p.y + 20 + by); ctx.textAlign = 'left'; }); @@ -769,5 +742,5 @@ function loop() { requestAnimationFrame(loop); } -startWave(); +resetGame(); loop();