UE4 | Инвентарь для Multiplayer #4 | Создание и подключение конейнера

от автора

В этой статье мы обсудим создание компонента инвентаря и подключение его к требуемому 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")); } 

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


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


ссылка на оригинал статьи https://habr.com/post/420115/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *