Красный Шарик: Квадраты и Босс
by TurboGamer88454 lines17.2 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 #e94560; border-radius: 8px; touch-action: none; max-width: 100vw; max-height: 80vh; }
#hud { font-size: 14px; margin-bottom: 4px; color: #e94560; font-weight: bold; text-align: center; }
.btn { position: absolute; bottom: 20px; width: 65px; height: 65px; border-radius: 50%; background: rgba(233,69,96,0.2); color: #e94560; font-size: 26px; border: 2px solid #e94560; 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; }
</style>
</head>
<body>
<div id="hud">🔴 Красный Шарик | Уровень: 1 | Убито: 0</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>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const hud = document.getElementById('hud');
let player, platforms, enemies, bombs, coins, boss, particles;
let keys = {}, score = 0, lives = 3, currentLevel = 1, gameOver = false, win = false;
let cameraX = 0, invincibleTimer = 0;
let bossActive = false;
const GRAVITY = 0.65;
const ACCEL = 0.9;
const MAX_SPEED = 6;
const FRICTION = 0.92;
const JUMP_FORCE = -14;
const MAX_FALL = 18;
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 sfxKill() { playSound(600, 0.1, 'square', 0.1); setTimeout(() => playSound(400, 0.15, 'square', 0.1), 60); }
function sfxBossHit() { playSound(200, 0.15, 'sawtooth', 0.15); }
function sfxBoom() { playSound(60, 0.4, 'sawtooth', 0.2); setTimeout(() => playSound(40, 0.3, 'sawtooth', 0.15), 100); }
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 initLevel(level) {
currentLevel = level;
// КРАСНЫЙ ШАРИК
player = { x: 100, y: 300, r: 20, vx: 0, vy: 0, onGround: false, rotation: 0, facing: 1 };
platforms = []; enemies = []; bombs = []; coins = []; particles = []; boss = null;
score = 0; lives = 3; gameOver = false; win = false; bossActive = false;
cameraX = 0; invincibleTimer = 0;
updateHUD();
// Земля
platforms.push({ x: 0, y: 360, w: 2500, h: 40, type: 'ground' });
// Платформы
let px = 300;
for (let i = 0; i < 8; i++) {
const py = 300 - (i % 2) * 60;
const pw = 100 + Math.random() * 50;
platforms.push({ x: px, y: py, w: pw, h: 20, type: 'platform' });
px += 250 + Math.random() * 80;
}
// 1. Голубые квадраты (1 HP)
for (let i = 0; i < 4; i++) {
enemies.push({ x: 400 + i * 250, y: 320, w: 30, h: 30, type: 'blue', hp: 1, maxHp: 1, alive: true, color: '#3498db', vx: 1.5, minX: 350 + i * 250, maxX: 550 + i * 250 });
}
// 2. Золотые квадраты (2 HP)
for (let i = 0; i < 3; i++) {
enemies.push({ x: 700 + i * 300, y: 320, w: 30, h: 30, type: 'gold', hp: 2, maxHp: 2, alive: true, color: '#ffd700', vx: 1, minX: 650 + i * 300, maxX: 850 + i * 300 });
}
// 3. Чёрные бомбы (взрываются)
for (let i = 0; i < 3; i++) {
bombs.push({ x: 1000 + i * 250, y: 340, w: 28, h: 28, alive: true, fuse: 0, armed: false, vx: 0 });
}
// Босс на 3 уровне (финальный)
if (level === 3) {
bossActive = true;
boss = {
x: 2000, y: 240, w: 90, h: 90,
hp: 8, maxHp: 8, alive: true, vx: 1.5, minX: 1800, maxX: 2200,
hitTimer: 0
};
}
// Монеты
for (let i = 0; i < 12; i++) {
coins.push({ x: 200 + i * 180, y: 280 + (i % 3) * 20, r: 8, collected: false });
}
}
function updateHUD() {
let text = `🔴 Красный Шарик | Уровень: ${currentLevel} | Убито: ${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) * 8, vy: (Math.random() - 0.5) * 8 - 1, life: 20 + Math.random() * 15, maxLife: 35, color, size: 2 + Math.random() * 4 });
}
}
function damagePlayer(fromX) {
lives--; invincibleTimer = 90;
player.vy = -8; player.vx = (player.x < fromX ? -1 : 1) * 6;
sfxHit(); spawnParticles(player.x, player.y, '#ff0066', 10);
if (lives <= 0) { gameOver = true; }
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.save();
ctx.translate(px, py);
ctx.rotate(player.rotation);
const grd = ctx.createRadialGradient(-player.r/3, -player.r/3, 2, 0, 0, player.r);
grd.addColorStop(0, '#e94560'); grd.addColorStop(1, '#b02a40');
ctx.beginPath(); ctx.arc(0, 0, player.r, 0, Math.PI * 2);
ctx.fillStyle = grd; ctx.fill();
ctx.strokeStyle = '#b02a40'; ctx.lineWidth = 3; ctx.stroke();
ctx.restore();
// Блик
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 - 6, py - 4, 6, 0, Math.PI * 2); ctx.arc(px + 6, py - 4, 6, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = '#000';
ctx.beginPath(); ctx.arc(px - 6 + player.facing*2, py - 4, 3, 0, Math.PI * 2); ctx.arc(px + 6 + player.facing*2, py - 4, 3, 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);
// HP (для золотых)
if (e.maxHp > 1) {
ctx.fillStyle = '#000'; ctx.fillRect(ex, e.y - 8, e.w, 4);
ctx.fillStyle = e.hp === 2 ? '#0f0' : '#ff0'; ctx.fillRect(ex, e.y - 8, e.w * (e.hp / e.maxHp), 4);
}
}
function drawBomb(b) {
if (!b.alive) return;
const bx = b.x - cameraX;
const blink = b.armed && Math.floor(b.fuse / 10) % 2 === 0;
ctx.fillStyle = blink ? '#ff0000' : '#222';
ctx.beginPath(); ctx.arc(bx + b.w/2, b.y + b.h/2, b.w/2, 0, Math.PI * 2); ctx.fill();
ctx.strokeStyle = '#000'; ctx.lineWidth = 2; ctx.stroke();
// Фитиль
ctx.strokeStyle = '#ff6600'; ctx.lineWidth = 2;
ctx.beginPath(); ctx.moveTo(bx + b.w/2, b.y); ctx.lineTo(bx + b.w/2 + 5, b.y - 8); ctx.stroke();
if (Math.floor(Date.now() / 100) % 2 === 0) {
ctx.fillStyle = '#ffdd00'; ctx.beginPath(); ctx.arc(bx + b.w/2 + 5, b.y - 8, 4, 0, Math.PI * 2); ctx.fill();
}
if (b.armed) {
ctx.fillStyle = '#fff'; ctx.font = 'bold 12px Arial'; ctx.textAlign = 'center';
ctx.fillText(Math.ceil(b.fuse / 60), bx + b.w/2, b.y + b.h/2 + 4); ctx.textAlign = 'left';
}
}
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();
// HP
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() {
const grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
grad.addColorStop(0, '#87CEEB'); grad.addColorStop(1, '#b8e0f0');
ctx.fillStyle = grad; ctx.fillRect(0, 0, canvas.width, canvas.height);
// Облака
ctx.fillStyle = 'rgba(255,255,255,0.7)';
for (let i = 0; i < 8; i++) {
const cx = (i * 200 - cameraX * 0.3) % 1200;
ctx.beginPath(); ctx.arc(cx, 50 + (i % 3) * 30, 25, 0, Math.PI * 2); ctx.arc(cx + 25, 50 + (i % 3) * 30, 20, 0, Math.PI * 2); ctx.fill();
}
// Платформы
platforms.forEach(p => {
ctx.fillStyle = '#8B4513'; ctx.fillRect(p.x - cameraX, p.y, p.w, p.h);
ctx.fillStyle = '#4CAF50'; ctx.fillRect(p.x - cameraX, p.y, p.w, 6);
});
// Монеты
coins.forEach(c => {
if (c.collected) return;
const bob = Math.sin(Date.now() / 200 + c.x) * 3;
ctx.fillStyle = '#ffd700';
ctx.beginPath(); ctx.arc(c.x - cameraX, c.y + bob, c.r, 0, Math.PI * 2); ctx.fill();
ctx.strokeStyle = '#daa520'; ctx.lineWidth = 2; ctx.stroke();
});
enemies.forEach(e => drawEnemy(e));
bombs.forEach(b => drawBomb(b));
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;
});
if (!gameOver) drawPlayer();
if (gameOver) {
ctx.fillStyle = 'rgba(0,0,0,0.7)'; 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);
}
if (win) {
ctx.fillStyle = 'rgba(0,0,0,0.7)'; ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#ffd700'; 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('Ты прошёл игру!', canvas.width/2, canvas.height/2 + 40);
}
}
function update() {
if (gameOver || win) return;
if (invincibleTimer > 0) invincibleTimer--;
const left = keys['ArrowLeft'] || keys['a'];
const right = keys['ArrowRight'] || keys['d'];
const control = player.onGround ? ACCEL : ACCEL * 0.6;
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.rotation += player.vx * 0.05;
player.vy += GRAVITY;
if (player.vy > MAX_FALL) player.vy = MAX_FALL;
// X
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;
}
});
// Y
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; }
}
});
// Монеты
coins.forEach(c => {
if (c.collected) return;
const dx = player.x - c.x, dy = player.y - c.y;
if (Math.sqrt(dx*dx + dy*dy) < player.r + c.r + 5) {
c.collected = true; score++; 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, 10);
score += (e.type === 'gold' ? 3 : 1);
updateHUD();
} else {
sfxBossHit();
}
} else if (invincibleTimer === 0) damagePlayer(e.x);
}
});
// Бомбы
bombs.forEach(b => {
if (!b.alive) return;
// Если игрок рядом — взводится
if (!b.armed && Math.abs(player.x - b.x) < 80 && Math.abs(player.y - b.y) < 80) {
b.armed = true;
b.fuse = 120;
}
if (b.armed) {
b.fuse--;
if (b.fuse <= 0) {
b.alive = false;
sfxBoom();
spawnParticles(b.x + b.w/2, b.y + b.h/2, '#ff4400', 20);
// Урон по игроку
if (Math.abs(player.x - b.x) < 100 && Math.abs(player.y - b.y) < 100 && invincibleTimer === 0) {
damagePlayer(b.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', 10);
if (boss.hp <= 0) {
boss.alive = false;
sfxWin();
win = true;
}
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() { update(); draw(); requestAnimationFrame(loop); }
// Управление
document.addEventListener('keydown', e => {
keys[e.key] = true;
if (e.key === ' ' || e.key === 'ArrowUp' || e.key === 'w') {
if (player.onGround) { player.vy = JUMP_FORCE; player.onGround = false; sfxJump(); }
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.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(); });
document.addEventListener('touchstart', initAudio, { once: true });
document.addEventListener('mousedown', initAudio, { once: true });
initLevel(3); // Сразу 3 уровень с боссом
loop();
</script>
</body>
</html>Game Source: Красный Шарик: Квадраты и Босс
Creator: TurboGamer88
Libraries: none
Complexity: complex (454 lines, 17.2 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-mvtky381" to link back to the original. Then publish at arcadelab.ai/publish.