В этой статье мы обсудим создание компонента инвентаря и подключение его к требуемому Actor. Поскольку данный компонент представляет собой просто хранилище предметов и логику их загрузки/выгрузки, то нет никакой разницы в применении его для персонажа или какой-нибудь коробки.


Создать компонент можно как с помощью Blueprint, так с посредством С++. Я предпочитаю второй способ, так как собираюсь активно использовать функционал С++.




В первую очередь создаем структуру ячейки для хранения одного предмета. Я предпочитаю хранить ее в отдельном .h файле, чтобы свободно подключать по необходимости там где нужно:


StructContainerStack.h
/// Copyright 2018 Dreampax Games, Inc. All Rights Reserved.

/* Struct for Container Stack. This file is used as #include */

#pragma once

/* Includes from Engine */
#include "GameplayTagContainer.h"

/* Includes from Dreampax */
#include "Data/StructItemFactors.h"

#include "StructContainerStack.generated.h"

/* Declaration for contaiter stack structure. BlueprintType required to use in BP */
USTRUCT(BlueprintType)
struct FContainerStack
{
    GENERATED_USTRUCT_BODY()

    /* Gameplay tag to store the name */
    UPROPERTY(EditAnywhere)
    FGameplayTag ItemNameTag;

    UPROPERTY(EditAnywhere)
    int ItemAmount;

    /* Specific factors such as durability, damage etc. */
    UPROPERTY(EditAnywhere)
    TArray <FItemFactor> ItemFactors;

    FContainerStack()
    {
        Clear();
    }

    void Clear()
    {
        ItemNameTag.FromExportString("NAME_None");
        ItemAmount = 0;
        ItemFactors.Empty();
    }

    FORCEINLINE FGameplayTag GetItemNameTag() const
    {
        return ItemNameTag;
    }

    void SetItemNameTag(FGameplayTag const & ItemNameTagNew)
    {
        if (ItemNameTagNew.IsValid())
        {
            ItemNameTag = ItemNameTagNew;
        }
        else
        {
            Clear();
        }
    }

    FORCEINLINE int GetItemAmount() const
    {
        return ItemAmount;
    }

    void SetItemAmount(int const & ItemAmountNew)
    {
        if (ItemAmountNew > 0)
        {
            ItemAmount = ItemAmountNew;
        }
        else
        {
            Clear();
        }
    }

    FORCEINLINE TArray<FItemFactor> * GetItemFactors()
    {
        return &ItemFactors;
    }

    void SetItemFactors(TArray<FItemFactor> const & ItemFactorsNew)
    {
        if (ItemFactorsNew.Num() > 0)
        {
            ItemFactors = ItemFactorsNew;
        }
    }
};

Да, наша ячейка инвентаря содержит всего 3 переменные: идентификатор, количество и уникальные параметры. Ничего лишнего. Все данные могут без проблем быть скопированы, сохранены и загружены. Никаких текстур, ссылок на Actors и т.п. тут нет. Вся дополнительная информация может быть загружена из базы данных на DataAsset, о которой мы говорили ранее.


Скорее всего, вы уже обратили внимание на еще одну структуру StructItemFactors.h, подключенную в начале. Это не что иное как хранилище любых уникальных свойств объекта (в виде float), таких как износ, урон и т.п. То есть свойств, которые присущи только этой копии предмета, и никакой другой такой же. Эта структура очень простая:


StructItemFactors.h
/// Copyright 2018 Dreampax Games, Inc. All Rights Reserved.

/* Struct for Factors. This file is used as #include */

#pragma once

/* Includes from Engine */
#include "GameplayTagContainer.h"

/* Includes from Dreampax */
// no includes

#include "StructItemFactors.generated.h"

USTRUCT(BlueprintType)
struct FItemFactor
{
    GENERATED_USTRUCT_BODY()

    /* Name of Item Attribute Factor */
    UPROPERTY(EditDefaultsOnly, Category = "ItemsDatabase")
    FGameplayTag ItemFactorTag;

    /* Factor for the Item Attribute */
    UPROPERTY(EditDefaultsOnly, Category = "ItemsDatabase")
    float ItemFactor;

    /* for this type to be comparable */
    friend bool operator==(const FItemFactor & Lhs, const FItemFactor & Rhs)
    {
        return Lhs.ItemFactorTag == Rhs.ItemFactorTag && Lhs.ItemFactor == Rhs.ItemFactor;
    }

    FItemFactor()
    {
        Clear();
    }

    void Clear()
    {
        ItemFactorTag.EmptyTag;
        ItemFactor = 0;
    }

    FORCEINLINE FGameplayTag GetItemFactorTag()
    {
        return ItemFactorTag;
    }

    void SetItemFactorTag(FGameplayTag const &ItemFactorTagNew)
    {
        if (ItemFactorTagNew.IsValid())
        {
            ItemFactorTag = ItemFactorTagNew;
        }
        else
        {
            Clear();
        }
    }

    FORCEINLINE float GetItemFactor()
    {
        return ItemFactor;
    }

    void SetItemFactor(float const & ItemFactorNew)
    {
        if (ItemFactorNew > 0.0f)
        {
            ItemFactor = ItemFactorNew;
        }
        else
        {
            Clear();
        }
    }
};

Стоит отметить одну очень интересную функцию в структуре выше, которая призвана существенно упростить нам жизнь:


friend bool operator==(const FItemFactor & Lhs, const FItemFactor & Rhs)
    {
        return Lhs.ItemFactorTag == Rhs.ItemFactorTag && Lhs.ItemFactor == Rhs.ItemFactor;
    }

Это не что иное как оператор сравнения ==, который мы сможем использовать для данной структуры, чтобы не извлекать каждый раз элементы для этого. Очень удобно.




Итак, со структурами закончили. Переходим к созданию компонента:


DreampaxContainerComponent.h
/// Copyright 2018 Dreampax Games, Inc. All Rights Reserved.

#pragma once

/* Includes from Engine */
#include "Components/ActorComponent.h"
#include "GameplayTagContainer.h"

/* Includes from Dreampax */
#include "Data/StructItemFactors.h"
#include "Data/StructContainerStack.h"

#include "DreampaxContainerComponent.generated.h"

//UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) // currently not required
UCLASS()
class DREAMPAX_API UDreampaxContainerComponent : public UActorComponent
{
    GENERATED_BODY()

private:

    UPROPERTY(Transient, Replicated, EditAnywhere, Category = "Container")
    TArray<FContainerStack> ContentOfContainer;

public:

    /* Sets default values for this component's properties */
    UDreampaxContainerComponent(const FObjectInitializer & ObjectInitializer);

    /*
    Далее идут функции для получения данных из компонента,
репликации и т.п..
    */
};

Если в коде выше активировать строку


UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )

то можно будет подключать данный компонент прямо в Blueprint. Я же предпочитаю делать это в С++. Для Character это выглядит вот так:


Inventory = CreateDefaultSubobject<UDreampaxContainerComponent>(TEXT("Inventory"));

Ну, а для какого-нибудь сундука вот так:


ADreampaxActorContainer::ADreampaxActorContainer(const FObjectInitializer& ObjectInitializer)
    : Super(ObjectInitializer)
{
    Container = CreateDefaultSubobject<UDreampaxContainerComponent>(TEXT("Container"));
}

Как видите, разница только в названиях переменных.




В следующей статье я расскажу об особенностях репликации (по простому на пальцах), что позволит сделать наш инвентарь действительно мультиплеерным.

Комментарии (1)


  1. SkylineIT
    14.08.2018 21:43

    Емко, понятно и по делу.
    Классно, спасибо :)