Космический Шарик: Цвета и Босс
by TurboGamer88419 lines21.6 KB
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Космический Шарик: Цвета и Босс</title>
<style>
body { margin: 0; background: #0a0a12; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; font-family: Arial; overflow: hidden; touch-action: none; }
canvas { background: #1a1a2e; border: 2px solid #00ffcc; border-radius: 8px; touch-action: none; max-width: 100vw; max-height: 80vh; }
#hud { font-size: 14px; margin-bottom: 4px; color: #00ffcc; font-weight: bold; text-align: center; }
.btn { position: absolute; bottom: 20px; width: 65px; height: 65px; border-radius: 50%; background: rgba(0,255,204,0.15); color: #00ffcc; font-size: 26px; border: 2px solid #00ffcc; display: flex; align-items: center; justify-content: center; user-select: none; touch-action: none; }
#left { left: 12px; }
#right { left: 88px; }
#jump { right: 12px; background: rgba(0,150,0,0.3); border-color: #0f0; color: #0f0; }
#dash { right: 88px; background: rgba(255,0,102,0.3); border-color: #ff0066; color: #ff0066; }
#pauseBtn { position: absolute; top: 10px; right: 10px; width: 50px; height: 50px; border-radius: 12px; background: rgba(0,0,0,0.5); color: #fff; font-size: 22px; border: 2px solid #fff; display: flex; align-items: center; justify-content: center; cursor: pointer; z-index: 10; }
</style>
</head>
<body>
<div id="hud">🚀 Космический Шарик</div>
<canvas id="game" width="600" height="400"></canvas>
<button class="btn" id="left">◀</button>
<button class="btn" id="right">▶</button>
<button class="btn" id="jump">▲</button>
<button class="btn" id="dash">⚡</button>
<button id="pauseBtn">⏸</button>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const hud = document.getElementById('hud');
const pauseBtn = document.getElementById('pauseBtn');
let state = 'menu';
let player = { x: 100, y: 300, r: 18, vx: 0, vy: 0, onGround: false, facing: 1, rotation: 0, color: '#00ffcc', stroke: '#0088aa' };
let platforms = [], enemies = [], stars = [], particles = [], boss = null;
let keys = {}, score = 0, lives = 3, gameOver = false, win = false;
let cameraX = 0, invincibleTimer = 0, screenShake = 0;
let menuPulse = 0, time = 0, currentLevel = 1, dashCooldown = 0;
const GRAVITY = 0.5, ACCEL = 1.0, MAX_SPEED = 6, FRICTION = 0.88, JUMP_FORCE = -13, MAX_FALL = 16, AIR_CONTROL = 0.8;
const COLORS = [
{ name: 'Бирюзовый', color: '#00ffcc', stroke: '#0088aa' },
{ name: 'Красный', color: '#e94560', stroke: '#b02a40' },
{ name: 'Синий', color: '#3498db', stroke: '#21618c' },
{ name: 'Зелёный', color: '#2ecc71', stroke: '#1e8449' },
{ name: 'Золотой', color: '#ffd700', stroke: '#daa520' },
{ name: 'Фиолетовый', color: '#9b59b6', stroke: '#6c3483' }
];
let currentColor = 0;
const btnPlay = { x: 200, y: 220, w: 200, h: 45 };
const btnColors = { x: 200, y: 275, w: 200, h: 35 };
const btnBack = { x: 20, y: 20, w: 90, h: 40 };
let audioCtx = null;
function initAudio() { if (!audioCtx) try { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } catch(e) {} }
function playSound(f, d, t, v) {
if (!audioCtx) return;
try {
const o = audioCtx.createOscillator(), g = audioCtx.createGain();
o.type = t || 'square'; o.frequency.value = f;
g.gain.setValueAtTime(v || 0.08, audioCtx.currentTime);
g.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + d);
o.connect(g); g.connect(audioCtx.destination); o.start(); o.stop(audioCtx.currentTime + d);
} catch(e) {}
}
function sfxJump() { playSound(350, 0.1, 'square', 0.06); }
function sfxHit() { playSound(150, 0.2, 'sawtooth', 0.12); }
function sfxCollect() { playSound(900, 0.08, 'sine', 0.08); }
function sfxWin() { playSound(523, 0.12, 'sine', 0.1); setTimeout(() => playSound(659, 0.12, 'sine', 0.1), 120); setTimeout(() => playSound(784, 0.25, 'sine', 0.1), 240); }
function sfxClick() { playSound(600, 0.05, 'square', 0.06); }
function sfxDash() { playSound(500, 0.15, 'sawtooth', 0.1); }
function sfxBossHit() { playSound(200, 0.15, 'sawtooth', 0.15); }
function initLevel(level) {
currentLevel = level;
player = { x: 100, y: 300, r: 18, vx: 0, vy: 0, onGround: false, facing: 1, rotation: 0, color: COLORS[currentColor].color, stroke: COLORS[currentColor].stroke };
platforms = []; enemies = []; stars = []; particles = []; boss = null;
score = 0; lives = 3; gameOver = false; win = false;
cameraX = 0; invincibleTimer = 0; screenShake = 0; dashCooldown = 0;
updateHUD();
const groundW = 2500 + level * 500;
platforms.push({ x: 0, y: 360, w: groundW, h: 40, type: 'ground' });
let px = 300;
for (let i = 0; i < 10; i++) {
const py = 300 - (i % 3) * 60;
const pw = 100 + Math.random() * 50;
platforms.push({ x: px, y: py, w: pw, h: 20, type: 'platform' });
px += 250 + Math.random() * 60;
}
if (level === 1) {
for (let i = 0; i < 3; i++) enemies.push({ x: 400 + i * 250, y: 320, w: 30, h: 30, hp: 1, maxHp: 1, alive: true, color: '#e94560', vx: 1.5, minX: 350 + i * 250, maxX: 550 + i * 250 });
for (let i = 0; i < 2; i++) enemies.push({ x: 700 + i * 300, y: 320, w: 30, h: 30, hp: 2, maxHp: 2, alive: true, color: '#3498db', vx: 1, minX: 650 + i * 300, maxX: 850 + i * 300 });
} else if (level === 2) {
for (let i = 0; i < 4; i++) enemies.push({ x: 400 + i * 220, y: 320, w: 30, h: 30, hp: 1, maxHp: 1, alive: true, color: '#2ecc71', vx: 2, minX: 350 + i * 220, maxX: 520 + i * 220 });
for (let i = 0; i < 3; i++) enemies.push({ x: 900 + i * 280, y: 320, w: 30, h: 30, hp: 1, maxHp: 1, alive: true, color: '#e94560', vx: 1.5, minX: 850 + i * 280, maxX: 1050 + i * 280 });
} else if (level === 3) {
boss = { x: 2000, y: 240, w: 90, h: 90, hp: 10, maxHp: 10, alive: true, vx: 1.2, minX: 1800, maxX: 2200, hitTimer: 0 };
}
for (let i = 0; i < 12; i++) stars.push({ x: 200 + i * 180, y: 260 + (i % 3) * 30, r: 10, collected: false });
}
function updateHUD() {
let text = `🚀 Уровень ${currentLevel}/3 | Счёт: ${score} | ❤${lives}`;
if (boss && boss.alive) text += ` | БОСС: ${boss.hp}/${boss.maxHp}`;
hud.textContent = text;
}
function spawnParticles(x, y, color, count) {
for (let i = 0; i < count; i++) particles.push({ x, y, vx: (Math.random() - 0.5) * 6, vy: (Math.random() - 0.5) * 6 - 1, life: 20 + Math.random() * 10, maxLife: 30, color, size: 2 + Math.random() * 3 });
}
function damagePlayer(fromX) {
lives--; invincibleTimer = 90; screenShake = 8;
player.vy = -7; player.vx = (player.x < fromX ? -1 : 1) * 5;
sfxHit(); spawnParticles(player.x, player.y, '#ff0066', 8);
if (lives <= 0) { gameOver = true; state = 'gameover'; }
updateHUD();
}
function drawPlayer() {
const px = player.x - cameraX, py = player.y;
if (invincibleTimer > 0 && Math.floor(invincibleTimer / 4) % 2 === 0) return;
ctx.fillStyle = 'rgba(0,0,0,0.25)';
ctx.beginPath(); ctx.ellipse(px, py + player.r + 2, player.r * 0.8, 5, 0, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = player.color + '33';
ctx.beginPath(); ctx.arc(px, py, player.r + 10, 0, Math.PI * 2); ctx.fill();
const grd = ctx.createRadialGradient(px - player.r/3, py - player.r/3, 2, px, py, player.r);
grd.addColorStop(0, player.color); grd.addColorStop(1, player.stroke);
ctx.beginPath(); ctx.arc(px, py, player.r, 0, Math.PI * 2);
ctx.fillStyle = grd; ctx.fill();
ctx.strokeStyle = player.stroke; ctx.lineWidth = 3; ctx.stroke();
ctx.fillStyle = 'rgba(255,255,255,0.5)';
ctx.beginPath(); ctx.arc(px - player.r*0.35, py - player.r*0.35, player.r*0.25, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = '#fff';
ctx.beginPath(); ctx.arc(px - 5, py - 4, 5, 0, Math.PI * 2); ctx.arc(px + 5, py - 4, 5, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = '#000';
ctx.beginPath(); ctx.arc(px - 5 + player.facing*2, py - 4, 2.5, 0, Math.PI * 2); ctx.arc(px + 5 + player.facing*2, py - 4, 2.5, 0, Math.PI * 2); ctx.fill();
}
function drawEnemy(e) {
if (!e.alive) return;
const ex = e.x - cameraX;
ctx.fillStyle = e.color;
ctx.fillRect(ex, e.y, e.w, e.h);
ctx.strokeStyle = '#000'; ctx.lineWidth = 2; ctx.strokeRect(ex, e.y, e.w, e.h);
ctx.fillStyle = '#fff'; ctx.fillRect(ex + 5, e.y + 8, 6, 6); ctx.fillRect(ex + e.w - 11, e.y + 8, 6, 6);
ctx.fillStyle = '#000'; ctx.fillRect(ex + 7, e.y + 10, 3, 3); ctx.fillRect(ex + e.w - 9, e.y + 10, 3, 3);
if (e.maxHp > 1) { ctx.fillStyle = '#000'; ctx.fillRect(ex, e.y - 8, e.w, 4); ctx.fillStyle = '#0f0'; ctx.fillRect(ex, e.y - 8, e.w * (e.hp / e.maxHp), 4); }
}
function drawBoss() {
if (!boss || !boss.alive) return;
const bx = boss.x - cameraX;
const flash = boss.hitTimer > 0 && Math.floor(boss.hitTimer / 3) % 2 === 0;
ctx.fillStyle = flash ? '#fff' : '#8e44ad';
ctx.fillRect(bx, boss.y, boss.w, boss.h);
ctx.strokeStyle = '#5b2c6f'; ctx.lineWidth = 4; ctx.strokeRect(bx, boss.y, boss.w, boss.h);
ctx.fillStyle = '#ff0066';
ctx.beginPath(); ctx.arc(bx + 25, boss.y + 35, 12, 0, Math.PI * 2); ctx.arc(bx + 65, boss.y + 35, 12, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = '#000';
ctx.beginPath(); ctx.arc(bx + 25, boss.y + 35, 5, 0, Math.PI * 2); ctx.arc(bx + 65, boss.y + 35, 5, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = '#8e44ad';
ctx.beginPath(); ctx.arc(bx - 10, boss.y + boss.h/2, 20, 0, Math.PI * 2); ctx.fill();
ctx.strokeStyle = '#5b2c6f'; ctx.lineWidth = 3; ctx.stroke();
ctx.beginPath(); ctx.arc(bx + boss.w + 10, boss.y + boss.h/2, 20, 0, Math.PI * 2); ctx.fill(); ctx.stroke();
ctx.fillStyle = '#000'; ctx.fillRect(bx, boss.y - 15, boss.w, 8);
ctx.fillStyle = '#ff0066'; ctx.fillRect(bx, boss.y - 15, boss.w * (boss.hp / boss.maxHp), 8);
}
function draw() {
ctx.save();
if (screenShake > 0) ctx.translate((Math.random() - 0.5) * 6, (Math.random() - 0.5) * 6);
const grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
grad.addColorStop(0, '#0a0a2e'); grad.addColorStop(0.5, '#1a1a4e'); grad.addColorStop(1, '#2a2a5e');
ctx.fillStyle = grad; ctx.fillRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < 50; i++) {
const sx = (i * 137 + time * 0.3) % canvas.width;
const sy = (i * 97) % canvas.height;
ctx.fillStyle = `rgba(255,255,255,${0.2 + (i % 3) * 0.15})`;
ctx.fillRect(sx, sy, 2, 2);
}
platforms.forEach(p => {
const pg = ctx.createLinearGradient(0, p.y, 0, p.y + p.h);
pg.addColorStop(0, '#4a4a6e'); pg.addColorStop(1, '#2a2a4e');
ctx.fillStyle = pg; ctx.fillRect(p.x - cameraX, p.y, p.w, p.h);
ctx.fillStyle = '#00ffcc'; ctx.fillRect(p.x - cameraX, p.y, p.w, 4);
});
stars.forEach(s => {
if (s.collected) return;
const bob = Math.sin(Date.now() / 200 + s.x) * 3;
const sx = s.x - cameraX, sy = s.y + bob;
ctx.fillStyle = '#ffd700';
ctx.beginPath();
for (let i = 0; i < 10; i++) {
const angle = (i * Math.PI / 5) - Math.PI / 2;
const rad = i % 2 === 0 ? s.r : s.r * 0.5;
ctx.lineTo(sx + Math.cos(angle) * rad, sy + Math.sin(angle) * rad);
}
ctx.closePath(); ctx.fill();
ctx.strokeStyle = '#daa520'; ctx.lineWidth = 2; ctx.stroke();
});
enemies.forEach(e => drawEnemy(e));
drawBoss();
particles.forEach(p => {
ctx.globalAlpha = p.life / p.maxLife; ctx.fillStyle = p.color;
ctx.fillRect(p.x - cameraX, p.y, p.size, p.size); ctx.globalAlpha = 1;
});
drawPlayer();
if (state === 'paused') {
ctx.fillStyle = 'rgba(0,0,0,0.7)'; ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#fff'; ctx.font = 'bold 30px Arial'; ctx.textAlign = 'center';
ctx.fillText('ПАУЗА', canvas.width/2, canvas.height/2); ctx.textAlign = 'left';
}
if (state === 'gameover') {
ctx.fillStyle = 'rgba(0,0,0,0.75)'; ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#e94560'; ctx.font = 'bold 30px Arial'; ctx.textAlign = 'center';
ctx.fillText('ИГРА ОКОНЧЕНА', canvas.width/2, canvas.height/2);
ctx.fillStyle = '#fff'; ctx.font = '18px Arial';
ctx.fillText('Счёт: ' + score, canvas.width/2, canvas.height/2 + 40);
ctx.fillText('Нажми, чтобы выйти в меню', canvas.width/2, canvas.height/2 + 80);
ctx.textAlign = 'left';
}
if (state === 'win') {
ctx.fillStyle = 'rgba(0,0,0,0.75)'; ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#00ffcc'; ctx.font = 'bold 30px Arial'; ctx.textAlign = 'center';
ctx.fillText('🎉 ПОБЕДА!', canvas.width/2, canvas.height/2);
ctx.fillStyle = '#fff'; ctx.font = '18px Arial';
ctx.fillText('Счёт: ' + score, canvas.width/2, canvas.height/2 + 40);
ctx.fillText('Нажми, чтобы выйти в меню', canvas.width/2, canvas.height/2 + 80);
ctx.textAlign = 'left';
}
ctx.restore();
}
function drawMenu() {
menuPulse += 0.05;
const grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
grad.addColorStop(0, '#0a0a2e'); grad.addColorStop(1, '#2a0a2e');
ctx.fillStyle = grad; ctx.fillRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < 40; i++) {
const sx = (i * 137 + Date.now() * 0.02) % 600;
const sy = (i * 97) % 400;
ctx.fillStyle = `rgba(255,255,255,${0.2 + (i % 3) * 0.15})`; ctx.fillRect(sx, sy, 2, 2);
}
const scale = 1 + Math.sin(menuPulse) * 0.03;
ctx.save(); ctx.translate(canvas.width/2, 100); ctx.scale(scale, scale);
ctx.fillStyle = '#00ffcc'; ctx.font = 'bold 30px Arial'; ctx.textAlign = 'center';
ctx.fillText('КОСМИЧЕСКИЙ', 0, 0); ctx.fillText('ШАРИК', 0, 35); ctx.restore();
ctx.fillStyle = COLORS[currentColor].color;
ctx.beginPath(); ctx.arc(canvas.width/2, 180, 25, 0, Math.PI * 2); ctx.fill();
ctx.strokeStyle = COLORS[currentColor].stroke; ctx.lineWidth = 3; ctx.stroke();
ctx.fillStyle = '#e94560'; ctx.fillRect(btnPlay.x, btnPlay.y, btnPlay.w, btnPlay.h);
ctx.strokeStyle = '#fff'; ctx.lineWidth = 3; ctx.strokeRect(btnPlay.x, btnPlay.y, btnPlay.w, btnPlay.h);
ctx.fillStyle = '#fff'; ctx.font = 'bold 22px Arial'; ctx.textAlign = 'center';
ctx.fillText('▶ ИГРАТЬ', btnPlay.x + btnPlay.w/2, btnPlay.y + 33);
ctx.fillStyle = 'rgba(255,215,0,0.2)'; ctx.fillRect(btnColors.x, btnColors.y, btnColors.w, btnColors.h);
ctx.strokeStyle = '#ffd700'; ctx.lineWidth = 2; ctx.strokeRect(btnColors.x, btnColors.y, btnColors.w, btnColors.h);
ctx.fillStyle = '#ffd700'; ctx.font = '16px Arial';
ctx.fillText('🎨 ЦВЕТ ПЕРСОНАЖА', btnColors.x + btnColors.w/2, btnColors.y + 23);
ctx.textAlign = 'left';
}
function drawColorsMenu() {
const grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
grad.addColorStop(0, '#0a0a2e'); grad.addColorStop(1, '#2a0a2e');
ctx.fillStyle = grad; ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#ffd700'; ctx.font = 'bold 22px Arial'; ctx.textAlign = 'center';
ctx.fillText('ВЫБЕРИ ЦВЕТ', canvas.width/2, 50);
const cols = 3, cellW = 140, cellH = 120;
const startX = (canvas.width - cols * cellW) / 2 + 20, startY = 100;
COLORS.forEach((c, i) => {
const col = i % cols, row = Math.floor(i / cols);
const x = startX + col * cellW + cellW/2, y = startY + row * cellH + cellH/2;
if (i === currentColor) {
ctx.strokeStyle = '#ffd700'; ctx.lineWidth = 3; ctx.strokeRect(x - 55, y - 50, 110, 100);
ctx.fillStyle = 'rgba(255,215,0,0.1)'; ctx.fillRect(x - 55, y - 50, 110, 100);
} else {
ctx.strokeStyle = 'rgba(255,255,255,0.2)'; ctx.lineWidth = 1; ctx.strokeRect(x - 55, y - 50, 110, 100);
}
const sg = ctx.createRadialGradient(x - 6, y - 16, 2, x, y - 10, 20);
sg.addColorStop(0, c.color); sg.addColorStop(1, c.stroke);
ctx.beginPath(); ctx.arc(x, y - 10, 20, 0, Math.PI * 2); ctx.fillStyle = sg; ctx.fill();
ctx.strokeStyle = c.stroke; ctx.lineWidth = 3; ctx.stroke();
ctx.fillStyle = i === currentColor ? '#ffd700' : '#fff'; ctx.font = '14px Arial';
ctx.fillText(c.name, x, y + 40);
});
ctx.fillStyle = 'rgba(255,255,255,0.15)'; ctx.fillRect(btnBack.x, btnBack.y, btnBack.w, btnBack.h);
ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.strokeRect(btnBack.x, btnBack.y, btnBack.w, btnBack.h);
ctx.fillStyle = '#fff'; ctx.font = '16px Arial'; ctx.textAlign = 'center';
ctx.fillText('← Назад', btnBack.x + btnBack.w/2, btnBack.y + 26);
ctx.textAlign = 'left';
}
function update() {
if (state !== 'playing' || gameOver || win) return;
if (invincibleTimer > 0) invincibleTimer--;
if (screenShake > 0) screenShake--;
if (dashCooldown > 0) dashCooldown--;
time++;
const left = keys['ArrowLeft'] || keys['a'];
const right = keys['ArrowRight'] || keys['d'];
const control = player.onGround ? ACCEL : ACCEL * AIR_CONTROL;
if (left && !right) { player.vx -= control; player.facing = -1; }
else if (right && !left) { player.vx += control; player.facing = 1; }
else if (player.onGround) player.vx *= FRICTION;
if (player.vx > MAX_SPEED) player.vx = MAX_SPEED;
if (player.vx < -MAX_SPEED) player.vx = -MAX_SPEED;
if (Math.abs(player.vx) < 0.1) player.vx = 0;
player.vy += GRAVITY;
if (player.vy > MAX_FALL) player.vy = MAX_FALL;
player.x += player.vx;
platforms.forEach(p => {
if (player.x + player.r > p.x && player.x - player.r < p.x + p.w && player.y + player.r > p.y && player.y - player.r < p.y + p.h) {
if (player.vx > 0) player.x = p.x - player.r;
else if (player.vx < 0) player.x = p.x + p.w + player.r;
player.vx = 0;
}
});
player.y += player.vy;
player.onGround = false;
platforms.forEach(p => {
if (player.x + player.r > p.x && player.x - player.r < p.x + p.w && player.y + player.r > p.y && player.y - player.r < p.y + p.h) {
if (player.vy > 0) { player.y = p.y - player.r; player.vy = 0; player.onGround = true; }
else if (player.vy < 0) { player.y = p.y + p.h + player.r; player.vy = 0; }
}
});
stars.forEach(s => {
if (s.collected) return;
const dx = player.x - s.x, dy = player.y - s.y;
if (Math.sqrt(dx*dx + dy*dy) < player.r + s.r + 5) {
s.collected = true; score++; sfxCollect(); spawnParticles(s.x, s.y, '#ffd700', 6); updateHUD();
}
});
enemies.forEach(e => {
if (!e.alive) return;
e.x += e.vx;
if (e.x < e.minX) { e.x = e.minX; e.vx = Math.abs(e.vx); }
if (e.x > e.maxX) { e.x = e.maxX; e.vx = -Math.abs(e.vx); }
if (player.x + player.r > e.x && player.x - player.r < e.x + e.w && player.y + player.r > e.y && player.y - player.r < e.y + e.h) {
if (player.vy > 0 && player.y - player.r < e.y + 10) {
e.hp--; player.vy = -10;
if (e.hp <= 0) { e.alive = false; sfxKill(); spawnParticles(e.x + e.w/2, e.y + e.h/2, e.color, 8); score += (e.maxHp > 1 ? 2 : 1); updateHUD(); }
else sfxBossHit();
} else if (invincibleTimer === 0) damagePlayer(e.x);
}
});
if (boss && boss.alive) {
if (boss.hitTimer > 0) boss.hitTimer--;
boss.x += boss.vx;
if (boss.x < boss.minX) { boss.x = boss.minX; boss.vx = Math.abs(boss.vx); }
if (boss.x > boss.maxX) { boss.x = boss.maxX; boss.vx = -Math.abs(boss.vx); }
if (player.x + player.r > boss.x && player.x - player.r < boss.x + boss.w && player.y + player.r > boss.y && player.y - player.r < boss.y + boss.h) {
if (player.vy > 0 && player.y - player.r < boss.y + 15) {
boss.hp--; boss.hitTimer = 20; player.vy = -12; sfxBossHit(); spawnParticles(boss.x + boss.w/2, boss.y, '#8e44ad', 8);
if (boss.hp <= 0) { boss.alive = false; win = true; state = 'win'; sfxWin(); }
updateHUD();
} else if (invincibleTimer === 0) damagePlayer(boss.x);
}
}
cameraX = player.x - 200;
if (cameraX < 0) cameraX = 0;
particles.forEach((p, i) => { p.x += p.vx; p.y += p.vy; p.vy += 0.2; p.life--; if (p.life <= 0) particles.splice(i, 1); });
if (player.y > 500) damagePlayer(player.x);
}
function loop() {
if (state === 'menu') drawMenu();
else if (state === 'colors') drawColorsMenu();
else { update(); draw(); }
requestAnimationFrame(loop);
}
document.addEventListener('keydown', e => {
keys[e.key] = true;
if (e.key === ' ' || e.key === 'ArrowUp' || e.key === 'w') {
if (player && player.onGround) { player.vy = JUMP_FORCE; player.onGround = false; sfxJump(); }
e.preventDefault();
}
if (e.key === 'Shift' && dashCooldown === 0 && state === 'playing') {
dashCooldown = 60; player.vx += player.facing * 10; sfxDash();
e.preventDefault();
}
});
document.addEventListener('keyup', e => { keys[e.key] = false; });
function holdBtn(id, key) {
const el = document.getElementById(id);
el.addEventListener('touchstart', e => { e.preventDefault(); keys[key] = true; initAudio(); });
el.addEventListener('touchend', e => { e.preventDefault(); keys[key] = false; });
el.addEventListener('mousedown', e => { e.preventDefault(); keys[key] = true; initAudio(); });
el.addEventListener('mouseup', e => { e.preventDefault(); keys[key] = false; });
}
holdBtn('left', 'ArrowLeft');
holdBtn('right', 'ArrowRight');
function doJump() { initAudio(); if (player && player.onGround) { player.vy = JUMP_FORCE; player.onGround = false; sfxJump(); } }
document.getElementById('jump').addEventListener('touchstart', e => { e.preventDefault(); doJump(); });
document.getElementById('jump').addEventListener('mousedown', e => { e.preventDefault(); doJump(); });
function doDash() { initAudio(); if (dashCooldown === 0 && state === 'playing') { dashCooldown = 60; player.vx += player.facing * 10; sfxDash(); } }
document.getElementById('dash').addEventListener('touchstart', e => { e.preventDefault(); doDash(); });
document.getElementById('dash').addEventListener('mousedown', e => { e.preventDefault(); doDash(); });
pauseBtn.addEventListener('click', () => {
initAudio();
if (state === 'playing') { state = 'paused'; sfxClick(); }
else if (state === 'paused') { state = 'playing'; sfxClick(); }
});
function handleMenuTap(x, y) {
if (state === 'menu') {
if (x > btnPlay.x && x < btnPlay.x + btnGame Source: Космический Шарик: Цвета и Босс
Creator: TurboGamer88
Libraries: none
Complexity: complex (419 lines, 21.6 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: game-turbogamer88-mvtlfekn" to link back to the original. Then publish at arcadelab.ai/publish.