Перейти к содержимому
Тема 5 из 5
Теория

Проект: арканоид

Собираем игру

В этой теме из кусочков прошлых тем получается настоящая игра — арканоид: ракетка внизу, мяч и стена из кирпичей. Разобьёшь все — победа, мяч упадёт три раза — проигрыш. Здесь нет ничего нового по синтаксису: главное — разложить игру на части и собрать их в правильном порядке.

Состояние игры

Сначала придумываем, какие числа описывают игру целиком. Всё остальное — рисование — строится из них:

const game = {
  ball:   { x: 200, y: 200, vx: 150, vy: -150, r: 6 },
  paddle: { x: 160, y: 280, w: 80, h: 10 },
  bricks: makeBricks(4, 7, 50, 14, 6),
  score: 0,
  lives: 3,
  over: false,
  width: 400,
  height: 300,
};

Хорошая проверка дизайна: если сохранить этот объект в JSON и потом загрузить, игра должна продолжиться с того же места. Значит, в состоянии есть всё нужное.

Уровень как данные

Стену из кирпичей не рисуют руками — её генерируют двумя циклами: строки и столбцы. Позиция кирпича считается из номера строки и столбца: шаг по x — ширина кирпича плюс зазор.

for (let row = 0; row < rows; row++) {
  for (let col = 0; col < cols; col++) {
    bricks.push({ x: gap + col * (w + gap), y: gap + row * (h + gap), w, h, alive: true });
  }
}

Уровни можно описывать и картой-строкой, где # — кирпич, а . — пусто, — и превращать её в массив тем же двойным циклом. Так делают редакторы уровней.

Порядок шагов в кадре

Порядок важен: сначала мяч двигается, потом проверяются все столкновения в новом положении, потом — условия конца игры:

function update(dt) {
  moveBall(dt);          // 1. движение
  bounceWalls();         // 2. стены (кроме нижней!)
  bouncePaddle();        // 3. ракетка
  hitBricks();           // 4. кирпичи и очки
  checkFall();           // 5. мяч упал — минус жизнь
  checkWin();            // 6. кирпичей не осталось — победа
}

Нижняя стена в арканоиде — не стена, а пропасть: мяч, улетевший вниз, отнимает жизнь.

Ракетка и «липкий» мяч

Если мяч глубоко вошёл в ракетку, на следующем кадре он всё ещё пересекается с ней. Простое vy = -vy развернуло бы его обратно вниз — мяч застрял бы в ракетке. Поэтому:

  • отбиваем только мяч, который летит вниз (vy > 0);
  • направление ставим явно: vy = -Math.abs(vy) — теперь он точно летит вверх.

В настоящих арканоидах угол отскока ещё зависит от того, в какую часть ракетки попал мяч: ближе к краю — круче в сторону. Это делает игру управляемой:

const hit = (ball.x - (paddle.x + paddle.w / 2)) / (paddle.w / 2);   // от -1 до 1
ball.vx = hit * 250;

Управление мышью

Ракетка следует за мышью по горизонтали, центр ракетки — под курсором, и она не выезжает за поле:

canvas.addEventListener("mousemove", (e) => {
  const x = e.clientX - canvas.getBoundingClientRect().left;
  paddle.x = Math.max(0, Math.min(canvas.width - paddle.w, x - paddle.w / 2));
});

Конец игры

Флаг over останавливает update, но не draw: последний кадр с надписью «GAME OVER» или «YOU WIN!» продолжает рисоваться. Перезапуск — просто создать состояние заново:

canvas.addEventListener("click", () => {
  if (game.over) Object.assign(game, newGame());
});

Куда расти дальше

  • бонусы, которые падают из кирпичей (расширить ракетку, второй мяч);
  • кирпичи с несколькими «жизнями» (hp) и разными цветами;
  • уровни из строк-карт и переход на следующий уровень;
  • звук удара через new Audio(...) и рекорд в localStorage.

Всё это — те же приёмы: числа в состоянии, update их меняет, draw показывает.

Итог

  • Сначала придумываем состояние игры, потом пишем update и draw вокруг него.
  • Уровень генерируем циклами из данных, а не рисуем руками.
  • Порядок кадра: движение → стены → ракетка → кирпичи → жизни → победа.
  • Отскок от ракетки только при vy > 0 и с Math.abs, чтобы мяч не залипал.

Стена из кирпичей

Лёгкая+10 XP

Напиши функцию makeBricks(rows, cols, w, h, gap), которая возвращает массив кирпичей { x, y, w, h, alive: true } — по рядам сверху вниз, в ряду слева направо. Между кирпичами и от края холста — отступ gap:

  • x = gap + col * (w + gap)
  • y = gap + row * (h + gap)
makeBricks(1, 2, 50, 10, 4)
→ [{ x: 4, y: 4, w: 50, h: 10, alive: true },
   { x: 58, y: 4, w: 50, h: 10, alive: true }]

Подключи коннектор, чтобы решать задачи

Код задач выполняется на твоём компьютере — так быстрее и надёжнее, чем в браузере. Для этого нужна бесплатная программа-коннектор CodeWithMe. Подключить её нужно один раз.

Ищем коннектор на этом компьютере…

  1. 1
    Скачай коннектор для своей системы (Windows, macOS или Linux).Скачать коннекторНа странице загрузки выбери файл со своей системой в названии, например windows-amd64.
  2. 2Запусти его и выбери папку, где будут лежать твои решения. Python и Node.js он поставит туда сам, когда понадобится.
  3. 3
    Введи ниже код из 6 цифр — он написан в окне коннектора («Код для сайта CodeWithMe»). Если браузер спросит про доступ к локальной сети — разреши.

Коннектор открытый (исходный код на GitHub), работает только на твоём компьютере и выполняет код лишь после ввода кода сопряжения.