UE4 | Inventaire pour Multijoueur # 1 | Entrepôt de données sur DataAsset




Dataasset Dans cet article, je vais essayer de révéler le sens et la méthodologie de la création de DataAsset , en tant que référentiel pour différents types de données, et dans notre cas, c'est une bibliothèque pour les acteurs et leurs paramètres.




Une petite introduction à sauter

Pour prendre la décision de créer un jeu il y a environ 2 ans, cela m'a aidé à tomber par hasard sur des informations sur Unreal Engine 4 et à lire à quel point c'est cool et simple. En fait, il est très difficile pour une personne qui ne sait pas écrire du code (un langage de programmation n'a pas d'importance dans ce contexte) de créer quelque chose de plus compliqué qu'une petite modification d'un ensemble standard de pièces à partir du moteur. Par conséquent, le désir initial de faire un super-méga jeu, avec la croissance des connaissances sur la réalité de ce projet, est progressivement devenu un passe-temps. Augmenter toutes les couches de développement de jeux, de la modélisation et de l'animation 3D à l'écriture de code, pour une personne semble une entreprise peu réalisable. Cependant, c'est un bon entraînement pour le cerveau.


Pourquoi avez-vous décidé d'écrire quelque chose? .. Probablement à cause de cela. que les manuels présentés donnent soit des connaissances très superficielles (et il y en a la plupart), soit même pour un très professionnel et ne contiennent que des instructions générales.


Il vaut presque toujours mieux recommencer depuis le début. Je ne peux pas dire que je fais toujours ça, mais je vais essayer de dire le plus systématiquement possible.


Bien sûr, il est préférable de commencer par la structure, mais, malheureusement, ayant une boîte fermée avec des outils, il est très difficile de comprendre ce qui peut être construit exactement avec leur aide. Ouvrons donc cette boîte et voyons ce qu'il y a à l'intérieur.




La première question à laquelle répondre. Pourquoi DataAsset ?


  1. Très souvent, dans les articles et les didacticiels, vous pouvez voir l'utilisation de DataTable . Pourquoi est-ce mauvais? Si vous stockez une adresse pour un Blueprint spécifique, lors du changement de nom ou du déplacement vers un autre dossier, vous serez obligé de modifier cette adresse manuellement. D'accord - mal à l'aise? Avec DataAsset , cela ne se produira pas. Toutes les communications seront mises à jour automatiquement. Si vous êtes absolument confiant dans la structure de votre projet pour les années à venir, vous pouvez bien sûr utiliser des tableaux.
  2. Le deuxième avantage indéniable est la possibilité de stocker des types de données complexes, tels que des structures ( Struct ).

Maintenant, un peu sur les inconvénients relatifs. En fait, je n'en vois qu'un. C'est la nécessité d'écrire du code C ++ .


Si vous comprenez déjà que sans travailler avec le code, vous ne ferez rien d' épique , alors ce n'est pas un inconvénient, mais une fonctionnalité.


Il convient de noter qu'il existe une solution de contournement: utiliser Actor en tant que tel référentiel. Mais une telle application ressemble à la dernière excuse pour ne pas vouloir apprendre le C ++ et détient le potentiel de se retrouver dans une impasse à l'avenir.
Si vous êtes convaincu que tout ce dont vous avez besoin pour votre projet peut être fait sur Blueprint , utilisez les tableaux.




Maintenant que vous avez déjà cru que DataAsset est bon, réfléchissez à la manière de le créer pour votre projet.


Pour ceux qui ne sont toujours pas dans le réservoir
Il y a une description très détaillée des étapes et des photos sur le forum de langue russe dédié à UE4 . Il suffit de google "UE4 DataAsset" pour . Il a maîtrisé les bases de ce manuel il y a environ un an.

Tout d'abord, créez une classe C ++ , comme Child à partir d' UDataAsset .
(Tout le code ci-dessous est tiré de mon projet à naître. Renommez simplement les noms comme vous le souhaitez.)


/// Copyright 2018 Dreampax Games, Inc. All Rights Reserved. #pragma once /* Includes from Engine */ #include "Engine/DataAsset.h" #include "Engine/Texture2D.h" #include "GameplayTagContainer.h" /* Includes from Dreampax */ //no includes #include "DreampaxItemsDataAsset.generated.h" UCLASS(BlueprintType) class DREAMPAX_API UDreampaxItemsDataAsset : public UDataAsset { GENERATED_BODY() } 

Maintenant, sur la base de cette classe, vous pouvez créer Blueprint en toute sécurité, mais il est trop tôt pour le faire ... jusqu'à présent, c'est juste un mannequin. Cependant, notez que des inclusions pour les textures et les noms ont déjà été faites.




