Avant de commencer
Assurez-vous d'avoir terminé les objectifs suivants dans la section précédente, Équiper votre personnage :
Vous avez créé un objet à ramasser qui réapparaît et l'avez ajouté à votre niveau.
Vous avez créé un lance-fléchettes à équiper que votre personnage peut attraper et utiliser.
Projectiles de base
Votre personnage peut attraper votre lance-fléchettes, et votre outil dispose de liaisons de contrôle pour l'utiliser, mais cela ne correspond pas tout à fait à son nom. Dans cette section, vous allez implémenter la logique de projectile pour que l'objet équipé lance des fléchettes.
L'Unreal Engine possède une classe de composant de mouvement de projectile que vous pouvez ajouter à un acteur pour le convertir en projectile. Il inclut de nombreuses variables utiles, telles que la vitesse du projectile, l'effet de rebond et l'échelle de gravité.
Pour gérer les calculs liés à l'implémentation des projectiles, vous devez prendre en compte plusieurs facteurs :
Quelles sont la transformation initiale, la vitesse et la direction du projectile ?
Souhaitez-vous faire apparaître des projectiles depuis le centre du personnage ou depuis l'outil dont il est équipé ?
Quel doit être le comportement du projectile lorsqu'il entre en collision avec un autre objet ?
Créer une classe de projectile de base
Vous allez commencer par créer une classe de projectile de base, puis en créer une sous-classe afin de concevoir des projectiles uniques pour vos outils.
Pour commencer à configurer une classe de projectile de base, procédez comme suit :
Dans l'Unreal Editor, accédez à Outils > Nouvelle classe C++. Sélectionnez Acteur comme classe parente et nommez la classe
FirstPersonProjectile. Cliquez sur Créer une classe.Dans VS, accédez à
FirstPersonProjectile.h. En haut du fichier, déclarez unUProjectileMovementComponentet unUSphereComponent.Vous utiliserez un simple composant de sphère pour simuler les collisions entre le projectile et d'autres objets.
C++// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "FirstPersonProjectile.generated.h" class UProjectileMovementComponent; class USphereComponent;Ajoutez les spécificateurs
BlueprintTypeetBlueprintablepour exposer cette classe aux blueprints :C++UCLASS(BlueprintType, Blueprintable) class FIRSTPERSON_API AFirstPersonProjectile : public AActorOuvrez
FirstPersonProjectile.cpp. En haut du fichier, ajoutez une instruction #include pour"GameFramework/ProjectileMovementComponent.h"et"Components/SphereComponent.h"afin d'inclure les classes de composants de déplacement et de collision de projectile.C++#include "FirstPersonProjectile.h" #include "GameFramework/ProjectileMovementComponent.h" #include "Components/SphereComponent.h" // Sets default values AFirstPersonProjectile::AFirstPersonProjectile()
Implémenter le comportement des projectiles lorsqu'ils touchent des objets
Pour rendre votre projectile plus réaliste, vous pouvez le configurer pour qu'il exerce une force (une impulsion) sur les objets qu'il touche. Par exemple, si vous visez un bloc activé par la physique, le projectile pousse le bloc le long du sol. Ensuite, retirez le projectile après l'impact au lieu de le laisser atteindre sa durée de vie par défaut. Créez une fonction OnHit() pour implémenter ce comportement.
Pour implémenter le comportement d'un projectile « à l'impact », procédez comme suit :
Dans
FirstPersonProjectile.h, dans la sectionpublic, définissez une propriétéfloatappeléePhysicsForce.Attribuez-lui une macro
UPROPERTY()avecEditAnywhere,BlueprintReadOnlyetCategory = "Projectile | Physics".Il s'agit de la force exercée par le projectile lorsqu'il touche un objet.
C++// The amount of force this projectile imparts on objects it collides with UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Projectile | Physics") float PhysicsForce = 100.0f;Définissez une fonction
voidOnHit(). Il s'agit d'une fonction d'AActor qui est appelée lorsque l'acteur entre en collision avec un autre composant ou acteur. Elle prend les arguments suivants :HitComp: composant ayant été touché.OtherActor: acteur ayant été touché.OtherComp: composant ayant créé l'impact (dans ce cas, le composant de collision du projectile).NormalImpulse: impulsion normale de l'impact.Hit: référenceFHitResultqui contient des données supplémentaires sur l'événement de collision, comme la durée, la distance et l'emplacement.
C++// Called when the projectile collides with an object UFUNCTION() void OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);Dans
FirstPersonProjectile.cpp, implémentez la fonctionOnHit()définie dans votre fichier d'en-tête. DansOnHit(), dans une instructionif, vérifiez les points suivants :OtherActorn'est pas nul et n'est pas le projectile proprement dit.OtherCompn'est pas nul et simule également la physique.
C++void AFirstPersonProjectile::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit) { // Only add impulse and destroy projectile if we hit a physics object if ((OtherActor != nullptr) && (OtherActor != this) && (OtherComp != nullptr) && OtherComp->IsSimulatingPhysics()) { } }Cela permet de vérifier que le projectile a touché un objet qui n'était pas lui-même et qui simulait la physique.
Dans l'instruction
if, utilisezAddImpulseAtLocation()pour ajouter une impulsion au composantOtherComp.Transmettez à cette fonction la vitesse du projectile multipliée par l'attribut
PhysicsForceet appliquez-la à l'emplacement de l'acteur de projectile.C++void AFirstPersonProjectile::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit) { // Only add impulse and destroy projectile if we hit a physics object if ((OtherActor != nullptr) && (OtherActor != this) && (OtherComp != nullptr) && OtherComp->IsSimulatingPhysics()) { // --- New Code Start --- OtherComp->AddImpulseAtLocation(GetVelocity() * PhysicsForce, GetActorLocation()); // --- New Code End --- } }AddImpulseAtLocation()est une fonction physique de l'Unreal Engine qui applique une force (impulsion) instantanée à un objet physique simulé à un emplacement spécifique dans l'espace du monde. Elle est utile pour simuler un impact, par exemple une explosion qui projette un objet, une balle qui frappe un objet ou une porte qui s'ouvre brusquement.Dans la mesure où ce projectile a touché un autre acteur, supprimez le projectile de la scène en appelant
Destroy().
Votre fonction OnHit() complète doit ressembler à ce qui suit :
void AFirstPersonProjectile::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
// Only add impulse and destroy projectile if we hit a physics
if ((OtherActor != nullptr) && (OtherActor != this) && (OtherComp != nullptr) && OtherComp->IsSimulatingPhysics())
{
OtherComp->AddImpulseAtLocation(GetVelocity() * PhysicsForce, GetActorLocation());
Destroy();
}
}Ajouter les composants de maillage, de mouvement et de collision du projectile
Vous devez ensuite ajouter un maillage statique, une logique de déplacement de projectile et une sphère de collision à votre projectile, puis déterminer le mode de déplacement du projectile.
Pour ajouter ces composants à votre projectile, procédez comme suit :
Dans
FirstPersonProjectile.h, dans la sectionpublic, déclarez unTObjectPtrdans unUStaticMeshComponentappeléProjectileMesh. Il s'agit du maillage statique du projectile dans le monde.Attribuez-lui une macro
UPROPERTY()avecEditAnywhere,BlueprintReadOnlyetCategory = “Projectile | Mesh“.C++// Mesh of the projectile in the world UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Projectile | Mesh") TObjectPtr<UStaticMeshComponent> ProjectileMesh;Dans la section
protected, déclarez :Un
TObjectPtrdans unUSphereComponentappeléCollisionComponent.Un
TObjectPtrdans unUProjectileMovementComponentappeléProjectileMovement.
Attribuez à ces deux composants une macro
UPROPERTY()avecVisibleAnywhere,BlueprintReadOnlyetCategory = "Projectile | Components".C++// Sphere collision component UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Projectile | Components") TObjectPtr<USphereComponent> CollisionComponent; // Projectile movement component UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Projectile | Components") TObjectPtr<UProjectileMovementComponent> ProjectileMovement;Le
ProjectileMovementComponentgère la logique de déplacement pour vous. Il calcule la direction que doit parcourir l'acteur parent en fonction de la vitesse, de la gravité et d'autres variables. Ensuite, pendant letick, il applique le déplacement au projectile.Dans
FirstPersonProjectile.cpp, dans la fonction de constructeurAFirstPersonProjectile(), créez des sous-objets par défaut pour les composants de maillage, de collision et de déplacement du projectile. Ensuite, reliez le maillage du projectile au composant de collision.C++// Sets default values AFirstPersonProjectile::AFirstPersonProjectile() { // 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; // --- New Code Start --- // Use a simple sphere as the collision representation CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("CollisionComponent")); check(CollisionComponent != nullptr);Appelez
InitSphereRadius()pour initialiser la taille duCollisionComponent.C++// Sets default values AFirstPersonProjectile::AFirstPersonProjectile() { // 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; // Use a simple sphere as the collision representation CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("CollisionComponent")); check(CollisionComponent != nullptr);Définissez le nom du profil de collision du composant de collision sur
"Projectile"en utilisantBodyInstance.SetCollisionProfileName().C++CollisionComponent->InitSphereRadius(5.0f); // --- New Code Start --- // Set the collision profile to the "Projectile" collision preset CollisionComponent->BodyInstance.SetCollisionProfileName("Projectile"); // --- New Code End ---Dans l'Unreal Editor, vos profils de collision sont stockés sous Paramètres du projet > Moteur > Collision, et vous pouvez définir jusqu'à 18 profils de collision personnalisés à utiliser dans le code. Le comportement par défaut de ce profil de collision "Projectile" est défini sur Block. Il crée des collisions sur tout objet avec lequel il entre en collision.
Vous avez déjà précédemment défini une fonction
OnHit()pour qu'elle s'active lorsque le projectile touche un objet, mais vous devez disposer d'un mode de notification indiquant quand cette collision se produit. Pour ce faire, utilisez la macroAddDynamic()pour abonner une fonction àOnComponentHitEventdansCollisionComponent. Transmettez à cette macro la fonctionOnHit().C++CollisionComponent->InitSphereRadius(5.0f); // Set the collision profile to the "Projectile" collision preset CollisionComponent->BodyInstance.SetCollisionProfileName("Projectile"); // --- New Code Start --- // Set up a notification for when this component hits something blocking CollisionComponent->OnComponentHit.AddDynamic(this, &AFirstPersonProjectile::OnHit); // --- New Code End ---Définissez le
CollisionComponenten tant queRootComponentdu projectile et en tant queUpdatedComponentdu composant de déplacement à suivre.C++CollisionComponent->InitSphereRadius(5.0f); // Set the collision profile to the "Projectile" collision preset CollisionComponent->BodyInstance.SetCollisionProfileName("Projectile"); // Set up a notification for when this component hits something blocking CollisionComponent->OnComponentHit.AddDynamic(this, &AFirstPersonProjectile::OnHit); // --- New Code Start ---Initialisez le composant
ProjectileMovementavec les valeurs suivantes :InitialSpeed: vitesse initiale du projectile lorsqu'il apparaît. Définissez cette option sur3000.0f.MaxSpeed: vitesse maximale du projectile. Définissez cette option sur3000.0f.bRotationFollowVelocity: indique si l'objet doit pivoter pour suivre la direction de sa vitesse. Par exemple, la façon dont un avion en papier plonge vers le haut et vers le bas lorsqu'il monte et descend. Définissez cette option surtrue.bShouldBounce: indique si le projectile doit rebondir sur les obstacles. Définissez cette option surtrue.Bounciness: Quelle part de la vitesse est conservée après une collision, les valeurs inférieures entraînant une plus grande perte d'énergie pour le projectile. Définissez cette valeur sur0.4f.Friction : Détermine la part de la vitesse tangentielle (latérale) conservée après l'impact. Définissez cette valeur sur
0.8f.
C++// Set as root component and UpdatedComponent RootComponent = CollisionComponent; ProjectileMovement->UpdatedComponent = CollisionComponent; // --- New Code Start --- ProjectileMovement->InitialSpeed = 3000.f; ProjectileMovement->MaxSpeed = 3000.f; ProjectileMovement->bRotationFollowsVelocity = true; ProjectileMovement->bShouldBounce = true; ProjectileMovement->Bounciness = 0.2f;
Définir la durée de vie du projectile
Par défaut, le projectile disparaît quelques secondes après avoir été lancé. Cependant, une fois que vous avez dérivé vos blueprints de projectiles dans l'éditeur, vous pouvez essayer de modifier ou de supprimer ce délai par défaut pour remplir votre niveau de fléchettes en mousse.
Pour faire disparaître le projectile au bout de quelques secondes, procédez comme suit :
Dans
FirstPersonProjectile.h, dans la sectionpublic, déclarez un float appeléProjectileLifespan.Attribuez-lui une macro
UPROPERTY()avecEditAnywhere,BlueprintReadOnlyetCategory = "Projectile | Lifespan".Il s'agit de la durée de vie du projectile, en secondes.
C++// Despawn after 5 seconds by default UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Projectile | Lifespan") float ProjectileLifespan = 5.0f;Dans
FirstPersonProjectile.cpp, à la fin de la fonction de constructeurAFirstPersonProjectile(), définissez l'optionInitialLifeSpandu projectile surProjectileLifespanpour qu'il disparaisse au bout de cinq secondes.C++ProjectileMovement->UpdatedComponent = CollisionComponent; ProjectileMovement->InitialSpeed = 3000.f; ProjectileMovement->MaxSpeed = 3000.f; ProjectileMovement->bRotationFollowsVelocity = true; ProjectileMovement->bShouldBounce = true; ProjectileMovement->Bounciness = 0.2f; ProjectileMovement->Friction = 0.8f; // --- New Code Start --- // Disappear after 5.0 seconds by default.La propriété
InitialLifeSpanest héritée de AActor. Il s'agit d'un float qui détermine la durée de vie d'un acteur avant de disparaître. Une valeur de0signifie que l'acteur reste en vie jusqu'à la fin de la partie.
Votre FirstPersonProjectile.h complet doit ressembler à ce qui suit :
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "FirstPersonProjectile.generated.h"
class UProjectileMovementComponent;
class USphereComponent;
Votre AFirstPersonProjectile.cpp complet devrait ressembler à ce qui suit :
// Copyright Epic Games, Inc. All Rights Reserved.
#include "FirstPersonProjectile.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Components/SphereComponent.h"
// Sets default values
AFirstPersonProjectile::AFirstPersonProjectile()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
Obtenir la direction de caméra du personnage
Les projectiles doivent apparaître depuis le lance-fléchettes proprement dit. Vous devrez donc effectuer certains calculs pour déterminer l'emplacement du lance-fléchettes et la direction dans laquelle il est orienté. Dans la mesure où le lance-fléchettes est associé au personnage joueur, ces valeurs vont changer en fonction de la position du personnage et de la direction dans laquelle il regarde.
Votre personnage à la première personne contient certaines des informations de position dont vous avez besoin pour lancer une fléchette. Commencez par modifier votre classe de personnage pour capturer ces informations avec un tracé de ligne et renvoyer le résultat.
Pour utiliser un tracé afin d'obtenir les informations nécessaires sur votre personnage, procédez comme suit :
Dans VS, ouvrez les fichiers
.het.cppde votre personnage.Dans le fichier
.h, dans la sectionpublic, déclarez une nouvelle fonction appeléeGetCameraTargetLocation()qui renvoie unFVector. Cette fonction renvoie la position dans le monde que le personnage regarde.C++// Returns the location in the world the character is looking at UFUNCTION() FVector GetCameraTargetLocation();Dans le fichier
.cppde votre personnage, implémentez la fonctionGetCameraTargetLocation(). Commencez par déclarer unFVectornomméTargetPositionà renvoyer.C++FVector AAdventureCharacter::GetCameraTargetLocation() { // The target position to return FVector TargetPosition; }Créez un pointeur vers
UWorlden appelantGetWorld().C++FVector AAdventureCharacter::GetCameraTargetLocation() { // The target position to return FVector TargetPosition; // --- New Code Start --- UWorld* const World = GetWorld(); // --- New Code End --- }Ajoutez une instruction
ifpour vérifier que lemonden’est pas nul. Dans l'instructionif, déclarez unFHitResultappeléHit.C++FVector AAdventureCharacter::GetCameraTargetLocation() { // The target position to return FVector TargetPosition; UWorld* const World = GetWorld(); // --- New Code Start --- if (World != nullptr) {FHitResultest une structure dans l'UE qui stocke des informations sur le résultat d'une requête de collision, y compris l'acteur ou le composant qui a été touché et l'emplacement de l'impact.Pour trouver le point vers lequel votre personnage regarde, vous allez simuler un tracé de ligne le long du vecteur que le personnage regarde vers un point distant. Si le tracé de ligne entre en collision avec un objet, vous savez dans quelle direction le personnage regarde.
Dans l'instruction if, déclarez deux valeurs
const FVectorappeléesTraceStartetTraceEnd:Définissez
TraceStartsur l'emplacement duFirstPersonCameraComponent.Définissez
TraceEndsurTraceStart, plus le vecteur avant du composant de caméra multiplié par une valeur très élevée. Cela garantit que le tracé ira suffisamment loin pour entrer en collision avec la plupart des objets de votre monde, à condition que votre personnage ne regarde pas vers le ciel. (Si le personnage regarde vers le ciel,TraceEndest le point final du tracé de ligne.)C++if (World != nullptr) { // The result of the line trace FHitResult Hit; // --- New Code Start --- // Simulate a line trace from the character along the vector they're looking down const FVector TraceStart = FirstPersonCameraComponent->GetComponentLocation(); const FVector TraceEnd = TraceStart + FirstPersonCameraComponent->GetForwardVector() * 10000.0; // --- New Code End ---
Appelez
LineTraceSingleByChannel()à partir deUWorldpour simuler le tracé. Transmettez-luiHit,TraceStart,TraceEndet unECollisionChannel::ECC_Visibility.Cela simule un tracé de ligne de
TraceStartversTraceEnd, qui entre en collision avec des objets visibles et stocke le résultat du tracé dansHit.ECollisionChannel::ECC_Visibilityest le canal à utiliser pour le tracé, et ces canaux déterminent les types d'objets que votre tracé doit tenter de toucher. UtilisezECC_Visibilitypour les contrôles de caméra en ligne de mire.C++if (World != nullptr) { // The result of the line trace FHitResult Hit; // Simulate a line trace from the character along the vector they're looking down const FVector TraceStart = FirstPersonCameraComponent->GetComponentLocation(); const FVector TraceEnd = TraceStart + FirstPersonCameraComponent->GetForwardVector() * 10000.0; // --- New Code Start ---La valeur
Hitcontient désormais des informations sur le résultat de l'impact, notamment la position et l'orientation de l'impact. Elle indique également si l'impact est dû à une collision d'objet. Le point d’impact (ou le point d’arrivée de la ligne de tracé) correspond à l'emplacement cible de la caméra à renvoyer.Utilisez un opérateur temporaire pour définir
TargetPositionsurHit.ImpactPointsi l'impact est un tir bloquant, ou surHit.TraceEndsi ce n'est pas le cas. Ensuite, renvoyezTargetPosition.C++if (World != nullptr) { // The result of the line trace FHitResult Hit; // Simulate a line trace from the character along the vector they're looking down const FVector TraceStart = FirstPersonCameraComponent->GetComponentLocation(); const FVector TraceEnd = TraceStart + FirstPersonCameraComponent->GetForwardVector() * 10000.0; // Simulate a line trace and save result in Hit
Votre fonction GetCameraTargetLocation() complète doit ressembler à ce qui suit :
FVector AAdventureCharacter::GetCameraTargetLocation()
{
// The target position to return
FVector TargetPosition;
UWorld* const World = GetWorld();
if (World != nullptr)
{
// The result of the line trace
Faire apparaître des projectiles avec DartLauncher::Use()
Maintenant que vous avez un moyen de savoir dans quelle direction le personnage regarde, vous pouvez implémenter le reste de la logique du projectile dans la fonction Use() de votre lance-fléchettes. Vous allez obtenir l'emplacement et la direction dans lesquels lancer le projectile, puis le faire apparaître.
Pour obtenir l'emplacement et la rotation auxquels le projectile doit apparaître, procédez comme suit :
Dans
DartLauncher.h, en haut du fichier, ajoutez une déclaration avancée pourAFirstPersonProjectile.Dans la section
public, déclarez une propriétéTSubclassOf<AFirstPersonProjectile>nomméeProjectileClass. Il s'agit du projectile généré par le lance-fléchettes. Attribuez-lui la macroUPROPERTY()avecEditAnywhereetCategory = "Projectile".DartLauncher.hdoit maintenant ressembler à ce qui suit :C++// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "EquippableToolBase.h" #include "DartLauncher.generated.h" class AFirstPersonProjectile;DartLauncher.cpp, ajoutez des déclarations d'inclusion pour :”Kismet/KismetMathLibrary.h”. Les calculs liés aux projectiles peuvent être complexes, et ce fichier comprend plusieurs fonctions que vous utiliserez pour lancer des projectiles."FirstPersonProjectile.h""EnhancedInputComponent.h"
C++#include "Tools/DartLauncher.h" #include "FirstPersonProjectile.h" #include "Kismet/KismetMathLibrary.h" #include "EnhancedInputComponent.h" #include "AdventureCharacter.h"Dans l'implémentation de
Use()de DartLauncher, après le message de débogage :Obtenez le
UWorlden appelantGetWorld().Ajoutez une instruction
ifpour vérifier que lemondeet laclasse Projectilene sont pas nuls.Dans l'instruction
if, obtenez la position vers laquelle le personnage regarde en appelantOwningCharacter->GetCameraTargetLocation().
C++void ADartLauncher::Use() { GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Using the dart launcher!")); UWorld* const World = GetWorld(); if (World != nullptr && ProjectileClass != nullptr) { FVector TargetPosition = OwningCharacter->GetCameraTargetLocation(); }Les projectiles devraient apparaître depuis l'outil que le personnage tient, mais pas depuis le centre de l'objet équipé. Le maillage
SKM_Pistoldu lance-fléchettes est doté d'un connecteur de « Muzzle » (canon) qui permet de définir un point d'apparition précis pour vos fléchettes.Dans l'instruction
if, déclarez un nouveauFVectornomméSocketLocationet définissez-le sur le résultat de l'appel deGetSocketLocation("Muzzle")sur leToolMeshComponent.C++if (World != nullptr && ProjectileClass != nullptr) { // Get the direction of the player camera FVector TargetPosition = OwningCharacter->GetCameraTargetLocation(); // --- New Code Start --- // Get the correct socket to spawn the projectile from FVector SocketLocation = ToolMeshComponent->GetSocketLocation("Muzzle"); // --- New Code End --- }Déclarez un
FRotatornomméSpawnRotation. Il s'agit de la rotation (valeurs de tangage, de lacet et de roulis) du projectile lorsqu'il apparaît.Définissez cette valeur sur le résultat de l'appel de
FindLookAtRotation()depuis la bibliothèque de calculs Kismet, en transmettant les valeursSocketLocationetTargetPositionque vous avez obtenues du personnage joueur.C++if (World != nullptr && ProjectileClass != nullptr) { // Get the direction of the player camera FVector TargetPosition = OwningCharacter->GetCameraTargetLocation(); // Get the correct socket to spawn the projectile from FVector SocketLocation = ToolMeshComponent->GetSocketLocation("Muzzle"); // --- New Code Start --- // Get projectile's rotation as it spawns so we know in what direction to apply an offsetFindLookAtRotationcalcule et renvoie la rotation dont vous avez besoin àSocketLocationpour faire face àTargetPosition.Déclarez un
FVectornomméSpawnLocationet définissez-le sur le résultat de l'ajout deSocketLocationet du vecteur avant deSpawnRotationmultiplié par10,0.Le connecteur de canon ne se trouve pas tout à fait à l'avant du lance-fléchettes. Vous devrez donc multiplier le vecteur par un décalage pour que le projectile soit tiré de l'emplacement correct.
C++if (World != nullptr && ProjectileClass != nullptr) { // Get the direction of the player camera FVector TargetPosition = OwningCharacter->GetCameraTargetLocation(); // Get the correct socket to spawn the projectile from FVector SocketLocation = ToolMeshComponent->GetSocketLocation("Muzzle"); // Get the rotation of the projectile as it spawns so we know in what direction to apply an offset FRotator SpawnRotation = UKismetMathLibrary::FindLookAtRotation(SocketLocation, TargetPosition);
Maintenant que vous avez déterminé l'emplacement et la rotation, vous pouvez faire apparaître le projectile.
Pour faire apparaître un projectile, procédez comme suit :
Toujours dans la fonction
Use(), dans l'instructionif, déclarez uneFActorSpawnParametersnomméeActorSpawnParams. Cette classe contient des informations sur l'emplacement et le mode d'apparition de l'acteur.C++void ADartLauncher::Use() { GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Using the dart launcher!")); UWorld* const World = GetWorld(); if (World != nullptr && ProjectileClass != nullptr) { // Get the direction of the player camera FVector TargetPosition = OwningCharacter->GetCameraTargetLocation();Définissez la valeur
SpawnCollisionHandlingOverridedansActorSpawnParamssurESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding.C++//Set Spawn Collision Handling Override FActorSpawnParameters ActorSpawnParams; // --- New Code Start --- ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding; // --- New Code End ---Cette ligne de code tente de trouver un endroit où faire apparaître le projectile sans qu'il n'entre en collision avec un autre Actor (comme à l'intérieur d'un mur) et ne le fera pas apparaître si aucun emplacement approprié n'est trouvé.
Utilisez
SpawnActor()pour générer le projectile au niveau du canon du lance-fléchettes, en transmettantProjectileClass,SpawnLocation,SpawnRotationetActorSpawnParams.C++ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding; // --- New Code Start --- // Spawn the projectile at the muzzle World->SpawnActor<AFirstPersonProjectile>(ProjectileClass, SpawnLocation, SpawnRotation, ActorSpawnParams); // --- New Code End ---
Votre fonction Use() complète doit maintenant ressembler à ce qui suit :
void ADartLauncher::Use()
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Using the dart launcher!"));
UWorld* const World = GetWorld();
if (World != nullptr && ProjectileClass != nullptr)
{
FVector TargetPosition = OwningCharacter->GetCameraTargetLocation();
// Get the correct socket to spawn the projectile from
Obtenir un blueprint et une classe de fléchette en mousse
Maintenant que la logique d'apparition est terminée, vous devez créer un véritable projectile ! Pour déclencher votre classe de lance-fléchettes, vous devez disposer d'une sous-classe de AFirstPersonProjectile. Vous devrez donc en créer une dans le code pour l'utiliser dans votre niveau.
Pour obtenir une classe de projectile de fléchette en mousse utilisable dans le jeu, procédez comme suit :
Dans l'Unreal Editor, accédez à Outils > Nouvelle classe C++.
Accédez à Toutes les classes, recherchez et sélectionnez la classe parente Projectile à la première personne, puis nommez la classe
FloatDart. Cliquez sur Créer une classe.Dans VS, laissez ces fichiers tels quels, enregistrez votre code et compilez-le.
Dans ce tutoriel, vous n'implémenterez aucun code de projectile personnalisé au-delà de ce que vous avez défini dans FirstPersonProjectile, mais pouvez modifier la classe FloatDart à votre convenance pour répondre aux besoins de votre projet. Par exemple, vous pourriez essayer de faire en sorte que les fléchettes adhèrent aux objets au lieu de disparaître ou de rebondir.
Pour créer un blueprint de fléchette en mousse, procédez comme suit :
Dans l'Unreal Editor, créez une classe de blueprint à partir de FloatDart et nommez-la
BP_FoamDart. Enregistrez-la dans le dossierFirstPerson/Blueprints/Outils.Une fois le blueprint ouvert, sélectionnez le composant de maillage de projectile et définissez le maillage statique sur
SM_FoamBullet.Le maillage de fléchette en mousse apparaît dans le hublot. Effectuez un zoom avant ou appuyez sur F pour regarder l'orientation du maillage. L'extrémité arrondie est l'avant de la fléchette, et elle doit être orientée vers l'axe X afin que vos fléchettes soient tirées dans la bonne direction (
ProjectileMovementconsidère +X comme étant l'avant).Faire pivoter la fléchette de +90 degrés sur l'axe Z (bleu).
Compilez et enregistrez votre blueprint.
Dans le navigateur de contenu, ouvrez
BP_DartLauncher.Dans son panneau Détails , dans la nouvelle section Projectile , définissez la Classe de projectile sur
BP_FoamDart.Si
BP_FoamDartne s'affiche pas dans la liste, accédez au navigateur de contenu, sélectionnezBP_FoamDart, puis revenez à la propriété Classe de projectile et cliquez sur Utiliser la ressource sélectionnée dans le navigateur de contenu.Cliquez sur Compiler, puis sur Enregistrer.
C'est le moment de vérité ! Enregistrez vos ressources et cliquez sur Jouer. Lorsque le jeu commence, vous pouvez courir vers le lance-fléchettes pour le ramasser. Faites un clic gauche pour faire apparaître un projectile dans le canon du lance-fléchettes et le faire rebondir dans le niveau. Ces projectiles doivent disparaître au bout de 5 secondes et générer une légère force physique sur les objets avec lesquels ils entrent en collision (y compris vous-même !).
Si le lanceur de fléchettes ne tire pas et que vous ne voyez pas le message "Utilisation du lanceur de fléchettes !" message de débogage, assurez-vous d'avoir affecté IA_UseTool à la propriété Utiliser l'action dans le blueprint de votre personnage.
Bonus : ajuster les projectiles et les outils
Implémentez éventuellement ces derniers ajustements pour optimiser l'apparence de votre lanceur et de vos fléchettes.
Mettre les projectiles tombés à plat
Dans FirstPersonProjectile.cpp, lorsque le projectile touche quelque chose, vérifiez si le projectile a touché le sol (une surface horizontale plane) et si c'est le cas, faites-le reposer à plat.
En haut de OnHit(), ajoutez ce code :
// If we hit the ground (mostly-up surface normal), lay the dart flat.
if (FVector::DotProduct(Hit.ImpactNormal, FVector::UpVector) > 0.7f)
{
FRotator Flat = GetActorRotation();
Flat.Pitch = 0.f;
Flat.Roll = 0.f;
SetActorRotation(Flat);
return;
}Suivant
Félicitations ! Vous avez terminé le tutoriel Programmeur à la première personne et avez beaucoup appris en cours de route !
Vous avez implémenté des personnages et des déplacements personnalisés, créé des objets à ramasser et des ressources de données, et même fabriqué des outils à équiper avec leurs propres projectiles. Vous disposez de tous les éléments dont vous avez besoin pour transformer ce projet en fonction de vos envies.
Voici quelques suggestions :
Pouvez-vous enrichir l'inventaire du joueur avec différents types d'objets ? Qu'en est-il des piles d'objets ?
Pouvez-vous combiner des objets à ramasser et des projectiles pour créer des munitions à ramasser ? Que diriez-vous d'implémenter ce système de munitions dans le lance-fléchettes ?
Pourriez-vous développer l'idée d'objets à usage unique pour en faire des objets à usage unique dont il est possible de s'équiper ? Que diriez-vous d'un pack de PV que le joueur pourrait porter sur lui, ou d'une balle qu'il pourrait ramasser et lancer ?
Code complet
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "FirstPersonProjectile.generated.h"
class UProjectileMovementComponent;
class USphereComponent;
// Copyright Epic Games, Inc. All Rights Reserved.
#include "FirstPersonProjectile.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Components/SphereComponent.h"
// Sets default values
AFirstPersonProjectile::AFirstPersonProjectile()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/Character.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "InputActionValue.h"
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AdventureCharacter.h"
#include "InventoryComponent.h"
#include "EquippableToolDefinition.h"
#include "EquippableToolBase.h"
// Sets default values
AAdventureCharacter::AAdventureCharacter()
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "EquippableToolBase.h"
#include "DartLauncher.generated.h"
class AFirstPersonProjectile;
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Tools/DartLauncher.h"
#include "FirstPersonProjectile.h"
#include "Kismet/KismetMathLibrary.h"
#include "EnhancedInputComponent.h"
#include "AdventureCharacter.h"
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "EnhancedInputSubsystems.h" //
#include "Animation/AnimBlueprint.h"
#include "Components/SkeletalMeshComponent.h"
#include "EquippableToolBase.generated.h"