---
title: "Игровой цикл (Game Loop) и физика столкновений (AABB, хитбоксы и триггеры) простыми словами и на уровне профи"
description: "Как устроен игровой цикл 2D-игры: бюджет кадра 16,67 мс, deltaTime, фиксированный шаг физики с аккумулятором, проверка пересечения AABB, выталкивание по осям, различие коллайдера и триггера и проверенный код приземления на JavaScript."
slug: igrovoj-cikl-i-fizika-stolknovenij-aabb
date: 2026-10-30
updated: 2026-11-02
scheduled_at: "2026-10-30T09:00:00+03:00"
rubric: ustrojstvo-2d-igr
tags: ["game-loop", "fizika", "aabb", "hitboksy", "profi"]
reading_time: 10
draft: false
summary: "Игровой цикл — это бесконечное повторение трёх фаз: чтение ввода, обновление состояния мира и отрисовка кадра; при 60 к/с на все три фазы есть 16,67 мс. Физику надёжнее считать фиксированными шагами (например, 1/60 с) через аккумулятор времени, чтобы прыжок и скорость не зависели от частоты кадров. Столкновение двух прямоугольников без поворота (AABB) проверяется четырьмя сравнениями, а разница между твёрдым коллайдером и триггером в том, что первый выталкивает объект, а второй только сообщает о пересечении."
---

## Коротко

Игровой цикл (Game Loop) — это повторяющаяся последовательность «прочитать ввод → обновить мир → нарисовать кадр», на которую при частоте `60 к/с` отводится `16,67 мс`, а при `30 к/с` — `33,33 мс`. Чтобы физика вела себя одинаково на любом компьютере, её обновляют фиксированными шагами через аккумулятор времени, а движение умножают на шаг времени. Столкновения прямоугольников без поворота (AABB — Axis-Aligned Bounding Box) проверяются четырьмя сравнениями и разрешаются выталкиванием по оси наименьшего перекрытия. В статье — формулы, готовый псевдокод, разница между коллайдером, триггером и хитбоксом и проверенный в Node.js код приземления и сбора монеты.

## Содержание

1. Три фазы игрового цикла и бюджет кадра в миллисекундах
2. deltaTime: почему без него игра работает быстрее на мониторах с высокой частотой обновления
3. Фиксированный шаг физики и аккумулятор времени
4. AABB: проверка пересечения двух прямоугольников
5. Разрешение столкновения: выталкивание по оси X, затем по оси Y
6. Коллайдер, триггер, хитбокс и хёртбокс: чем они отличаются
7. Туннелирование: почему быстрые объекты проходят сквозь стены
8. Практический сценарий: проверяем приземление и сбор монеты в коде на JavaScript
9. Когда вам это не подойдёт

## Три фазы игрового цикла и бюджет кадра в миллисекундах

Игровой цикл каждые `16,67 мс` (при `60 к/с`) выполняет три фазы: чтение ввода, обновление состояния и отрисовку. Бюджет кадра считается как обратная величина частоты:

```text
t_кадра = 1000 мс / FPS
60 к/с → 16,67 мс
30 к/с → 33,33 мс
```

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

В браузере цикл обычно привязан к функции `requestAnimationFrame`, которую браузер вызывает перед каждой перерисовкой экрана; её частота совпадает с частотой обновления дисплея — `60 Гц` на большинстве мониторов, `120 Гц` и выше на игровых (источник: спецификация HTML Living Standard, раздел «Animation frames», и документация MDN `window.requestAnimationFrame`, проверено: октябрь 2026).

## deltaTime: почему без него игра работает быстрее на мониторах с высокой частотой обновления

deltaTime — это время в секундах между текущим и предыдущим кадром; умножение скорости на него делает движение независимым от частоты кадров. Если сдвигать персонажа на `5` пикселей каждый кадр, то при `60 к/с` он пройдёт `300` пикселей за секунду, при `30 к/с` — `150`, а на мониторе `144 Гц` — `720`. С deltaTime скорость задаётся в пикселях в секунду:

```text
x_новый = x + v * dt
при v = 300 пикс/с: 60 к/с → 5 пикс/кадр, 30 к/с → 10 пикс/кадр
```

За секунду в обоих случаях получается ровно `300` пикселей.

## Фиксированный шаг физики и аккумулятор времени

