Разработка игр
Введение в разработку игр
60 FPS - это 16.6 мс на кадр. За эти 16.6 мс движок должен обработать ввод, обновить физику всех объектов сцены, пересчитать AI, анимации, LOD, frustum culling - и передать команды рендеру. В AAA-игре это 10 000+ объектов. Red Dead Redemption 2 рендерит 1 миллион объектов травы в одном кадре. Бюджет - 16.6 мс, превысить нельзя. Добро пожаловать в разработку игр.
- **Game loop (requestAnimationFrame):** тот же паттерн в браузере - React Three Fiber, Three.js, WebGL-визуализации; 60 FPS = 16.6 мс бюджет на кадр
- **ECS в Unity DOTS и Overwatch**: Blizzard перешёл на ECS для серверной симуляции 60 Hz с тысячами объектов; Unity DOTS даёт x10-100 производительность против MonoBehaviour
- **LOD (Level of Detail) в AAA**: персонаж вблизи - 100K полигонов, вдали - 100; автоматическое переключение по дистанции; Red Dead Redemption 2, Cyberpunk 2077
- **Procedural generation в Minecraft и No Man's Sky**: seed → детерминированный мир; весь бесконечный мир Minecraft генерируется на лету из числа
Game Loop
60 FPS = 16.6 мс на кадр. 30 FPS = 33 мс. Это не предпочтение - это жёсткий бюджет. За каждые 16.6 мс движок обрабатывает ввод, обновляет физику, AI, анимации и рисует сцену. Каждая игра - от Pong до Cyberpunk - работает по одному принципу: бесконечный цикл **game loop**, бьющийся как сердце игры.
Проблема наивного цикла: на мощном компьютере мяч летит быстрее, чем на слабом - update() вызывается чаще. В 1999 году Quake III Arena работал именно так и был привязан к FPS. Решение - **fixed timestep**: физика обновляется с фиксированным шагом (60 Hz = 16.6 мс), рендеринг - как можно чаще.
**Fixed timestep** гарантирует детерминизм: одинаковый ввод даёт одинаковый результат на любом оборудовании. Это критично для онлайн-игр (lockstep networking), воспроизведения реплеев и стабильности физики.
Параметр alpha в render(alpha) - **интерполяция**: физика 60 Hz, монитор 144 Hz. Между «физическими» кадрами рисуются промежуточные положения объектов. Результат: плавная картинка при стабильной физике. Именно так Cyberpunk 2077 выглядит плавно на 144 Hz мониторе, не разгоняя физический симулятор.
Зачем в game loop используется fixed timestep?
Entity-Component-System
Как организовать код игры? Наследование (Enemy extends Character extends GameObject) быстро превращается в nightmare: diamond problem, ромбовидное наследование, взрыв иерархии. Blizzard столкнулся с этим в Overwatch - 40+ типов героев, каждый с уникальными способностями. Решение: **Entity-Component-System (ECS)**, где данные отделены от логики.
**Entity** - просто идентификатор (число). **Component** - контейнер данных без поведения: Position {x, y}, Health {current, max}, Sprite {texture, frame}. **System** - функция, которая обрабатывает все entity с определённым набором компонентов.
Преимущества ECS: **композиция вместо наследования** (любая комбинация компонентов), **cache-friendly** (данные хранятся в массивах, а не разбросаны по куче), **параллелизм** (системы, работающие с разными компонентами, можно запускать параллельно).
ECS используют Unity DOTS (x10-100 производительность против MonoBehaviour), Unreal Mass Entity, Bevy (Rust), Flecs (C/C++). Overwatch (Blizzard) перешёл на ECS для 60 Hz серверной симуляции с тысячами объектов. Даже в классическом Unity MonoBehaviour - это уже компонент на GameObject (entity): частичный ECS без осознания.
В архитектуре ECS, что содержит логику поведения?
Управление сценами
Игра состоит из экранов: главное меню, игровой уровень, пауза, магазин, экран поражения. Каждый из них - **сцена** (scene). В AAA-игре переход между уровнями - это 2-5 GB текстур и моделей. Загрузить всё сразу нельзя. Scene manager управляет загрузкой и выгрузкой ресурсов - именно в этом его главная задача, а не только переходы.
Сцены бывают **стековые** и **заменяющие**. Push-сцена (пауза) накладывается поверх предыдущей - при снятии паузы игра продолжается. Replace-сцена (переход меню → игра) полностью заменяет текущую - меню выгружается из памяти.
Загрузка ресурсов (текстуры, звуки, модели) - самая медленная часть перехода. Решения: **loading screen** (асинхронная загрузка с прогресс-баром), **предзагрузка** (загружать следующий уровень во время текущего), **пулинг** (переиспользование объектов вместо создания/уничтожения).
Жизненный цикл сцены: **onEnter()** - загрузка ресурсов и инициализация, **update(dt)** + **render()** - основной цикл, **onPause()** / **onResume()** - при push/pop другой сцены, **onExit()** - освобождение ресурсов. Пропустить onExit - утечка памяти. В Cyberpunk 2077 при первом релизе были именно такие утечки на PS4, приводившие к crash.
Чем push-сцена отличается от replace-сцены?
Обработка ввода
Игрок взаимодействует с игрой через устройства ввода: клавиатура, мышь, геймпад, тачскрин. Задача системы ввода - абстрагировать конкретные устройства в **действия** (actions), чтобы игровая логика не зависела от оборудования.
Различают три типа событий ввода: **pressed** (кнопка только что нажата - один кадр), **held** (кнопка удерживается), **released** (кнопка отпущена - один кадр). Для движения обычно используют held, для прыжка - pressed, для стрельбы - зависит от оружия.
| Устройство | Тип ввода | Особенности |
|---|---|---|
| Клавиатура | Дискретный (0/1) | Множество одновременных клавиш, нет аналоговых осей |
| Мышь | Позиция + кнопки | Субпиксельная точность, delta movement, скролл |
| Геймпад | Стики + кнопки + триггеры | Аналоговые оси (-1..1), dead zone, вибрация |
| Тачскрин | Мультитач + жесты | Нет hover, виртуальные кнопки перекрывают экран |
**Dead zone** - область вокруг центра аналогового стика, где ввод игнорируется. Без dead zone персонаж «дрейфует» из-за неточности стика. Типичное значение: 0.15-0.25 от полного отклонения.
Ввод обрабатывается в начале game loop, до update(). Все события за кадр собираются в буфер и обрабатываются разом - физика получает консистентный snapshot. В сетевых играх (Valorant, CS2) input lag измеряется в единицах: 1 кадр @ 128 Hz сервер = 7.8 мс. Именно поэтому профессионалы играют на 240 Hz мониторах.
Полная картина game loop: processInput() читает устройства и маппит в действия, update(dt) обновляет мир с fixed timestep 16.6 мс, render() рисует результат с интерполяцией. Три шага - фундамент любой игры от Tetris до GTA VI. Бюджет 16.6 мс никуда не девается.
Игровой движок - это и есть игра
Движок - инструмент (рендеринг, физика, ввод, звук). Игра - геймплей, дизайн, контент, нарратив и арт поверх движка.
Fortnite и PUBG оба на Unreal Engine. Ori and the Blind Forest и Temple Run оба на Unity. Движок не определяет жанр и качество. Minecraft написан без AAA-движка (Java → C++) и продался в 238 миллионов копий. Среди самых успешных инди-игр многие используют собственные движки или Godot.
Зачем нужен input mapping?
Ключевые идеи
- **Game loop** - 16.6 мс на кадр при 60 FPS: input → update (fixed timestep) → render; fixed timestep гарантирует детерминизм физики на любом железе
- **ECS** разделяет Entity (ID) / Component (данные) / System (логика); Unity DOTS и Overwatch используют ECS для x10-100 производительности против наследования
- **Scene manager** - стек сцен: push (пауза поверх игры) и replace (меню → уровень); жизненный цикл onEnter/onExit управляет загрузкой ресурсов
- **Input mapping** абстрагирует устройства в действия - один код работает для клавиатуры, геймпада и тачскрина
Связанные темы
Основы разработки игр связаны со множеством дисциплин:
- Игровые движки — Unity, Unreal и Godot реализуют game loop, ECS и scene management «из коробки»
- 2D рендеринг — Функция render() из game loop - целая наука: спрайты, тайлмапы, анимации, параллакс
Вопросы для размышления
- Почему ECS победил наследование в геймдеве? React использует похожую архитектуру (компоненты без состояния + хуки). Одна и та же идея?
- Что произойдёт с физикой если убрать fixed timestep: Quake III Arena был привязан к FPS, и на быстрых машинах появлялись баги. Конкретно какие?
- 16.6 мс бюджет кадра. LOD, frustum culling, draw call batching - расставьте по приоритету что даст максимальный выигрыш в современных AAA-играх.
Связанные уроки
- cg-01 — Компьютерная графика - основа рендеринга в играх: матрицы трансформаций, шейдеры, z-буфер
- alg-01-big-o — Алгоритмы критичны в геймдеве: collision detection, pathfinding (A*), spatial partitioning
- se-01 — Архитектурные паттерны (Entity-Component-System, Observer, State Machine) - те же, что в обычной разработке
- gd-02 — Понимание движков открывает путь к игровым механикам и физике
- la-06-transformations