開始する前に
前のセクションの「キャラクターを装備する」で次の目標が完了していることを確認してください。
リスポーンするピックアップ アイテムを作成し、レベルに追加する
キャラクターが持って使用できる装備可能なダーツ ランチャーを作成する
基本の発射物
キャラクターはダーツ ランチャーを保持でき、ツールにはそれを使用するためのコントロール バインディングがありますが、名付けただけでそのとおりに動くわけではありません。 このセクションでは、発射物ロジックを実装し、装備したアイテムからダーツを発射するようにします。
Unreal Engine には、アクタに追加して発射物に変えることができる発射物移動コンポーネント クラスがあります。 これには、発射物の速度、弾性、重力スケールなど、多くの便利な変数が含まれています。
発射物の実装による演算を処理するには、以下のようなことについて検討する必要があります。
発射物の初期トランスフォーム、速度、方向。
キャラクターの中心から発射物をスポーンするか、装備しているツールの中心からスポーンするか。
発射物が別のオブジェクトと衝突したときに、どのような動作をさせるか。
発射物の基本クラスを作成する
まず、基本となる発射物クラスを作成し、次にサブクラス化してツール用の固有の発射物を作成します。
基本となる発射物クラスの設定を開始するには、次の手順を実行します。
Unreal Editor で、[Tools (ツール)] > [New C++ Class (新規C++クラス)] に移動します。 親クラスとして [Actor (アクタ)] を選択し、クラスに「
FirstPersonProjectile」という名前を付けます。 [Create Class (クラスを作成)] をクリックします。VS で、「
FirstPersonProjectile.h」 に移動します。 ファイルの先頭で、UProjectileMovementComponentとUSphereComponentの両方を前方宣言します。発射物と他のオブジェクトとの間の当たり判定をシミュレートするために、シンプルなスフィア コンポーネントを使用します。
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;BlueprintType指定子とBlueprintable指定子を追加し、このクラスをブループリントに公開します。C++UCLASS(BlueprintType, Blueprintable) class FIRSTPERSON_API AFirstPersonProjectile : public AActorFirstPersonProjectile.cppを開きます。ファイルの先頭に、"GameFramework/ProjectileMovementComponent.h"および"Components/SphereComponent.h"を追加し、発射物移動コンポーネント クラスと当たり判定コンポーネント クラスを含めます。C++#include "FirstPersonProjectile.h" #include "GameFramework/ProjectileMovementComponent.h" #include "Components/SphereComponent.h" // Sets default values AFirstPersonProjectile::AFirstPersonProjectile()
オブジェクトにヒットしたときの発射物の動作を実装する
発射物をよりリアルにするには、発射物が当たるオブジェクトに力 (力積) を及ぼすようにします。 たとえば、物理対応のブロックを射撃している場合、発射物は地面に沿ってそのブロックを押し動かします。 次に、デフォルトの有効期限を待たずに、衝撃後に発射物を除去します。 この動作を実装するために OnHit() 関数を作成します。
発射物のオンヒット動作を実装するには、次の手順を実行します。
「
FirstPersonProjectile.h」のpublicセクションで、「PhysicsForce」という名前のfloatプロパティを定義します。EditAnywhere、BlueprintReadOnly、Category = “Projectile | Physics”のUPROPERTY()マクロを追加します。これは、発射物が何かにヒットしたときに受ける力の量です。
C++// The amount of force this projectile imparts on objects it collides with UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Projectile | Physics") float PhysicsForce = 100.0f;void関数OnHit()を定義します。 これは、アクタが別のコンポーネントまたはアクタと衝突したときに呼び出される AActor の関数です。 以下の引数を指定します。HitComp:ヒットされたコンポーネント。OtherActor:ヒットされたアクタ。OtherComp:ヒットを作成したコンポーネント (この場合は、発射物の CollisionComponent)。NormalImpulse:ヒットの法線の力積。Hit:時間、距離、場所など、ヒット イベントに関するより多くのデータを含むFHitResult参照。
C++// Called when the projectile collides with an object UFUNCTION() void OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);「
FirstPersonProjectile.cpp」で、ヘッダ ファイルで定義したOnHit()関数を実装します。OnHit()内のif文で、以下を確認します。OtherActorが null でなく、発射物自体ではないこと。OtherCompが null でなく、物理もシミュレートされていること。
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()) { } }これは、発射物が、自身以外のオブジェクトにヒットし、物理に関与していることを確認します。
if文内で、AddImpulseAtLocation()を使用して、OtherCompコンポーネントに力積を追加します。PhysicsForceで乗算された発射物の速度をこの関数に渡し、発射物アクタの位置で適用します。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()は、Unreal Engine の物理関数で、ワールド空間の特定の位置であり、シミュレートされた物理オブジェクトに瞬間的な力 (力積) を加えます。 何かが飛び出す爆発、オブジェクトにヒットする弾丸、素早く開くドアなど、衝撃をシミュレートする場合に便利です。この発射物が別のアクタにヒットしたため、
Destroy()を呼び出すことで、発射物をシーンから削除します。
完成した OnHit() 関数は次のようになります。
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();
}
}発射物のメッシュ、移動、コリジョン コンポーネントを追加する
次に、スタティックメッシュ、発射物移動ロジック、コリジョン球体を発射物に追加し、発射物がどのように移動するかを定義します。
これらのコンポーネントを発射物に追加するには、次の手順を実行します。
「
FirstPersonProjectile.h」のパブリックセクションに、ProjectileMesh という名前のUStaticMeshComponentへのTObjectPtrを宣言します。これはワールド内の発射物のスタティックメッシュです。EditAnywhere、BlueprintReadOnly、Category = “発射物 | メッシュ”のUPROPERTY()マクロを追加します。C++// Mesh of the projectile in the world UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Projectile | Mesh") TObjectPtr<UStaticMeshComponent> ProjectileMesh;Protectedセクションで、以下を宣言します。「
CollisionComponent」という名前のUSphereComponentにTObjectPtrを宣言。「
ProjectileMovement」という名前のUProjectileMovementComponentにTObjectPtrを宣言。
両方に
VisibleAnywhere、BlueprintReadOnly、Category = “発射物 | コンポーネント”のUPROPERTY()マクロを追加します。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;ProjectileMovementComponentが移動ロジックを処理します。 このクラスは、速度、重力、その他の変数に基づいて親アクタが行く場所を計算します。 それから、ティック中に発射物に動きを適用します。「
FirstPersonProjectile.cpp」のAFirstPersonProjectile()コンストラクタ関数で、発射物のメッシュ、コリジョン、発射物移動コンポーネントのデフォルトのサブオブジェクトを作成します。 次に、発射物メッシュをコリジョン コンポーネントにアタッチします。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);InitSphereRadius()を呼び出して、CollisionComponentのサイズを初期化します。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);BodyInstance.SetCollisionProfileName()を使用して、コリジョン コンポーネントのコリジョン プロファイルの名前を「Projectile」に設定します。C++CollisionComponent->InitSphereRadius(5.0f); // --- New Code Start --- // Set the collision profile to the "Projectile" collision preset CollisionComponent->BodyInstance.SetCollisionProfileName("Projectile"); // --- New Code End ---Unreal Editor では、コリジョン プロファイルは [Project Settings (プロジェクト設定)] > [Engine (エンジン)] > [Collision (コリジョン)] に格納され、コードで使用するために最大 18 個のカスタム コリジョン プロファイルを定義できます。 この「Projectile」コリジョンプロファイルのデフォルトの動作は「Block」であり、衝突したすべてのオブジェクトとコリジョンを発生させます。
発射物が何かにヒットしたときに起動する
OnHit()関数を先ほど定義しましたが、そのコリジョンが発生したときを通知する方法が必要です。 これを実行するには、AddDynamic()マクロを使用して、CollisionComponent内のOnComponentHitEventに関数をサブスクライブします。 このマクロにOnHit()関数を渡します。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 ---CollisionComponentを発射物のRootComponentとして、およびトラックする移動コンポーネントのUpdatedComponentとして設定します。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 ---ProjectileMovementコンポーネントで次の値を初期化します。InitialSpeed:発射物がスポーンするときの初期速度。 これを3000.0fに設定します。MaxSpeed:発射物の最高速度。 これを3000.0fに設定します。bRotationFollowVelocity:オブジェクトを速度の方向に回転させるかどうか。 例えるなら、紙飛行機が上昇したり下降したりするにつれて、どのように上下に傾くかです。 これを、trueに設定します。bShouldBounce:発射物が障害物を跳ね返るかどうか。 これを、trueに設定します。弾性:当たり判定後に速度がどれだけ維持されるか。値が低いほど、プロジェクタイルがより多くのエネルギーを失います。 これを0.4fに設定します。摩擦:衝撃後に接線 (横方向) 速度がどれだけ保持されるか。 これを
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;
発射物の有効期限を設定する
デフォルトでは、発射してから数秒後に発射物が消えます。 しかし、エディタで発射物のブループリントを派生させると、このデフォルト時間を変更したり削除したりして、レベルにフォーム ダーツを追加してみることができます。
数秒後に発射物を消すには、次の手順を実行します。
「
FirstPersonProjectile.h」のpublicセクションで、「ProjectileLifespan」という名前の float プロパティを宣言します。EditAnywhere、BlueprintReadOnly、Category = “発射物 | 有効期限”のUPROPERTY()マクロを追加します。これは秒単位の発射物の有効期限です。
C++// Despawn after 5 seconds by default UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Projectile | Lifespan") float ProjectileLifespan = 5.0f;「
FirstPersonProjectile.cpp」で、AFirstPersonProjectile()コンストラクタ関数の最後に、発射物のInitialLifeSpanをProjectileLifespanに設定し、5 秒後に消えるようにします。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.InitialLifeSpanは、AActor から継承されたプロパティです。 アクタが消滅するまでの期間を設定する float です。 値が0の場合、アクタはゲームが停止するまで存続します。
完成した FirstPersonProjectile.h は次のようになります。
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "FirstPersonProjectile.generated.h"
class UProjectileMovementComponent;
class USphereComponent;
完成した AFirstPersonProjectile.cpp は次のようになります。
// 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.
キャラクターのカメラ方向を取得する
発射物はダーツ ランチャー自体からスポーンする必要があるため、ダーツ ランチャーがどこにあり、どこを指しているのかを知るために何らかの計算を行う必要があります。 ランチャーはプレイヤー キャラクターにアタッチされているため、これらの値はキャラクターがどこにいて、どこを見ているのかに応じて変化します。
一人称視点キャラクターにはダーツを発射するために必要な位置情報が含まれているため、まずキャラクター クラスを変更してライン トレースでその情報をキャプチャし、結果を返します。
トレースを使用してキャラクターから必要な情報を取得するには、次の手順を実行します。
VS でキャラクターの
.hファイルと.cppファイルを開きます。.hファイルのpublicセクションで、FVectorを返す「GetCameraTargetLocation()」という名前の新しい関数を宣言します。 この関数は、キャラクターが見ているワールド内の位置を返します。C++// Returns the location in the world the character is looking at UFUNCTION() FVector GetCameraTargetLocation();キャラクターの
.cppファイルはGetCameraTargetLocation()関数を実装します。 まず、TargetPositionという名前のFVectorを宣言して返します。C++FVector AAdventureCharacter::GetCameraTargetLocation() { // The target position to return FVector TargetPosition; }GetWorld()を呼び出すことでUWorldへのポインタを作成します。C++FVector AAdventureCharacter::GetCameraTargetLocation() { // The target position to return FVector TargetPosition; // --- New Code Start --- UWorld* const World = GetWorld(); // --- New Code End --- }if文を追加して、Worldが null ではないことを確認します。if文内で、「Hit」という名前のFHitResultを宣言します。C++FVector AAdventureCharacter::GetCameraTargetLocation() { // The target position to return FVector TargetPosition; UWorld* const World = GetWorld(); // --- New Code Start --- if (World != nullptr) {FHitResultは、ヒットしたアクタまたはコンポーネント、ヒットした場所などのコリジョン クエリの結果に関する情報を格納する UE の構造体です。キャラクターが見ているポイントを見つけるには、キャラクターが見下ろしているどこか遠くのポイントまでベクターに沿ってライン トレースをシミュレートします。 ライン トレースがオブジェクトに衝突した場合は、キャラクターがワールド内でどこを見ているかがわかります。
if 文内で、
TraceStartとTraceEndという名前の 2 つのconst FVector値を宣言します。TraceStartをFirstPersonCameraComponentの位置に設定します。TraceEndをTraceStartおよびカメラ コンポーネントの前方ベクターに非常に大きな数を乗算した値に設定します。 これにより、キャラクターが空を見つめていないかぎり、トレースはワールド内のほとんどのオブジェクトと衝突できる距離に到達します。 (キャラクターが空を見ている場合、TraceEndがライン トレースの終点です。)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 ---
UWorldからLineTraceSingleByChannel()を呼び出し、トレースをシミュレートします。 これに、Hit、TraceStart、TraceEnd、ECollisionChannel::ECC_Visibilityを渡します。これは
TraceStartからTraceEndまでのライン トレースをシミュレートし、表示されているオブジェクトと衝突して、トレースの結果をHitに格納します。ECollisionChannel::ECC_Visibilityは、トレーシングに使用するチャンネルです。これらのチャンネルは、トレースがヒットを試行するオブジェクトのタイプを定義します。 視線カメラのチェックにはECC_Visibilityを使用します。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 ---Hit値に衝撃の位置や法線などのヒット結果の情報が含まれるようになりました。 また、ヒットがオブジェクトのコリジョンによるものであるかどうかも判断します。 衝撃の位置 (またはトレース ラインの終点) は、返すカメラ ターゲットの位置です。三項演算子を使用して、ブロッキング ヒットだった場合は
Hit.ImpactPointに、そうでなかった場合はHit.TraceEndにTargetPositionを設定します。 それから、TargetPositionを返します。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
完成した GetCameraTargetLocation() 関数は次のようになります。
FVector AAdventureCharacter::GetCameraTargetLocation()
{
// The target position to return
FVector TargetPosition;
UWorld* const World = GetWorld();
if (World != nullptr)
{
// The result of the line trace
DartLauncher::Use() で発射物をスポーンする
これでキャラクターがどこを見ているかを把握できるようになったため、ダーツ ランチャーの Use() 関数に残りの発射物ロジックを実装することができるようになりました。 発射物を発射する位置と方向を取得して、発射物をスポーンします。
発射物がスポーンする位置と回転を取得するには、次の手順を実行します。
「
DartLauncher.h」のファイルの先頭に、AFirstPersonProjectileの前方宣言を追加します。publicセクションで、ProjectileClassという名前のプロパティTSubclassOf<AFirstPersonProjectile>を宣言します。 これがダーツ ランチャーがスポーンする発射物です。 これに、EditAnywhereとCategory = "Projectile"のUPROPERTY()マクロを追加します。「
DartLauncher.h」は次のようになります。C++// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "EquippableToolBase.h" #include "DartLauncher.generated.h" class AFirstPersonProjectile;DartLauncher.cppに include 文を追加します。”Kismet/KismetMathLibrary.h”発射物の計算は複雑になることがありますが、このファイルには発射物の発射に使用するいくつかの関数が含まれています。"FirstPersonProjectile.h""EnhancedInputComponent.h"
C++#include "Tools/DartLauncher.h" #include "FirstPersonProjectile.h" #include "Kismet/KismetMathLibrary.h" #include "EnhancedInputComponent.h" #include "AdventureCharacter.h"DartLauncher の
Use()実装における、デバッグ メッセージの後:GetWorld()を呼び出すことでUWorldを取得します。if文を追加して、WorldとProjectileClassが null でないことを確認します。if文内でOwningCharacter->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(); }発射物は、キャラクターが持っているツールからスポーンさせますが、装備オブジェクトの中心からスポーンさせないようにします。 ダーツ ランチャーの
SKM_Pistolメッシュにはダーツの正確なスポーン ポイントを設定するために使用できる「マズル」ソケットがあります。if文内で、SocketLocationという名前の新しいFVectorを宣言し、ToolMeshComponentでGetSocketLocation(“Muzzle”)を呼び出した結果に設定します。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 --- }SpawnRotationという名前のFRotatorを宣言します。 これは、発射物がスポーンするときの回転 (ピッチ、ヨー、ロールの値) です。Kismet 数学ライブラリから
FindLookAtRotation()を呼び出した結果にこれを設定し、プレイヤー キャラクターから取得したSocketLocationとTargetPositionを渡します。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 offsetFindLookAtRotationは、SocketLocationでTargetPositionを向くために必要な回転を計算し、返します。SpawnLocationという名前のFVectorを宣言し、それをSocketLocationと、SpawnRotationの前方ベクターに10.0を乗算したものを追加した結果に設定します。銃口ソケットはランチャーの正面にないため、ベクターにオフセットを乗算して、発射物が正しい位置から発射されるようにする必要があります。
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);
位置と回転を取得したため、発射物をスポーンする準備ができました。
発射物をスポーンするには、次の手順を実行します。
そのまま
Use()関数で、if文内で、ActorSpawnParamsという名前のFActorSpawnParametersを宣言します。 このクラスには、アクタをどこで、どのようにスポーンするかに関する情報が含まれています。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();ActorSpawnParamsのSpawnCollisionHandlingOverride値をESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfCollidingに設定します。C++//Set Spawn Collision Handling Override FActorSpawnParameters ActorSpawnParams; // --- New Code Start --- ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding; // --- New Code End ---このコード行は、発射物が出現する、別のアクタと衝突しない場所 (壁の中など) の検索を試み、適切な位置が見つからない場合は出現しません。
SpawnActor()を使用してProjectileClass、SpawnLocation、SpawnRotation、ActorSpawnParamsを渡し、ダーツ ランチャーの銃口から発射物がスポーンするようにします。C++ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding; // --- New Code Start --- // Spawn the projectile at the muzzle World->SpawnActor<AFirstPersonProjectile>(ProjectileClass, SpawnLocation, SpawnRotation, ActorSpawnParams); // --- New Code End ---
完成した Use() 関数は次のようになります。
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
フォーム ダーツ クラスとブループリントを派生させる
すべてのスポーン ロジックが完成したため、次に実際の発射物を作成します。 ダーツ ランチャー クラスでは、起動する AFirstPersonProjectile のサブクラスが必要であるため、レベルで使用するサブクラスをコード内でビルドする必要があります。
インゲームで使用するフォーム ダーツ発射物クラスを派生させるには、次の手順を実行します。
Unreal Editor で、[Tools] > [New C++ Class] に移動します。
[All Classes (すべてのクラス)] で、「First Person Projectile」を検索して、親クラスとして選択し、クラスに「
FoamDart」と名前を付けます。 [Create Class] をクリックします。VS で、これらのファイルをそのままにし、コードを保存してコンパイルします。
このチュートリアルでは、FirstPersonProjectile で定義したもの以外のカスタム発射物コードは実装しませんが、プロジェクトのニーズに合わせて FoamDart クラスを独自に変更することができます。 たとえば、ダーツの発射物が消えたり跳ねたりするのではなく、オブジェクトにくっつくようにすることもできます。
フォーム ダーツのブループリントを作成するには、次の手順を実行します。
Unreal Editor で、FoamDart に基づいてブループリント クラスを作成し、「
BP_FoamDart」という名前を付けます。 これを「FirstPerson/Blueprints/Tools」フォルダに保存します。ブループリントを開いた状態で、発射物メッシュ コンポーネントを選択し、スタティックメッシュ を SM_FoamBullet に設定します。
フォーム ダーツ メッシュが ビューポート に表示されます。 メッシュの向きを確認するには、ズームインするか、F キーを押します。 ダーツの丸い先端が前方で、X 軸の正の方向を向いている必要があります。これにより、ダーツが正しいオリエンテーションで発射されます (
ProjectileMovementは +X を前方と仮定します)。ダーツを Z 軸 (青色) で +90 度回転させます。
ブループリントをコンパイルして保存します。
コンテンツ ブラウザで、「
BP_DartLauncher」を開きます。詳細パネルの新しい [Projectile (プロジェクタイル)] セクションで、[Projectile Class (発射物クラス)] を
BP_FoamDartに設定します。BP_FoamDartがリストに表示されない場合は、コンテンツ ブラウザでBP_FoamDartを選択し、Projectile Class プロパティに戻って [コンテンツ ブラウザで選択中のアセットを使用] をクリックします。[Compile (コンパイル)]、[Save (保存)] の順にクリックします。
ついにお披露目です。 アセットを保存し、[Play (プレイ)] をクリックします。 ゲームが開始したら、ダーツ ランチャーまで走ってダーツを手に入れることができます。 マウスの左ボタンを押すと、ダーツ ランチャーの銃口から発射物がスポーンし、レベル内を跳ねます。 これらの発射物は 5 秒後に消え、衝突するオブジェクトに小さな物理的力を与えるはずです (自分自身を含む)。
ダーツ ランチャーが発射せず、デバッグ メッセージ「Using the dart launcher! (ダーツ ランチャーは使用中!)」が表示されない場合、キャラクターのブループリントで、IA_UseTool が Use Action (使用アクション) プロパティに割り当てられていることを確認してください。
ボーナス:発射物とツールを調整する
オプションで、これらの最終調整を実装して、ダーツランチャーとダーツを最高に見せましょう。
落下した投射物を平らにする
FirstPersonProjectile.cpp 内で、発射物が何かにヒットしたとき、発射物が地面 (平らな水平面) にヒットしたかどうかを確認し、ヒットした場合は、発射物を平ら横たわらせます。
OnHit() の先頭に、このコードを追加します。
// 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;
}次の内容
おつかれさまでした! 一人称視点のプログラマー トラック チュートリアルを完了し、多くのことを学ぶことができました。
カスタム キャラクターとカスタム移動を実装し、ピックアップとデータ アセットを作成して、独自の発射物のある装備可能なツールを作成しました。 このプロジェクトを独自のものに仕上げるために必要なものがすべて揃いました。
以下は推奨事項です。
さまざまなタイプのアイテムでプレイヤーのインベントリを拡張できますか? アイテムのスタックについてはどうでしょうか?
ピックアップと発射物を組み合わせてピックアップ可能な弾薬を作成できますか? ダーツランチャーにこの弾薬システムを実装するのはどうでしょうか?
消耗品を装備可能な消耗品に発展させることはできるでしょうか? プレイヤーが持つヘルス パックや、拾って投げることができるボールはどうでしょうか?
完全なコード
// 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"