Unreal EngineMiddleTechnical
Что такое Subsystems в Unreal Engine и как они улучшают организацию кода?
Subsystems — автоматически управляемые синглтоны UE с жизненным циклом, привязанным к владельцу (Engine, GameInstance, World, LocalPlayer). Они заменяют ручные менеджеры: движок создаёт и уничтожает их сам, доступ — через GetSubsystem<T>().
Subsystems в Unreal Engine
Subsystems — это управляемые синглтоны, автоматически создаваемые и уничтожаемые движком в соответствии с жизненным циклом их владельца. Они устраняют необходимость вручную создавать глобальные менеджеры, регистрировать их в GameInstance или передавать через весь стек вызовов.
Типы Subsystems и их владельцы
- UEngineSubsystem — живёт весь сеанс движка. Подходит для глобальных сервисов (аналитика, логирование).
- UGameInstanceSubsystem — живёт от создания GameInstance до выхода. Подходит для сессионных данных (инвентарь, профиль игрока).
- UWorldSubsystem — живёт вместе с World. Пересоздаётся при смене уровня. Подходит для систем уровня (спаун, economy).
- ULocalPlayerSubsystem — по одному на LocalPlayer. Подходит для split-screen, UI per-player.
- UEditorSubsystem — только в редакторе. Подходит для редакторских тулов.
Создание Subsystem
// Header: MyInventorySubsystem.h
#pragma once
#include "Subsystems/GameInstanceSubsystem.h"
#include "MyInventorySubsystem.generated.h"
UCLASS()
class MYGAME_API UMyInventorySubsystem
: public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
virtual void Initialize(FSubsystemCollection& Collection) override;
virtual void Deinitialize() override;
UFUNCTION(BlueprintCallable)
void AddItem(FName ItemId, int32 Count);
UFUNCTION(BlueprintCallable, BlueprintPure)
int32 GetItemCount(FName ItemId) const;
private:
TMap<FName, int32> Inventory;
};
// CPP: MyInventorySubsystem.cpp
void UMyInventorySubsystem::Initialize(FSubsystemCollection& Collection)
{
Super::Initialize(Collection);
UE_LOG(LogTemp, Log, TEXT("InventorySubsystem initialized"));
}
void UMyInventorySubsystem::Deinitialize()
{
Inventory.Empty();
Super::Deinitialize();
}
void UMyInventorySubsystem::AddItem(FName ItemId, int32 Count)
{
Inventory.FindOrAdd(ItemId) += Count;
}
int32 UMyInventorySubsystem::GetItemCount(FName ItemId) const
{
const int32* Found = Inventory.Find(ItemId);
return Found ? *Found : 0;
}
Получение Subsystem
// Из любого UObject с доступом к World:
UMyInventorySubsystem* Inv = GetGameInstance()
->GetSubsystem<UMyInventorySubsystem>();
Inv->AddItem(FName("Sword"), 1);
// Или через статический хелпер:
UMyInventorySubsystem* Inv =
UGameInstance::GetSubsystem<UMyInventorySubsystem>(
GetGameInstance());
// WorldSubsystem:
UMyWorldSubsystem* WS = GetWorld()
->GetSubsystem<UMyWorldSubsystem>();
Зависимости между Subsystems
Если один Subsystem зависит от другого, укажите зависимость в Initialize:
void UMyAdvancedSubsystem::Initialize(FSubsystemCollection& Collection)
{
Collection.InitializeDependency<UMyInventorySubsystem>();
Super::Initialize(Collection);
// Теперь UMyInventorySubsystem гарантированно инициализирован
}
Почему Subsystems лучше классических паттернов
- Не требуют ручной регистрации в GameMode/GameInstance — движок создаёт их автоматически.
- Жизненный цикл чётко привязан к владельцу — нет проблем с dangling pointers.
- Легко находятся через typed GetSubsystem — нет глобальных переменных или CastChecked цепочек.
- Blueprints вызывают их нативно без дополнительной обёртки.
Подводные камни
- WorldSubsystem сбрасывается при смене уровня — не храните в нём данные, которые должны пережить level transition. Используйте GameInstanceSubsystem.
- Порядок инициализации не гарантирован без явного
InitializeDependency. Не обращайтесь к другим Subsystems в конструкторе — только в Initialize. - Subsystem != синглтон по паттерну — LocalPlayerSubsystem создаётся по одному на каждого LocalPlayer; GetSubsystem<> возвращает экземпляр для конкретного Player.
- Тестируемость — для unit-тестов Subsystem нужен полноценный GameInstance/World. Вынесите бизнес-логику в обычные классы, а Subsystem используйте как тонкий фасад.
- Нельзя создать вручную через NewObject — Subsystems создаются только движком. Попытка создать через NewObject приведёт к ошибке.
Common mistakes
- Объяснять Subsystems только по синтаксису, без жизненного цикла и стоимости.
- Игнорировать ошибки, null/empty состояния, порядок инициализации или режим сборки.
- Давать пример, который работает в демо, но ломается при изменении владельца ресурса.
- Использовать макросы или specifier-и наугад и забывать про UObject GC.
What the interviewer is testing
- Кандидат формулирует точную модель для Subsystems, а не только определение.
- Пример компилируем, безопасен по lifetime и соответствует версии технологии.
- Названы trade-off, ограничения и диагностируемые симптомы ошибки.
- Понимает границу между C++ кодом, runtime/framework metadata и editor/UI слоем.