И таааак сойдет!И снова 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.Добавляем методы в ULyraInventorySubsystempublic: /** 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.ВыводТеперь подсистема управляет всем, следит за порядком и даже заметает следы за собой.Как говорится, «Хороший код — это когда даже временные решения выглядят намеренно»,…но мы-то знаем, что лучше просто сделать всё правильно сразу.#lyra #unrealengine #cpp #inventory