Фиксированный шаг означает, что физика всегда обновляется порциями одинаковой длины (например, `dt_физ = 1/60 с`), даже если кадры приходят неравномерно. Переменный шаг даёт разную высоту прыжка на разных компьютерах из-за ошибки численного интегрирования, а при подвисании вкладки браузера один огромный шаг может «телепортировать» героя сквозь пол.

Схема с аккумулятором описана Гленном Фидлером в статье *«Fix Your Timestep!»* (gafferongames.com, 2004 год, проверено: октябрь 2026):

```
const STEP = 1 / 60;          // 16,67 мс
const MAX_FRAME = 0.25;       // защита от «спирали смерти»
let accumulator = 0;

function frame(now) {
  let dt = (now - last) / 1000;
  last = now;
  dt = Math.min(dt, MAX_FRAME);
  accumulator += dt;

  readInput();
  while (accumulator >= STEP) {
    physicsStep(STEP);        // всегда одинаковый шаг
    accumulator -= STEP;
  }
  const alpha = accumulator / STEP; // доля до следующего шага
  render(alpha);              // интерполяция позиций для плавности
  requestAnimationFrame(frame);
}
```

Ограничение `MAX_FRAME = 0,25 с` нужно, чтобы после возвращения на вкладку игра не пыталась догнать пропущенные секунды сотнями физических шагов подряд.

## AABB: проверка пересечения двух прямоугольников

Два прямоугольника, стороны которых параллельны осям, пересекаются тогда и только тогда, когда их проекции перекрываются одновременно по оси X и по оси Y. Если у прямоугольника `A` левый верхний угол `(A_x, A_y)`, ширина `A_w` и высота `A_h`, то условие записывается четырьмя сравнениями:

```text
A_x < B_x + B_w  И  A_x + A_w > B_x  И  A_y < B_y + B_h  И  A_y + A_h > B_y
```

Строгие знаки `<` и `>` означают, что прямоугольники, которые только касаются гранью, не считаются пересекающимися — так персонаж, стоящий точно на полу, не «застревает» в нём.

**Проверочный пример:** персонаж `A = (100, 200, 32, 48)` и монета `B = (120, 230, 16, 16)`. Проверяем: `100 < 136`, `132 > 120`, `200 < 246`, `248 > 230` — все четыре условия истинны, монета собрана.

## Разрешение столкновения: выталкивание по оси X, затем по оси Y

Разрешение столкновения — это сдвиг объекта из стены на минимальное расстояние по той оси, где перекрытие меньше. Глубина перекрытия по осям считается через центры `(cx, cy)` и половины размеров `(hx, hy)`:

```text
px = (hx_A + hx_B) - abs(cx_A - cx_B)
py = (hy_A + hy_B) - abs(cy_A - cy_B)
```

Если `px < py`, объект выталкивается по X на `px` в сторону от центра стены, иначе — по Y на `py`. В платформерах надёжнее двигать персонажа по осям раздельно: сначала сместить по X и сразу разрешить столкновения по X, затем сместить по Y и разрешить по Y. Так персонаж, бегущий вдоль пола, не цепляется за стыки соседних блоков `32×32`, а при приземлении обнуляется только вертикальная скорость.

## Коллайдер, триггер, хитбокс и хёртбокс: чем они отличаются

Коллайдер физически не пускает объект внутрь, а триггер только сообщает о пересечении и пропускает объект насквозь.

Таблица: Типы зон столкновения в 2D-игре

| Тип зоны | Физически выталкивает? | Что делает при пересечении | Пример на сцене |
|---|---|---|---|
| Твёрдый коллайдер | Да | Останавливает движение, обнуляет скорость по оси удара | `платформа`, стена, пол |
| Триггер | Нет | Запускает событие один раз при входе или каждый кадр внутри зоны | `монета`, `ключ`, `флаг`, зона падения |
| Хитбокс (hitbox) | Нет | Наносит урон тому, чей хёртбокс он пересёк | удар мечом, пуля, `шипы` |
| Хёртбокс (hurtbox) | Нет | Получает урон | тело `персонажа` или `врага` |

Хорошее правило честности: хёртбокс персонажа делают на `2–4` пикселя меньше видимого спрайта, а хитбокс опасности — не больше видимой формы, чтобы игрок не погибал от касания пустого воздуха.

## Туннелирование: почему быстрые объекты проходят сквозь стены

