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 слоем.

Sources

Related topics