A partir de ce moment, vous commencez à créer la structure de votre référentiel. Il sera refait plusieurs fois, donc je recommande fortement de ne pas immédiatement remplir votre espace de stockage. Trois à cinq éléments, dans notre cas les éléments d'inventaire, suffisent pour les tests. Parfois, après la compilation, votre Blueprint peut être vierge vide, ce qui est extrêmement désagréable si vous avez déjà rempli une douzaine ou deux postes.


Vous pouvez créer une structure directement dans le fichier d'en-tête, car dans ce cas, il est peu probable qu'il soit appliqué ailleurs. Habituellement, je préfère le faire en tant que fichier d'en-tête séparé "SrtuctName.h" , et le connecter si nécessaire selon les besoins.


Dans mon cas, ça ressemble à ça
 USTRUCT(BlueprintType) struct FItemsDatabase { GENERATED_USTRUCT_BODY() /* Storage for any float constant data */ UPROPERTY(EditDefaultsOnly, Category = "ItemsDatabase") TMap<FGameplayTag, float> ItemData; /* Gameplay tag container to store the properties */ UPROPERTY(EditDefaultsOnly, Category = "ItemsDatabase") FGameplayTagContainer ItemPropertyTags; /* Texture for showing in the inventory */ UPROPERTY(EditDefaultsOnly, Category = "ItemsDatabase") UTexture2D* IconTexture; /* The class put on the Mesh on the character */ UPROPERTY(EditDefaultsOnly, Category = "ItemsDatabase") TSubclassOf<class ADreampaxOutfitActor> ItemOutfitClass; /* The class to spawn the Mesh in the level then it is dropped */ UPROPERTY(EditDefaultsOnly, Category = "ItemsDatabase") TSubclassOf<class ADreampaxPickupActor> ItemPickupClass; //TODO internal call functions }; 

Soyez des comptes avec TMap . Pas répliqué! Dans ce cas, cela n'a pas d'importance.


Veuillez noter que je n'utilise pas FName . Selon les tendances modernes, l'utilisation de FGameplayTag est considérée comme plus correcte, car réduit considérablement le risque d'erreur et présente plusieurs avantages qui vous seront utiles ultérieurement.


GameplayTag dans l'éditeur
Gameplaytag

DataTable pour GameplayTag exporté depuis Excel
DataTable

Il est également recommandé de définir des fonctions pour appeler des variables dans la structure, telles que GetSomething () . Apparemment, j'ai encore besoin de travailler sur mon éducation, car c'est dans cette base de données, je n'ai pas encore fait un tel appel.


Voici comment cela peut être fait en utilisant une autre base de données comme exemple.
 USTRUCT(BlueprintType) struct FBlocksDatabase { GENERATED_USTRUCT_BODY() /* The class put on the Mesh for the building block */ UPROPERTY(EditDefaultsOnly, Category = "BlocksDatabase") TSubclassOf<class ADreampaxBuildingBlock> BuildingBlockClass; UPROPERTY(EditDefaultsOnly, Category = "BlocksDatabase") FVector DefaultSize; UPROPERTY(EditDefaultsOnly, Category = "BlocksDatabase") FVector SizeLimits; UPROPERTY(EditDefaultsOnly, Category = "BlocksDatabase") TArray<class UMaterialInterface *> BlockMaterials; FORCEINLINE TSubclassOf<class ADreampaxBuildingBlock> * GetBuildingBlockClass() { return &BuildingBlockClass; } FORCEINLINE FVector GetDefaultSize() { return DefaultSize; } FORCEINLINE FVector GetSizeLimits() { return SizeLimits; } FORCEINLINE TArray<class UMaterialInterface *> GetBlockMaterials() { return BlockMaterials; } }; 

Et le point le plus important est la déclaration de la base de données:


 UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "ItemsDatabase") TMap<FGameplayTag, FItemsDatabase> ItemsDataBase; 

Vous pouvez maintenant déjà créer notre Blueprint et le remplir.


Exemple de remplissage DataAsset
Exemple de remplissage DataAsset

Mais avant cela, nous écrirons quelques fonctions d'appel supplémentaires afin de pouvoir recevoir des données de la base de données.


