Стикмен Бой: Арены и Скины
by HyperRider36367 lines15.4 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: #1a1a2e; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; font-family: Arial; overflow: hidden; touch-action: none; }
canvas { background: #2a2a4e; border: 2px solid #fff; border-radius: 8px; touch-action: none; max-width: 100vw; max-height: 80vh; }
#hud { font-size: 16px; margin-bottom: 5px; color: #fff; font-weight: bold; text-align: center; }
.controls { position: absolute; bottom: 10px; width: 100%; display: flex; justify-content: space-between; padding: 0 10px; box-sizing: border-box; pointer-events: none; }
.player-controls { display: flex; gap: 10px; pointer-events: auto; }
.btn { width: 55px; height: 55px; border-radius: 50%; background: rgba(255,255,255,0.2); color: #fff; font-size: 20px; border: 2px solid #fff; display: flex; align-items: center; justify-content: center; user-select: none; touch-action: none; }
#p1-left { background: rgba(0,150,255,0.4); } #p1-right { background: rgba(0,150,255,0.4); }
#p1-jump { background: rgba(0,255,0,0.4); } #p1-punch { background: rgba(255,0,0,0.4); }
#p2-left { background: rgba(255,100,100,0.4); } #p2-right { background: rgba(255,100,100,0.4); }
#p2-jump { background: rgba(0,255,0,0.4); } #p2-punch { background: rgba(255,0,0,0.4); }
</style>
</head>
<body>
<div id="hud">Стикмен Бой</div>
<canvas id="game" width="800" height="400"></canvas>
<div class="controls">
<div class="player-controls">
<div class="btn" id="p1-left">◀</div>
<div class="btn" id="p1-right">▶</div>
<div class="btn" id="p1-jump">▲</div>
<div class="btn" id="p1-punch">👊</div>
</div>
<div class="player-controls">
<div class="btn" id="p2-left">◀</div>
<div class="btn" id="p2-right">▶</div>
<div class="btn" id="p2-jump">▲</div>
<div class="btn" id="p2-punch">👊</div>
</div>
</div>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const hud = document.getElementById('hud');
let state = 'menu'; // menu, playing, win
let p1, p2, platforms, particles;
let winner = null;
let currentMenuPage = 0; // 0 - главная, 1 - выбор карты, 2 - выбор скина P1, 3 - выбор скина P2
const groundY = 340;
const GRAVITY = 0.8;
const JUMP_POWER = -15;
const MOVE_SPEED = 5;
const PUNCH_RANGE = 60;
// --- НАСТРОЙКИ ---
let currentMapIndex = 0;
let p1SkinIndex = 0;
let p2SkinIndex = 0;
const MAPS = [
{ name: 'Классика', bg1: '#2a2a4e', bg2: '#1a1a2e', platform: '#444' },
{ name: 'Закат', bg1: '#ff7e5f', bg2: '#feb47b', platform: '#8B4513' },
{ name: 'Неон', bg1: '#000000', bg2: '#1a0033', platform: '#ff0066' },
{ name: 'Лес', bg1: '#1b4d1b', bg2: '#0d260d', platform: '#654321' }
];
const SKINS = [
{ name: 'Классика', body: '#3498db', dark: '#21618c' },
{ name: 'Тень', body: '#333', dark: '#111' },
{ name: 'Огонь', body: '#ff4400', dark: '#aa2200' },
{ name: 'Золото', body: '#ffd700', dark: '#daa520' },
{ name: 'Кислота', body: '#00ff00', dark: '#008800' }
];
const keys = {
p1: { left: false, right: false, jump: false, punch: false },
p2: { left: false, right: false, jump: false, punch: false }
};
// --- ИНИЦИАЛИЗАЦИЯ ---
function initGame() {
p1 = { x: 200, y: 300, vx: 0, vy: 0, w: 30, h: 50, hp: 100, color: SKINS[p1SkinIndex].body, dark: SKINS[p1SkinIndex].dark, facing: 1, punchTimer: 0, onGround: false };
p2 = { x: 600, y: 300, vx: 0, vy: 0, w: 30, h: 50, hp: 100, color: SKINS[p2SkinIndex].body, dark: SKINS[p2SkinIndex].dark, facing: -1, punchTimer: 0, onGround: false };
platforms = [
{ x: 0, y: groundY, w: 800, h: 60 },
{ x: 250, y: 260, w: 80, h: 15 },
{ x: 470, y: 260, w: 80, h: 15 },
{ x: 360, y: 180, w: 80, h: 15 }
];
particles = [];
winner = null;
}
// --- ФИЗИКА ---
function updatePhysics(p, other) {
p.vy += GRAVITY;
p.y += p.vy;
p.x += p.vx;
p.vx = 0;
if (p.x < 0) p.x = 0;
if (p.x + p.w > canvas.width) p.x = canvas.width - p.w;
p.onGround = false;
platforms.forEach(pl => {
if (p.x + p.w > pl.x && p.x < pl.x + pl.w && p.y + p.h > pl.y && p.y + p.h < pl.y + pl.h + 20 && p.vy >= 0) {
p.y = pl.y - p.h; p.vy = 0; p.onGround = true;
}
});
if (p.punchTimer > 0) p.punchTimer--;
if (p.punchTimer === 15) {
const hitX = p.facing > 0 ? p.x + p.w : p.x - PUNCH_RANGE;
if (hitX < other.x + other.w && hitX + PUNCH_RANGE > other.x && p.y < other.y + other.h && p.y + p.h > other.y) {
other.hp -= 10;
particles.push({ x: other.x + other.w/2, y: other.y + other.h/2, vx: (Math.random() - 0.5) * 8, vy: -5, life: 30, color: '#fff' });
}
}
if (p.hp < 0) p.hp = 0;
}
function update() {
if (state !== 'playing') return;
if (keys.p1.left) { p1.x -= MOVE_SPEED; p1.facing = -1; }
if (keys.p1.right) { p1.x += MOVE_SPEED; p1.facing = 1; }
if (keys.p1.jump && p1.onGround) { p1.vy = JUMP_POWER; p1.onGround = false; }
if (keys.p1.punch && p1.punchTimer === 0) p1.punchTimer = 20;
if (keys.p2.left) { p2.x -= MOVE_SPEED; p2.facing = -1; }
if (keys.p2.right) { p2.x += MOVE_SPEED; p2.facing = 1; }
if (keys.p2.jump && p2.onGround) { p2.vy = JUMP_POWER; p2.onGround = false; }
if (keys.p2.punch && p2.punchTimer === 0) p2.punchTimer = 20;
updatePhysics(p1, p2);
updatePhysics(p2, p1);
particles.forEach((pt, i) => {
pt.x += pt.vx; pt.y += pt.vy; pt.vy += 0.2; pt.life--;
if (pt.life <= 0) particles.splice(i, 1);
});
if (p1.hp <= 0) { winner = 'Победил ' + SKINS[p2SkinIndex].name + '!'; state = 'win'; }
if (p2.hp <= 0) { winner = 'Победил ' + SKINS[p1SkinIndex].name + '!'; state = 'win'; }
}
// --- ОТРИСОВКА СТИКМЕНА ---
function drawStickman(p) {
const cx = p.x + p.w / 2;
const cy = p.y + p.h / 2;
ctx.fillStyle = 'rgba(0,0,0,0.3)';
ctx.beginPath(); ctx.ellipse(cx, p.y + p.h + 2, p.w * 0.6, 5, 0, 0, Math.PI * 2); ctx.fill();
ctx.strokeStyle = p.color;
ctx.lineWidth = 5;
ctx.lineCap = 'round';
ctx.beginPath(); ctx.arc(cx, p.y + 10, 10, 0, Math.PI * 2);
ctx.fillStyle = p.dark; ctx.fill(); ctx.stroke();
ctx.fillStyle = '#fff';
ctx.beginPath(); ctx.arc(cx - 3 + p.facing * 2, p.y + 8, 3, 0, Math.PI * 2); ctx.arc(cx + 3 + p.facing * 2, p.y + 8, 3, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = '#000';
ctx.beginPath(); ctx.arc(cx - 3 + p.facing * 3, p.y + 8, 1.5, 0, Math.PI * 2); ctx.arc(cx + 3 + p.facing * 3, p.y + 8, 1.5, 0, Math.PI * 2); ctx.fill();
ctx.beginPath(); ctx.moveTo(cx, p.y + 20); ctx.lineTo(cx, p.y + 35); ctx.stroke();
const punchOffset = p.punchTimer > 0 ? (p.facing > 0 ? 20 : -20) : 0;
ctx.beginPath(); ctx.moveTo(cx, p.y + 25); ctx.lineTo(cx - 15, p.y + 30); ctx.stroke();
ctx.beginPath(); ctx.moveTo(cx, p.y + 25); ctx.lineTo(cx + 15 + punchOffset, p.y + 30); ctx.stroke();
ctx.beginPath(); ctx.moveTo(cx, p.y + 35); ctx.lineTo(cx - 10, p.y + p.h); ctx.stroke();
ctx.beginPath(); ctx.moveTo(cx, p.y + 35); ctx.lineTo(cx + 10, p.y + p.h); ctx.stroke();
}
// --- МЕНЮ ---
function drawMenu() {
const map = MAPS[currentMapIndex];
ctx.fillStyle = map.bg1; ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = map.bg2; ctx.fillRect(0, canvas.height / 2, canvas.width, canvas.height / 2);
ctx.fillStyle = '#fff';
ctx.font = 'bold 40px Arial';
ctx.textAlign = 'center';
if (currentMenuPage === 0) {
ctx.fillText('СТИКМЕН БОЙ', canvas.width/2, 100);
ctx.font = '18px Arial';
ctx.fillText('Карта: ' + MAPS[currentMapIndex].name, canvas.width/2, 180);
ctx.fillText('Синий (P1): ' + SKINS[p1SkinIndex].name, canvas.width/2, 210);
ctx.fillText('Красный (P2): ' + SKINS[p2SkinIndex].name, canvas.width/2, 240);
// Кнопки
ctx.fillStyle = '#3498db'; ctx.fillRect(50, 300, 150, 50);
ctx.fillStyle = '#fff'; ctx.font = '18px Arial'; ctx.fillText('Скины P1', 125, 330);
ctx.fillStyle = '#e94560'; ctx.fillRect(220, 300, 150, 50);
ctx.fillStyle = '#fff'; ctx.fillText('Скины P2', 295, 330);
ctx.fillStyle = '#ffd700'; ctx.fillRect(390, 300, 150, 50);
ctx.fillStyle = '#000'; ctx.fillText('Карты', 465, 330);
ctx.fillStyle = '#00ffcc'; ctx.fillRect(560, 300, 190, 50);
ctx.fillStyle = '#000'; ctx.fillText('▶ ИГРАТЬ', 655, 330);
}
else if (currentMenuPage === 1) {
ctx.fillText('ВЫБОР КАРТЫ', canvas.width/2, 80);
ctx.font = '20px Arial';
for (let i = 0; i < MAPS.length; i++) {
const x = 100 + i * 160;
ctx.fillStyle = MAPS[i].bg1;
ctx.fillRect(x, 150, 120, 80);
ctx.fillStyle = MAPS[i].platform;
ctx.fillRect(x, 200, 120, 30);
ctx.strokeStyle = (i === currentMapIndex) ? '#ffd700' : '#fff';
ctx.lineWidth = (i === currentMapIndex) ? 4 : 2;
ctx.strokeRect(x, 150, 120, 80);
ctx.fillStyle = '#fff'; ctx.font = '16px Arial';
ctx.fillText(MAPS[i].name, x + 60, 250);
}
ctx.fillStyle = '#fff'; ctx.font = '18px Arial';
ctx.fillText('Нажми на карту, чтобы выбрать', canvas.width/2, 350);
ctx.fillText('Нажми в любом месте, чтобы вернуться', canvas.width/2, 380);
}
else if (currentMenuPage === 2 || currentMenuPage === 3) {
const player = currentMenuPage === 2 ? 'Синий (P1)' : 'Красный (P2)';
ctx.fillText('СКИН: ' + player, canvas.width/2, 80);
ctx.font = '20px Arial';
for (let i = 0; i < SKINS.length; i++) {
const x = 50 + i * 140;
// Рисуем мини-стикмена
ctx.strokeStyle = SKINS[i].body; ctx.lineWidth = 4;
ctx.beginPath(); ctx.arc(x + 60, 160, 15, 0, Math.PI * 2); ctx.fillStyle = SKINS[i].dark; ctx.fill(); ctx.stroke();
ctx.beginPath(); ctx.moveTo(x + 60, 175); ctx.lineTo(x + 60, 210); ctx.stroke();
ctx.beginPath(); ctx.moveTo(x + 45, 190); ctx.lineTo(x + 75, 190); ctx.stroke();
ctx.beginPath(); ctx.moveTo(x + 60, 210); ctx.lineTo(x + 45, 235); ctx.stroke();
ctx.beginPath(); ctx.moveTo(x + 60, 210); ctx.lineTo(x + 75, 235); ctx.stroke();
const isSel = (currentMenuPage === 2 && i === p1SkinIndex) || (currentMenuPage === 3 && i === p2SkinIndex);
ctx.strokeStyle = isSel ? '#ffd700' : '#fff'; ctx.lineWidth = isSel ? 4 : 1;
ctx.strokeRect(x, 130, 120, 120);
ctx.fillStyle = '#fff'; ctx.font = '14px Arial';
ctx.fillText(SKINS[i].name, x + 60, 270);
}
ctx.fillStyle = '#fff'; ctx.font = '18px Arial';
ctx.fillText('Нажми на скин, чтобы выбрать', canvas.width/2, 350);
ctx.fillText('Нажми в любом месте, чтобы вернуться', canvas.width/2, 380);
}
}
function draw() {
if (state === 'menu') { drawMenu(); return; }
const map = MAPS[currentMapIndex];
const grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
grad.addColorStop(0, map.bg1); grad.addColorStop(1, map.bg2);
ctx.fillStyle = grad; ctx.fillRect(0, 0, canvas.width, canvas.height);
platforms.forEach(pl => {
ctx.fillStyle = map.platform;
ctx.fillRect(pl.x, pl.y, pl.w, pl.h);
ctx.strokeStyle = '#222'; ctx.lineWidth = 2; ctx.strokeRect(pl.x, pl.y, pl.w, pl.h);
});
// HP бары
ctx.fillStyle = '#000'; ctx.fillRect(20, 20, 300, 20);
ctx.fillStyle = p1 ? p1.color : '#3498db'; ctx.fillRect(20, 20, p1 ? (p1.hp / 100) * 300 : 0, 20);
ctx.fillStyle = '#000'; ctx.fillRect(480, 20, 300, 20);
ctx.fillStyle = p2 ? p2.color : '#e94560'; ctx.fillRect(480, 20, p2 ? (p2.hp / 100) * 300 : 0, 20);
if (p1) drawStickman(p1);
if (p2) drawStickman(p2);
particles.forEach(pt => {
ctx.globalAlpha = pt.life / 30; ctx.fillStyle = pt.color;
ctx.fillRect(pt.x, pt.y, 4, 4); ctx.globalAlpha = 1;
});
if (state === 'win') {
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(winner, canvas.width/2, canvas.height/2 - 20);
ctx.font = '20px Arial'; ctx.fillText('Нажмите, чтобы выйти в меню', canvas.width/2, canvas.height/2 + 30);
}
}
function loop() { update(); draw(); requestAnimationFrame(loop); }
// --- КНОПКИ УПРАВЛЕНИЯ ---
function holdBtn(id, player, action) {
const el = document.getElementById(id);
el.addEventListener('touchstart', (e) => { e.preventDefault(); keys[player][action] = true; });
el.addEventListener('touchend', (e) => { e.preventDefault(); keys[player][action] = false; });
el.addEventListener('mousedown', (e) => { e.preventDefault(); keys[player][action] = true; });
el.addEventListener('mouseup', (e) => { e.preventDefault(); keys[player][action] = false; });
}
holdBtn('p1-left', 'p1', 'left'); holdBtn('p1-right', 'p1', 'right'); holdBtn('p1-jump', 'p1', 'jump'); holdBtn('p1-punch', 'p1', 'punch');
holdBtn('p2-left', 'p2', 'left'); holdBtn('p2-right', 'p2', 'right'); holdBtn('p2-jump', 'p2', 'jump'); holdBtn('p2-punch', 'p2', 'punch');
// --- КЛАВИАТУРА ---
document.addEventListener('keydown', e => {
if (e.key === 'a' || e.key === 'A') keys.p1.left = true;
if (e.key === 'd' || e.key === 'D') keys.p1.right = true;
if (e.key === 'w' || e.key === 'W') keys.p1.jump = true;
if (e.key === 'f' || e.key === 'F') keys.p1.punch = true;
if (e.key === 'ArrowLeft') keys.p2.left = true;
if (e.key === 'ArrowRight') keys.p2.right = true;
if (e.key === 'ArrowUp') keys.p2.jump = true;
if (e.key === 'Enter') keys.p2.punch = true;
});
document.addEventListener('keyup', e => {
if (e.key === 'a' || e.key === 'A') keys.p1.left = false;
if (e.key === 'd' || e.key === 'D') keys.p1.right = false;
if (e.key === 'w' || e.key === 'W') keys.p1.jump = false;
if (e.key === 'f' || e.key === 'F') keys.p1.punch = false;
if (e.key === 'ArrowLeft') keys.p2.left = false;
if (e.key === 'ArrowRight') keys.p2.right = false;
if (e.key === 'ArrowUp') keys.p2.jump = false;
if (e.key === 'Enter') keys.p2.punch = false;
});
// --- КЛИКИ ПО МЕНЮ ---
canvas.addEventListener('click', (e) => {
const rect = canvas.getBoundingClientRect();
const x = (e.clientX - rect.left) * (canvas.width / rect.width);
const y = (e.clientY - rect.top) * (canvas.height / rect.height);
if (state === 'menu') {
if (currentMenuPage === 0) {
if (x > 50 && x < 200 && y > 300 && y < 350) { currentMenuPage = 2; return; }
if (x > 220 && x < 370 && y > 300 && y < 350) { currentMenuPage = 3; return; }
if (x > 390 && x < 540 && y > 300 && y < 350) { currentMenuPage = 1; return; }
if (x > 560 && x < 750 && y > 300 && y < 350) { initGame(); state = 'playing'; return; }
}
else if (currentMenuPage === 1) {
for (let i = 0; i < MAPS.length; i++) {
const bx = 100 + i * 160;
if (x > bx && x < bx + 120 && y > 150 && y < 230) { currentMapIndex = i; return; }
}
currentMenuPage = 0; return;
}
else if (currentMenuPage === 2 || currentMenuPage === 3) {
for (let i = 0; i < SKINS.length; i++) {
const bx = 50 + i * 140;
if (x > bx && x < bx + 120 && y > 130 && y < 250) {
if (currentMenuPage === 2) p1SkinIndex = i;
else p2SkinIndex = i;
return;
}
}
currentMenuPage = 0; return;
}
}
else if (state === 'win') {
state = 'menu';
currentMenuPage = 0;
}
});
initGame();
state = 'menu';
loop();
</script>
</body>
</html>Game Source: Стикмен Бой: Арены и Скины
Creator: HyperRider36
Libraries: none
Complexity: complex (367 lines, 15.4 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-hyperrider36-mvuxnagu" to link back to the original. Then publish at arcadelab.ai/publish.