Platformer
2D-платформер на Unity (C#), построенный вокруг дискретно-событийного симулятора и собственного кинематического контроллера физики. Игра объединяет продвинутую механику передвижения (двойной прыжок, цепляние и отскок от стен, водная среда с изменением физики), две боевые системы (дальний и ближний бой), врагов с маршрутами патрулирования, систему здоровья и сбора предметов, параллакс-окружение и меню. Логика построена не на развешивании кода по Update()-методам, а на изолированных, переиспользуемых игровых событиях с детерминированным порядком исполнения, что превращает прототип-платформер в расширяемую и предсказуемую кодовую базу промышленного качества.
Задача
Требовалось реализовать полнофункциональный платформер с богатой механикой, избежав типичной для жанра проблемы - спагетти из состояний, когда передвижение, бой, урон, смерть, респаун и UI завязаны друг на друга через прямые ссылки и флаги. По мере роста числа механик (прыжки по стенам, два типа оружия, урон от среды, очки и жизни) такой код становится хрупким: любое изменение в одной системе ломает соседние, а тайминги (отложенный урон, задержки атаки, респаун) разъезжаются между кадрами.
Ключевые требования:
- Расширяемость: добавление новой механики не должно затрагивать существующие системы.
- Детерминированность: физика и игровые события должны вести себя одинаково независимо от частоты кадров.
- Производительность: отсутствие аллокаций и GC-пиков в горячем цикле обработки событий.
- Удобство гейм-дизайна: уровни и поведение врагов должны настраиваться визуально, без правки кода.
Решение
В основу положена архитектура на дискретно-событийном симуляторе: все, что происходит в игре (получение урона, прыжок, приземление, столкновение с врагом, вход в зону смерти, сбор токена, смерть врага), оформлено как самостоятельный класс-событие. События планируются с временным смещением (Schedule<PlayerDamage>(), отложенная атака, респаун) и исполняются централизованным тиком симуляции, когда наступает их время.
Это дает жесткую развязку систем: PlayerController не знает, что делает урон - он лишь планирует событие PlayerDamage, а обработчик события сам решает логику. Новая механика добавляется как новый класс-событие, не трогая ни строчки в существующих контроллерах.
Поверх ядра реализованы прикладные механики:
- Передвижение: конечный автомат состояний прыжка (Grounded - PrepareToJump - Jumping - InFlight - Landed), двойной прыжок, детекция стен через OverlapCircle с определением направления, цепляние за стену с понижением гравитации и усиленным настенным прыжком.
- Боевые системы: дальний бой (инстанцирование снарядов с автоудалением и направлением по спрайту) и ближний бой мечом с динамическим расширением коллайдера атаки на время удара и синхронизацией с анимацией.
- Среда: водные зоны, замедляющие игрока и наносящие периодический урон; зоны смерти и победы; патрулирующие враги с возможностью прыжка вдоль маршрута.
Архитектура и сложности
Дискретно-событийное ядро с пулом объектов. События не создаются и не уничтожаются в рантайме, а берутся из типизированных пулов (Dictionary<Type, Stack<Event>>) и возвращаются после исполнения. Это устраняет аллокации в цикле обработки - главную причину GC-фризов в Unity-играх. Каждое событие поддерживает Precondition() - проверку актуальности на момент исполнения (условия мира могли измениться с момента планирования), и Cleanup() для сброса ссылок перед возвратом в пул.
Очередь с приоритетом на бинарной куче. Планирование событий по времени потребовало структуры данных с быстрой вставкой и извлечением минимума. Реализована собственная HeapQueue<T> - бинарная min-куча с операциями Push/Pop за O(log n) и просеиванием вверх/вниз. Тик симуляции извлекает и исполняет все события, чье время наступило, поддерживая корректный порядок при любом числе отложенных действий.
Собственный кинематический контроллер вместо встроенной динамики. Платформерная физика на Rigidbody2D-динамике дает резиновые столкновения и недетерминированное поведение. Вместо этого реализовано ручное разрешение коллизий методом swept-collision: на каждом FixedUpdate тело прокидывается через Rigidbody2D.Cast, обрабатываются нормали поверхностей (определение земли по minGroundNormalY, проекция скорости на нормаль для корректного движения по склонам, гашение скорости при ударе в воздухе). Движение разделено на горизонтальную и вертикальную фазы вдоль нормали земли - это дает стабильное поведение на склонах и предсказуемые, не зависящие от FPS прыжки.
Разделяемая модель через реестр инстансов. Игровые параметры вынесены в PlatformerModel, доступную через типизированный InstanceRegister. Одна и та же ссылка разделяется между симуляцией, событиями и инспектором Unity - гейм-дизайнер настраивает баланс в редакторе, а события читают эти же значения в рантайме.
Инструментарий для гейм-дизайна. Написан кастомный редактор (CustomEditor + OnSceneGUI + DrawGizmo): маршруты патрулирования врагов редактируются прямо в сцене перетаскиванием хэндлов, с отрисовкой пунктирной траектории, контрольных точек и подписи длины пути. Настройка поведения врагов перестала требовать правки кода.
Визуальный слой: параллакс-прокрутка фона относительно камеры с масштабными коэффициентами по слоям, анимированные тайлы, переключение контекстов «меню - геймплей» через MetaGameController с управлением Time.timeScale.
Результат
- Завершенный играбельный платформер с двумя сценами (меню и уровень), полным боевым циклом, продвинутым передвижением и системой прогресса (жизни, токены, UI-бары).
- 14+ изолированных игровых событий как отдельных классов - новая механика подключается без модификации существующих систем (архитектура, открытая на расширение и закрытая на изменение).
- Нулевые аллокации в горячем цикле благодаря пулингу событий - отсутствие GC-пиков и просадок кадров при интенсивном геймплее.
- Планирование событий за O(log n) через собственную бинарную кучу - стабильные тайминги при любом числе отложенных действий.
- Детерминированная, независимая от частоты кадров физика на базе swept-collision и обработки нормалей поверхностей - корректные склоны, приземления и прыжки.
- 5+ интегрированных механик (двойной прыжок, настенный прыжок и цепляние, дальний бой, ближний бой, водная среда) поверх единого физического и событийного фундамента.
- Ускорение итераций гейм-дизайна за счет собственного редактор-инструментария: визуальная настройка уровней и маршрутов врагов без участия программиста.
Стек
Движок и язык: Unity, C#
Архитектура: дискретно-событийная симуляция (Discrete Event Simulation), пул объектов, событийно-ориентированный дизайн, конечные автоматы (FSM), модель данных через типизированный реестр инстансов
Структуры данных и алгоритмы: бинарная min-куча (priority queue), swept-collision detection, обработка нормалей поверхностей и проекций скорости
Физика: Unity 2D Physics (Rigidbody2D.Cast, ContactFilter2D, LayerMask), кинематический контроллер собственной реализации
Графика и UI: Tilemap, спрайтовая анимация (Animator), параллакс-скроллинг, Canvas UI, TextMesh Pro
Инструментарий: Unity Editor scripting (CustomEditor, Handles, Gizmos), Resources, корутины
Архитектура: дискретно-событийная симуляция (Discrete Event Simulation), пул объектов, событийно-ориентированный дизайн, конечные автоматы (FSM), модель данных через типизированный реестр инстансов
Структуры данных и алгоритмы: бинарная min-куча (priority queue), swept-collision detection, обработка нормалей поверхностей и проекций скорости
Физика: Unity 2D Physics (Rigidbody2D.Cast, ContactFilter2D, LayerMask), кинематический контроллер собственной реализации
Графика и UI: Tilemap, спрайтовая анимация (Animator), параллакс-скроллинг, Canvas UI, TextMesh Pro
Инструментарий: Unity Editor scripting (CustomEditor, Handles, Gizmos), Resources, корутины
