Lyra Inventory Fix 03: Исправляем UE-127172. Делаем как надо, а не как "пока работает"

И таааак сойдет!
И таааак сойдет!

И снова TODO в Lyra… 93-я строка LyraInventoryManagerComponent.cpp. Очередное вырубленное в камне послание от Epic Games: TODO: Using the actor instead of component as the outer due to UE-127172.

Всё ясно. Тодошка из отряда костылеобразных. Как говорится, «нет ничего более постоянного, чем временное решение».

Но не сегодня.

Сегодня мы выкорчуем этот костыль и сделаем как надо.

Пора перестать плясать вокруг багов и управлять экземплярами предметов грамотно и централизованно.

В чем проблема?

Баг UE-127172 заставил разработчиков использовать актор вместо компонента в качестве Outer для экземпляров предметов.

А это:

  • Ломает иерархию владения— предметы должны принадлежать компоненту, а не актору.
  • Убивает модульность— чем больше привязок к акторам, тем сложнее гибко управлять инвентарём.
  • Портит отладку— если что-то идёт не так, готовьтесь к головной боли.
  • Выглядит как костыль — такой код лучше не показывать никому.

Наше решение: подсистема как менеджер экземпляров предметов

Мы добавим централизованное управление жизненным циклом предметов через реализованную в прошлый раз подсистему ULyraInventorySubsystem. То, что должно было быть изначально.

Больше никаких «временных решений». Только чистый, логичный, мощный код.

Спойлер: Для выполнения дальнейших телодвижений необходимо завершить предыдущий гайд: Lyra Inventory Fix 02: Подсистема для фрагментов инвентаря. Решаем TODO с умом.

Теперь подсистема — станет боссом. Она начнет управлять.

  • Создавать экземпляры предметов— централизованно, без хаоса.
  • Инициализировать их правильно— без костылей, как и должно быть.
  • Контролировать их жизненный цикл — удалит ненужное, очистит мусор.

Теперь экземпляры предметов будут принадлежать системе, а не расползутся по акторам, как бездомные. Не будет багов с владением — всё структурировано и предсказуемо.

В своих потугах будем использовать UE5.5.0.

Добавляем методы в ULyraInventorySubsystem

public: /** Create a new inventory item instance */ UFUNCTION(BlueprintCallable, Category="Lyra|Inventory") ULyraInventoryItemInstance* CreateInventoryItemInstance(UObject* Outer, TSubclassOf<ULyraInventoryItemDefinition> ItemDef); /** Initialize a newly created inventory item instance */ void InitializeItemInstance(ULyraInventoryItemInstance* Instance, TSubclassOf<ULyraInventoryItemDefinition> ItemDef); private: /** Cache of created inventory items for proper lifecycle management */ UPROPERTY() TArray<TObjectPtr<ULyraInventoryItemInstance>> ManagedItems;

Новые методы:

  • CreateInventoryItemInstance — создаёт экземпляр предмета и настраивает его внешний объект (outer).
  • InitializeItemInstance — выполняет дополнительную инициализацию экземпляра.
  • ManagedItems — массив для отслеживания всех созданных экземпляров.

LyraInventorySubsystem.cpp:

// Надеюсь знаем куда вставлять? #include "LyraInventoryItemInstance.h" // Добавляем в функцию очистку управляемых предметов void ULyraInventorySubsystem::Deinitialize() { // Cleanup managed items ManagedItems.Empty(); // Вот эта строка RegisteredInventoryComponents.Empty(); Super::Deinitialize(); } // И добавляем реализации объявленных функций: ULyraInventoryItemInstance* ULyraInventorySubsystem::CreateInventoryItemInstance(UObject* Outer, TSubclassOf<ULyraInventoryItemDefinition> ItemDef) { if (!ItemDef || !Outer) { return nullptr; } // Create the instance with the provided outer ULyraInventoryItemInstance* NewInstance = NewObject<ULyraInventoryItemInstance>(Outer); if (NewInstance) { InitializeItemInstance(NewInstance, ItemDef); ManagedItems.Add(NewInstance); } return NewInstance; } void ULyraInventorySubsystem::InitializeItemInstance(ULyraInventoryItemInstance* Instance, TSubclassOf<ULyraInventoryItemDefinition> ItemDef) { if (!Instance || !ItemDef) { return; } Instance->SetItemDef(ItemDef); // Initialize with fragments if (const ULyraInventoryItemDefinition* Definition = ItemDef.GetDefaultObject()) { for (const ULyraInventoryItemFragment* Fragment : Definition->Fragments) { if (Fragment) { Fragment->OnInstanceCreated(Instance); } } } }

