System Design — разбиение монолита на подсистемы, без 1000-строчного Character
На middle-собеседованиях спрашивают, как реализовать конкретную фичу. На senior — как разбить её на подсистемы, провести границы ответственности и пережить рефакторинг через год разработки. System Design в Unreal — это не «знание API», это умение видеть оси декомпозиции: где живёт состояние, кто авторитет в сети, что грузится из данных, как мигрирует сохранение, что делает компонент vs actor vs subsystem.
Senior-вопросы в этой области сводятся к нескольким повторяющимся осям: state ownership (кто хранит данные — Pawn, PlayerState, GameState, Subsystem), authority (где принимается решение — сервер или клиент), data vs code (что в DataAsset/DataTable, что в C++), persistence (что переживает смерть pawn, переход уровня, рестарт игры), composition vs inheritance (компонент или базовый класс). Каждая хорошо спроектированная система — это конкретный ответ на эти пять вопросов.
Типичные смерти больших UE-проектов выглядят одинаково: god-actor (ACharacter на 3000 строк), hard references (один header тянет полпроекта при компиляции), client-side authority (хитбоксы и здоровье на клиенте → читерство), hardcoded numbers (балансы зашиты в C++ → дизайнер не может крутить), наивная сериализация (memcpy структур → save ломается между сборками). Каждая из 12 подтем ниже — отдельная ось защиты от одной из этих смертей.
Карта темы
- Gameplay Systems — что такое «геймплейная система», где её границы, чем отличается от UI/рендера/сети.
- System Decomposition — single responsibility, разбиение монолита, антипаттерн god-actor.
- Component Architecture —
UActorComponent-композиция, ECS-flavored UE design, переиспользование без наследования. - State Management — где живёт состояние (Pawn / PlayerState / GameState / Subsystem), FSM, иерархические state machines.
- Data-Driven Design —
UDataTable,UDataAsset,FPrimaryAssetId, hot-reload значений без рекомпиляции. - Persistence Systems —
FArchive, tagged property serialization, JSON, бинарный сериализатор. - Network Architecture — server authority, eventual consistency, RPC vs property replication как design choice.
- Save / Load System —
USaveGame,UGameplayStatics::SaveGameToSlot, версионирование схемы, миграция. - Inventory System — слоты, стакинг, репликация массива, persistence как case study.
- Weapon System — базовый класс vs компонент, ammo, cooldown, интеграция со способностями.
- Stat Component — base + modifiers, формулы, репликация, сравнение с GAS.
- Respawn System — death → wait → spawn цикл, выбор
PlayerStart, player vs AI.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
Положить инвентарь, оружие, статы и сохранение в ACharacter | God-actor; через год проект не рефакторится |
| Считать «компонент» = «маленький actor» | UActorComponent намного легче — не имеет transform, не тикает по умолчанию |
| Хранить постоянные данные игрока на Pawn | Pawn уничтожается при смерти; данные теряются — это работа APlayerState |
| Доверять клиенту в вычислении урона/здоровья | Тривиальный читинг; авторитет должен быть на сервере |
Зашить балансы в C++ как const float | Дизайнер не может крутить значения; пересборка на каждое изменение |
Сериализовать живые AActor* указатели в USaveGame | После загрузки указатели битые; нужно хранить id и пересоздавать |
Использовать memcpy или сырой бинарь для сохранений | Ломается между сборками, платформами, версиями структур |
Пропустить версию схемы в USaveGame | Невозможно мигрировать старые сохранения после обновления |
Спавнить полноценный AActor на каждый предмет инвентаря | Тысячи мёртвых actor'ов в памяти; нужна ссылка на UItemDataAsset |
| Применять модификаторы статов прямой записью в итоговое значение | Теряется база; при истечении баффа невозможно откатить |
| Реюзать мёртвый Pawn для респавна | Накапливаются битые компоненты, делегаты, ссылки; уничтожать и спавнить новый |
| Циклические зависимости между Gameplay-модулями | Build system отвергает; one-way dependency graph обязателен |
Значение для собеседований
System Design — обязательный senior-блок для любого серьёзного UE5-собеседования. Проверяется не знание API, а умение проводить границы между подсистемами. Конкретные вопросы:
- «Как бы вы спроектировали инвентарь?» — компонент или actor? Стакинг где? Репликация как? UI как подписывается?
- «Как разнести
ACharacterна 3000 строк?» — какие компоненты выделить, что унаследовать, что вPlayerState. - «Где хранить health?» — компонент на Pawn, но persistence через
PlayerStateили save. - «Как мигрировать сохранения после обновления?» — версия в
USaveGame, миграционные шаги, tagged property serialization. - «Server authority — что это означает конкретно для механики X?» — кто принимает решение, что реплицируется, что предсказывается клиентом.
- «Data-driven design — что вы выносите в DataAsset, а что оставляете в коде?»
Типичный неверный ответ — назвать конкретные классы без обоснования границ: «Я бы сделал UInventoryComponent, UWeaponComponent, UStatComponent». Это перечень, а не дизайн. Senior-ответ — обосновать, почему именно компонент, а не actor, почему данные в DataAsset, а не в коде, почему авторитет на сервере, а не на клиенте, и как система переживает смерть pawn, переход уровня и обновление игры.