Гайд Lyra Inventory Fix 01: Фильтрация предметов. Когда порядок — не роскошь, а необходимость.

Начинающий
Статус
Оффлайн
Регистрация
9 Июл 2023
Сообщения
11
Реакции[?]
14
Поинты[?]
14K
Epic Games снова кинули нас, оставив в LyraInventoryManagerComponent.cpp полуфабрикат. Очередной недоношенный ребёнок разработки — фильтры инвентаря.

Вот где зарыта собака: закомментированные строки с ULyraInventoryFilter и его производным ULyraInventoryFilter_HasTag. Код как бы есть, но его как бы нет. Будто кто-то начал делать, отвлёкся на мемы в Твиттере и забыл допилить. Нам остаётся только дописать этот недостающий кусок, чтобы он не оставил нас с головной болью, а работал как надо.

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

Базовый функционал: ULyraInventoryFilter

Начнём с основ. ULyraInventoryFilter — это абстрактный класс, задающий интерфейс для фильтрации предметов. Его сердце — виртуальный метод PassesFilter. Он решает, проходит ли объект проверку или идёт лесом. Наследники смогут переопределять его, задавая свои правила отбора.

И да, добавим поддержку Blueprint. Потому что даже те, кто боится C++ больше, чем налоговую, должны иметь возможность ковыряться в этом механизме.

LyraInventoryFilter.h:
// Copyright Cainoli. You can use me to fulfill your fantasies. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "System/GameplayTagStack.h"
#include "GameplayTagContainer.h"
#include "LyraInventoryItemInstance.h"
#include "LyraInventoryFilter.generated.h"

class ULyraInventoryItemInstance;
struct FGameplayTag;

/**
* Base class for inventory filters
*/
UCLASS(DefaultToInstanced, EditInlineNew, Abstract, Blueprintable)
class LYRAGAME_API ULyraInventoryFilter : public UObject
{
    GENERATED_BODY()

public:
    ULyraInventoryFilter() {}

    // Main filter function that determines if an item passes the filter
    UFUNCTION(BlueprintNativeEvent, Category = "Inventory")
    bool PassesFilter(const ULyraInventoryItemInstance* ItemInstance) const;
    virtual bool PassesFilter_Implementation(const ULyraInventoryItemInstance* ItemInstance) const;
};

/**
* Filter that checks if an item has a specific gameplay tag
*/
UCLASS(Blueprintable)
class LYRAGAME_API ULyraInventoryFilter_HasTag : public ULyraInventoryFilter
{
    GENERATED_BODY()

public:
    // The gameplay tag to check for
    UPROPERTY(EditAnywhere, Category = "Filter")
    FGameplayTag RequiredTag;

    virtual bool PassesFilter_Implementation(const ULyraInventoryItemInstance* ItemInstance) const override;
};

/**
* Filter that checks item definition class
*/
UCLASS(Blueprintable)
class LYRAGAME_API ULyraInventoryFilter_ItemDefinition : public ULyraInventoryFilter
{
    GENERATED_BODY()

public:
    // The item definition class to filter by
    UPROPERTY(EditAnywhere, Category = "Filter")
    TSubclassOf<class ULyraInventoryItemDefinition> ItemDefinitionClass;

    virtual bool PassesFilter_Implementation(const ULyraInventoryItemInstance* ItemInstance) const override;
};

/**
* Composite filter that combines multiple filters using logical operations
*/
UCLASS(Blueprintable)
class LYRAGAME_API ULyraInventoryFilter_Composite : public ULyraInventoryFilter
{
    GENERATED_BODY()

public:

    // List of filters to combine
    UPROPERTY(EditAnywhere, Instanced, Category = "Filter")
    TArray<TObjectPtr<ULyraInventoryFilter>> Filters;

    // If true, ALL filters must pass (AND operation)
    // If false, ANY filter must pass (OR operation)
    UPROPERTY(EditAnywhere, Category = "Filter")
    bool bRequireAllFilters = true;

    virtual bool PassesFilter_Implementation(const ULyraInventoryItemInstance* ItemInstance) const override;
};
LyraInventoryFilter.cpp:
// Copyright Cainoli. You can use me to fulfill your fantasies. All Rights Reserved.

#include "LyraInventoryFilter.h"
#include "LyraInventoryItemInstance.h"
#include "LyraInventoryItemDefinition.h"

bool ULyraInventoryFilter::PassesFilter_Implementation(const ULyraInventoryItemInstance* ItemInstance) const
{
    // Base class implementation always returns true
    // Derived classes should override this to implement specific filtering logic
    return true;
}

bool ULyraInventoryFilter_HasTag::PassesFilter_Implementation(const ULyraInventoryItemInstance* ItemInstance) const
{
    if (!ItemInstance || !RequiredTag.IsValid())
    {
        return false;
    }
    // Check if the item instance has the required tag as a stat tag
    return ItemInstance->HasStatTag(RequiredTag);
}

bool ULyraInventoryFilter_ItemDefinition::PassesFilter_Implementation(const ULyraInventoryItemInstance* ItemInstance) const
{
    if (!ItemInstance || !ItemDefinitionClass)
    {
        return false;
    }
    return ItemInstance->GetItemDef() == ItemDefinitionClass;
}

bool ULyraInventoryFilter_Composite::PassesFilter_Implementation(const ULyraInventoryItemInstance* ItemInstance) const
{
    if (!ItemInstance || Filters.Num() == 0)
    {
        return false;
    }

    for (const TObjectPtr<ULyraInventoryFilter>& Filter : Filters)
    {
        if (!Filter)
        {
            continue;
        }

        const bool bPassesFilter = Filter->PassesFilter(ItemInstance);
        if (bRequireAllFilters)
        {
            // AND operation - if any filter fails, return false
            if (!bPassesFilter)
            {
                return false;
            }
        }
        else
        {
            // OR operation - if any filter passes, return true
            if (bPassesFilter)
            {
                return true;
            }
        }
    }

    // If we require all filters and haven't returned false yet, all filters passed
    // If we require any filter and haven't returned true yet, no filter passed
    return bRequireAllFilters;
}
Конкретные фильтры: бери и пользуйся