Что происходит?

Теперь всё под контролем.

  • Экземпляр привязывается к компоненту, а не к актору. Больше никаких левых владельцев. Правильная иерархия.
  • Все экземпляры хранятся в ManagedItems.Теперь мы управляем их жизненным циклом, а не надеемся на удачу.
  • При деинициализации подсистемы ManagedItems очищается. Никаких утечек памяти. Всё чисто и логично.

Обновляем FLyraInventoryList::AddEntry

Раньше мы колхозили. Создавали экземпляры кустарно, надеясь, что всё не рухнет. Теперь всё по уму — используем нашу подсистему.

Инвентарь больше не стихийное бедствие. Теперь он управляемый процесс.

LyraInventoryManagerComponent.cpp:

/* Заменяем вот этот код: FLyraInventoryEntry& NewEntry = Entries.AddDefaulted_GetRef(); NewEntry.Instance = NewObject<ULyraInventoryItemInstance>(OwnerComponent->GetOwner()); //[USER=891005]@Todo[/USER]: Using the actor instead of component as the outer due to UE-127172 NewEntry.Instance->SetItemDef(ItemDef); for (ULyraInventoryItemFragment* Fragment : GetDefault<ULyraInventoryItemDefinition>(ItemDef)->Fragments) { if (Fragment != nullptr) { Fragment->OnInstanceCreated(NewEntry.Instance); } } NewEntry.StackCount = StackCount; Result = NewEntry.Instance; //const ULyraInventoryItemDefinition* ItemCDO = GetDefault<ULyraInventoryItemDefinition>(ItemDef); MarkItemDirty(NewEntry); return Result; ...на вот этот:*/ // Create the item instance through the subsystem if (UGameInstance* GameInstance = OwnerComponent->GetWorld()->GetGameInstance()) { if (ULyraInventorySubsystem* InventorySubsystem = GameInstance->GetSubsystem<ULyraInventorySubsystem>()) { FLyraInventoryEntry& NewEntry = Entries.AddDefaulted_GetRef(); NewEntry.Instance = InventorySubsystem->CreateInventoryItemInstance(OwnerComponent, ItemDef); if (NewEntry.Instance) { NewEntry.StackCount = StackCount; NewEntry.LastObservedCount = StackCount; Result = NewEntry.Instance; //MarkItemDirty(NewEntry); BroadcastChangeMessage(NewEntry, /[I]OldCount=[/I]/ 0, /[I]NewCount=[/I]/ StackCount); } } } return Result;

Изменения:

  • AddEntry вызывает метод CreateInventoryItemInstance из подсистемы.
  • Внешним объектом (outer) теперь корректно выступает компонент, а не актор.
  • Инициализация экземпляра проводится через InitializeItemInstance.

LyraInventoryItemInstance.h:

// После строки "friend struct FLyraInventoryList;" добавим: friend class ULyraInventorySubsystem;

Что мы сделали?

Мы разобрались с хаосом. Теперь экземпляры предметов — не беспризорники, а управляемые ресурсы.

  • Централизованное управлениечерез подсистему.
  • Компонент — главный, актор больше не вмешивается.
  • UE-127172 — отправлен в могилу.

Добавленные возможности

  • Масштабируемость — кастомная логика создания предметов? Без проблем.
  • Жизненный цикл под контролем — никаких утечек, никаких зомби-объектов.
  • Оптимизация — массив экземпляров теперь под присмотром.

Преимущества нового подхода

  • Чистый код— логика создания и управления в одном месте.
  • Правильная иерархия— предметы принадлежат кому надо.
  • Гибкость— добавляем пуллинг, асинхронность, что угодно.
  • Blueprint-дружественность — новые методы CreateInventoryItemInstance и InitializeItemInstance доступны из BP.

Вывод

Теперь подсистема управляет всем, следит за порядком и даже заметает следы за собой.

Как говорится, «Хороший код — это когда даже временные решения выглядят намеренно»,…но мы-то знаем, что лучше просто сделать всё правильно сразу.

1
Начать дискуссию