Туннелирование происходит, когда за один физический шаг объект смещается дальше, чем суммарная толщина стены и его собственный размер. Пуля со скоростью `1 800 пикс/с` при шаге `1/60 с` проходит `30` пикселей за шаг; стену толщиной `16` пикселей при размере пули `8` пикселей она перепрыгнет, если окажется перед ней в одном шаге и за ней в следующем. Безопасное условие:

```text
v * dt < w_стены + w_объекта
v_max < (16 + 8) * 60 = 1 440 пикс/с
```

Если скорость нужна выше, шаг дробят на подшаги или проверяют пересечение отрезка движения с прямоугольником стены (swept AABB).

## Практический сценарий: проверяем приземление и сбор монеты в коде на JavaScript

Персонаж, падающий на платформу с высоты `128` пикселей, должен встать точно на её верхнюю грань без проваливания и собрать монету, лежащую на `16` пикселей выше платформы; это проверяется прогоном фиксированных шагов без графики. Код ниже проверен в Node.js 24; комментарии показывают его фактический вывод.

```js
// Прямоугольник: левый верхний угол (x, y), ширина w, высота h; ось Y направлена вниз
function overlaps(a, b) {
  return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
}

const STEP = 1 / 60, GRAVITY = 1800, MAX_FALL = 720;
const platform = { x: 0, y: 480, w: 640, h: 32 };                // твёрдый коллайдер
const coin = { x: 100, y: 448, w: 16, h: 16, active: true };      // триггер на 16 пикс выше платформы
const player = { x: 100, y: 304, w: 32, h: 48, vy: 0, grounded: false, score: 0 }; // низ на 352: 128 пикс над платформой

function physicsStep(p) {
  // Ось Y: сначала сдвигаем, потом выталкиваем
  p.vy = Math.min(MAX_FALL, p.vy + GRAVITY * STEP);
  p.y += p.vy * STEP;
  p.grounded = false;
  if (overlaps(p, platform) && p.vy > 0) {
    p.y = platform.y - p.h;   // ставим низ персонажа ровно на верхнюю грань
    p.vy = 0;
    p.grounded = true;
  }
  // Триггер: только сообщает о пересечении и не выталкивает
  if (coin.active && overlaps(p, coin)) { coin.active = false; p.score += 1; }
}

let landedAt = -1;
for (let i = 0; i < 120; i++) {                  // 2 секунды фиксированными шагами
  physicsStep(player);
  if (player.grounded && landedAt < 0) landedAt = i + 1;
}
console.log("приземлился на шаге", landedAt);                    // 23
console.log("низ персонажа:", player.y + player.h, "vy:", player.vy); // 480 0
console.log("очки:", player.score);                              // 1
```

Скорость падения ограничена `720 пикс/с`, то есть `12` пикселями за шаг `1/60 с`, — это меньше суммы толщины платформы (`32`) и высоты персонажа (`48`), поэтому туннелирования нет. Монета — триггер: проверка `overlaps` только меняет счёт и не трогает координаты персонажа.

## Когда вам это не подойдёт

AABB и раздельное выталкивание по осям не подходят для повёрнутых и круглых тел и для сложной физики.

- **Вращающиеся объекты и наклонные поверхности:** ящики, которые кувыркаются, и склоны под произвольным углом требуют проверки ориентированных прямоугольников (OBB) по теореме о разделяющей оси или полигональной физики; AABB вокруг повёрнутого объекта даст «пустые» углы.
- **Сотни одновременно движущихся тел без пространственного разбиения:** прямой перебор всех пар растёт как `N²` — `1 000` объектов дают почти `500 000` проверок за шаг.
- **Стопки, шарниры и цепи:** ящики, которые давят друг на друга, маятники и верёвки требуют решателя ограничений; ручное выталкивание по осям даст дрожание стопки.

---

Мы разрабатываем **J2DRU** — браузерный 2D игровой движок; он в разработке и публично пока не открыт, заявку можно оставить на [странице раннего доступа](/early-access?utm_source=blog&utm_medium=article&utm_campaign=igrovoj-cikl-i-fizika-stolknovenij-aabb) (подача заявки не гарантирует приглашение).

**Читайте также:**
- [Тайловые сетки и устройство 2D-миров](/blog/tajlovye-setki-i-ustrojstvo-2d-mirov)
- [Идеальный прыжок и секрет Coyote Time](/blog/idealnyj-pryzhok-i-sekret-coyote-time)
- [Патрулирование врагов и читаемые опасности](/blog/patrulirovanie-vragov-i-chitaemye-opasnosti)
