🎮ArcadeLab

Стикмен Бой: Арены и Скины

by HyperRider36
367 lines15.4 KB
▶ Play
<!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.