Теперь натягиваем на скелет мясо. Берём базовый класс и клепаем три полезных фильтра:

ULyraInventoryFilter_ItemDefinition
Фильтр для элиты. Работает на уровне классов и позволяет искать предметы по их типам. Учитывает наследование — если фильтруете по родительскому классу, то и его наследники пройдут проверку.

ULyraInventoryFilter_HasTag
Прямолинейный, как лом. Ищет предметы по тегам. Если тег есть — пропускает, если нет — идёт лесом.

ULyraInventoryFilter_Composite
Мозговитый. Позволяет комбинировать несколько фильтров, создавая сложные условия.
  • AND — все фильтры должны быть пройдены.
  • OR — достаточно одного совпадения.
С этим зверем можно строить логические цепочки, сложные, но элегантные. Больше никакой лапши-кода, только красота и модульность.

Добавляем фильтрацию в LyraInventoryManagerComponent

Теперь на сцену выходит GetFilteredItems — метод в ULyraInventoryManagerComponent, который принимает фильтр и возвращает отфильтрованный список предметов.

Работает безопасно — фильтр не передан? Не беда, просто вернёт пустой массив, а не уйдёт в закат с ошибкой. Никаких неожиданных вылетов, только контроль и порядок.

LyraInventoryManagerComponent.h:
public:
// Returns all items that pass the specified filter
UFUNCTION(BlueprintCallable, Category="Lyra|Inventory")
TArray<ULyraInventoryItemInstance*> GetFilteredItems(TSubclassOf<ULyraInventoryFilter> Filter) const;
LyraInventoryManagerComponent.cpp:
TArray<ULyraInventoryItemInstance*> ULyraInventoryManagerComponent::GetFilteredItems(TSubclassOf<ULyraInventoryFilter> Filter) const
{
TArray<ULyraInventoryItemInstance*> Results;

if (Filter == nullptr)
{
// If no filter is specified, return all items
return InventoryList.GetAllItems();
}
// Get all items and filter them
TArray<ULyraInventoryItemInstance*> AllItems = InventoryList.GetAllItems();
for (ULyraInventoryItemInstance* Item : AllItems)
{
if (const auto* FilterDef = Filter.GetDefaultObject())
{
if (FilterDef->PassesFilter(Item))
{
Results.Add(Item);
}
}
}
return Results;
}
Как это использовать: простота — сестра таланта
Пример на C++:

cpp:
// Создание фильтра по типу предмета
auto ItemFilter = NewObject<ULyraInventoryFilter_ItemDefinition>();
ItemFilter->ItemDefinitionClass = USpecificItemDefinition::StaticClass();

// Получение отфильтрованных предметов
TArray<ULyraInventoryItemInstance*> FilteredItems = InventoryComponent->GetFilteredItems(ItemFilter);
Пример создания композитного фильтра:
cpp:
// Создание композитного фильтра
auto CompositeFilter = NewObject<ULyraInventoryFilter_Composite>();
CompositeFilter->bRequireAllFilters = true; // AND операция

// Добавление подфильтров
CompositeFilter->Filters.Add(ItemFilter1);
CompositeFilter->Filters.Add(ItemFilter2);

// Получение предметов, которые проходят все фильтры
TArray<ULyraInventoryItemInstance*> FilteredItems = InventoryComponent->GetFilteredItems(CompositeFilter);
fil.PNG

Что в итоге?
Мы не просто подчинили фильтры — мы сделали их такими, какими они должны были быть с самого начала.
  • Просто — код не вызывает когнитивный диссонанс, а пользоваться им можно без ритуалов с бубном.
  • Расширяемо — хочешь новый фильтр? Просто наследуйся от ULyraInventoryFilter и делай, что вздумается.
  • Гибко — комбинируй фильтры в ULyraInventoryFilter_Composite и строй логические конструкции уровня «убийца-головоломка».
  • Доступно для Blueprint — даже если C++ для тебя тёмный лес, ты всё равно сможешь собрать свой фильтр без боли.
  • Грамотно встроено в Lyra — использует систему геймплейных тегов, идеально ложится в архитектуру проекта.
Теперь хаоса в инвентаре нет. Теперь это контролируемый порядок. С такими фильтрами найти нужный предмет проще, чем потерять его.
Пожалуйста, авторизуйтесь для просмотра ссылки.
 
Последнее редактирование:
Пользователь
Статус
Оффлайн
Регистрация
3 Июл 2023
Сообщения
61
Реакции[?]
159
Поинты[?]
164K
Спасибо, это может быть полезно. Товарищ, у них ещё в тодошках висят стакающиеся предметы. Я свою реализацию делал - все вроде работает, но она отходит от принципов универсальности проекта. Думаю многие скажут спасибо, если у тебя это выйдет проще, универсально и расширяемо во все стороны 😁.
 
Начинающий
Статус
Оффлайн
Регистрация
9 Июл 2023
Сообщения
11
Реакции[?]
14
Поинты[?]
14K
Думаю многие скажут спасибо, если у тебя это выйдет проще, универсально и расширяемо во все стороны 😁.
Да, у них есть TO-DO на этот счёт, а у меня есть реализация этого: Lyra Inventory Fix 05: Ограничения инвентаря. Логика стопок и "сколько вешать в граммах?"
 
Последнее редактирование:
Сверху Снизу