Bevor du anfängst
Stellen Sie sicher, dass Sie die folgenden Ziele im vorherigen Abschnitt, Ihren Charakter ausrüsten, erreicht haben:
Einen respawnenden Abhol-Gegenstand erstellt und Ihrem Level hinzugefügt
Einen ausrüstbaren Dart-Launcher erstellt, den Ihr Charakter halten und benutzen kann
Einfache Projektile
Ihr Charakter kann Ihren Dart-Launcher halten, und Ihr Werkzeug hat Steuerelementbindungen, um ihn zu benutzen, aber er wird seinem Namen nicht ganz gerecht. In diesem Abschnitt werden Sie Projektil-Logik implementieren, damit Pfeile aus dem ausgerüsteten Gegenstand schießen.
Die Unreal Engine hat eine Projektil-Bewegung-Komponente, die Sie einem Actor hinzufügen können, um ihn in ein Projektil zu verwandeln. Sie enthält viele hilfreiche Variablen wie Projektilgeschwindigkeit, Sprungkraft und Schwerkraftskalierung.
Hinsichtlich der Mathematik für die Implementierung von Projektilen müssen Sie verschiedene Dinge bedenken:
Die anfängliche Transformation, Geschwindigkeit und Richtung des Projektils.
Ob Sie Projektile aus der Mitte des Charakters oder dem Werkzeug, mit dem er ausgerüstet ist, spawnen wollen.
Welches Verhalten das Projektil zeigen soll, wenn es mit einem anderen Objekt kollidiert.
Eine Projektil-Basis-Klasse erstellen
Sie erstellen zuerst eine Basis-Projektil-Klasse und legen dann eine Subklasse davon an, um einzigartige Projektile für Ihre Werkzeuge zu erstellen.
Befolgen Sie diese Schritte, um eine Basis-Projektil-Klasse einzurichten:
Gehen Sie im Unreal Editor zu Werkzeuge > Neue C++-Klasse. Wählen Sie Actor als Parent-Klasse aus und benennen Sie die Klasse
FirstPersonProjectile. Klicken Sie auf Klasse erstellen.Gehen Sie in VS zu
FirstPersonProjectile.h. Deklarieren Sie oben in der Datei eineUProjectileMovementComponentund eineUSphereComponent.Du wirst eine einfache Kugel-Komponente verwenden, um Kollisionen zwischen dem Projektil und anderen Objekten (Kreativmodus) zu simulieren.
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;Fügen Sie die Bezeichnet
BlueprintTypeundBlueprint-fähighinzu, um diese Klasse für Blueprints bereitzustellen:C++UCLASS(BlueprintType, Blueprintable) class FIRSTPERSON_API AFirstPersonProjectile : public AActorÖffnen Sie
FirstPersonProjectile.cpp, fügen Sie ganz oben in der Datei eine include-Anweisung für"GameFramework/ProjectileMovementComponent.h"und"Components/SphereComponent.h"hinzu, um die Klassen für die Projektilbewegung und die Kollisionskomponente einzubinden.C++#include "FirstPersonProjectile.h" #include "GameFramework/ProjectileMovementComponent.h" #include "Components/SphereComponent.h" // Sets default values AFirstPersonProjectile::AFirstPersonProjectile()
Projektilverhalten beim Tauftreffen auf Objekte implementieren
Um Ihr Projektil realistischer zu gestalten, können Sie dafür sorgen, dass es eine Kraft (einen Impuls) auf Objekte ausübt, die es trifft. Wenn Sie zum Beispiel auf einen physikfähigen Block schießen, würde das Projektil den Block über den Boden bewegen. Entfernen Sie dann das Projektil nach dem Einschlag, anstatt es seine Standard-Lebensdauer ablaufen zu lassen. Erstellen Sie eine OnHit()-Funktion, um dieses Verhalten zu implementieren.
Befolgen Sie diese Schritte, um das Trefferverhalten des Projektils zu implementieren:
Definiere in
FirstPersonProjectile.himpublic-Abschnitt einefloat-Eigenschaft namensPhysicsForce.Geben Sie ihr ein
UPROPERTY()-Makro mitEditAnywhere,BlueprintReadOnlyundCategory = „Projectile | Physics“.Das ist die ausgeübte Kraft des Projektils, wenn es auf etwas trifft.
C++// The amount of force this projectile imparts on objects it collides with UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Projectile | Physics") float PhysicsForce = 100.0f;Definieren Sie eine
void-FunktionOnHit(). Dies ist eine Funktion von AActor, die aufgerufen wird, wenn der Actor mit einer anderen Komponente oder einem anderen Actor kollidiert. Sie nimmt die folgenden Argumente entgegen:HitComp: Die Komponente, die getroffen wurde.OtherActor: Der Actor, der getroffen wurde.OtherComp: Die Komponente, die den Treffer erstellt hat (in diesem Fall die CollisionComponent des Projektils).NormalImpulse: Der normale Impuls des Treffers.Hit: EineFHitResult-Referenz, die mehr Daten über das Treffer-Event enthält (z. B. Zeit, Distanz und Ort).
C++// Called when the projectile collides with an object UFUNCTION() void OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);Implementieren Sie in
FirstPersonProjectile.cppdieOnHit()-Funktion, die Sie in Ihrer Header-Datei definiert haben. Prüfen Sie inOnHit()in einerif-Anweisung folgendes:OtherActorist nicht null und ist nicht das Projektil selbst.OtherCompist nicht null und simuliert ebenfalls Physik.
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()) { } }Damit wird geprüft, ob das Projektil ein Objekt getroffen hat, das nicht es selbst war, und zur Physik fähig ist.
Verwenden Sie in der
if-AnweisungAddImpulseAtLocation(), um einen Impuls zurOtherComp-Komponente hinzuzufügen.Übergeben Sie dieser Funktion die Geschwindigkeit des Projektils multipliziert mit der
PhysicsForceund wenden Sie diese am Ort des Projektil-Actors an.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()ist eine Physik-Funktion in der Unreal Engine, die eine momentane Kraft (Impuls) auf ein simuliertes Physik-Objekt an einem spezifischen Ort im Welt-Bereich anwendet. Das ist nützlich, wenn Sie einen Aufprall simulieren möchten, etwa eine Explosion, die etwas fortschleudert, eine Kugel, die ein Objekt trifft, oder eine Tür, die sich öffnet.Da dieses Projektil einen anderen Actor getroffen hat, entfernen Sie das Projektil aus der Szene, indem Sie
Destroy()aufrufen.
Ihre fertige Funktion OnHit() sollte wie folgt aussehen:
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();
}
}Mesh, Bewegung und Kollisionskomponenten des Projektils hinzufügen
Fügen Sie als Nächstes ein statisches Mesh, Projektilbewegungslogik und eine Kollisionskugel zu Ihrem Projektil hinzu und definieren Sie, wie sich das Projektil bewegen soll.
Um diese Komponenten zu deinem Projektil hinzuzufügen, befolge diese Schritte:
Deklariere in
FirstPersonProjectile.himöffentlicher-Abschnitt, deklarieren Sie einenTObjectPtrzu einerUStaticMeshComponentmit dem NamenProjectileMesh. Dies ist das statische Mesh des Projektils in der Welt.Geben Sie ihm ein
UPROPERTY()-Makro mitEditAnywhere,BlueprintReadOnlyundCategory = “Projectile | Mesh“.C++// Mesh of the projectile in the world UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Projectile | Mesh") TObjectPtr<UStaticMeshComponent> ProjectileMesh;Deklarieren Sie im
protected-Abschnitt:Ein
TObjectPtrzu einerUSphereComponentnamensCollisionComponent.Ein
TObjectPtrzu einerUProjectileMovementComponentnamensProjectileMovement.
Geben Sie beiden ein
UPROPERTY()-Makro mitVisibleAnywhere,BlueprintReadOnlyundCategory = „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;Der
ProjectileMovementComponentübernimmt die Bewegungslogik für dich. Sie berechnet basierend auf Geschwindigkeit, Schwerkraft und anderen Variablen, wohin sich der Parent-Actor bewegen soll. Dann wendet sie während desTicksdie Bewegung auf das Projektil an.Erstellen Sie in
FirstPersonProjectile.cppin der Constructor-FunktionAFirstPersonProjectile()Standard-Unterobjekte für Mesh, Kollision und Projektilbewegungskomponenten des Projektils. Koppeln Sie dann das Projektil-Mesh an der Kollisionskomponente an.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);Rufen Sie
InitSphereRadius()auf, um die Größe vonCollisionComponentzu initialisieren.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);Legen Sie den Name des Kollisionsprofils der Kollisionskomponente auf
„Projectile“fest, indem SieBodyInstance.SetCollisionProfileName()verwenden.C++CollisionComponent->InitSphereRadius(5.0f); // --- New Code Start --- // Set the collision profile to the "Projectile" collision preset CollisionComponent->BodyInstance.SetCollisionProfileName("Projectile"); // --- New Code End ---Im Unreal Editor werden Ihre Kollisionsprofile unter Projekt-Einstellungen > Engine > Kollision gespeichert, und Sie können bis zu 18 benutzerdefinierte Kollisionsprofile definieren, die Sie im Code verwenden können. Das Standardverhaltenn dieses „Projektil“-Kollisionsprofils ist Block und erstellt Kollisionen für jedes Objekt, mit dem es kollidiert.
Sie haben vorhin eine
OnHit()-Funktion definiert, die aktiviert wird, wenn das Projektil etwas trifft, aber Sie benötigen eine Möglichkeit, um zu melden, wenn es zu einer solchen Kollision kommt. Verwenden Sie dazu dasAddDynamic()-Makro, um eine Funktion beiOnComponentHitEventinCollisionComponentzu abonnieren. Übergeben Sie diesem Makro dieOnHit()-Funktion.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 ---Legen Sie die
CollisionComponentals dieRootComponentdes Projektils und alsUpdatedComponentfür die Bewegung fest, die verfolgt werden soll.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 ---Initialisieren Sie die
ProjectileMovement-Komponente mit den folgenden Werten:InitialSpeed:Die anfängliche Geschwindigkeit des Projektils, wenn es spawnt. Setzen Sie dies auf3000.0f.MaxSpeed: Die maximale Geschwindigkeit des Projektils. Setzen Sie dies auf3000.0f.bRotationFollowVelocity: Ob das Objekt rotieren soll, um die Richtung seiner Geschwindigkeit zu erreichen. Zum Beispiel, wie ein Papierflieger beim Steigen und Fallen hoch- und runtergeht. Setzen Sie dies auf“True.bShouldBounce: Ob das Projektil von Hindernissen abprallen soll. Setzen Sie dies auf“True.Bounciness: Wie viel Geschwindigkeit nach einer Kollision erhalten bleibt, wobei niedrigere Werte dazu führen, dass das Projektil mehr Energie verliert. Setzen Sie diesen Wert auf0.4f.Reibung: Wie viel tangentiale (seitliche) Geschwindigkeit nach dem Aufprall erhalten bleibt. Setze diesen Wert auf
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;
Lebensdauer des Projektils festlegen
Standardmäßig lassen Sie das Projektil einige Sekunden nach dem Abfeuern verschwinden. Allerdings können Sie beim Ableiten Ihrer Projektil-Blueprints im Editor damit experimentieren, diese Zeit zu ändern oder zu entfernen, um Ihr Level mit Schaumstoffdarts zu füllen!
Um das Projektil nach einigen Sekunden verschwinden zu lassen, befolgen Sie diese Schritte:
Deklarieren Sie in
FirstPersonProjectile.himpublic-Abschnitt einen Float namensProjectileLifespan.Geben Sie ihm ein
UPROPERTY()-Makro mitEditAnywhere,BlueprintReadOnlyundCategory = „Projectile | Lifespan“.Dies ist die Lebensdauer des Projektils in Sekunden.
C++// Despawn after 5 seconds by default UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Projectile | Lifespan") float ProjectileLifespan = 5.0f;Legen Sie in
FirstPersonProjectile.cppam Ende derAFirstPersonProjectile()-Constructor-Funktion dieInitialLifeSpandes Projektils aufProjectileLifespanfest, damit es nach fünf Sekunden verschwindet.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.InitialLifeSpanist eine Eigenschaft, die von AActor geerbt wird. Ein Float-Wert bestimmt, wie lange der Actor lebt, bevor er stirbt. Ein Wert von0bedeutet, dass der Actor so lange lebt, bis das Spiel stoppt.
Ihre fertige FirstPersonProjectile.h sollte wie folgt aussehen:
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "FirstPersonProjectile.generated.h"
class UProjectileMovementComponent;
class USphereComponent;
Ihre fertige AFirstPersonProjectile.cpp sollte wie folgt aussehen:
// 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.
Charakter-Kamerarichtung abrufen
Projektile sollen aus dem Dart-Launcher selbst spawnen. Sie müssen also ein wenig rechnen, um herauszufinden, wo der Launcher ist und wohin er zeigt. Da der Launcher am Spieler-Charakter angekoppelt ist, ändern sich diese Werte basierend auf, wo sich der Charakter befindet und wohin er schaut.
Ihr First-Person-Charakter enthält einige der Positionsinformationen, die Sie zum Start eines Darts benötigen. Beginnen Sie also damit, Ihre Charakter-Klasse zu modifizieren, damit sie diese Informationen mit einem Linien-Trace erfasst und das Ergebnis zurückgibt.
Befolgen Sie diese Schritte, um mit einem Trace die benötigten Informationen von Ihrem Charakter abzurufen:
Öffnen Sie in VS die
.h- und.cpp-Dateien Ihres Charakters.Deklarieren Sie in der
.h- Datei impublic-Abschnitt eine neue Funktion namensGetCameraTargetLocation(), die einenFVectorzurückgibt. Diese Funktion gibt den Ort in der Welt zurück, zu dem der Charakter blickt.C++// Returns the location in the world the character is looking at UFUNCTION() FVector GetCameraTargetLocation();In der Datei
.cppIhres Charakters die FunktionGetCameraTargetLocation(). Beginnen Sie mit der Deklaration einesFVectornamensTargetPosition, der zurückgegeben werden soll.C++FVector AAdventureCharacter::GetCameraTargetLocation() { // The target position to return FVector TargetPosition; }Erstellen Sie einen Zeiger auf
UWorld, indem SieGetWorld()aufrufen.C++FVector AAdventureCharacter::GetCameraTargetLocation() { // The target position to return FVector TargetPosition; // --- New Code Start --- UWorld* const World = GetWorld(); // --- New Code End --- }Fügen Sie eine
if-Anweisung hinzu, um zu prüfen, obWeltnicht null ist. Deklarieren Sie in derif-Anweisung einFHitResultnamensHit.C++FVector AAdventureCharacter::GetCameraTargetLocation() { // The target position to return FVector TargetPosition; UWorld* const World = GetWorld(); // --- New Code Start --- if (World != nullptr) {FHitResultist eine Struktur in der UE, die Informationen über das Ergebnis einer Kollisionsabfrage speichert, darunter den Actor oder die Komponente, die getroffen wurden, und wo sie getroffen wurden.Um den Punkt zu finden, den der Charakter betrachtet, simulieren Sie einen Linien-Trace entlang des Vektors, den der Charakter betrachtet, zu einem entfernten Punkt. Wenn der Linien-Trace mit einem Objekt kollidiert, wissen Sie, wohin in der Welt Ihr Charakter blickt.
Deklarieren Sie in der if-Anweisung zwei
const FVector-Werte mit den NamenTraceStartundTraceEnd:Setzen Sie
TraceStartauf die Position derFirstPersonCameraComponent.Setzen Sie
TraceEndaufTraceStartplus den Vorwärtsvektor der Kamera-Komponente, multipliziert mit einer sehr großen Zahl. Dies stellt sicher, dass der Trace weit genug geht, um mit den meisten Objekten in Ihrer Welt zu kollidieren, solange Ihr Charakter nicht in den Himmel blickt. (Wenn der Charakter in den Himmel blickt, istTraceEndder Endpunkt des Linien-Trace.)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 ---
Rufen Sie
LineTraceSingleByChannel()aus derUWorldauf, um den Trace zu simulieren. Übergeben SieHit,TraceStart,TraceEndund einECollisionChannel::ECC_Visibility.Das simuliert eine Linien-Trace von
TraceStartbisTraceEnd, der mit allen sichtbaren Objekten kollidiert, und speichert das Ergebnis des Trace inHit.ECollisionChannel::ECC_Visibilityist der Kanal für das Tracing, und diese Kanäle definieren, welche Objekttypen Ihr Trace treffen soll. Nutzen SieECC_Visibilityfür Sichtlinie-Kameraprüfungen.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 ---Der
Hit-Wert enthält nun Informationen über das Trefferergebnis, etwa die Position und die Normale des Einschlags. Er weiß auch, ob der Treffer das Ergebnis einer Kollision mit einem Objekt war. Der Ort des Aufschlags (oder der Endpunkt der Trace-Linie) ist die Kameraziel-Position, die zurückgegeben werden soll.Nutzen Sie einen ternären Operator, um
TargetPositionentweder aufHit.ImpactPointzu setzen, wenn der Treffer ein blockierender Treffer war, oder aufHit.TraceEnd, wenn dies nicht der Fall war. Geben sie dann dieTargetPositionzurück.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
Ihre fertige Funktion GetCameraTargetLocation() sollte wie folgt aussehen:
FVector AAdventureCharacter::GetCameraTargetLocation()
{
// The target position to return
FVector TargetPosition;
UWorld* const World = GetWorld();
if (World != nullptr)
{
// The result of the line trace
Projektile mit DartLauncher::Use() spawnen
Nun, da Sie wissen, wohin der Charakter blickt, können Sie die restliche Projektil-Logik in der Use()-Funktion Ihres Dart-Launchers implementieren. Sie rufen den Ort und die Richtung zum Starten des Projektils ab, und spawnen dann das Projektil.
Befolgen Sie diese Schritte, um den Ort und die Drehung abzurufen, an denen das Projektil spawnen soll:
Fügen Sie in
DartLauncher.hoben in der Datei eine Vorwärts-Deklaration fürAFirstPersonProjectilehinzu.Deklarieren Sie im
public-Abschnitt eineTSubclassOf<AFirstPersonProjectile>-Eigenschaft namensProjectileClass. Das ist das Projektil, das der Launcher spawnt. Geben Sie ihm dasUPROPERTY()-Makro mitEditAnywhereundKategorie = "Projectile".DartLauncher.hsollte nun wie folgt aussehen:C++// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "EquippableToolBase.h" #include "DartLauncher.generated.h" class AFirstPersonProjectile;DartLauncher.cpp, fügen Sie eine include-Anweisung für „Kismet/KismetMathLibrary.h“ hinzu.”Kismet/KismetMathLibrary.h”. Die Mathematik von Projektilen kann kompliziert sein, und diese Datei enthält mehrere Funktionen, die du zum Abfeuern von Projektilen verwenden wirst."FirstPersonProjectile.h""EnhancedInputComponent.h"
C++#include "Tools/DartLauncher.h" #include "FirstPersonProjectile.h" #include "Kismet/KismetMathLibrary.h" #include "EnhancedInputComponent.h" #include "AdventureCharacter.h"In der
Use()-Implementierung des DartLauncher, nach der Debug-Nachricht:Rufen Sie
UWorldmitGetWorld()ab.Fügen Sie eine
if-Anweisung ein, um zu prüfen, obWorldund dieProjectileClassnicht null sind.Rufen Sie in der
if-Anweisung die Position ab, zu der der Charakter blickt, indem SieOwningCharacter->GetCameraTargetLocation()aufrufen.
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(); }Projektile sollten aus dem Werkzeug spawnen, das der Charakter hält, aber nicht aus der Mitte des ausgerüsteten Objekts. Das
SKM_Pistol-Mesh des Dart-Launchers hat einen „Mündung“-Socket, den Sie verwenden können, um einen präzisen Spawnpunkt für Ihre Pfeile festzulegen.Deklariere in der
if-Anweisung einen neuenFVectornamensSocketLocationund setze ihn auf das Ergebnis des Aufrufs vonGetSocketLocation(“Muzzle”)auf derToolMeshComponent.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 --- }Deklarieren Sie einen
FRotatormit dem NamenSpawnRotation. Dies ist die Drehung (Nickachsen-, Gierachsen- und Rollachsenwerte) des Projektils, während es spawnt.Stellen Sie dies auf das Ergebnis des Aufrufs von
FindLookAtRotation()aus der kismet-Mathematik-Bibliothek und übergeben SieSocketLocationundTargetPosition, die Sie vom Spieler-Charakter abgerufen haben.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 offsetFindLookAtRotationberechnet die Drehung, die Sie amSocketLocationbenötigen, um sich derTargetPositionzuzuwenden, und gibt diese zurück.Deklarieren Sie einen
FVectornamensSpawnLocationund setzen Sie ihn auf das Ergebnis der Addition vonSocketLocationund dem Vorwärts-Vektor derSpawnRotationmultipliziert mit10,0.Das Mündung-Socket befindet sich nicht ganz vorne am Launcher. Daher müssen Sie den Vektor mit einem Versatz multiplizieren, damit das Projektil vom richtigen Ort abgefeuert wird.
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);
Nachdem Sie jetzt den Ort und die Drehung festgelegt haben, können Sie das Projektil spawnen.
Um ein Projektil zu spawnen, befolgen Sie diese Schritte:
Immer noch in der
Use()-Funktion, in derif-Anweisung, deklarieren Sie eineFActorSpawnParametersnamensActorSpawnParams. Diese Klasse enthält Informationen dazu, wo und wie der Actor gespawnt wird.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();Legen Sie den
SpawnCollisionHandlingOverride-Wert inActorSpawnParamsaufESpawnActorCollisionHandlingMethod::AdjustWennPossibleButDontSpawnWennCollidingfest.C++//Set Spawn Collision Handling Override FActorSpawnParameters ActorSpawnParams; // --- New Code Start --- ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding; // --- New Code End ---Diese Codezeile versucht, einen Ort zu platzieren, an dem der Spawn des Projektils nicht mit einem anderen Actor kollidiert (z. B. in einer Wand), und es erfolgt kein Spawn, wenn kein geeigneter Ort gefunden wird.
Verwenden Sie
SpawnActor(), um das Projektil an der Mündung des Launchers zu spawnen und übergeben SieProjectileClass,SpawnLocation,SpawnRotationundActorSpawnParams.C++ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding; // --- New Code Start --- // Spawn the projectile at the muzzle World->SpawnActor<AFirstPersonProjectile>(ProjectileClass, SpawnLocation, SpawnRotation, ActorSpawnParams); // --- New Code End ---
Deine fertige Funktion Use() sollte jetzt wie folgt aussehen:
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
Eine Foam-Dart-Klasse und einen Blueprint ableiten
Jetzt ist die gesamte Spawn-Logik erledigt und es ist Zeit ein echtes Projektil bauen! Ihre Dart-Launcher -Klasse benötigt eine Subklasse von AFirstPersonProjectile zum Feuern. Also müssen Sie eine im Code erstellen, um sie in Ihrem Level zu verwenden.
Befolgen Sie diese Schritte, um eine Foam-Dart-Projektil-Klasse für die Verwendung im Spiel abzuleiten:
Gehen Sie im Unreal Editor zu Werkzeuge > Neue C++-Klasse.
Gehen Sie zu Alle Klassen, suchen Sie nach First Person Projectile und wählen Sie diese als Parent-Klasse aus. Nennen Sie die Klasse
FoamDart. Klicken Sie auf Klasse erstellen.Lassen Sie diese Dateien in VS unverändert, speichern Sie Ihren Code und kompilieren Sie ihn.
In diesem Tutorial werden Sie keinen benutzerdefiniert Projektil-Code implementieren, der über das hinausgeht, was Sie in FirstPersonProjectile definiert haben. Sie können die FoamDart-Klasse aber selbst modifizieren, um sie an die Anforderungen Ihres Projekts anzupassen. Sie könnten zum Beispiel versuchen, die Projektile an Objekten haften zu lassen, anstatt sie verschwinden oder abprallen zu lassen.
Um einen Foam-Dart-Blueprint zu erstellen, befolgen Sie diese Schritte:
Erstellen Sie im Unreal Editor eine Blueprint-Klasse basierend auf FoamDart und nennen Sie diese
BP_FoamDart. Speichern Sie dies in Ihrem OrdnerFirstPerson/Blueprints/Tools.Bei geöffnetem Blueprint wählst du die Komponente Projectile Mesh und setzt das statische Mesh auf
SM_FoamBullet.Das Schaumstoffpfeil-Mesh erscheint im Viewport. Zoome hinein oder drücke F, um die Ausrichtung des Mesh zu betrachten. Das abgerundete Ende ist die Vorderseite des Pfeils und sollte entlang der X-Achse ausgerichtet sein, damit deine Pfeile in der richtigen Ausrichtung abgefeuert werden (
ProjectileMovementgeht davon aus, dass +X die Vorwärtsrichtung ist).Drehen Sie den Dart auf der Z-Achse (blau) um +90 Grad.
Kompilieren und speichern Sie Ihren Blueprint.
Öffnen Sie im Inhaltsbrowser den
BP_DartLauncher.Legen Sie in seinem Details -Panel im neuen Projektil - Bereich die Projektilklasse auf
BP_FoamDartfest.Wenn Sie
BP_FoamDartnicht in der Liste sehen, gehen Sie zum Inhaltsbrowser, wählen SieBP_FoamDartaus, kehren Sie dann zur Eigenschaft Projektilklasse zurück und klicken Sie auf Ausgewähltes Asset aus dem Inhaltsbrowser verwenden.Klicke auf Kompilieren und Speichern.
Es ist Zeit für die große Enthüllung. Speichern Sie Ihre Assets und klicken auf Spielen. Wenn das Spiel beginnt, können Sie zum Dart-Launcher laufen, um ihn aufzuheben. Ein Druck auf die linke Maustaste spawnt ein Projektil aus der Mündung des Launchers und lässt es durch das Level hüpfen! Diese Projektile sollten nach fünf Sekunden verschwinden und auf die Objekte, mit denen sie kollidieren, eine geringe physikalische Kraft ausüben (Sie eingeschlossen!).
Wenn der Dart-Launcher nicht feuert und Sie die Meldung „Dart-Launcher wird verwendet!“ nicht sehen, Debug-Nachricht, stelle sicher, dass du IA_UseTool der Eigenschaft Use Action in deinem Charakter-Blueprint zugewiesen hast.
Bonus: Projektile und Tools anpassen
Implementieren Sie optional diese letzten Anpassungen, damit Ihr Dart-Launcher und die Darts optimal aussehen.
Gefallene Projektile flach liegen lassen
Überprüfen Sie in FirstPersonProjectile.cpp, wenn das Projektil auf etwas trifft, ob das Projektil auf den Boden getroffen hat (eine flache horizontale Oberfläche) und wenn ja, lassen Sie es flach liegen.
Oben in OnHit() füge diesen Code hinzu:
// 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;
}Als Nächstes
Glückwunsch! Sie haben das Tutorial des First-Person-Programmierer-Tracks abgeschlossen und dabei viel gelernt!
Sie haben benutzerdefinierte Charaktere und Bewegung implementiert, Abholpunkte und Daten-Assets erstellt und sogar ausrüstbare Werkzeuge mit eigenen Projektilen entwickelt! Sie haben alles, was Sie brauchen, um aus diesem Projekt etwas ganz Besonderes zu machen.
Hier sind ein paar Vorschläge:
Können Sie das Inventar der Spieler mit verschiedenen Arten von Gegenständen erweitern? Was ist mit Stapeln von Gegenständen?
Können Sie Abholpunkte und Projektile kombinieren, um aufnehmbare Munition zu erstellen? Wie wäre es, dieses Munitionssystem im Dart-Launcher zu implementieren?
Können Sie die Idee von Verbrauchsgegenständen zu ausrüstbaren Verbrauchsgegenständen entwickeln? Wie wäre es mit einem Gesundheitspaket, das der Spieler bei sich trägt, oder einem Ball, den er aufheben und werfen kann?
Vollständiger Code
// 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"