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

Начинающий
Статус
Оффлайн
Регистрация
9 Июл 2023
Сообщения
11
Реакции[?]
14
Поинты[?]
14K
И снова 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.
То, что должно было быть изначально.

Больше никаких «временных решений». Только чистый, логичный, мощный код.
Теперь подсистема — станет боссом. Она начнет управлять.
  • Создавать экземпляры предметов — централизованно, без хаоса.
  • Инициализировать их правильно — без костылей, как и должно быть.
  • Контролировать их жизненный цикл — удалит ненужное, очистит мусор.
Теперь экземпляры предметов будут принадлежать системе, а не расползутся по акторам как бездомные. Не будет багов с владением — всё структурировано и предсказуемо.

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

Добавляем методы в ULyraInventorySubsystem
LyraInventorySubsystem.h:
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;
Новые методы:
  1. CreateInventoryItemInstance — создаёт экземпляр предмета и настраивает его внешний объект (outer).
  2. InitializeItemInstance — выполняет дополнительную инициализацию экземпляра.
  3. 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;
Изменения:
  1. AddEntry вызывает метод CreateInventoryItemInstance из подсистемы.
  2. Внешним объектом (outer) теперь корректно выступает компонент, а не актор.
  3. Инициализация экземпляра проводится через InitializeItemInstance.
LyraInventoryItemInstance.h:
// После строки "friend struct FLyraInventoryList;" добавим:
    friend class ULyraInventorySubsystem;
Что мы сделали?
Мы разобрались с хаосом. Теперь экземпляры предметов — не беспризорники, а управляемые ресурсы.
  • Централизованное управление через подсистему.
  • Компонент — главный, актор больше не вмешивается.
  • UE-127172 — отправлен в могилу.
Добавленные возможности
  • Масштабируемость — кастомная логика создания предметов? Без проблем.
  • Жизненный цикл под контролемникаких утечек, никаких зомби-объектов.
  • Оптимизация — массив экземпляров теперь под присмотром.
Плюсы нового подхода
  • Чистый код — логика создания и управления в одном месте.
  • Правильная иерархия — предметы принадлежат кому надо.
  • Гибкость — добавляем пуллинг, асинхронность, что угодно.
  • Blueprint-дружественность — новые методы CreateInventoryItemInstance и InitializeItemInstance доступны из BP.
Вывод
  • TODO? Сожжены.
  • Баги? Закопаны.
  • Костыли? Переломаны через колено.
Теперь подсистема управляет всем, следит за порядком и даже заметает следы за собой.

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