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")); }
Как видите, разница только в названиях переменных.
В следующей статье я расскажу об особенностях репликации (по простому на пальцах), что позволит сделать наш инвентарь действительно мультиплеерным.
ссылка на оригинал статьи https://habr.com/post/420115/
Добавить комментарий