Гузариш ба мундариҷа
Мавзӯи 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), танҳо дар компютери ту кор мекунад ва кодро танҳо пас аз ворид кардани рамзи пайвастшавӣ иҷро мекунад.