UE4 | Инвентарь для Multiplayer #1 | Хранилище данных на DataAsset
UE4 | Инвентарь для Multiplayer #2 | Подключение Blueprint к C++
UE4 | Инвентарь для Multiplayer #3 | Структура взаимодействия
UE4 | Инвентарь для Multiplayer #4 | Создание и подключение конейнера
UE4 | Инвентарь для Multiplayer #5 | Передача информации между Сервером и Клиентом
В этой статье мы обсудим создание компонента инвентаря и подключение его к требуемому Actor. Поскольку данный компонент представляет собой просто хранилище предметов и логику их загрузки/выгрузки, то нет никакой разницы в применении его для персонажа или какой-нибудь коробки.
Создать компонент можно как с помощью Blueprint, так с посредством С++. Я предпочитаю второй способ, так как собираюсь активно использовать функционал С++.
В первую очередь создаем структуру ячейки для хранения одного предмета. Я предпочитаю хранить ее в отдельном .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), таких как износ, урон и т.п. То есть свойств, которые присущи только этой копии предмета, и никакой другой такой же. Эта структура очень простая:
/// 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;
}
Это не что иное как оператор сравнения ==, который мы сможем использовать для данной структуры, чтобы не извлекать каждый раз элементы для этого. Очень удобно.
Итак, со структурами закончили. Переходим к созданию компонента:
/// 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"));
}
Как видите, разница только в названиях переменных.
В следующей статье я расскажу об особенностях репликации (по простому на пальцах), что позволит сделать наш инвентарь действительно мультиплеерным.
SkylineIT
Емко, понятно и по делу.
Классно, спасибо :)