-
Автор темы
- #1
Фикс 1: Валидация, инкапсуляция
Первый небольшой фикс мы начнем с комментария в LyraInventoryManagerComponent.cpp на строке 136:
Где будем искать решение? Спрячем логику валидации в подсистему, где ей и место, а компоненту оставим только вызов готовых методов. Такой подход делает код чище, надёжнее и легче для поддержки.
В своих потугах использую UE5.5.0.
Что мы делаем?
Обновляем FLyraInventoryList
Теперь компонент инвентаря делегирует проверку валидности и очистку подсистеме.
Что мы сделали?
Мы спрятали логику валидации туда, где ей место — в ULyraInventorySubsystem. Компонент инвентаря стал проще, а код — чище и надёжнее. Как говорится: "Меньше проверок руками — больше времени на крутые фичи!"
Фикс 2: Имплементация
Молодость… Вечно что-то unimplemented оставляют, а потом чешут репу: "Так и должно быть или кто-то заснул на клавише?" Ну давайте разберём этот вопрос как опытный старик, который видел все — и баги, и багфиксы, и бессонные ночи программистов.
Два метода AddEntry — зачем они нужны?
Потому что жизнь сложнее, чем кажется.
Почему unimplemented() — это плохо?
Потому что забыть реализовать метод — это как забыть завинтить гайку в колесе: всё работает, пока машина не разгонится. Этот метод очень нужен, и сейчас он пустует как заброшенная деревенская хата.
Как его реализовать?
Давайте правильно заполним пробел. Вот что получится:
Теперь всё на своих местах:
Два метода — два подхода: создать или добавить. Оба важны, оба выполняют свои роли. А если какой-то из них пустует и ждёт реализации — не медлите, как старик на утренней рыбалке. Лучше сразу доделать, чем потом искать баг, когда всё уже развалилось!
Первый небольшой фикс мы начнем с комментария в LyraInventoryManagerComponent.cpp на строке 136:
TODO: Would prefer to not deal with this here and hide it further?
. Он указывает на то, что валидация (nullptr и другие проверки состояния Entry.Instance) выполняется прямо в компоненте. Это нарушает инкапсуляцию и засоряет логику компонента инвентаря.Где будем искать решение? Спрячем логику валидации в подсистему, где ей и место, а компоненту оставим только вызов готовых методов. Такой подход делает код чище, надёжнее и легче для поддержки.
Что мы делаем?
- Переносим логику проверки валидности и очистки в ULyraInventorySubsystem.
- Добавляем методы для:
- Валидации экземпляров предметов (например, на nullptr).
- Фильтрации списка предметов — возвращает только валидные.
- Очистки невалидных предметов — автоматически убирает мусор.
- Обновляем FLyraInventoryList в компоненте, чтобы он делегировал эту работу подсистеме.
LyraInventorySubsystem.h:
public:
/** Validate if an inventory item instance is still valid */
UFUNCTION(BlueprintCallable, Category="Lyra|Inventory")
bool IsValidItemInstance(const ULyraInventoryItemInstance* Instance) const;
/** Get all valid items from an inventory entry array */
UFUNCTION(BlueprintCallable, Category="Lyra|Inventory")
TArray<ULyraInventoryItemInstance*> GetValidItemsFromList(const TArray<struct FLyraInventoryEntry>& Entries) const;
private:
/** Remove invalid items from management */
void CleanupInvalidItems();
LyraInventorySubsystem.cpp:
void ULyraInventorySubsystem::CleanupInvalidItems()
{
ManagedItems.RemoveAll([](const ULyraInventoryItemInstance* Item) {
return !IsValid(Item);
});
}
bool ULyraInventorySubsystem::IsValidItemInstance(const ULyraInventoryItemInstance* Instance) const
{
return IsValid(Instance) && Instance->GetItemDef() != nullptr && ManagedItems.Contains(Instance);
}
TArray<ULyraInventoryItemInstance*> ULyraInventorySubsystem::GetValidItemsFromList(const TArray<FLyraInventoryEntry>& Entries) const
{
TArray<ULyraInventoryItemInstance*> Results;
Results.Reserve(Entries.Num());
for (const FLyraInventoryEntry& Entry : Entries)
{
if (IsValidItemInstance(Entry.Instance))
{
Results.Add(Entry.Instance);
}
}
return Results;
}
LyraInventoryManagerComponent.h:
// Добавляем после #include "LyraInventoryManagerComponent.generated.h"
class ULyraInventorySubsystem;
// В FLyraInventoryEntry после строки friend ULyraInventoryManagerComponent;
friend ULyraInventorySubsystem;
Теперь компонент инвентаря делегирует проверку валидности и очистку подсистеме.
LyraInventoryManagerComponent.cpp:
// Полностью заменяем реализацию функции FLyraInventoryList::GetAllItems()
TArray<ULyraInventoryItemInstance*> FLyraInventoryList::GetAllItems() const
{
if (UGameInstance* GameInstance = OwnerComponent->GetWorld()->GetGameInstance())
{
if (ULyraInventorySubsystem* InventorySubsystem = GameInstance->GetSubsystem<ULyraInventorySubsystem>())
{
return InventorySubsystem->GetValidItemsFromList(Entries);
}
}
return TArray<ULyraInventoryItemInstance*>();
}
- Добавили методы в подсистему:
- IsValidItemInstance — проверяет валидность экземпляра.
- GetValidItemsFromList — фильтрует и возвращает валидные предметы.
- CleanupInvalidItems — удаляет невалидные предметы.
- Перенесли логику валидации из компонента в подсистему:
- FLyraInventoryList теперь вызывает методы подсистемы, а не сам проверяет nullptr.
- Код компонента стал чище и теперь только делегирует задачи.
- Улучшили инкапсуляцию и масштабируемость:
- Вся логика валидации сосредоточена в одном месте — в ULyraInventorySubsystem.
- Добавление новой логики (например, асинхронной проверки или логирования ошибок) теперь проще.
- Централизованная валидация — всё сосредоточено в подсистеме.
- Чистый и модульный код — компонент больше не занимается чужими заботами.
- Автоматическая очистка — подсистема убирает невалидные предметы, предотвращая ошибки и утечки.
- Гибкость — в будущем можно добавить расширенные проверки или асинхронное обновление списка.
Мы спрятали логику валидации туда, где ей место — в ULyraInventorySubsystem. Компонент инвентаря стал проще, а код — чище и надёжнее. Как говорится: "Меньше проверок руками — больше времени на крутые фичи!"
Фикс 2: Имплементация
Молодость… Вечно что-то unimplemented оставляют, а потом чешут репу: "Так и должно быть или кто-то заснул на клавише?" Ну давайте разберём этот вопрос как опытный старик, который видел все — и баги, и багфиксы, и бессонные ночи программистов.
Два метода AddEntry — зачем они нужны?
- ULyraInventoryItemInstance* AddEntry(TSubclassOf<ULyraInventoryItemDefinition> ItemClass, int32 StackCount)
Это наш молодой и амбициозный метод. Он создаёт новый предметс нуля:- Берёт определение предмета (ItemClass).
- Создаёт экземпляр через подсистему.
- Устанавливает размер стека (потому что не всегда нужен одиночный батончик хлеба — иногда их надо 20).
- Используется при создании новых предметов: игрок нашёл лопату, квест наградил булкой, или админ выдал баночку зелья.
- void AddEntry(ULyraInventoryItemInstance* Instance)
А вот это старый волк в системе — работает с уже существующими экземплярамипредметов:- Экземпляр уже есть — неважно, откуда он: репликация, загрузка сохранёнки или переложили из другого инвентаря.
- Нужна только проверка валидности (а то мало ли, пока предмет полз к нам, превратился в тыкву).
- Подсистема может его дополнительно инициализировать, если что-то не так.
Потому что жизнь сложнее, чем кажется.
- Первый метод (AddEntry с ItemClass) — для создания нового экземпляра. Это как роддом для предметов: класс определили, вес (стек) назначили, и вот вам новый герой в системе инвентаря.
- Второй метод (AddEntry с Instance) — для существующих предметов. Например:
- Репликация: предмет приехал по сети — надо его добавить.
- Загрузка сохранения: предметы уже есть в сейве, их просто надо вернуть в инвентарь.
- Перемещение между инвентарями: переложили лопату из рюкзака в сундук.
- Синхронизация: при сетевой игре надо следить, чтобы предметы были везде одинаковы.
Почему unimplemented() — это плохо?
Потому что забыть реализовать метод — это как забыть завинтить гайку в колесе: всё работает, пока машина не разгонится. Этот метод очень нужен, и сейчас он пустует как заброшенная деревенская хата.
Как его реализовать?
Давайте правильно заполним пробел. Вот что получится:
LyraInventoryManagerComponent.cpp:
void FLyraInventoryList::AddEntry(ULyraInventoryItemInstance* Instance)
{
// Добавляем реализацию вместо unimplemented();
if (!Instance || !OwnerComponent)
{
return;
}
AActor* OwningActor = OwnerComponent->GetOwner();
if (!OwningActor || !OwningActor->HasAuthority())
{
return;
}
// Check if the instance is already managed by the inventory subsystem
if (UGameInstance* GameInstance = OwnerComponent->GetWorld()->GetGameInstance())
{
if (ULyraInventorySubsystem* InventorySubsystem = GameInstance->GetSubsystem<ULyraInventorySubsystem>())
{
if (!InventorySubsystem->IsValidItemInstance(Instance))
{
// If not managed, initialize it through the subsystem
InventorySubsystem->InitializeItemInstance(Instance, Instance->GetItemDef());
}
}
}
// Add to inventory
FLyraInventoryEntry& NewEntry = Entries.AddDefaulted_GetRef();
NewEntry.Instance = Instance;
NewEntry.StackCount = 1;
NewEntry.LastObservedCount = 1;
BroadcastChangeMessage(NewEntry, /[I]OldCount=[/I]/ 0, /[I]NewCount=[/I]/ 1);
}
- AddEntry(ItemClass, StackCount) — создаёт новый предмет.
- AddEntry(Instance) — добавляет существующий экземпляр.
Два метода — два подхода: создать или добавить. Оба важны, оба выполняют свои роли. А если какой-то из них пустует и ждёт реализации — не медлите, как старик на утренней рыбалке. Лучше сразу доделать, чем потом искать баг, когда всё уже развалилось!
Последнее редактирование: