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;
-}