From 22410c3a6c1eca65bdf3190f7a8b3c792e04b880 Mon Sep 17 00:00:00 2001 From: "m.kamzabaev" Date: Fri, 31 Jul 2026 11:13:05 +0000 Subject: [PATCH] Vietnam jungle theme, mech spider boss, ending scene, improved graphics --- index.html | 14 +- script.js | 834 ++++++++++++++++++++++++++++++++--------------------- style.css | 45 ++- 3 files changed, 541 insertions(+), 352 deletions(-) diff --git a/index.html b/index.html index 38015f7..3fbff4f 100644 --- a/index.html +++ b/index.html @@ -2,21 +2,21 @@ - Contra-style Shooter + Vietnam Shooter
-
HP: 100/100
-
Оружие: Пистолет
-
Волна: 1 (5/5)
-
Счёт: 0
+
HP: 100/100
+
Оружие: Пистолет
+
Волна: 1 (6/6)
+
Счёт: 0
+
-
- ← → — бег | ↑ — прыжок | Z — стрельба | X — сменить оружие + ← → — бег | Пробел — прыжок | Z — огонь | ↑ — прицел вверх | X — смена оружия | P — пауза
diff --git a/script.js b/script.js index cc41014..ecf8476 100644 --- a/script.js +++ b/script.js @@ -21,17 +21,18 @@ const player = { weaponLevel: 1, score: 0, invincible: 0, - wave: 1 + wave: 1, + aimUp: false }; const weapons = [ { name: 'Пистолет', damage: 25, fireRate: 300, color: '#ffff00', bulletSpeed: 10, bulletSize: 6, glow: '#ffff88' }, - { name: 'Автомат', damage: 15, fireRate: 100, color: '#ff8800', bulletSpeed: 12, bulletSize: 5, glow: '#ffcc00' }, - { name: 'Дробовик', damage: 12, fireRate: 600, color: '#ff0000', bulletSpeed: 9, bulletSize: 4, count: 5, glow: '#ff6666' }, - { name: 'Лазер', damage: 45, fireRate: 350, color: '#00ffff', bulletSpeed: 20, bulletSize: 4, glow: '#88ffff', laser: true }, - { name: 'Ракетница', damage: 80, fireRate: 800, color: '#aa00ff', bulletSpeed: 7, bulletSize: 10, glow: '#dd88ff', explosive: true }, - { name: 'Огнемёт', damage: 8, fireRate: 50, color: '#ff4400', bulletSpeed: 6, bulletSize: 12, glow: '#ff8800', flame: true }, - { name: 'Плазма', damage: 35, fireRate: 200, color: '#00ff00', bulletSpeed: 11, bulletSize: 14, glow: '#88ff88', plasma: true } + { name: 'Автомат', damage: 18, fireRate: 120, color: '#ff8800', bulletSpeed: 12, bulletSize: 5, glow: '#ffcc00' }, + { name: 'Дробовик', damage: 15, fireRate: 700, color: '#ff0000', bulletSpeed: 9, bulletSize: 4, count: 6, glow: '#ff6666' }, + { name: 'Лазер', damage: 50, fireRate: 400, color: '#00ffff', bulletSpeed: 20, bulletSize: 4, glow: '#88ffff', laser: true }, + { name: 'Ракетница', damage: 90, fireRate: 900, color: '#aa00ff', bulletSpeed: 7, bulletSize: 10, glow: '#dd88ff', explosive: true }, + { name: 'Огнемёт', damage: 10, fireRate: 60, color: '#ff4400', bulletSpeed: 6, bulletSize: 12, glow: '#ff8800', flame: true }, + { name: 'Плазма', damage: 40, fireRate: 250, color: '#00ff00', bulletSpeed: 11, bulletSize: 14, glow: '#88ff88', plasma: true } ]; let bullets = []; @@ -42,7 +43,6 @@ let explosions = []; let keys = {}; let paused = false; let gameTime = 0; -let waveTimer = 0; let lastShot = 0; let backgroundOffset = 0; let shake = 0; @@ -51,21 +51,24 @@ let boss = null; let waveNum = 1; let enemiesRemaining = 0; let spawnTimer = 0; +let gameWon = false; +let endingTimer = 0; const enemyTypes = [ - { name: 'soldier', width: 32, height: 48, hp: 50, speed: 1.5, color: '#8b0000', score: 100, weaponDrop: 0.25 }, - { name: 'tank', width: 60, height: 35, hp: 150, speed: 0.8, color: '#2d5016', score: 300, weaponDrop: 0.4 }, - { name: 'helicopter', width: 55, height: 30, hp: 80, speed: 2.2, color: '#4a4a6a', score: 200, weaponDrop: 0.35, fly: true }, - { name: 'commando', width: 34, height: 50, hp: 100, speed: 2.0, color: '#6a006a', score: 250, weaponDrop: 0.5 }, - { name: 'mech', width: 70, height: 55, hp: 250, speed: 0.6, color: '#004a6a', score: 500, weaponDrop: 0.7 } + { name: 'soldier', width: 32, height: 48, hp: 60, speed: 1.5, color: '#4a5a2a', score: 100, weaponDrop: 0.3 }, + { name: 'tank', width: 75, height: 40, hp: 180, speed: 0.7, color: '#3a5a3a', score: 350, weaponDrop: 0.5 }, + { name: 'helicopter', width: 70, height: 35, hp: 100, speed: 2.5, color: '#5a6a7a', score: 250, weaponDrop: 0.4, fly: true }, + { name: 'commando', width: 34, height: 50, hp: 120, speed: 2.2, color: '#5a3a5a', score: 300, weaponDrop: 0.5 }, + { name: 'mech', width: 75, height: 60, hp: 300, speed: 0.5, color: '#2a5a6a', score: 600, weaponDrop: 0.8 } ]; function startNextWave() { - if (bossActive) return; + if (bossActive || gameWon) return; waveNum++; - enemiesRemaining = 5 + waveNum * 2; + enemiesRemaining = 6 + waveNum * 2; spawnTimer = 0; document.getElementById('wave-num').textContent = waveNum; + document.getElementById('wprogress').textContent = enemiesRemaining + '/' + (6 + waveNum * 2); } function spawnEnemy() { @@ -76,10 +79,10 @@ function spawnEnemy() { enemies.push({ x: canvas.width + 50, - y: isFly ? 100 + Math.random() * 150 : 452 - type.height, + y: isFly ? 80 + Math.random() * 120 : 452 - type.height, ...type, - maxHp: type.hp + waveNum * 15, - hp: type.hp + waveNum * 15, + maxHp: type.hp + waveNum * 20, + hp: type.hp + waveNum * 20, shootTimer: 0, animFrame: 0, fly: isFly @@ -89,22 +92,28 @@ function spawnEnemy() { function spawnBoss() { bossActive = true; boss = { - x: canvas.width - 150, - y: 280, - width: 120, - height: 100, - hp: 3000, - maxHp: 3000, - speed: 0.5, - color: '#8b0000', - score: 5000, + x: canvas.width - 180, + y: 320, + width: 160, + height: 90, + hp: 5000, + maxHp: 5000, + speed: 0.4, + color: '#2a3a4a', + score: 10000, animFrame: 0, - moveTimer: 0 + moveTimer: 0, + legOffset: [0,0,0,0,0,0,0,0] }; enemies = []; enemiesRemaining = 0; } +function showEnding() { + gameWon = true; + endingTimer = 0; +} + const powerupTypes = [ { type: 'pistol', color: '#ffff00', label: 'P', weapon: 0 }, { type: 'machinegun', color: '#ff8800', label: 'M', weapon: 1 }, @@ -114,7 +123,7 @@ const powerupTypes = [ { type: 'flame', color: '#ff4400', label: 'F', weapon: 5 }, { type: 'plasma', color: '#00ff00', label: 'G', weapon: 6 }, { type: 'upgrade', color: '#ff00ff', label: '↑', upgrade: true }, - { type: 'health', color: '#00ff00', label: '+', heal: 30 }, + { type: 'health', color: '#00ff00', label: '+', heal: 35 }, { type: 'maxhealth', color: '#ff0066', label: '♥', maxHeal: true }, { type: 'shield', color: '#0088ff', label: 'S', shield: true }, { type: 'bomb', color: '#ff6600', label: 'B', bomb: true } @@ -123,7 +132,7 @@ const powerupTypes = [ function spawnPowerup(x, y) { const t = powerupTypes[Math.floor(Math.random() * powerupTypes.length)]; powerups.push({ - x, y, width: 28, height: 28, + x, y, width: 30, height: 30, ...t, vy: -6, grounded: false, @@ -131,45 +140,45 @@ function spawnPowerup(x, y) { }); } -function createExplosion(x, y, color, count = 15) { +function createExplosion(x, y, color, count = 20) { for (let i = 0; i < count; i++) { const angle = (Math.PI * 2 / count) * i; - const speed = 2 + Math.random() * 4; + const speed = 3 + Math.random() * 5; particles.push({ x, y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, - life: 30 + Math.random() * 20, + life: 40 + Math.random() * 20, color: color, - size: 3 + Math.random() * 4 + size: 4 + Math.random() * 5 }); } } -function createBlood(x, y, count = 10) { +function createBlood(x, y, count = 15) { + for (let i = 0; i < count; i++) { + particles.push({ + x, y, + vx: (Math.random() - 0.5) * 10, + vy: (Math.random() - 0.5) * 10 - 3, + life: 50 + Math.random() * 20, + color: '#cc0000', + size: 3 + Math.random() * 4, + gravity: 0.5 + }); + } +} + +function createSparks(x, y, count = 12) { for (let i = 0; i < count; i++) { particles.push({ x, y, vx: (Math.random() - 0.5) * 8, - vy: (Math.random() - 0.5) * 8 - 3, - life: 40 + Math.random() * 20, - color: '#cc0000', - size: 2 + Math.random() * 3, - gravity: 0.4 - }); - } -} - -function createSparks(x, y, count = 8) { - for (let i = 0; i < count; i++) { - particles.push({ - x, y, - vx: (Math.random() - 0.5) * 6, - vy: (Math.random() - 0.5) * 6, - life: 20 + Math.random() * 10, + vy: (Math.random() - 0.5) * 8, + life: 25 + Math.random() * 15, color: '#ffff00', - size: 2 + Math.random() * 2, - gravity: 0.2 + size: 3 + Math.random() * 3, + gravity: 0.3 }); } } @@ -183,79 +192,31 @@ document.addEventListener('keydown', e => { paused = !paused; document.getElementById('pause-btn').textContent = paused ? '▶ Продолжить' : '⏸ Пауза'; } + if (e.code === 'Space' && player.grounded) { + player.vy = player.jumpForce; + player.grounded = false; + } }); document.addEventListener('keyup', e => keys[e.code] = false); function update() { - if (paused) return; + if (paused || gameWon) return; gameTime++; - waveTimer++; if (shake > 0) shake--; const wpn = weapons[player.weapon]; - const damageMult = 1 + (player.weaponLevel - 1) * 0.15; + const fireRateReduction = (player.weaponLevel - 1) * 25; + const currentFireRate = Math.max(30, wpn.fireRate - fireRateReduction); + const damageMult = 1 + (player.weaponLevel - 1) * 0.2; - // Спавн врагов - if (!bossActive && enemiesRemaining > 0) { - spawnTimer++; - if (spawnTimer > 50) { - spawnEnemy(); - enemiesRemaining--; - spawnTimer = 0; - document.getElementById('wprogress').textContent = enemiesRemaining + '/' + (5 + waveNum * 2); - } - } + player.aimUp = keys['ArrowUp'] && !player.grounded; - // Проверка завершения волны - if (!bossActive && enemiesRemaining === 0 && enemies.length === 0) { - if (player.score >= 10000 && waveNum >= 3) { - spawnBoss(); - } else { - startNextWave(); - } - } - - // Босс - if (bossActive && boss) { - boss.moveTimer++; - boss.animFrame++; - boss.y += Math.sin(boss.moveTimer * 0.03) * 1.5; - if (boss.x > 500) boss.x -= boss.speed; - - if (player.invincible <= 0 && - player.x < boss.x + boss.width && player.x + player.width > boss.x && - player.y < boss.y + boss.height && player.y + player.height > boss.y) { - player.hp -= 30; - player.invincible = 90; - shake = 10; - createBlood(player.x + player.width/2, player.y + player.height/2, 20); - } - - if (boss.hp <= 0) { - player.score += boss.score; - createExplosion(boss.x + boss.width/2, boss.y + boss.height/2, '#ff0000', 100); - createBlood(boss.x + boss.width/2, boss.y + boss.height/2, 50); - for (let i = 0; i < 5; i++) { - spawnPowerup(boss.x + Math.random() * boss.width, boss.y + Math.random() * boss.height); - } - boss = null; - bossActive = false; - 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; @@ -269,19 +230,20 @@ function update() { 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) { + if (keys['KeyZ'] && Date.now() - lastShot > currentFireRate) { const count = wpn.count || 1; - const shootUp = (keys['ArrowUp'] || (keys['ArrowUp'] && !player.grounded)) ? -1 : 0; + const shootUp = player.aimUp; 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 angle = (count > 1) ? (i - (count-1)/2) * 0.2 : 0; + const spread = wpn.flame ? (Math.random() - 0.5) * 0.5 : 0; + const baseAngle = shootUp ? -Math.PI/2 + angle : angle; + 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), + vx: Math.cos(baseAngle + spread) * wpn.bulletSpeed * (shootUp ? 1 : player.facing) + (wpn.flame ? (Math.random() - 0.5) * 2 : 0), + vy: Math.sin(baseAngle + spread) * wpn.bulletSpeed + (wpn.flame ? (Math.random() - 0.5) * 2 : 0), damage: wpn.damage * damageMult, color: wpn.color, size: wpn.bulletSize, @@ -296,7 +258,6 @@ function update() { lastShot = Date.now(); } - // Пули bullets = bullets.filter(b => { b.x += b.vx; b.y += b.vy; @@ -305,57 +266,104 @@ function update() { return b.life > 0 && b.x > -50 && b.x < canvas.width + 50 && b.y > -50 && b.y < canvas.height + 50; }); - // Враги + if (!bossActive && enemiesRemaining > 0) { + spawnTimer++; + if (spawnTimer > 45) { + spawnEnemy(); + enemiesRemaining--; + spawnTimer = 0; + document.getElementById('wprogress').textContent = enemiesRemaining + '/' + (6 + waveNum * 2); + } + } + + if (!bossActive && enemiesRemaining === 0 && enemies.length === 0) { + if (player.score >= 30000 && waveNum >= 2) { + spawnBoss(); + } else { + startNextWave(); + } + } + + if (bossActive && boss) { + boss.moveTimer++; + boss.animFrame++; + + for (let i = 0; i < 8; i++) { + boss.legOffset[i] = Math.sin(boss.moveTimer * 0.08 + i * 0.8) * 8; + } + + boss.y += Math.sin(boss.moveTimer * 0.02) * 0.5; + if (boss.x > 450) boss.x -= boss.speed; + + if (player.invincible <= 0 && + player.x < boss.x + boss.width && player.x + player.width > boss.x && + player.y < boss.y + boss.height && player.y + player.height > boss.y) { + player.hp -= 35; + player.invincible = 90; + shake = 12; + createBlood(player.x + player.width/2, player.y + player.height/2, 25); + } + + if (boss.hp <= 0) { + player.score += boss.score; + createExplosion(boss.x + boss.width/2, boss.y + boss.height/2, '#ff4400', 150); + createBlood(boss.x + boss.width/2, boss.y + boss.height/2, 80); + for (let i = 0; i < 8; i++) { + spawnPowerup(boss.x + Math.random() * boss.width, boss.y + Math.random() * boss.height); + } + boss = null; + bossActive = false; + showEnding(); + } + } + 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.06) * 2.5; if (player.invincible <= 0 && player.x < e.x + e.width && player.x + player.width > e.x && player.y < e.y + e.height && player.y + player.height > e.y) { - player.hp -= 20; + player.hp -= 25; player.invincible = 60; - shake = 5; - createBlood(player.x + player.width/2, player.y + player.height/2, 15); + shake = 6; + createBlood(player.x + player.width/2, player.y + player.height/2, 18); } 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.laser ? 15 : 10); - createSparks(b.x, b.y, 5); + createExplosion(b.x, b.y, b.color, b.laser ? 20 : 12); + createSparks(b.x, b.y, 8); } - // Обычные враги 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, 10); + createExplosion(b.x, b.y, b.color, 12); if (e.hp <= 0) { player.score += e.score; - createBlood(e.x + e.width/2, e.y + e.height/2, 12); + createBlood(e.x + e.width/2, e.y + e.height/2, e.name === 'tank' || e.name === 'mech' ? 25 : 15); 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, alpha: 1 }); - shake = 3; + shake = 4; } } }); @@ -363,14 +371,12 @@ function update() { return !hit; }); - // Взрывы explosions = explosions.filter(e => { - e.radius += 4; - e.alpha -= 0.05; + e.radius += 5; + e.alpha -= 0.04; return e.alpha > 0; }); - // Предметы powerups = powerups.filter(p => { p.vy += 0.5; p.y += p.vy; @@ -389,30 +395,29 @@ function update() { } if (p.upgrade) { player.weaponLevel = Math.min(5, player.weaponLevel + 1); - createExplosion(p.x + p.width/2, p.y + p.height/2, '#ff00ff', 30); + createExplosion(p.x + p.width/2, p.y + p.height/2, '#ff00ff', 35); } if (p.heal) player.hp = Math.min(player.maxHp, player.hp + p.heal); if (p.maxHeal) { player.maxHp += 50; player.hp = player.maxHp; } 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.forEach(e => { e.hp -= 200; createExplosion(e.x + e.width/2, e.y + e.height/2, '#ff6600', 15); }); enemies = enemies.filter(e => e.hp > 0); - if (bossActive && boss) { boss.hp -= 500; shake = 15; } + if (bossActive && boss) { boss.hp -= 800; shake = 20; } } player.score += 50; - createExplosion(p.x + p.width/2, p.y + p.height/2, p.color, 20); + createExplosion(p.x + p.width/2, p.y + p.height/2, p.color, 25); return false; } return true; }); - // Частицы particles = particles.filter(p => { p.x += p.vx; p.y += p.vy; p.vy += p.gravity || 0.3; p.life--; - p.vx *= 0.95; + p.vx *= 0.94; return p.life > 0; }); @@ -420,7 +425,6 @@ 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) : ''); @@ -435,11 +439,11 @@ function update() { function resetGame() { 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; + waveNum = 1; enemiesRemaining = 6; spawnTimer = 0; bullets = []; enemies = []; powerups = []; particles = []; explosions = []; - boss = null; bossActive = false; + boss = null; bossActive = false; gameWon = false; document.getElementById('wave-num').textContent = '1'; - document.getElementById('wprogress').textContent = '5/5'; + document.getElementById('wprogress').textContent = '6/6'; } function draw() { @@ -448,134 +452,246 @@ function draw() { ctx.translate((Math.random() - 0.5) * shake * 2, (Math.random() - 0.5) * shake * 2); } - // Небо + if (gameWon) { + endingTimer++; + const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height); + gradient.addColorStop(0, '#ff6600'); + gradient.addColorStop(0.5, '#ff9933'); + gradient.addColorStop(1, '#cc3366'); + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + const sunX = 400 + Math.sin(endingTimer * 0.01) * 50; + const sunGradient = ctx.createRadialGradient(sunX, 150, 0, sunX, 150, 120); + sunGradient.addColorStop(0, '#ffff00'); + sunGradient.addColorStop(0.5, '#ff6600'); + sunGradient.addColorStop(1, 'transparent'); + ctx.fillStyle = sunGradient; + ctx.beginPath(); + ctx.arc(sunX, 150, 120, 0, Math.PI * 2); + ctx.fill(); + + for (let i = 0; i < 10; i++) { + const palmX = 100 + i * 80; + ctx.fillStyle = '#3a2a1a'; + ctx.fillRect(palmX, 300, 12, 200); + ctx.fillStyle = '#2a5a2a'; + for (let j = 0; j < 8; j++) { + const angle = j * 0.4 - 1.6; + ctx.save(); + ctx.translate(palmX + 6, 300); + ctx.rotate(angle + Math.sin(endingTimer * 0.02 + j) * 0.1); + ctx.fillRect(0, -60, 8, 60); + ctx.restore(); + } + } + + ctx.fillStyle = '#2a3a2a'; + ctx.fillRect(0, 450, canvas.width, 50); + + const carX = 300 + Math.min(endingTimer * 0.5, 300); + ctx.fillStyle = '#f0f0f0'; + ctx.fillRect(carX, 410, 180, 50); + ctx.fillRect(carX + 20, 385, 120, 35); + ctx.fillStyle = '#88ccff'; + ctx.fillRect(carX + 30, 390, 40, 25); + ctx.fillRect(carX + 90, 390, 50, 25); + ctx.fillStyle = '#1a1a1a'; + ctx.beginPath(); + ctx.arc(carX + 35, 460, 18, 0, Math.PI * 2); + ctx.arc(carX + 145, 460, 18, 0, Math.PI * 2); + ctx.fill(); + ctx.fillStyle = '#ffccaa'; + ctx.fillRect(carX + 60, 375, 15, 20); + ctx.fillStyle = '#ff6699'; + ctx.fillRect(carX + 55, 380, 25, 15); + + ctx.fillStyle = '#fff'; + ctx.font = 'bold 36px monospace'; + ctx.textAlign = 'center'; + ctx.fillText('ПОБЕДА!', 400, 100); + ctx.font = '20px monospace'; + ctx.fillText('Ты спас девушку и уезжаешь в закат', 400, 150); + ctx.fillText('Счёт: ' + player.score, 400, 190); + ctx.textAlign = 'left'; + + ctx.fillStyle = '#fff'; + ctx.font = '16px monospace'; + ctx.fillText('Нажми R для рестарта', 20, 30); + + if (keys['KeyR']) resetGame(); + + if (shake > 0) ctx.restore(); + return; + } + const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height); - gradient.addColorStop(0, '#0a0a1a'); - gradient.addColorStop(0.5, '#16213e'); - gradient.addColorStop(1, '#1a1a2e'); + gradient.addColorStop(0, '#87CEEB'); + gradient.addColorStop(0.5, '#98D8C8'); + gradient.addColorStop(1, '#6B8E6B'); 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; - const y = (i * 37) % (canvas.height - 100); - const size = (i % 3) + 1; - ctx.globalAlpha = 0.3 + Math.sin(gameTime * 0.05 + i) * 0.2; - ctx.fillRect(x, y, size, size); - } - ctx.globalAlpha = 1; - - // Горы - ctx.fillStyle = '#1a1a2e'; - for (let i = 0; i < 8; i++) { - const x = ((i * 120 - backgroundOffset * 0.2) % 1200) - 200; + ctx.fillStyle = '#5a8a5a'; + for (let i = 0; i < 15; i++) { + const treeX = ((i * 80 - backgroundOffset * 0.2) % 1000) - 100; + ctx.fillRect(treeX, 250, 16, 200); + ctx.fillStyle = '#3a6a3a'; ctx.beginPath(); - ctx.moveTo(x, 350); - ctx.lineTo(x + 60, 180); - ctx.lineTo(x + 120, 350); + ctx.moveTo(treeX - 30, 280); + ctx.lineTo(treeX + 8, 180); + ctx.lineTo(treeX + 46, 280); + ctx.fill(); + ctx.fillStyle = '#5a8a5a'; + } + + ctx.fillStyle = '#4a7a4a'; + for (let i = 0; i < 20; i++) { + const bushX = ((i * 50 - backgroundOffset * 0.3) % 900) - 50; + ctx.beginPath(); + ctx.arc(bushX, 420, 25, 0, Math.PI * 2); + ctx.arc(bushX + 30, 425, 20, 0, Math.PI * 2); + ctx.arc(bushX - 25, 425, 18, 0, Math.PI * 2); ctx.fill(); } - // Город - ctx.fillStyle = '#2a2a3e'; - for (let i = 0; i < canvas.width; i += 60) { - 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 + c * 14, 452 - h + 10 + w * 20, 8, 12); - ctx.fillStyle = '#2a2a3e'; - } - } + ctx.fillStyle = '#3a5a3a'; + for (let i = 0; i < 12; i++) { + const palmX = ((i * 90 - backgroundOffset * 0.15) % 950) - 80; + ctx.fillStyle = '#4a3a2a'; + ctx.fillRect(palmX + 10, 280, 14, 170); + ctx.fillStyle = '#3a6a3a'; + for (let j = 0; j < 7; j++) { + const angle = j * 0.35 - 1.2; + ctx.save(); + ctx.translate(palmX + 17, 280); + ctx.rotate(angle); + ctx.fillRect(0, -50, 10, 50); + ctx.restore(); } } - // Земля - 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 gh = 4 + Math.sin(gameTime * 0.05 + i) * 2; - ctx.fillRect(i, 452 - gh, 8, gh); - } + ctx.fillRect(0, 452, canvas.width, 48); ctx.fillStyle = '#5a4a3a'; - ctx.fillRect(120, 456, 16, 10); - ctx.fillRect(340, 460, 20, 8); - ctx.fillRect(580, 454, 14, 12); - ctx.fillRect(720, 458, 18, 8); + ctx.fillRect(0, 452, canvas.width, 8); + ctx.fillStyle = '#3a6a3a'; + for (let i = 0; i < canvas.width; i += 15) { + const gh = 5 + Math.sin(gameTime * 0.06 + i * 0.1) * 3; + ctx.fillRect(i, 452 - gh, 6, gh); + } - // Игрок - if (player.invincible % 4 < 2) { - 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 + 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); - ctx.fillStyle = '#4a90d9'; - ctx.fillRect(player.x + 4, player.y + 14, 24, 26); - ctx.fillStyle = '#2a2a3a'; - ctx.fillRect(player.x + 4, player.y + 28, 24, 6); - ctx.fillStyle = '#5a7a9a'; - ctx.fillRect(player.x + 8, player.y + 16, 16, 14); - ctx.fillStyle = '#ffccaa'; - ctx.fillRect(player.x + 6, player.y - 6, 20, 22); - ctx.fillStyle = '#3a3a4a'; - ctx.fillRect(player.x + 4, player.y - 10, 24, 10); - ctx.fillStyle = '#2a3a4a'; - ctx.fillRect(player.x + 8, player.y - 6, 14, 6); + if (paused) { + ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#fff'; - 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 ? ex + 2 : ex, player.y + 3, 3, 2); + ctx.font = 'bold 48px monospace'; + ctx.textAlign = 'center'; + ctx.fillText('ПАУЗА', canvas.width/2, canvas.height/2 - 30); + ctx.font = '20px monospace'; + ctx.fillText('Нажми P или ESC для продолжения', canvas.width/2, canvas.height/2 + 20); + ctx.textAlign = 'left'; + } + + if (player.invincible % 4 < 2) { + const lo = player.vx !== 0 ? Math.sin(Date.now() * 0.02) * 7 : 0; + + ctx.fillStyle = '#1a2a3a'; + ctx.fillRect(player.x + 8, player.y + 36, 7, 14 + lo); + ctx.fillRect(player.x + 17, player.y + 36, 7, 14 - lo); + + ctx.fillStyle = '#2a3a4a'; + ctx.fillRect(player.x + 6, player.y + 46, 10, 5); + ctx.fillRect(player.x + 16, player.y + 46, 10, 5); + + for (let t = 0; t < 3; t++) { + ctx.fillStyle = '#ffccaa'; + ctx.fillRect(player.x + 7 + t*3, player.y + 44, 3, 4); + ctx.fillRect(player.x + 17 + t*3, player.y + 44, 3, 4); + } + + ctx.fillStyle = '#3a5a4a'; + ctx.fillRect(player.x + 6, player.y + 16, 20, 24); + + ctx.fillStyle = '#2a3a3a'; + ctx.fillRect(player.x + 6, player.y + 30, 20, 5); + + ctx.fillStyle = '#5a7a6a'; + ctx.fillRect(player.x + 10, player.y + 18, 12, 14); + ctx.fillStyle = '#ffccaa'; - ctx.fillRect(player.x + 14, player.y + 16, 10, 10); - ctx.fillStyle = '#4a90d9'; - const wx = player.facing === 1 ? player.x + 20 : player.x - 8; - ctx.fillRect(wx, player.y + 18, 28, 10); - ctx.fillStyle = '#333'; - ctx.fillRect(player.facing === 1 ? wx + 20 : wx, player.y + 20, 12, 6); + ctx.fillRect(player.x + 8, player.y - 4, 16, 22); + ctx.fillRect(player.x + 6, player.y - 2, 6, 10); + ctx.fillRect(player.x + 20, player.y - 2, 6, 10); + + ctx.fillStyle = '#2a3a3a'; + ctx.fillRect(player.x + 6, player.y - 10, 20, 10); + + ctx.fillStyle = '#1a2a2a'; + ctx.fillRect(player.x + 10, player.y - 8, 12, 6); + + ctx.fillStyle = '#fff'; + const ex = player.facing === 1 ? player.x + 17 : player.x + 11; + const ey = player.aimUp ? player.y - 2 : player.y + 1; + ctx.fillRect(ex, ey, 5, 4); + ctx.fillStyle = '#000'; + ctx.fillRect(player.facing === 1 ? ex + 2 : ex, ey + 1, 3, 2); + + ctx.fillStyle = '#ffccaa'; + const armY = player.aimUp ? player.y + 8 : player.y + 18; + const armX = player.facing === 1 ? player.x + 18 : player.x + 2; + ctx.fillRect(armX, armY, 8, 10); + + for (let f = 0; f < 4; f++) { + ctx.fillStyle = '#ffccaa'; + const fingerY = player.aimUp ? armY + 8 : armY + 6; + ctx.fillRect(armX + player.facing * (f * 2), fingerY + f, 3, 2); + } + + const wx = player.facing === 1 ? player.x + 22 : player.x - 10; + const wy = player.aimUp ? player.y - 10 : player.y + 18; + const wa = player.aimUp ? -Math.PI/2 : 0; + + ctx.save(); + ctx.translate(player.x + player.width/2, player.y + 20); + ctx.rotate(wa * player.facing); + ctx.fillStyle = '#4a5a6a'; + ctx.fillRect(player.facing === 1 ? 8 : -20, player.aimUp ? -25 : -4, 28, 8); + ctx.fillStyle = '#3a4a5a'; + ctx.fillRect(player.facing === 1 ? 24 : -14, player.aimUp ? -27 : -5, 14, 6); + ctx.restore(); if (player.invincible > 60) { - ctx.strokeStyle = 'rgba(0, 136, 255, 0.5)'; - ctx.lineWidth = 3; + ctx.strokeStyle = 'rgba(0, 136, 255, 0.6)'; + ctx.lineWidth = 4; ctx.beginPath(); - ctx.arc(player.x + player.width/2, player.y + player.height/2, 40, 0, Math.PI * 2); + ctx.arc(player.x + player.width/2, player.y + player.height/2, 45, 0, Math.PI * 2); ctx.stroke(); } } - // Пули bullets.forEach(b => { if (b.laser) { ctx.strokeStyle = b.glow; ctx.lineWidth = b.size * 2; - ctx.shadowBlur = 20; + ctx.shadowBlur = 25; ctx.shadowColor = b.color; ctx.beginPath(); - ctx.moveTo(b.x - b.vx * 5, b.y - b.vy * 5); + ctx.moveTo(b.x - b.vx * 6, b.y - b.vy * 6); ctx.lineTo(b.x, b.y); ctx.stroke(); ctx.shadowBlur = 0; ctx.fillStyle = '#fff'; ctx.beginPath(); - ctx.arc(b.x, b.y, b.size, 0, Math.PI * 2); + ctx.arc(b.x, b.y, b.size + 2, 0, Math.PI * 2); ctx.fill(); } else { - const gg = ctx.createRadialGradient(b.x, b.y, 0, b.x, b.y, b.size * 3); + const gg = ctx.createRadialGradient(b.x, b.y, 0, b.x, b.y, b.size * 4); 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.arc(b.x, b.y, b.size * 4, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = b.color; ctx.beginPath(); @@ -584,17 +700,19 @@ function draw() { } }); - // Взрывы explosions.forEach(e => { - ctx.fillStyle = `rgba(255, 100, 0, ${e.alpha})`; + ctx.fillStyle = `rgba(255, 80, 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.8})`; + ctx.beginPath(); + ctx.arc(e.x, e.y, e.radius * 0.6, 0, Math.PI * 2); + ctx.fill(); }); - // Частицы particles.forEach(p => { - ctx.globalAlpha = p.life / 50; + ctx.globalAlpha = p.life / 60; ctx.fillStyle = p.color; ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); @@ -602,134 +720,206 @@ function draw() { }); ctx.globalAlpha = 1; - // Босс if (bossActive && boss) { boss.animFrame++; - ctx.fillStyle = '#3a1a1a'; - ctx.fillRect(boss.x + 10, boss.y + 50, 100, 50); + + ctx.fillStyle = '#1a2a3a'; + for (let i = 0; i < 8; i++) { + const legAngle = boss.legOffset[i]; + const legX = boss.x + (i < 4 ? 20 : boss.width - 20); + const legY = boss.y + 40 + (i % 2) * 15; + ctx.save(); + ctx.translate(legX, legY); + ctx.rotate(legAngle * 0.05); + ctx.fillRect(-8, -10, 8, 35); + ctx.fillRect(-6, 25, 6, 20); + ctx.restore(); + } + ctx.fillStyle = boss.color; - ctx.fillRect(boss.x + 20, boss.y + 20, 80, 40); - ctx.fillRect(boss.x + 40, boss.y, 40, 30); - ctx.fillStyle = '#ff4400'; ctx.beginPath(); - ctx.arc(boss.x + 60, boss.y + 15, 12, 0, Math.PI * 2); + ctx.ellipse(boss.x + boss.width/2, boss.y + 45, 70, 35, 0, 0, Math.PI * 2); ctx.fill(); - ctx.fillStyle = '#ff0000'; + + ctx.fillStyle = '#3a4a5a'; ctx.beginPath(); - ctx.arc(boss.x + 60, boss.y + 15, 6, 0, Math.PI * 2); + ctx.ellipse(boss.x + boss.width/2, boss.y + 35, 50, 25, 0, 0, Math.PI * 2); ctx.fill(); - ctx.fillStyle = '#5a2a2a'; - ctx.fillRect(boss.x, boss.y + 60, 20, 40); - ctx.fillRect(boss.x + 100, boss.y + 60, 20, 40); + + ctx.fillStyle = '#ff3300'; + ctx.beginPath(); + ctx.arc(boss.x + 50, boss.y + 30, 18, 0, Math.PI * 2); + ctx.arc(boss.x + boss.width - 50, boss.y + 30, 18, 0, Math.PI * 2); + ctx.fill(); + + ctx.fillStyle = '#ffff00'; + ctx.beginPath(); + ctx.arc(boss.x + 50, boss.y + 30, 8, 0, Math.PI * 2); + ctx.arc(boss.x + boss.width - 50, boss.y + 30, 8, 0, Math.PI * 2); + ctx.fill(); + + ctx.fillStyle = '#000'; + ctx.beginPath(); + ctx.arc(boss.x + 50, boss.y + 30, 4, 0, Math.PI * 2); + ctx.arc(boss.x + boss.width - 50, boss.y + 30, 4, 0, Math.PI * 2); + ctx.fill(); + + ctx.fillStyle = '#4a5a6a'; + ctx.fillRect(boss.x + boss.width/2 - 15, boss.y + 10, 30, 30); + ctx.fillStyle = '#2a3a4a'; + ctx.beginPath(); + ctx.arc(boss.x + boss.width/2, boss.y + 20, 12, 0, Math.PI * 2); + ctx.fill(); + + ctx.fillStyle = '#6a7a8a'; + for (let i = 0; i < 3; i++) { + ctx.fillRect(boss.x + 20 + i * 40, boss.y + 50, 12, 25); + } const hpPct = boss.hp / boss.maxHp; ctx.fillStyle = '#333'; - ctx.fillRect(boss.x, boss.y - 25, boss.width, 12); + ctx.fillRect(boss.x, boss.y - 35, boss.width, 16); ctx.fillStyle = hpPct > 0.5 ? '#0f0' : hpPct > 0.25 ? '#ff0' : '#f00'; - ctx.fillRect(boss.x + 2, boss.y - 23, (boss.width - 4) * hpPct, 8); + ctx.fillRect(boss.x + 3, boss.y - 32, (boss.width - 6) * hpPct, 10); ctx.fillStyle = '#fff'; - ctx.font = 'bold 14px monospace'; + ctx.font = 'bold 16px monospace'; ctx.textAlign = 'center'; - ctx.fillText('БОСС', boss.x + boss.width/2, boss.y - 28); + ctx.fillText('МЕХА-ПАУК', boss.x + boss.width/2, boss.y - 38); ctx.textAlign = 'left'; } - // Враги enemies.forEach(e => { e.animFrame++; + if (e.name === 'soldier') { - const lo = Math.sin(e.animFrame * 0.3) * 6; - ctx.fillStyle = '#1a1a2e'; - 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 = '#444'; - ctx.fillRect(e.x + 28, e.y + 20, 12, 6); - } else if (e.name === 'tank') { - ctx.fillStyle = '#1a1a1a'; - 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); - } else if (e.name === 'helicopter') { - ctx.fillStyle = '#333'; - const ro = Math.sin(e.animFrame * 0.8) * 20; - ctx.fillRect(e.x - 15, e.y - 8, 85, 4); - 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); - ctx.fill(); - ctx.fillStyle = '#6a8aaa'; - ctx.beginPath(); - ctx.arc(e.x + 40, e.y + 5, 12, 0, Math.PI * 2); - ctx.fill(); - } else if (e.name === 'commando') { - const lo = Math.sin(e.animFrame * 0.4) * 8; - ctx.fillStyle = '#2a1a2a'; - 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 = '#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 lo = Math.sin(e.animFrame * 0.15) * 4; + const lo = Math.sin(e.animFrame * 0.3) * 7; ctx.fillStyle = '#1a2a3a'; - ctx.fillRect(e.x + 10, e.y + 40, 14, 15 + lo); - ctx.fillRect(e.x + 46, e.y + 40, 14, 15 - lo); + ctx.fillRect(e.x + 8, e.y + 38, 7, 12 + lo); + ctx.fillRect(e.x + 17, e.y + 38, 7, 12 - lo); + ctx.fillStyle = '#2a3a4a'; + ctx.fillRect(e.x + 6, e.y + 46, 9, 5); + ctx.fillRect(e.x + 17, e.y + 46, 9, 5); ctx.fillStyle = e.color; - ctx.fillRect(e.x + 8, e.y + 20, 54, 28); - ctx.fillRect(e.x + 20, e.y + 8, 30, 20); - ctx.fillStyle = '#006688'; + ctx.fillRect(e.x + 6, e.y + 16, 20, 24); + ctx.fillStyle = '#3a4a3a'; + ctx.fillRect(e.x + 6, e.y + 28, 20, 5); + ctx.fillStyle = '#5a6a5a'; + ctx.fillRect(e.x + 10, e.y + 18, 12, 12); + ctx.fillStyle = '#ffccaa'; + ctx.fillRect(e.x + 8, e.y + 2, 16, 16); + ctx.fillStyle = '#3a4a3a'; + ctx.fillRect(e.x + 6, e.y - 4, 20, 8); + ctx.fillStyle = '#4a5a4a'; + ctx.fillRect(e.x + 18, e.y + 20, 14, 7); + ctx.fillStyle = '#3a4a3a'; + ctx.fillRect(e.x + 30, e.y + 21, 14, 5); + } else if (e.name === 'tank') { + ctx.fillStyle = '#1a2a1a'; + const to = Math.sin(e.animFrame * 0.08) * 3; + ctx.fillRect(e.x, e.y + 28 + to, e.width, 14); + for (let w = 0; w < 6; w++) { + ctx.fillRect(e.x + 8 + w * 11, e.y + 30 + to, 8, 10); + } + ctx.fillStyle = e.color; + ctx.fillRect(e.x + 8, e.y + 12, e.width - 16, 20); + ctx.fillRect(e.x + 20, e.y + 4, 35, 14); + ctx.fillStyle = '#4a6a4a'; + ctx.fillRect(e.x + 35, e.y + 8, 30, 8); + ctx.fillStyle = '#3a5a3a'; + ctx.fillRect(e.x + 55, e.y + 10, 35, 10); + ctx.fillStyle = '#2a4a2a'; ctx.beginPath(); - ctx.arc(e.x + 35, e.y + 18, 10, 0, Math.PI * 2); + ctx.arc(e.x + 45, e.y + 12, 8, 0, Math.PI * 2); + ctx.fill(); + } else if (e.name === 'helicopter') { + ctx.fillStyle = '#2a3a3a'; + const ro = Math.sin(e.animFrame * 0.9) * 35; + ctx.fillRect(e.x - 25, e.y - 12, 120, 6); + ctx.fillRect(e.x + 20 + ro, e.y - 18, 10, 12); + ctx.fillRect(e.x + 55 + ro, e.y - 18, 10, 12); + ctx.fillStyle = e.color; + ctx.beginPath(); + ctx.ellipse(e.x + 35, e.y + 10, 40, 18, 0, 0, Math.PI * 2); + ctx.fill(); + ctx.fillStyle = '#88aacc'; + ctx.beginPath(); + ctx.arc(e.x + 50, e.y + 5, 15, 0, Math.PI * 2); + ctx.fill(); + ctx.fillStyle = '#4a5a6a'; + ctx.fillRect(e.x - 15, e.y + 8, 30, 12); + ctx.fillRect(e.x - 25, e.y + 4, 12, 20); + } else if (e.name === 'commando') { + const lo = Math.sin(e.animFrame * 0.45) * 9; + ctx.fillStyle = '#2a1a2a'; + ctx.fillRect(e.x + 8, e.y + 40, 7, 12 + lo); + ctx.fillRect(e.x + 19, e.y + 40, 7, 12 - lo); + ctx.fillStyle = '#1a2a3a'; + ctx.fillRect(e.x + 6, e.y + 48, 10, 5); + ctx.fillRect(e.x + 17, e.y + 48, 10, 5); + ctx.fillStyle = e.color; + ctx.fillRect(e.x + 8, e.y + 18, 18, 24); + ctx.fillStyle = '#4a3a4a'; + ctx.fillRect(e.x + 8, e.y + 30, 18, 5); + ctx.fillStyle = '#6a5a6a'; + ctx.fillRect(e.x + 12, e.y + 20, 10, 12); + ctx.fillStyle = '#ffccaa'; + ctx.fillRect(e.x + 10, e.y + 4, 14, 16); + ctx.fillStyle = '#4a3a4a'; + ctx.fillRect(e.x + 8, e.y - 2, 18, 8); + ctx.fillStyle = '#ffccaa'; + ctx.fillRect(e.x + 18, e.y + 22, 14, 7); + ctx.fillStyle = '#3a3a3a'; + ctx.fillRect(e.x + 30, e.y + 23, 16, 6); + } else if (e.name === 'mech') { + const lo = Math.sin(e.animFrame * 0.12) * 5; + ctx.fillStyle = '#1a2a3a'; + ctx.fillRect(e.x + 12, e.y + 45, 16, 18 + lo); + ctx.fillRect(e.x + 47, e.y + 45, 16, 18 - lo); + ctx.fillStyle = '#2a3a4a'; + ctx.fillRect(e.x + 8, e.y + 58, 20, 8); + ctx.fillRect(e.x + 43, e.y + 58, 20, 8); + ctx.fillStyle = e.color; + ctx.fillRect(e.x + 10, e.y + 22, 55, 30); + ctx.fillRect(e.x + 22, e.y + 10, 36, 18); + ctx.fillStyle = '#3a5a6a'; + ctx.fillRect(e.x + 28, e.y + 25, 24, 20); + ctx.fillStyle = '#4a6a7a'; + ctx.beginPath(); + ctx.arc(e.x + 40, e.y + 35, 12, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#ff4400'; ctx.beginPath(); - ctx.arc(e.x + 35, e.y + 18, 4, 0, Math.PI * 2); + ctx.arc(e.x + 40, e.y + 35, 5, 0, Math.PI * 2); ctx.fill(); + ctx.fillStyle = '#5a6a7a'; + ctx.fillRect(e.x + 50, e.y + 28, 30, 12); } const hpPct = e.hp / e.maxHp; ctx.fillStyle = '#333'; - ctx.fillRect(e.x, e.y - 16, e.width, 8); + ctx.fillRect(e.x, e.y - 18, e.width, 10); ctx.fillStyle = hpPct > 0.5 ? '#0f0' : hpPct > 0.25 ? '#ff0' : '#f00'; - ctx.fillRect(e.x + 1, e.y - 15, (e.width - 2) * hpPct, 6); + ctx.fillRect(e.x + 2, e.y - 16, (e.width - 4) * hpPct, 6); }); - // Предметы powerups.forEach(p => { - 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); + const by = p.grounded ? Math.sin(p.bobOffset) * 5 : 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, 25); 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 + by, 20, 0, Math.PI * 2); + ctx.arc(p.x + p.width/2, p.y + p.height/2 + by, 25, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = p.color; - ctx.fillRect(p.x + 2, p.y + 4 + by, p.width - 4, p.height - 4); + ctx.fillRect(p.x + 3, p.y + 5 + by, p.width - 6, p.height - 6); ctx.strokeStyle = '#fff'; - ctx.lineWidth = 2; - ctx.strokeRect(p.x + 2, p.y + 4 + by, p.width - 4, p.height - 4); + ctx.lineWidth = 3; + ctx.strokeRect(p.x + 3, p.y + 5 + by, p.width - 6, p.height - 6); ctx.fillStyle = '#fff'; - ctx.font = 'bold 18px monospace'; + ctx.font = 'bold 20px monospace'; ctx.textAlign = 'center'; - ctx.fillText(p.label, p.x + p.width/2, p.y + 20 + by); + ctx.fillText(p.label, p.x + p.width/2, p.y + 23 + by); ctx.textAlign = 'left'; }); diff --git a/style.css b/style.css index 1117300..34c47fd 100644 --- a/style.css +++ b/style.css @@ -23,7 +23,7 @@ body { #game { display: block; - background: linear-gradient(to bottom, #16213e 0%, #1a1a2e 100%); + background: linear-gradient(to bottom, #87CEEB 0%, #98D8C8 50%, #6B8E6B 100%); } #ui { @@ -33,21 +33,40 @@ body { right: 10px; display: flex; justify-content: space-between; + align-items: center; color: #fff; - font-size: 18px; + font-size: 16px; font-weight: bold; text-shadow: 2px 2px 4px rgba(0,0,0,0.8); pointer-events: none; } #ui div { - background: rgba(0, 0, 0, 0.5); + background: rgba(0, 0, 0, 0.6); padding: 8px 12px; border-radius: 4px; border: 2px solid #4a4a6a; backdrop-filter: blur(4px); } +#pause-btn { + background: rgba(0, 0, 0, 0.6); + color: #fff; + border: 2px solid #4a4a6a; + padding: 8px 16px; + border-radius: 4px; + font-family: 'Courier New', monospace; + font-size: 14px; + cursor: pointer; + transition: all 0.2s; + pointer-events: auto; +} + +#pause-btn:hover { + background: rgba(74, 74, 106, 0.8); + border-color: #6a6a8a; +} + #controls-hint { position: absolute; bottom: -35px; @@ -61,23 +80,3 @@ body { pointer-events: none; white-space: nowrap; } - -#pause-btn { - position: absolute; - top: 10px; - right: 10px; - background: rgba(0, 0, 0, 0.6); - color: #fff; - border: 2px solid #4a4a6a; - padding: 8px 16px; - border-radius: 4px; - font-family: 'Courier New', monospace; - font-size: 14px; - cursor: pointer; - transition: all 0.2s; -} - -#pause-btn:hover { - background: rgba(74, 74, 106, 0.8); - border-color: #6a6a8a; -}