Gameplay Framework — кто чем владеет в Unreal
Когда говорят «игрок в Unreal», на самом деле имеют в виду связку из шести-семи объектов, у каждого из которых своя роль. AActor — базовый объект мира, всё остальное — его потомки. APawn — это AActor, который может быть захвачен контроллером (Possess). ACharacter — это APawn, к которому уже прикручены CapsuleComponent, SkeletalMeshComponent и UCharacterMovementComponent. APlayerController — это «мозг» игрока: ввод, камера, HUD. APlayerState — реплицируемая идентичность игрока: счёт, имя, команда. AGameMode — правила матча, существует только на сервере. AGameState — реплицируемое состояние матча, видимое всем клиентам.
Это разделение ролей — не академическая красота, а сетевой контракт. AGameMode не реплицируется, потому что правила игры — это серверная вещь; клиент не должен иметь возможность «решить, что он выиграл». APlayerState реплицируется, потому что счёт нужен всем для таблицы лидеров. APlayerController существует на сервере и только у владельца на клиенте — другие игроки не видят ваш ввод. APawn реплицируется как видимое тело. Перепутать, кто где живёт, — мгновенный fail на сетевом собеседовании.
Параллельно с этой иерархией наследования Unreal активно использует composition over inheritance через UActorComponent. Здоровье, инвентарь, способности, движение — всё это компоненты, прикрепляемые к AActor. А поверх всей этой системы стоят USubsystem — синглтоны разной длительности жизни (GameInstance, World, LocalPlayer, Editor), которые заменяют классический паттерн «менеджер-всего».
Карта темы
- Actor Fundamentals —
AActor, жизненный цикл (BeginPlay→Tick→EndPlay→Destroy),SpawnActor,FActorSpawnParameters. - Pawn Control —
APawnvsACharacter, possession,OnPossess/OnUnPossess,SetupPlayerInputComponent. - Player Controller —
APlayerController, ввод, камера, HUD, переживает Pawn между respawn. - Player State —
APlayerState, реплицируемая идентичность,CopyPropertiesпри seamless travel. - GameMode (Authority) —
AGameMode/AGameModeBase, server-only, правила матча, default classes. - GameState (Shared) —
AGameState/AGameStateBase, реплицируется всем, общее состояние матча. - Character Movement —
UCharacterMovementComponent, режимы движения, репликация, client-side prediction. - Component Composition — actor как набор компонентов,
AttachToComponent, scene-иерархия. - Component Reuse — переиспользование компонентов между классами,
ClassGroup,BlueprintSpawnableComponent. - Health Component — канонический паттерн health, репликация,
OnDamage-делегат. - Damage System —
TakeDamage,DamageType,UGameplayStatics::ApplyDamage, модификаторы урона. - Ability Cooldown — паттерны перезарядки: timestamp, GAS,
FTimerManager. - Subsystems —
UGameInstanceSubsystem,UWorldSubsystem,ULocalPlayerSubsystem,UEditorSubsystem.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
Считать, что APawn — отдельная ветка от AActor | APawn наследуется от AActor; pawn — это actor с возможностью possession |
Хранить счёт игрока на APawn | Pawn умирает при respawn — счёт пропадёт; место счёта — APlayerState |
Класть ввод/камеру на APlayerState | PlayerState реплицируется всем, ввод — приватная вещь; ввод живёт на APlayerController |
Хранить реплицируемое состояние в AGameMode | AGameMode существует только на сервере — клиенты его не видят; общее состояние — в AGameState |
Применять урон через прямое вычитание Health -= Damage | Минует TakeDamage, DamageType, OnDamage — никаких модификаторов, никаких событий |
Отсчитывать cooldown в Tick | Дрейфует с FPS; используйте GetWorld()->GetTimeSeconds() как timestamp |
Считать AIController и APlayerController взаимозаменяемыми | Это разные потомки AController, у них разная роль и разный жизненный цикл |
Делать NewObject<AActor>() вместо SpawnActor | Actor не зарегистрируется в мире, не получит BeginPlay, не будет тикать |
Привязывать вход в конструкторе вместо SetupPlayerInputComponent | Привязки до старта игры отбрасываются; обработчики не вызываются |
| Доверять урону, который шлёт клиент числом | Любой клиент пришлёт «миллион»; клиент шлёт намерение, сервер вычисляет |
Считать AGameInstance и AGameMode одним и тем же | AGameInstance живёт всё время процесса (даже между уровнями), AGameMode пересоздаётся на каждый match |
| Полагать, что Subsystem нужно вручную создавать и регистрировать | Движок сам создаёт и владеет subsystems; вы только наследуетесь от нужного базового класса |
Значение для собеседований
Gameplay Framework — это базовый middle-вопрос на любом UE5-собеседовании на gameplay-программиста. На junior+ проверяют:
- Иерархию:
AActor→APawn→ACharacter; что pawn — это actor с possession. - Разделение Controller vs Pawn: контроллер думает, pawn выражает.
- Где живёт счёт игрока:
APlayerState, не Pawn. - Что
AGameModeсуществует только на сервере, аAGameStateреплицируется. - Что
APlayerControllerпереживает Pawn (respawn-логика).
На middle/senior проверяют:
CopyPropertiesдля миграцииAPlayerStateчерез seamless travel.- Почему урон маршрутизируется через
ApplyDamage/TakeDamage, а не через прямое вычитание. - Серверную валидацию урона (клиент шлёт намерение, не число).
- Composition: когда выносить логику в компонент, когда оставлять на actor.
- Subsystems: почему они заменяют синглтон-менеджеры, и какой scope для какой задачи.
Типичный неверный ответ: «Я храню счёт на Pawn, потому что Pawn — это игрок». Реальный ответ — Pawn умирает при respawn, и счёт умирает вместе с ним. Pawn — это тело, а не игрок. Игрок — это связка PlayerController + PlayerState; счёт принадлежит PlayerState.