Avant de commencer
Assurez-vous d'avoir terminé les objectifs suivants dans la section précédente, Gérer les éléments et les données:
Configurez une structure de données d'objet, une classe
UDataAsset, une instance de ressource de données de type objet à usage unique nomméeDA_Pickup_001et une table de données.
Créer une nouvelle classe d'objets à ramasser
Jusqu'ici, vous avez appris à définir et stocker les données et la structure d'un élément. Dans cette section, vous apprendrez à transformer ces données en "pickup" en jeu, une représentation concrète de données de tableau avec lesquelles le joueur peut interagir pour obtenir un effet. Il peut s'agir d'un gadget équipable, d'une boîte qui lui fournit des matériaux ou d'un bonus qui lui donne un bonus temporaire.
Pour commencer à paramétrer une classe d'objets à ramasser avec des déclarations initiales, suivez les étapes ci-dessous :
Dans l'Unreal Editor, allez sur Tools > New C++ Class. Sélectionnez Actor en tant que classe parente et nommez la classe
PickupBase.Cliquez sur Créer une classe.Dans Visual Studio, ouvrez
PickupBase.het ajoutez les instructions suivantes en haut du fichier :#include ”Components/SphereComponent.h”. Vous ajouterez un composant de sphère à l'objet à ramasser pour détecter les collisions entre le joueur et l'objet à ramasser.#include ”AdventureCharacter.h”. Ajouter une référence à votre classe de personnage à la première personne afin de vérifier les chevauchements entre l'objet et les personnages de cette classe. (Dans ce tutoriel, nous utilisonsAdventureCharacter.)Une déclaration directe pour
UItemDefinition. Il s'agit de l'élément de ressource de données associé auquel chaque objet à ramasser fait référence.
C++// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Components/SphereComponent.h" #include "CoreMinimal.h" #include "AdventureCharacter.h" #include "GameFramework/Actor.h" #include "PickupBase.generated.h"Dans la macro
UCLASS(), au-dessus de la définition de classeAPickupBase, ajoutez les spécificateursBlueprintTypeetBlueprintablepour l'exposer comme classe de base pour la création de blueprints.C++UCLASS(BlueprintType, Blueprintable) class ADVENTUREGAME_API APickupBase : public AActor {Dans
PickupBase.cpp, ajoutez une instructionincludepourItemDefinition.h.C++// Copyright Epic Games, Inc. All Rights Reserved. #include "PickupBase.h" #include "ItemDefinition.h"
Initialiser l'objet à ramasser avec table de données
Votre objet n'est qu'un acteur vide. Quand le jeu commence, vous devez lui fournir les données dont il a besoin pour fonctionner correctement. Le ramassage doit extraire une ligne de valeurs de la table de données et enregistrer ces valeurs dans une ressource de données ItemDefinition (l'"élément de référence").
Extraire les données d'une table de données
Dans PickupBase.h, dans la section public, déclarez une nouvelle fonction void InitializePickup(). Vous utiliserez cette fonction pour initialiser l'objet à ramasser avec des valeurs de la table de données.
// Initializes this pickup with values from the data table.
void InitializePickup();Pour extraire les données de la table, le blueprint de ramassage a besoin de deux propriétés : le ressource de table de données et le nom de la ligne (que vous avez configuré pour être le même que l'identifiant d'élément).
Dans la section protected, déclarez une propriété FName nommée PickupItemID. Donnez-lui les spécificateurs EditInstanceOnly et Category = “Pickup | Item Table”. C'est l'identifiant de cet objet à ramasser dans la table de données associée.
// The ID of this pickup in the associated data table.
UPROPERTY(EditInstanceOnly, Category = "Pickup | Item Table")
FName PickupItemID;Les objets à ramasser ne doivent pas avoir d'identifiant d'élément par défaut, donc le spécificateur EditInstanceOnly vous permet d'éditer cette propriété dans les instances d'objets à ramasser dans le monde, mais pas dans l'archétype (ou la classe par défaut).
Dans le texte de Category, la barre verticale (|) crée une sous-section imbriquée. Dans cet exemple, l'Unreal Engine crée une section Pickup avec une sous-section appelée Item Table dans le panneau Détails de la ressource.
Ensuite, déclarez un TSoftObjectPtr dans une UDataTable appelée PickupDataTable. Je lui donne les mêmes spécificateurs que pour le PickupItemID. Voici la table de données que l'objet à ramasser utilise pour obtenir ses données.
La table de données peut ne pas être chargée à temps d'exécution, donc utilisez un TSoftObjectPtr ici afin de pouvoir le charger de façon asynchrone.
Enregistrez le fichier d'en-tête et passez à PickupBase.cpp pour implémenter InitializePickup().
Dans la fonction, dans une instruction if, vérifiez si le PickupDataTable fourni est valide et que PickupItemID a une valeur.
/**
* Initializes the pickup with default values by retrieving them from the associated data table.
*/
void APickupBase::InitializePickup()
{
if (PickupDataTable && !PickupItemID.IsNone())
{
}
}Dans l'instruction if, ajoutez du code pour récupérer la ligne de valeurs de la table de données. Déclarez un pointeur const FItemData nommé ItemDataRow et assignez-le au résultat de l'appel de FindRow() sur votre PickupDataTable. Spécifiez FItemData comme type de ligne à trouver.
const FItemData* ItemDataRow = PickupDataTable->FindRow<FItemData>();FindRow() accepte deux arguments :
Un nom de ligne
FNameà trouver. PassezPickupItemIDen nom de ligne.Une chaîne de contexte de type
FStringque vous pouvez utiliser pour le débogage si la ligne est introuvable. Vous pouvez utiliserText(“My context here.”). pour ajouter une chaîne de contexte ou utiliserToString()pour convertir l'identifiant d'élément en chaîne de contexte.
if (PickupDataTable && !PickupItemID.IsNone())
{
// Retrieve the item data associated with this pickup from the Data Table
const FItemData* ItemDataRow = PickupDataTable->FindRow<FItemData>(PickupItemID, PickupItemID.ToString());
}Créer un élément de référence
Une fois que vous avez récupéré les données de ligne de la collecte, créez et initialisez un ReferenceItem de type ressource de données pour stocker cette information.
En enregistrant les données dans un élément de référence comme celui-ci, l'Unreal Engine peut facilement référencer ces données lorsqu'il a besoin d'en savoir plus, au lieu de procéder à des recherches dans les tables de données, qui sont moins efficaces.
Dans PickupBase.h, dans la section protected, déclarez un TObjectPtr dans une UItemDefinition nommée ReferenceItem. Cette ressource de données contient les données de collecte. Donnez-lui les spécificateurs VisibleAnywhere et Category = “Pickup | Reference Item”.
// Data asset associated with this item.
UPROPERTY(VisibleAnywhere, Category = "Pickup | Reference Item")
TObjectPtr<UItemDefinition> ReferenceItem;Enregistrez le fichier d'en-tête et revenez à PickupBase.cpp.
Dans InitializePickup(), après l'appel FindRow(), réglez ReferenceItem sur un NewObject de type UItemDefinition.
Dans l'Unreal Engine, NewObject<T>() est une fonction fondée sur un modèle qui permet de créer de façon dynamique des instances dérivées de UObject à temps d'exécution. Elle renvoie un pointeur vers le nouvel Objet. Il a généralement la syntaxe suivante :
T* Object = NewObject<T>(Outer, Class);
T est le type de UObject que vous créez, Outer est la personne qui possède cet objet et Class est la classe de l'objet que vous créez. L'argument de classe est généralement la fonction T::StaticClass(), qui donne un pointeur UClass qui représente le type de classe de T. Cependant, vous pouvez souvent omettre les deux arguments, car l'UE présume que Outer est la classe actuelle et utilise T pour inférer la UClass.
Transmettez this en tant que classe extérieure et la UItemDefinition::StaticClass() en tant que type de classe pour créer une UItemDefinition de base.
ReferenceItem = NewObject<UItemDefinition>(this, UItemDefinition::StaticClass());Pour copier les informations de ramassage dans ReferenceItem, réglez chaque champ de ReferenceItem sur le champ associé de ItemDataRow. Pour le WorldMesh, tirez la propriété WorldMesh de l'ItemBase référencé dans ItemDataRow.
ReferenceItem = NewObject<UItemDefinition>(this, UItemDefinition::StaticClass());
ReferenceItem->ID = ItemDataRow->ID;
ReferenceItem->ItemType = ItemDataRow->ItemType;
ReferenceItem->ItemText = ItemDataRow->ItemText;
ReferenceItem->WorldMesh = ItemDataRow->ItemBase->WorldMesh;Appelez InitializePickup()
Dans BeginPlay(), appelez InitializePickup() pour initialiser le ramassage au début du jeu.
// Called when the game starts or when spawned
void APickupBase::BeginPlay()
{
Super::BeginPlay();
// Initialize this pickup with default values
InitializePickup();
}Enregistrer le fichier. PickupBase.cpp devrait maintenant ressembler à ceci :
// Copyright Epic Games, Inc. All Rights Reserved.
#include "PickupBase.h"
// Sets default values
APickupBase::APickupBase()
{
// Set this actor to call Tick() every frame.
PrimaryActorTick.bCanEverTick = true;
}
Créer des fonctionnalités en jeu
Votre objet à ramasser dispose des données nécessaires, mais il doit encore savoir comment apparaître et fonctionner dans le monde du jeu. Il a besoin d'un maillage que le joueur puisse voir, d'un volume de collision pour déterminer quand il le touche et d'une logique qui permet de faire disparaître l'objet (comme si le joueur l'avait ramassé) et de générer au bout d'un certain temps.
Ajouter un composant de maillage
Comme vous l'avez fait pour paramétrer le personnage joueur dans Ajouter une caméra à la première personne, un maillage et une animation, vous utiliserez la fonction de modèle CreateDefaultSubobject pour créer un objet de maillage statique en tant que composant enfant de votre classe d'objet à ramasser puis appliquerez le maillage de cet élément à ce composant.
Dans PickupBase.h, dans la section protégée, déclarez un TObjectPtr dans un UStaticMeshComponent nommé PickupMeshComponent. Voici le maillage qui représentera l'objet à ramasser dans le monde.
Vous utiliserez du code pour assigner le maillage de la ressource de données à cette propriété, donc donnez-lui les VisibleDefaultsOnly et les spécificateurs Category = “Pickup | Mesh” pour qu'il soit visible, mais pas modifiable, dans l'Unreal Editor.
// The mesh component to represent this pickup in the world.
UPROPERTY(VisibleDefaultsOnly, Category = "Pickup | Mesh")
TObjectPtr<UStaticMeshComponent> PickupMeshComponent;Enregistrez le fichier d'en-tête et passez à PickupBase.cpp.
Dans la fonction de construction APickupBase, définissez le pointeur PickupMeshComponent sur le résultat de l'appel de CreateDefaultSubobject() de type UStaticMeshComponent. Dans l'argument Text, nommez l'objet “PickupMesh”.
Ensuite, pour assurer que le maillage a été correctement instancié, vérifiez que PickupMeshComponent n'est pas null.
// Sets default values
APickupBase::APickupBase()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
// Create this pickup's mesh component
PickupMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("PickupMesh"));
check(PickupMeshComponent != nullptr);
}Allez dans l'implémentation de InitializePickup().
Avant d'appliquer le WorldMesh au composant de maillage de l'objet à ramasser, vous devez vérifier que le maillage est chargé puisque vous l'avez défini avec un TSoftObjectPtr.
D'abord, déclarez un nouveau pointeur UItemDefinition appelé TempItemDefinition et assignez-le au résultat de l'appel ItemDataRow->ItemBase.Get().
UItemDefinition* TempItemDefinition = ItemDataRow->ItemBase.Get();Ensuite, dans une instruction if, vérifiez si WorldMesh est actuellement chargé en appelant WorldMesh.IsValid().
// Check if the mesh is currently loaded by calling IsValid().
if (TempItemDefinition->WorldMesh.IsValid()) {
}Le cas échéant, définissez le PickupMeshComponent en appelant SetStaticMesh() et en récupérant le WorldMesh avec Get() :
// Check if the mesh is currently loaded by calling IsValid().
if (TempItemDefinition->WorldMesh.IsValid()) {
// Set the pickup's mesh to the associated item's mesh
PickupMeshComponent->SetStaticMesh(TempItemDefinition->WorldMesh.Get());
}Si le maillage n'est pas chargé, forcez-le à se charger en appelant LoadSynchronous() sur le maillage. Cette fonction charge et renvoie un pointeur de ressource vers cet Objet.
Après l'instruction if, dans une instruction else, déclarez un nouveau pointeur UStaticMesh appelé WorldMesh et configurez-le en appelant WorldMesh.LoadSynchronous().
Ensuite, définissez PickupMeshComponent avec SetStaticMesh().
else {
// If the mesh isn't loaded, load it by calling LoadSynchronous().
UStaticMesh* WorldMesh = TempItemDefinition->WorldMesh.LoadSynchronous();
PickupMeshComponent->SetStaticMesh(WorldMesh);
}Après l'instruction else, rendez le PickupMeshComponent visible en utilisant SetVisibility(true) :
// Set the mesh to visible.
PickupMeshComponent->SetVisibility(true);Ajouter une forme de collision
Ajouter un composant sphère pour servir de volume de collision, puis activer les requêtes de collision sur ce composant.
Dans PickupBase.h, dans la section protégée, déclarez un TObjectPtr dans un USphereComponent nommé SphereComponent. Il s'agit du composant sphère utilisé pour la détection de collision. Donnez-lui les spécificateurs EditAnywhere, BlueprintReadOnly et Category = “Pickup | Components”.
// Sphere Component that defines the collision radius of this pickup for interaction purposes.
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Pickup | Components")
TObjectPtr<USphereComponent> SphereComponent;Enregistrez le fichier d'en-tête et passez à PickupBase.cpp.
Dans la fonction de construction APickupBase, après avoir défini PickupMeshComponent, réglez SphereComponent sur le résultat de l'appel de CreateDefaultSubobject avec le type USphereComponent et le nom “SphereComponent”. Ajouter une vérification null par la suite.
// Create this pickup's sphere component
SphereComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
check(SphereComponent != nullptr);Maintenant que vous avez les deux composants, utilisez SetupAttachment() pour joindre le PickupMeshComponent au SphereComponent :
// Attach the sphere component to the mesh component
SphereComponent->SetupAttachment(PickupMeshComponent);Après avoir joint le SphereComponent au MeshComponent, définissez de taille de la sphère à l'aide de SetSphereRadius(). Cette valeur devrait rendre votre collisionneur d'objets à ramasser assez grand pour que celui-ci puisse entrer en collision, mais pas trop pour que votre personnage ne puisse pas le percuter par accident.
// Set the sphere's collision radius
SphereComponent->SetSphereRadius(32.f);Dans InitializePickup(), après la ligne SetVisibility(true), rendez le SphereComponent capable de collision en appelant SetCollisionEnabled().
Cette fonction accepte une enum (ECollisionEnabled) qui indique au moteur quel type de collision utiliser. Vous voulez que le Personnage puisse entrer en collision et déclencher des requêtes de collision avec l'objet à ramasser, mais celui-ci ne doit pas avoir de physique qui le fait rebondir lorsqu'il est touché. Passez donc l'option ECollisionEnabled::QueryOnly.
// Set the mesh to visible and collidable.
PickupMeshComponent->SetVisibility(true);
SphereComponent->SetCollisionEnabled(ECollisionEnabled::QueryOnly); PickupBase.cpp doit maintenant ressembler à ce qui suit :
// Copyright Epic Games, Inc. All Rights Reserved.
#include "PickupBase.h"
#include "ItemDefinition.h"
// Sets default values
APickupBase::APickupBase()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
Simuler les collisions d'objets à ramasser
Maintenant que votre objet à ramasser a une forme de collision, ajoutez une logique pour détecter une collision entre l'objet et le joueur, afin de faire disparaître l'objet lorsque celui-ci entre en collision.
Configurer l'événement de collision
Dans PickupBase.h, dans la section protégée, déclarez une fonction void appelée OnSphereBeginOverlap().
Tout composant qui hérite de UPrimitiveComponent, par exemple USphereComponent, peut implémenter cette fonction pour exécuter du code lorsque le composant chevauche un autre acteur. Cette fonction accepte plusieurs paramètres que vous n'utiliserez pas ; vous ne transmettrez que les paramètres suivants :
UPrimitiveComponent* OverlappedComponent: composant qui a été chevauché.AActor* OtherActor: acteur qui chevauche ce composant.UPrimitiveComponent* OtherComp: composant de l'acteur qui se chevauchait.int32 OtherBodyIndex: index du composant chevauché.bool bFromSweep, const FHitResult& SweepResult: informations sur la collision, telles que l'emplacement où elle s'est produite et sous quel angle.
// Code for when something overlaps the SphereComponent.
UFUNCTION()
void OnSphereBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);Enregistrez le fichier d'en-tête et passez à PickupBase.cpp.
Les événements de collision de l'Unreal Engine sont implémentés à l'aide de délégués multidiffusion dynamiques. Dans l'UE, ce système de délégué permet à un Objet d'appeler plusieurs fonctions à la fois, un peu comme diffuser un message à une liste de diffusion où vos abonnés sont ces autres fonctions. Quand on relie des fonctions au délégué, c'est comme si on les inscrivait dans notre liste de diffusion. Le "délégué" est notre événement. Ici, il s'agit d'une collision entre le joueur et l'objet. Lorsque l'événement survient, l'Unreal Engine appelle toutes les fonctions liées à cet événement.
L'Unreal Engine inclut d'autres fonctions de liaison, mais vous devrez utiliser AddDynamic(), car votre délégué, OnComponentBeginOverlap, est un délégué dynamique. Par ailleurs, étant donné que vous liez une UFUNCTION dans une classe UObject, vous devez disposer de AddDynamic() pour la prise en charge des reflets. Pour en savoir plus sur les délégués de multidiffusion dynamiques, consultez la page Multi-cast Delegates.
Dans PickupBase.cpp, dans InitializePickup(), utilisez la macro AddDynamic pour lier OnSphereBeginOverlap() à l'événement OnComponentBeginOverlap du composant de sphère.
// Register the Overlap Event
SphereComponent->OnComponentBeginOverlap.AddDynamic(this, &APickupBase::OnSphereBeginOverlap);Enregistrez votre travail. À présent, l'événement OnSphereBeginOverlap() est exécuté lorsqu'un personnage entre en collision avec le composant de sphère de l'objet à ramasser.
Masquer l'objet à ramasser après une collision
Dans PickupBase.cpp, implémentez OnSphereBeginOverlap() pour que votre objet disparaisse comme si le joueur l'avait ramassé.
Commencez par ajouter un message de débogage pour signaler quand cette fonction est déclenchée.
void APickupBase::OnSphereBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Attempting a pickup collision"));
}Étant donné que cette fonction est exécutée chaque fois qu'un autre acteur entre en collision avec l'objet à ramasser, vous devez vous assurer que c'est bien votre personnage à la première personne qui effectue la collision.
Déclarez un nouveau pointeur AAdventureCharacter nommé Personnage et configurez-le en convertissant OtherActor en nom de votre classe de personnage (dans ce tutoriel, nous utilisons AAdventureCharacter).
// Checking if it's an AdventureCharacter overlapping
AAdventureCharacter* Character = Cast<AAdventureCharacter>(OtherActor);Dans une instruction if, vérifiez si Personnage n'est pas nul. Nul indique que la conversion a échoué et que OtherActor n'était pas un type de AAdventureCharacter.
Dans l’instruction if, désabonnez OnComponentBeginOverlap de cette fonction en appelant RemoveAll() afin qu’il ne soit pas déclenché de manière répétée. La collision est terminée.
if (Character != nullptr)
{
// Unregister from the Overlap Event so it is no longer triggered
SphereComponent->OnComponentBeginOverlap.RemoveAll(this);
}Ensuite, rendez le composant PickupMeshComponent invisible à l'aide de SetVisibility(false) et définissez le maillage de ramassage et le composant de sphère sur Non susceptible d'entrer en collision à l'aide de SetCollisionEnabled(), en transmettant l'option NoCollision.
if (Character != nullptr)
{
// Unregister from the Overlap Event so it is no longer triggered
SphereComponent->OnComponentBeginOverlap.RemoveAll(this);
// Set this pickup to be invisible and disable collision
PickupMeshComponent->SetVisibility(false);
PickupMeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
SphereComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}Enregistrez PickupBase.cpp.
Générer un réapparition de l'objet à ramasser
Maintenant que le personnage ne peut pas interagir avec l'objet, faites-le regénérer au bout d'un certain temps.
Dans PickupBase.h, dans la section protected, déclarez un booléen nommé bShouldRespawn. Vous pouvez l'utiliser pour activer ou désactiver la génération.
Déclarez un float appelé RespawnTime initialisé sur 4.0f. C'est le moment d'attendre que l'objet à ramasser réapparaisse.
Ajoutez aux deux propriétés les spécificateurs EditAnywhere, BlueprintReadOnly et Category = "Pickup | Respawn".
// Whether this pickup should respawn after being picked up.
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Pickup | Respawn")
bool bShouldRespawn;
// The time in seconds to wait before respawning this pickup.
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Pickup | Respawn")
float RespawnTime = 4.0f;Déclarez un FTimerHandle appelé RespawnTimerHandle.
// Timer handle to distinguish the respawn timer.
FTimerHandle RespawnTimerHandle;Dans l'Unreal Engine, les chronomètres du jeu sont gérés par la classe FTimerManager. Cette classe inclut la fonction SetTimer(), qui appelle une fonction ou un délégué après un délai défini. Les fonctions de FTimerManager utilisent un FTimerHandle pour démarrer, mettre en pause et reprendre la fonction, ou la lire en boucle à l'infini. Utilisez RespawnTimerHandle pour indiquer quand regénérer l'objet à ramasser. Pour en savoir plus sur l'utilisation du gestionnaire de chronomètre, consultez la page Chronomètres du jeu.
Enregistrez le fichier d'en-tête et passez à PickupBase.cpp.
Pour implémenter la réapparition de l'objet à ramasser, utilisez le gestionnaire de chronomètre afin de définir un chronomètre qui appelle InitializePickup() après un bref délai.
Ne regénérez l'objet à ramasser que si les réapparitions sont activées ; par conséquent, en bas de OnSphereBeginOverlap, ajoutez une instruction if qui vérifie si bShouldRespawn est true.
if (bShouldRespawn)
{
}Dans l'instruction if, obtenez le gestionnaire de chronomètre à l'aide de GetWorldTimerManager() et appelez SetTimer() sur le gestionnaire de chronomètre. Cette fonction a la syntaxe suivante :
SetTimer(InOutHandle, Object, InFuncName, InRate, bLoop, InFirstDelay);
Où :
InOutHandlecorrespond auFTimerHandlequi contrôle le chronomètre (votreRespawnTimerHandle).Objectcorrespond à l'UObjectqui possède la fonction que vous appelez. Utilisez ceci.InFuncNamecorrespond à un pointeur vers la fonction que vous souhaitez appeler (en l'occurrence,InitializePickup()).InRatecorrespond à une valeur float qui spécifie le temps à attendre, en secondes, avant d'appeler votre fonction.bLooppermet au chronomètre de se répéter toutes lesTimesecondes (true) ou de se déclencher une seule fois (false).InFirstDelay(facultatif) correspond à un délai initial avant le premier appel de fonction dans un chronomètre en boucle. S'il n'est pas spécifié, l'UE utilise le délaiInRate.
Dans la mesure où vous ne souhaitez appeler InitializePickup() qu'une seule fois pour remplacer l'objet à ramasser, définissez bLoop sur false.
Choisissez votre durée de réapparition préférée. Dans ce tutoriel, l'objet génère au bout de 4 secondes, sans délai initial.
// If the pickup should respawn, wait an fRespawnTime amount of seconds before calling InitializePickup() to respawn it
if (bShouldRespawn)
{
GetWorldTimerManager().SetTimer(RespawnTimerHandle, this, &APickupBase::InitializePickup, RespawnTime, false, 0);
}Votre fonction OnSphereBeginOverlap() complète doit ressembler à ce qui suit :
/**
* Broadcasts an event when a character overlaps this pickup's SphereComponent. Sets the pickup to invisible and uninteractable, and respawns it after a set time.
* @param OverlappedComponent - the component that was overlapped.
* @param OtherActor - the Actor overlapping this component.
* @param OtherComp - the Actor's component that overlapped this component.
* @param OtherBodyIndex - the index of the overlapped component.
* @param bFromSweep - whether the overlap was generated from a sweep.
* @param SweepResult - contains info about the overlap such as surface normals and faces.
*/
void APickupBase::OnSphereBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
Enregistrez votre code et compilez-le à partir de Visual Studio.
Implémenter des objets à ramasser dans le niveau
Maintenant que vous avez défini le code qui permet de créer vos objets à ramasser, il est temps de les tester dans votre jeu !
Pour ajouter des objets à ramasser dans votre niveau, procédez comme suit :
Dans l'arborescence des ressources du navigateur de contenu de l'Unreal Editor, accédez à Contenu > FirstPerson > Blueprints.
Dans le dossier Blueprints, créez un dossier enfant appelé Ramassages pour y stocker vos classes de ramassage.
Dans l'arborescence des ressources, accédez à votre dossier Classes C++ . Faites un clic droit sur votre classe PickupBase pour créer un blueprint à partir de cette classe.
Nommez-le
BP_PickupBase.Pour le chemin, sélectionnez Content/FirstPerson/blueprints/Pickups et cliquez sur Créer la classe de base de ramassage.
Revenez au dossier Blueprints > Ramassages. Faites glisser votre blueprint
BP_PickupBasedans le niveau. Une instance de PickupBase apparaît dans votre niveau ; elle est automatiquement sélectionnée dans le panneau Organiseur. Mais il n'a pas encore de maillage.Tout en conservant l'acteur
BP_PickupBasesélectionné, définissez les propriétés suivantes dans le panneau Détails :Définissez Identifiant de l'objet à ramasser sur
pickup_001.Définissez Table de données de ramassage sur
DT_PickupData.Définissez Doit réapparaître sur true.
Lorsque vous cliquez sur Jouer pour tester votre jeu, votre objet à ramasser utilise l'identifiant de l'objet à ramasser pour interroger la table de données et récupérer les données associées à pickup_001. L'objet à ramasser utilise les données de la table et la référence à votre ressource de données DA_Pickup_001 pour initialiser un objet de référence et charger son maillage statique.
Lorsque vous passez sur l'objet à ramasser, il doit disparaître, puis réapparaître quatre secondes plus tard.
Charger un autre objet à ramasser
Si vous définissez une valeur différente pour Identifiant de l'objet à ramasser, l'objet à ramasser récupère les données de cette ligne dans la table.
Pour tester le changement d'identifiant de l'objet à ramasser, procédez comme suit :
Créez une ressource de données nommée DA_Pickup_002. Définissez les propriétés suivantes dans cette ressource :
Identifiant : pickup_002
Type d'objet : objet à usage unique
Nom : Nom de test 2
Description : Description de test 2
Maillage de monde :
SM_ChamferCube
Ajoutez une nouvelle ligne dans la table
DT_PickupDataet saisissez les informations relatives à la ressource de données dans les champs de la nouvelle ligne.Dans l'acteur
BP_PickupBase, définissez le champ Identifiant de l'objet à ramasser surpickup_002.
Lorsque vous cliquez sur Jouer pour tester votre jeu, l'objet à ramasser doit apparaître avec les valeurs de DA_Pickup_002.
Mettre à jour les acteurs à ramasser dans l'éditeur
Les objets à ramasser fonctionnent en jeu, mais il peut être difficile de les visualiser dans l'éditeur, car ils ne disposent pas de maillage par défaut.
Pour résoudre ce problème, utilisez la fonction PostEditChangeProperty(). L'Unreal Engine appelle cette fonction intégrée à l'éditeur lorsque l'éditeur modifie une propriété. Vous pouvez donc l'utiliser pour tenir vos acteurs à jour dans le hublot lorsque leurs propriétés changent. Il peut s'agir de mettre à jour un élément IU quand vous modifiez la santé par défaut d'un joueur, ou de changer l'échelle d'une sphère quand vous l'approchez ou vous éloignez de son point d'origine.
Dans ce projet, vous l'utiliserez pour appliquer le nouveau maillage statique de l'objet à ramasser chaque fois que l'identifiant de l'objet à ramasser change. Vous pourrez ainsi changer votre type de collecte et le voir se mettre à jour immédiatement dans le hublot sans avoir besoin de cliquer sur jouer !
Pour que des modifications soient immédiatement visibles dans l'éditeur, suivez les étapes ci-dessous :
Dans
PickupBase.h, dans la sectionprotected, déclarez une macro#if WITH_EDITOR. Cette macro indique à l'outil Unreal Header Tool que les éléments qu'elle contient ne doivent être empaquetés que pour les builds de l'éditeur et non compilés pour les versions finales du jeu. Terminez cette macro par une instruction#endif.C++#if WITH_EDITOR #endifDans la macro, déclarez un remplacement de fonction void virtuelle pour
PostEditChangeProperty(). Cette fonction prend une référence àFPropertyChangedEvent, qui inclut des informations sur la propriété modifiée, le type de changement, etc. Enregistrez le fichier d'en-tête.C++#if WITH_EDITOR // Runs whenever a property on this object is changed in the editor virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; #endifDans
PickupBase.cpp, implémentez la fonctionPostEditChangeProperty(). Commencez par appeler la fonctionSuper::PostEditChangeProperty()pour gérer les modifications de propriétés de la classe parente.C++/** * Updates this pickup whenever a property is changed. * @param PropertyChangedEvent - contains info about the property that was changed. */ void APickupBase::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { // Handle parent class property changes Super::PostEditChangeProperty(PropertyChangedEvent); }Créez une nouvelle variable
const FNameappeléeChangedPropertypour stocker le nom de la propriété modifiée.C++// Handle parent class property changes Super::PostEditChangeProperty(PropertyChangedEvent); const FName ChangedPropertyName;Pour vérifier que
PropertyChangedEventinclut unepropriétéet enregistre cette propriété, utilisez un opérateur ternaire avec la conditionPropertyChangedEvent.Property. DéfinissezChangedPropertyNamesurPropertyChangedEvent.Property->GetFName()si la condition est true et définissez-la surNAME_Nonesi la condition est false.C++// If a property was changed, get the name of the changed property. Otherwise use none. const FName ChangedPropertyName = PropertyChangedEvent.Property ? PropertyChangedEvent.Property->GetFName() : NAME_None;NAME_Noneest une constante globale de l'Unreal Engine de typeFNamequi signifie qu'il n'y a pas de nom valide ou que le nom est nul.Maintenant que vous connaissez le nom de la propriété, vous pouvez demander à l'Unreal Engine d'actualiser le maillage s'il a détecté un changement d'identifiant.
Dans une instruction
if, vérifiez queChangePropertyNameest égal au résultat de l'appel deGET_MEMBER_NAME_CHECKED(), en transmettant cette classeAPickupBaseet lePickupItemID. Cette macro effectue une vérification au moment de la compilation pour assurer que la propriété que vous passez existe dans la classe transmise.Vous allez également récupérer des valeurs de la table de données ; par conséquent, vérifiez également que la table est valide avant de saisir l'instruction
if.C++// Verify that the changed property exists in this class and that the PickupDataTable is valid. if (ChangedPropertyName == GET_MEMBER_NAME_CHECKED(APickupBase, PickupItemID) && PickupDataTable) { }Dans l'instruction
if, récupérez la ligne de données associée à cet objet à ramasser de la même manière que vous l'avez fait dansInitializePickup()en appelantFindRow.Cette fois, pour assurer que
PickupItemIDse trouve bien dans la table avant de continuer, placez la ligneFindRowdans une instructionifimbriquée.C++// Verify that the changed property exists in this class and that the PickupDataTable is valid. if (ChangedPropertyName == GET_MEMBER_NAME_CHECKED(APickupBase, PickupItemID) && PickupDataTable) { // Retrieve the associated ItemData for this pickup. if (const FItemData* ItemDataRow = PickupDataTable->FindRow<FItemData>(PickupItemID, PickupItemID.ToString())) { } }Si l'UE détecte les données de ligne correctement, créez une variable
TempItemDefinitionpour stocker la ressource de données (qui contient le nouveau maillage) référencée dansItemDataRow.C++if (const FItemData* ItemDataRow = PickupDataTable->FindRow<FItemData>(PickupItemID, PickupItemID.ToString())) { UItemDefinition* TempItemDefinition = ItemDataRow->ItemBase;Pour mettre à jour le maillage, utilisez
SetStaticMeshdansPickupMeshComponent, en transmettant leWorldMeshde la ressource de données temporaire.C++// Set the pickup's mesh to the associated item's mesh PickupMeshComponent->SetStaticMesh(TempItemDefinition->WorldMesh.Get());Définissez le rayon de collision du composant de sphère avec la fonction
SetSphereRadius(32.f).C++// Set the sphere's collision radius SphereComponent->SetSphereRadius(32.f);Enregistrez votre code et compilez-le depuis Visual Studio.
Votre fonction PostEditChangeProperty() complète doit ressembler à ce qui suit :
/**
* Updates this pickup whenever a property is changed.
* @param PropertyChangedEvent - contains info about the property that was changed.
*/
void APickupBase::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
// Handle parent class property changes
Super::PostEditChangeProperty(PropertyChangedEvent);
// If a property was changed, get the name of the changed property. Otherwise use none.
De retour dans l'éditeur, accédez à l'organiseur pour vous assurer que l'acteur BP_PickupBase est sélectionné. Modifiez l'identifiant de l'objet à ramasser de pickup_001 à pickup_002, puis rétablissez-le. Lorsque vous modifiez l'identifiant, le maillage de votre objet se met à jour dans le hublot.
Si vous testez d'autres maillages, il peut être nécessaire de jouer une fois au jeu pour forcer le chargement complet du nouveau maillage avant de pouvoir le voir dans le hublot de l'éditeur.
Suivant
Ensuite, vous allez continuer à enrichir votre classe à ramasser afin de créer un gadget personnalisé et de l'équiper de votre personnage !
Équiper votre personnage
Apprenez à utiliser C++ pour créer des objets personnalisés et les associer à votre personnage.
Code terminé
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Components/SphereComponent.h"
#include "CoreMinimal.h"
#include "AdventureCharacter.h"
#include "GameFramework/Actor.h"
#include "PickupBase.generated.h"
// Copyright Epic Games, Inc. All Rights Reserved.
#include "PickupBase.h"
#include "ItemDefinition.h"
// Sets default values
APickupBase::APickupBase()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;