Добавить в корзинуПозвонить
Найти в Дзене
Дмитрий Брунько

Почему игра не отпускает: петли и game loop изнутри

Вы открываете браузерную игру «на минутку» — и обнаруживаете себя за ней через час. Это не случайность и не магия. За этим стоит конкретная конструкция: игровой цикл. Разберём как он устроен с двух сторон — со стороны дизайна и со стороны кода.
Любая инкрементальная игра работает одновременно на трёх уровнях времени.
Микро-петля — до 5 минут. Купил юнита, отбил волну, получил золото, купил ещё.
Оглавление

Вы открываете браузерную игру «на минутку» — и обнаруживаете себя за ней через час. Это не случайность и не магия. За этим стоит конкретная конструкция: игровой цикл. Разберём как он устроен с двух сторон — со стороны дизайна и со стороны кода.

Три петли, которые держат игрока

Любая инкрементальная игра работает одновременно на трёх уровнях времени.

Микро-петля — до 5 минут. Купил юнита, отбил волну, получил золото, купил ещё. Немедленная награда. Игрок видит результат каждого действия почти мгновенно.

Макро-петля — от получаса до часа. Накопил на нового типа юнита, открыл ветку навыков, добрался до босса. Цели чуть дальше, но всё ещё достижимы за сессию.

Мета-петля — дни и недели. Система Prestige: сбрасываешь прогресс в обмен на постоянный бонус. Следующее прохождение сильнее. Возвращаешься снова.

Если в игре есть только микро-петля — она надоедает за час. Только мета — игрок не понимает зачем заходить сегодня. Все три вместе создают ощущение, что всегда есть куда двигаться.

Именно поэтому при открытии игры всегда виден и текущий бой, и прогресс-бар к следующему навыку, и счётчик Prestige-очков. Три горизонта одновременно.

Как это устроено в коде

Технически весь игровой процесс держится на одной функции — requestAnimationFrame. Браузер вызывает её примерно 60 раз в секунду, и каждый такой вызов — один кадр игры.

Главное что нужно считать внутри кадра — delta time: сколько миллисекунд прошло с прошлого вызова. Без этого игра будет работать быстрее на мощных машинах и медленнее на слабых.

Реализация requestAnimationFrame
Реализация requestAnimationFrame

На каждом кадре выполняется цепочка систем:

  • WaveManager — двигает врагов, управляет спавном
  • CombatSystem — считает урон, начисляет ресурсы
  • ResourceManager — обновляет пассивный доход
  • Renderer — рисует всё на Canvas

Почему Canvas, а не React?

Игровая сцена — это сотни объектов, которые обновляются 60 раз в секунду. React не рассчитан на такую частоту обновлений: каждый setState запускает цикл согласования, и на сложных сценах это даёт заметные просадки.

Решение: Canvas рисует игру, React рисует интерфейс поверх неё. Магазин юнитов, HUD с ресурсами, дерево навыков — всё это обычные React-компоненты, которые лежат поверх Canvas через position: absolute. Они обновляются редко и реагируют на действия игрока. Canvas работает в своём ритме и не знает о React ничего.

Два слоя не мешают друг другу. Canvas получает свои 60fps, React — свой привычный жизненный цикл.

Защита от заморозки

Когда игрок переключает вкладку, браузер приостанавливает requestAnimationFrame. При возврате delta time может оказаться огромной — несколько минут или часов.

Для этого в игре стоит кап: delta не может превышать 100 миллисекунд за кадр. Всё что сверху — обрабатывается отдельно как оффлайн-прогресс. Так игра не «взрывается» после долгого отсутствия.

Что в итоге

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

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

#gamedev, #инди-игры, #разработка игр, #javascript, #typescript