DreampaxItemsDataAsset.h
 /// Copyright 2018 Dreampax Games, Inc. All Rights Reserved. #pragma once /* Includes from Engine */ #include "Engine/DataAsset.h" #include "Engine/Texture2D.h" #include "GameplayTagContainer.h" /* Includes from Dreampax */ //no includes #include "DreampaxItemsDataAsset.generated.h" USTRUCT(BlueprintType) struct FItemsDatabase { GENERATED_USTRUCT_BODY() /* Storage for any float constant data */ UPROPERTY(EditDefaultsOnly, Category = "ItemsDatabase") TMap<FGameplayTag, float> ItemData; /* Gameplay tag container to store the properties */ UPROPERTY(EditDefaultsOnly, Category = "ItemsDatabase") FGameplayTagContainer ItemPropertyTags; /* Texture for showing in the inventory */ UPROPERTY(EditDefaultsOnly, Category = "ItemsDatabase") UTexture2D* IconTexture; /* The class put on the Mesh on the character */ UPROPERTY(EditDefaultsOnly, Category = "ItemsDatabase") TSubclassOf<class ADreampaxOutfitActor> ItemOutfitClass; /* The class to spawn the Mesh in the level then it is dropped */ UPROPERTY(EditDefaultsOnly, Category = "ItemsDatabase") TSubclassOf<class ADreampaxPickupActor> ItemPickupClass; //TODO internal call functions }; UCLASS(BlueprintType) class DREAMPAX_API UDreampaxItemsDataAsset : public UDataAsset { GENERATED_BODY() protected: /* This GameplayTag is used to find a Max size of the stack for the Item. This tag can be missed in the ItemData */ UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "ItemsDatabase") FGameplayTag DefaultGameplayTagForMaxSizeOfStack; /* This is the main Database for all Items. It contains constant common variables */ UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "ItemsDatabase") TMap<FGameplayTag, FItemsDatabase> ItemsDataBase; public: FORCEINLINE TMap<FGameplayTag, float> * GetItemData(const FGameplayTag &ItemNameTag); FORCEINLINE FGameplayTagContainer * GetItemPropertyTags(const FGameplayTag &ItemNameTag); /* Used in the widget */ UFUNCTION(BlueprintCallable, Category = "ItemDatabase") FORCEINLINE UTexture2D * GetItemIconTexture(const FGameplayTag & ItemNameTag) const; FORCEINLINE TSubclassOf<class ADreampaxOutfitActor> * GetItemOutfitClass(const FGameplayTag & ItemNameTag); FORCEINLINE TSubclassOf<class ADreampaxPickupActor> * GetItemPickupClass(const FGameplayTag & ItemNameTag); int GetItemMaxStackSize(const FGameplayTag & ItemNameTag); FORCEINLINE bool ItemIsFound(const FGameplayTag & ItemNameTag) const; }; 

DreampaxItemsDataAsset.pp
 /// Copyright 2018 Dreampax Games, Inc. All Rights Reserved. #include "DreampaxItemsDataAsset.h" /* Includes from Engine */ // no includes /* Includes from Dreampax */ // no includes TMap<FGameplayTag, float>* UDreampaxItemsDataAsset::GetItemData(const FGameplayTag & ItemNameTag) { return & ItemsDataBase.Find(ItemNameTag)->ItemData; } FGameplayTagContainer * UDreampaxItemsDataAsset::GetItemPropertyTags(const FGameplayTag & ItemNameTag) { return & ItemsDataBase.Find(ItemNameTag)->ItemPropertyTags; } UTexture2D* UDreampaxItemsDataAsset::GetItemIconTexture(const FGameplayTag &ItemNameTag) const { if (ItemNameTag.IsValid()) { return ItemsDataBase.Find(ItemNameTag)->IconTexture; } return nullptr; } TSubclassOf<class ADreampaxOutfitActor>* UDreampaxItemsDataAsset::GetItemOutfitClass(const FGameplayTag &ItemNameTag) { return & ItemsDataBase.Find(ItemNameTag)->ItemOutfitClass; } TSubclassOf<class ADreampaxPickupActor>* UDreampaxItemsDataAsset::GetItemPickupClass(const FGameplayTag &ItemNameTag) { return & ItemsDataBase.Find(ItemNameTag)->ItemPickupClass; } int UDreampaxItemsDataAsset::GetItemMaxStackSize(const FGameplayTag & ItemNameTag) { // if DefaultGameplayTagForMaxSizeOfStack is missed return 1 for all items if (!DefaultGameplayTagForMaxSizeOfStack.IsValid()) { return 1; } int MaxStackSize = floor(GetItemData(ItemNameTag)->FindRef(DefaultGameplayTagForMaxSizeOfStack)); if (MaxStackSize > 0) { return MaxStackSize; } // if Tag for MaxStackSize is "0" return 1 return 1; } bool UDreampaxItemsDataAsset::ItemIsFound(const FGameplayTag & ItemNameTag) const { if (ItemsDataBase.Find(ItemNameTag)) { return true; } return false; } 

Du multijoueur il n'y a toujours rien. Mais c'est le premier pas qui a été fait dans la bonne direction.


Dans le prochain article, je parlerai des méthodes de connexion de DataAsset (oui, et de tout Blueprint ) pour lire les données en C ++, et montrer laquelle est la plus correcte.


Si vous avez des questions ou des suggestions pour divulguer un aspect plus en détail, veuillez écrire dans les commentaires.

Source: https://habr.com/ru/post/fr418913/


All Articles