Вы когда-нибудь задумывались, что заставляет игру жить? Что дарит ощущение динамики, реакции и настоящего взаимодействия? Не графика и не сюжет, а невидимый двигатель — игровой цикл. Это пульсирующее сердце любой игры, обрабатывающее ваши действия, обновляющее мир и рисующее кадры 60 раз в секунду.

Что такое игровой цикл?

Представьте типичный игровой момент. На экране — поле битвы, враги, интерфейс. Вы нажимаете клавишу или кликаете мышкой. Что происходит дальше?

  1. Игра фиксирует ваш ввод.
  2. Преобразует его в действие — выстрел, прыжок, движение.
  3. Пересчитывает состояние мира — физика, искусственный интеллект, анимации.
  4. Обновляет изображение на экране.

И затем цикл повторяется — снова, снова, снова.

Этот простой, но мощный шаблон лежит в основе любых игр — от спокойных головоломок до динамичных шутеров. Единственное отличие — в том, как часто и когда запускается этот цикл. Для казуальной игры — он может срабатывать по событию, например, клику. Для быстрой игры — непрерывно, синхронизируясь с обновлением кадров на экране.

Почему именно JavaScript?

Современные браузеры — отличная среда для игровых циклов. Их "часовой механизм" — функция window.requestAnimationFrame(), которая идеально подходит для синхронизации обновлений с частотой обновления экрана (обычно 60 кадров в секунду).

Вот базовый каркас игрового цикла на JavaScript:

(function () {
function main(timestamp) {
requestAnimationFrame(main);
// Логика игры: обновляем состояние и рисуем
updateGameState(timestamp);
renderGame();
}
main();
})();

Что делает этот код особенным?

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

Управление циклом: остановка, запуск и точное время

Чтобы остановить цикл, достаточно сохранить ID и вызвать cancelAnimationFrame():

let animationID = requestAnimationFrame(main);
// Позже
cancelAnimationFrame(animationID);

Важный момент — для расчёта времени лучше использовать performance.now(), а не устаревший Date. Это позволяет работать с точностью до миллисекунд и плавно рассчитывать изменения между кадрами.

Технические вызовы: что делать, если не успеваем за 60 FPS?

Не всегда устройство способно обновлять логику и отрисовку 60 раз в секунду. Что делать?

  • Упрощать рендеринг, снижать детализацию.
  • Разделять обновление логики и отрисовку.
  • Использовать экстраполяцию — рисовать промежуточные состояния между обновлениями логики.
  • Выносить тяжёлые вычисления в отдельные потоки (Web Workers), чтобы не блокировать основной поток.

Итог: философия живой игры

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

Минимальный шаблон game loop

Ниже базовый каркас, который можно положить в любой мини-проект. Он отделяет обновление логики от отрисовки и сразу задаёт правильный ритм.

let lastTime = 0;

function gameLoop(timestamp) {
  const deltaTime = (timestamp - lastTime) / 1000;
  lastTime = timestamp;

  update(deltaTime);   // Логика игры
  render();            // Отрисовка кадра

  requestAnimationFrame(gameLoop);
}

requestAnimationFrame((ts) => {
  lastTime = ts;
  requestAnimationFrame(gameLoop);
});

Delta time (dt): почему без него всё ломается

Если двигать объекты «на фиксированное число пикселей за кадр», игра будет идти с разной скоростью на разных мониторах. На 144 Гц всё может неожиданно ускориться.

deltaTime решает это: ты считаешь, сколько реального времени прошло между кадрами, и умножаешь скорость на это значение.

  • Без dt скорость зависит от FPS.
  • С dt скорость зависит от времени, а не от железа.
  • Игра ведёт себя предсказуемо и на 60 Гц, и на 120+ Гц.

requestAnimationFrame vs setInterval

Для современного браузерного цикла requestAnimationFrame почти всегда лучше.

  • requestAnimationFrame: синхронен с перерисовкой, плавнее, экономит ресурсы в фоне.
  • setInterval: может «плыть» по таймингу и давать рваную анимацию.

Если цель — живая плавная игра, стартуй именно с requestAnimationFrame.

Как ставить на паузу и останавливать цикл

Рабочий цикл должен уметь корректно останавливаться и запускаться снова, иначе получишь дублирование кадров и хаотичные баги.

let rafId = null;
let isRunning = false;

function startLoop() {
  if (isRunning) return;
  isRunning = true;
  rafId = requestAnimationFrame(loop);
}

function stopLoop() {
  if (!isRunning) return;
  isRunning = false;
  cancelAnimationFrame(rafId);
}

Частые ошибки новичков

  • Запускают цикл несколько раз — игра «ускоряется сама».
  • Не используют deltaTime и получают разную скорость на разных устройствах.
  • Смешивают ввод, физику, отрисовку и UI в одну большую функцию.
  • Не проверяют, что происходит после переключения вкладки.
  • Пытаются чинить поведение без профилирования и логов.

Чеклист рабочего игрового цикла

  • Есть явные этапы: input -> update -> render.
  • Скорость движения рассчитывается через dt.
  • Цикл нельзя запустить повторно случайно.
  • Есть pause/stop без утечек.
  • Поведение предсказуемо после сворачивания вкладки.
  • Код делится на небольшие функции, а не «комбайн» на 300 строк.

FAQ

Почему игра ускоряется со временем?

Чаще всего ты запускаешь второй (или третий) цикл поверх первого. Проверь, что старт цикла защищён флагом isRunning.

Можно ли сделать цикл без delta time?

Можно, но поведение станет зависеть от FPS. Для стабильной скорости на разных экранах лучше использовать dt.

Почему в фоне игра ведёт себя иначе?

В неактивной вкладке браузер снижает частоту обновления. Это нормальное поведение, его нужно учитывать в логике паузы и восстановления.

Практический вывод

Игровой цикл на JavaScript — это не «магия 60 FPS», а дисциплина структуры: чёткие этапы, requestAnimationFrame, контроль времени кадра и управляемая пауза. Если это настроено, игра ощущается живой и предсказуемой.

Обложка книги JavaScript с нуля: учимся программировать, создавая игры

Книга по JavaScript для начинающих

«JavaScript с нуля: учимся программировать, создавая игры»

Пошаговый формат с маленькими практическими задачами: от базового синтаксиса до первых игровых механик без перегруза теорией.

Читать книгу на ЛитРес →