開始する前に
前のセクションの「アイテムとデータを管理する」で次の目標が完了していることを確認してください。
アイテム データ構造体、
UDataAssetクラス、「DA_Pickup_001」という名前の消耗品型データ アセット インスタンス、データ テーブルを設定する。
新しいピックアップ クラスを作成する
ここまでで、アイテムの構造とデータを定義して格納する方法を学びました。 このセクションでは、このデータをインゲームの「ピックアップ」、つまりプレイヤーが操作してエフェクトを得ることができるテーブル データの具体的な表現に変換する方法について説明します。 ピックアップには、装備可能なガジェット、マテリアルを提供するボックス、一時的なブーストを提供するパワーアップなどがあります。
初期宣言でピックアップ クラスの設定を開始するには、次の手順を実行します。
Unreal Editor で、[Tools (ツール)] > [New C++ Class (新規 C++ クラス)] に移動します。 親クラスとして [Actor (アクタ)] を選択し、クラスに「
PickupBase」という名前を付けます。 [Create Class] をクリックします。Visual Studio で、「
PickupBase.h」を開き、ファイルの先頭に次の文を追加します。#include ”Components/SphereComponent.h”。 プレイヤーとピックアップの間のコリジョンを検出するために、ピックアップに球体コンポーネントを追加します。#include ”AdventureCharacter.h”。 一人称キャラクター クラスへの参照を追加し、このクラスのピックアップとキャラクター間のオーバーラップをチェックできるようにします。 (このチュートリアルではAdventureCharacterを使用します。)UItemDefinitionの前方宣言。 これは、各ピックアップが参照する、関連付けられているデータ アセット アイテムです。
C++// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Components/SphereComponent.h" #include "CoreMinimal.h" #include "AdventureCharacter.h" #include "GameFramework/Actor.h" #include "PickupBase.generated.h"APickupBaseクラス定義の上にあるUCLASS()マクロで、BlueprintType指定子とBlueprintable指定子を追加し、ブループリントを作成するための基本クラスとして公開します。C++UCLASS(BlueprintType, Blueprintable) class ADVENTUREGAME_API APickupBase : public AActor {「
PickupBase.cpp」で、ItemDefinition.hの#includeを追加します。C++// Copyright Epic Games, Inc. All Rights Reserved. #include "PickupBase.h" #include "ItemDefinition.h"
テーブル データでピックアップを初期化する
ピックアップは単なる空のアクタであるため、ゲームが開始したら、適切に動作するために必要なデータを提供する必要があります。 ピックアップはデータ テーブルから値の行を取り出し、それらの値を ItemDefinition データ アセット (「参照アイテム」) に保存する必要があります。
データ テーブルからデータを取得する
「PickupBase.h」の public セクションで新しい void 関数 InitializePickup() を宣言します。 この関数を使用して、データ テーブルからの値でピックアップを初期化します。
// Initializes this pickup with values from the data table.
void InitializePickup();テーブルからデータを取得するために、ピックアップ ブループリントには、データ テーブル アセットと行名 (アイテム ID と同じになるように設定済み) の 2 つのプロパティが必要です。
Protected セクションで、「PickupItemID」という名前の FName プロパティを宣言します。 EditInstanceOnly 指定子と Category = “ピックアップ | アイテム テーブル” 指定子を与えます。 これは、関連するデータ テーブルでのこのピックアップの ID です。
// The ID of this pickup in the associated data table.
UPROPERTY(EditInstanceOnly, Category = "Pickup | Item Table")
FName PickupItemID;ピックアップにはデフォルトのアイテム ID を設定すべきではないため、EditInstanceOnly 指定子を使用することで、ワールド内のピックアップのインスタンスでこのプロパティを編集できるようになりますが、アーキタイプ (またはクラスのデフォルト) では編集できません。
Category テキストでは、縦線 (|) によってネスティングされたサブセクションが作成されます。 この例では、Unreal Engine は、アセットの [Details (詳細)] パネルに「アイテム テーブル」というサブセクションを含むピックアップ セクションを作成します。
次に、PickupDataTable という名前の UDataTable への TSoftObjectPtr を宣言します。 PickupItemID と同じ指定子を与えます。 これは、ピックアップがデータを取得するために使用するデータ テーブルです。
データ テーブルはランタイム時にロードされない可能性があるため、ここでは TSoftObjectPtr を使用し、非同期でロードできるようにします。
ヘッダ ファイルを保存し、「PickupBase.cpp」に切り替えて InitializePickup() を実装します。
関数内の if 文で、指定された PickupDataTable が有効であり、PickupItemID に値があることを確認します。
/**
* Initializes the pickup with default values by retrieving them from the associated data table.
*/
void APickupBase::InitializePickup()
{
if (PickupDataTable && !PickupItemID.IsNone())
{
}
}if 文に、データ テーブルから値の行を取得するコードを追加します。 「ItemDataRow」という名前の const FItemData ポインタを宣言し、これを PickupDataTable で FindRow() を呼び出した結果に設定します。 検索する行のタイプとして FItemData を指定します。
const FItemData* ItemDataRow = PickupDataTable->FindRow<FItemData>();FindRow() は、次の 2 つの引数を受け取ります。
検索する
FName行名。 行名としてPickupItemIDを渡します。行が見つからない場合にデバッグに使用できる、
FString型のコンテキスト文字列。Text(“ここにコンテキストを入力。”)を使用して、 コンテキスト文字列を追加、またはToString()を使用してアイテム ID をコンテキスト文字列に変換できます。
if (PickupDataTable && !PickupItemID.IsNone())
{
// Retrieve the item data associated with this pickup from the Data Table
const FItemData* ItemDataRow = PickupDataTable->FindRow<FItemData>(PickupItemID, PickupItemID.ToString());
}参照アイテムを作成する
ピックアップの行データを取得したら、その情報を保持するためにデータ アセット型の ReferenceItem を作成して初期化します。
このように参照アイテムにデータを保存すると、Unreal Engine は、効率の悪いテーブル データのルックアップを実行する代わりに、そのアイテムを把握する必要があるときにそのデータを簡単に参照することができます。
「PickupBase.h」の protected セクションで、「ReferenceItem」という名前の UItemDefinition に TObjectPtr を宣言します。 これは、ピックアップのデータを格納するデータ アセットです。 VisibleAnywhere 指定子と Category = “ピックアップ | 参照アイテム” 指定子を与えます。
// Data asset associated with this item.
UPROPERTY(VisibleAnywhere, Category = "Pickup | Reference Item")
TObjectPtr<UItemDefinition> ReferenceItem;ヘッダ ファイルを保存し、「PickupBase.cpp」に戻します。
InitializePickup() で、FindRow() の呼び出しのあと、ReferenceItem を UItemDefinition 型の NewObject に設定します。
Unreal Engine では、NewObject<T>() はランタイム時に UObject 派生インスタンスを動的に作成するためのテンプレート化された関数です。 新しいオブジェクトへのポインタを返します。 通常、次の構文が含まれます。
T* Object = NewObject<T>(Outer, Class);
T は、作成する UObject の型、Outer はこのオブジェクトの所有者、Class は作成するオブジェクトのクラスです。 多くの場合、Class 引数は T::StaticClass() であり、T のクラス タイプを表す UClass ポインタが与えられます。 ただし、UE では、Outer が現在のクラスであると想定し、T を使用して UClass を推測するため、多くの場合、両方の引数を省略できます。
これを外部クラスとして渡し、UItemDefinition::StaticClass() をクラス タイプとして渡すことで、ベースの UItemDefinition を作成します。
ReferenceItem = NewObject<UItemDefinition>(this, UItemDefinition::StaticClass());ピックアップの情報を ReferenceItem にコピーするには、ReferenceItem の各フィールドを ItemDataRow の関連するフィールドに設定します。 WorldMesh については、ItemDataRow で参照される ItemBase から WorldMesh プロパティを取得します。
ReferenceItem = NewObject<UItemDefinition>(this, UItemDefinition::StaticClass());
ReferenceItem->ID = ItemDataRow->ID;
ReferenceItem->ItemType = ItemDataRow->ItemType;
ReferenceItem->ItemText = ItemDataRow->ItemText;
ReferenceItem->WorldMesh = ItemDataRow->ItemBase->WorldMesh;InitializePickup() を呼び出す
BeginPlay() で、InitializePickup() を呼び出し、ゲームが開始されたときにピックアップを初期化します。
// Called when the game starts or when spawned
void APickupBase::BeginPlay()
{
Super::BeginPlay();
// Initialize this pickup with default values
InitializePickup();
}ファイルを保存します。 「PickupBase.cpp」は次のようになります。
// Copyright Epic Games, Inc. All Rights Reserved.
#include "PickupBase.h"
// Sets default values
APickupBase::APickupBase()
{
// Set this actor to call Tick() every frame.
PrimaryActorTick.bCanEverTick = true;
}
インゲーム機能を作成する
ピックアップには必要なアイテム データがありますが、ゲーム ワールド内での表示や操作の方法を知っておく必要があります。 プレイヤーに表示するためのメッシュ、プレイヤーが接触したタイミングを判断するためのコリジョン ボリューム、ピックアップが (プレイヤーが拾ったように) 消えて一定時間後にリスポーンするためのロジックが必要です。
メッシュ コンポーネントを追加する
「一人称視点のカメラ、メッシュ、アニメーションを追加する」でプレイヤー キャラクターを設定したときと同様に、「CreateDefaultSubobject」テンプレート関数を使用して、ピックアップ クラスの子コンポーネントとしてスタティックメッシュ オブジェクトを作成し、アイテムのメッシュをこのコンポーネントに適用することができます。
「PickupBase.h」の protected セクションで、「PickupMeshComponent」という名前の UStaticMeshComponent に TObjectPtr を宣言します。 これはワールド内でのピックアップを表すメッシュです。
コードを使用して、データ アセットのメッシュをこのプロパティに割り当てるため、Unreal Editor で編集はできないものの表示はされるように、VisibleDefaultsOnly と Category = “ピックアップ | メッシュ” 指定子を与えます。
// The mesh component to represent this pickup in the world.
UPROPERTY(VisibleDefaultsOnly, Category = "Pickup | Mesh")
TObjectPtr<UStaticMeshComponent> PickupMeshComponent;ヘッダ ファイルを保存し、「PickupBase.cpp」に切り替えます。
APickupBase コンストラクション関数で、PickupMeshComponent ポインタを UStaticMeshComponent 型の CreateDefaultSubobject() を呼び出した結果に設定します。 Text 引数で、オブジェクトに「PickupMesh」という名前を付けます
次に、メッシュが適切にインスタンス化されたことを確認するために、PickupMeshComponent が null ではないことを確認します。
// Sets default values
APickupBase::APickupBase()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
// Create this pickup's mesh component
PickupMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("PickupMesh"));
check(PickupMeshComponent != nullptr);
}InitializePickup() の実装に移動します。
TSoftObjectPtr でメッシュを定義したため、WorldMesh をピックアップのメッシュ コンポーネントに適用する前に、メッシュがロードされたことを確認する必要があります。
まず、「TempItemDefinition」という名前の新しい UItemDefinition ポインタを宣言し、これを ItemDataRow->ItemBase.Get() を呼び出した結果に設定します。
UItemDefinition* TempItemDefinition = ItemDataRow->ItemBase.Get();次に、if 文で、WorldMesh.IsValid() を呼び出すことで WorldMesh が現在ロードされているかを確認します。
// Check if the mesh is currently loaded by calling IsValid().
if (TempItemDefinition->WorldMesh.IsValid()) {
}ロードされている場合、SetStaticMesh() を呼び出して PickupMeshComponent を設定し、Get() を使用して WorldMesh を取得します。
// Check if the mesh is currently loaded by calling IsValid().
if (TempItemDefinition->WorldMesh.IsValid()) {
// Set the pickup's mesh to the associated item's mesh
PickupMeshComponent->SetStaticMesh(TempItemDefinition->WorldMesh.Get());
}メッシュがロードされていない場合は、メッシュで LoadSynchronous() を呼び出すことで強制的にロードさせます。 この関数は、そのオブジェクトへのアセット ポインタをロードし、返します。
if 文のあとの else 文で、「WorldMesh」という名前の新しい UStaticMesh ポインタを宣言し、WorldMesh.LoadSynchronous() を呼び出すことで設定します。
次に、SetStaticMesh() を使用して PickupMeshComponent を設定します。
else {
// If the mesh isn't loaded, load it by calling LoadSynchronous().
UStaticMesh* WorldMesh = TempItemDefinition->WorldMesh.LoadSynchronous();
PickupMeshComponent->SetStaticMesh(WorldMesh);
}else 文のあと、SetVisiblity(true) を使用して PickupMeshComponent を表示します。
// Set the mesh to visible.
PickupMeshComponent->SetVisibility(true);コリジョン形状を追加する
コリジョン ボリュームとして機能する球体コンポーネントを追加し、そのコンポーネントでのコリジョン クエリを有効にします。
「PickupBase.h」の protected セクションで、「SphereComponent」という名前の USphereComponent に TObjectPtr を宣言します。 これは、コリジョン検出に使用する球体コンポーネントです。 EditAnywhere 指定子、BlueprintReadOnly 指定子、Category = “ピックアップ | コンポーネント” 指定子を与えます。
// Sphere Component that defines the collision radius of this pickup for interaction purposes.
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Pickup | Components")
TObjectPtr<USphereComponent> SphereComponent;ヘッダ ファイルを保存し、「PickupBase.cpp」に切り替えます。
APickupBase コンストラクション関数で、PickupMeshComponent を設定したあと、「SphereComponent」という名前の USphereComponent 型の CreateDefaultSubobject を呼び出した結果に SphereComponent を設定します。 その後に null check を追加します。
// Create this pickup's sphere component
SphereComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
check(SphereComponent != nullptr);両方のコンポーネントを作成できたので、SetupAttachment() を使用して PickupMeshComponent を SphereComponent にアタッチします。
// Attach the sphere component to the mesh component
SphereComponent->SetupAttachment(PickupMeshComponent);SphereComponent を MeshComponent にアタッチしたあと、SetSphereRadius() を使用して球体のサイズを設定します。 この値によって、ピックアップ コライダーをコリジョンできるほどの大きさにしますが、キャラクターが誤ってそれにぶつかるほど大きくならないようにする必要があります。
// Set the sphere's collision radius
SphereComponent->SetSphereRadius(32.f);InitializePickup() で、SetVisibility(true) 行のあと、SetCollisionEnabled() を呼び出すことで SphereComponent をコリジョン可能にします。
この関数は使用するコリジョンのタイプを Unreal Engine に伝える列挙型 (ECollisionEnabled) を受け取ります。 キャラクターがピックアップとコリジョンし、コリジョン クエリをトリガーできるようにしますが、ヒットしたときにピックアップが跳ねる物理的性質を持たないことが必要であるため、ECollisionEnabled::QueryOnly オプションを渡します。
// Set the mesh to visible and collidable.
PickupMeshComponent->SetVisibility(true);
SphereComponent->SetCollisionEnabled(ECollisionEnabled::QueryOnly); 「PickupBase.cpp」は次のようになります。
// Copyright Epic Games, Inc. All Rights Reserved.
#include "PickupBase.h"
#include "ItemDefinition.h"
// Sets default values
APickupBase::APickupBase()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
ピックアップ コリジョンをシミュレーションする
これでピックアップにコリジョンの形状が設定されたので、ピックアップとプレイヤーのコリジョンを検出するロジックを追加し、コリジョンしたときにピックアップが非表示になるようにします。
コリジョン イベントを設定する
「PickupBase.h」の protected セクションで、void 関数 OnSphereBeginOverlap() を宣言します。
USphereComponent など UPrimitiveComponent から継承するコンポーネントは、コンポーネントが他のアクタとオーバーラップする場合にこの関数を実装し、コードを実行できます。 この関数は使用しないパラメータをいくつか受け取ります。渡すのは以下のパラメータのみです。
UPrimitiveComponent* OverlappedComponent:オーバーラップされたコンポーネントAActor* OtherActor:そのコンポーネントとオーバーラップしているアクタUPrimitiveComponent* OtherComp:オーバーラップしたアクタのコンポーネントint32 OtherBodyIndex:オーバーラップしたコンポーネントのインデックスbool bFromSweep, const FHitResult& SweepResult:発生した場所や角度など、コリジョンに関する情報
// Code for when something overlaps the SphereComponent.
UFUNCTION()
void OnSphereBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);ヘッダ ファイルを保存し、「PickupBase.cpp」に切り替えます。
Unreal Engine のコリジョン イベントは、動的マルチキャスト デリゲートを使用して実装されます。 UE では、このデリゲート システムを使用することで、1 つのオブジェクトで一度に複数の関数を呼び出すことができます。これは、他の関数を登録者とするメーリング リストにメッセージをブロードキャストするようなものです。 関数をデリゲートにバインドすると、関数がメーリング リストに登録されているようなものになります。 「デリゲート」はイベントで、この場合、プレイヤーとピックアップのコリジョンです。 イベントが発生すると、Unreal Engine はそのイベントにバインドされているすべての関数を呼び出します。
Unreal Engine には他にもいくつかのバインディング関数がありますが、OnComponentBeginOverlap は動的デリゲートであるため、AddDynamic() を使用することをお勧めします。 また、UObject クラスで UFUNCTION をバインドしており、リフレクション サポートには AddDynamic() が必要となります。 動的マルチキャスト デリゲートの詳細については、「マルチキャスト デリゲートマルチキャスト デリゲート」を参照してください。
「PickupBase.cpp」の InitializePickup() で、AddDynamic マクロを使用して、OnSphereBeginOverlap() を球体コンポーネントの OnComponentBeginOverlap イベントにバインドします。
// Register the Overlap Event
SphereComponent->OnComponentBeginOverlap.AddDynamic(this, &APickupBase::OnSphereBeginOverlap);作業を保存します。 これで、キャラクターがピックアップの球体コンポーネントとコリジョンしたときに OnSphereBeginOverlap() が実行されるようになりました。
コリジョン後にピックアップを非表示にする
「PickupBase.cpp」で、OnSphereBeginOverlap() を実装し、プレイヤーが拾ったように見えるようにピックアップが消えるようにします。
まず、この関数がトリガーされるタイミングを知らせるため、デバッグ メッセージを追加します。
void APickupBase::OnSphereBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Attempting a pickup collision"));
}この関数は別のアクタがピックアップにコリジョンするたびに実行されるため、コリジョンするのが自分の一人称キャラクターであることを確認する必要があります。
「Character」という名前の新しい AAdventureCharacter ポインタを宣言し、OtherActor をキャラクター クラス名にキャストすることで設定します (このチュートリアルは AAdventureCharacter を使用します)。
// Checking if it's an AdventureCharacter overlapping
AAdventureCharacter* Character = Cast<AAdventureCharacter>(OtherActor);if 文で、キャラクターが null でないことを確認します。 Null は、キャストが失敗したことと、OtherActor が AAdventureCharacter やそのサブクラスではなかったことを示します。
if 文内で、RemoveAll() を呼び出すことで OnComponentBeginOverlap をこの関数から登録解除し、繰り返しトリガーされないようにします。 これによりコリジョンが終了します。
if (Character != nullptr)
{
// Unregister from the Overlap Event so it is no longer triggered
SphereComponent->OnComponentBeginOverlap.RemoveAll(this);
}次に、SetVisibility(false) を使用して PickupMeshComponent を非表示に設定して、SetCollisionEnabled() を使用して NoCollision オプションを渡し、ピックアップ メッシュと球体コンポーネントの両方をコリジョン不可に設定します。
if (Character != nullptr)
{
// Unregister from the Overlap Event so it is no longer triggered
SphereComponent->OnComponentBeginOverlap.RemoveAll(this);
// Set this pickup to be invisible and disable collision
PickupMeshComponent->SetVisibility(false);
PickupMeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
SphereComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}「PickupBase.cpp」を保存します。
ピックアップがリスポーンするように設定する
これでキャラクターがピックアップとインタラクトできなくなったため、一定の時間が経過したあとにピックアップがリスポーンされるようにします。
「PickupBase.h」の protected セクションで、「bShouldRespawn」という名前のブール型を宣言します。 これを使用して、リスポーンのオンとオフを切り替えることができます。
RespawnTime という名前の float を宣言し、4.0f で初期化します。 これはピックアップがリスポーンするまで待機する時間です。
EditAnywhere 指定子、BlueprintReadOnly 指定子、Category = “ピックアップ | リスポーン” 指定子を両方のプロパティに与えます。
// Whether this pickup should respawn after being picked up.
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Pickup | Respawn")
bool bShouldRespawn;
// The time in seconds to wait before respawning this pickup.
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Pickup | Respawn")
float RespawnTime = 4.0f;「RespawnTimerHandle」という名前の FTimerHandle を宣言します。
// Timer handle to distinguish the respawn timer.
FTimerHandle RespawnTimerHandle;Unreal Engine では、ゲームプレイ タイマーは FTimerManager によって処理されます。 このクラスには、設定されたディレイ時間の後に関数またはデリゲートを呼び出す SetTimer() 関数が含まれています。 FTimerManager の関数は、FTimerHandle を使用して、関数を開始、一時停止、再開、または無限ループにします。 ピックアップをリスポーンするタイミングを知らせるため RespawnTimerHandle を使用します。 Timer Manager の使用方法の詳細については、「ゲームプレイ タイマー」を参照してください。
ヘッダ ファイルを保存し、「PickupBase.cpp」に切り替えます。
ピックアップのリスポーンを実装するには、Timer Manager を使用して、少し待機したあとに InitializePickup() を呼び出すタイマーを設定します。
ピックアップをリスポーンさせるのは、リスポーンが有効である場合だけにするため、OnSphereBeginOverlap の下に bShouldRespawn が true かどうかをチェックする if 文を追加します。
if (bShouldRespawn)
{
}if 文で、GetWorldTimerManager() を使用して Timer Manager を取得し、タイマー マネージャーで SetTimer() を呼び出します。 この関数の構文は次のとおりです。
SetTimer(InOutHandle, Object, InFuncName, InRate, bLoop, InFirstDelay);
ここでは、次のようになります
InOutHandleは、タイマーを制御しているFTimerHandleです (自分のRespawnTimerHandle)。Objectは、呼び出す関数を所有するUObjectです。 これを使用してください。InFuncNameは呼び出す関数へのポインタです (この場合は、InitializePickup())。InRateは、関数を呼び出す前に待機する時間 (秒単位) を指定する浮動小数値です。bLoopにより、タイマーがTime秒ごとに繰り返される (true) か、1 回のみ実行される (false) ようになります。InFirstDelay(オプション) は、ループ タイマーでの最初の関数呼び出し前の初期ディレイ時間です。 指定しない場合、UE はInRateをディレイとして使用します。
ピックアップを置き換えるために、InitializePickup() を 1 回だけ呼び出す必要があるため、bLoop を false に設定します。
リスポーン時間を設定します。このチュートリアルでは、初期のディレイなしで 4 秒後にピックアップがリスポーンされるようにします。
// If the pickup should respawn, wait an fRespawnTime amount of seconds before calling InitializePickup() to respawn it
if (bShouldRespawn)
{
GetWorldTimerManager().SetTimer(RespawnTimerHandle, this, &APickupBase::InitializePickup, RespawnTime, false, 0);
}完成した OnSphereBeginOverlap() 関数は次のようになります。
/**
* Broadcasts an event when a character overlaps this pickup's SphereComponent. Sets the pickup to invisible and uninteractable, and respawns it after a set time.
* @param OverlappedComponent - the component that was overlapped.
* @param OtherActor - the Actor overlapping this component.
* @param OtherComp - the Actor's component that overlapped this component.
* @param OtherBodyIndex - the index of the overlapped component.
* @param bFromSweep - whether the overlap was generated from a sweep.
* @param SweepResult - contains info about the overlap such as surface normals and faces.
*/
void APickupBase::OnSphereBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
コードを保存して Visual Studio からコンパイルします。
レベルにピックアップを実装する
ピックアップを構成するコードを定義したので、次はゲームでピックアップをテストします。
レベルにピックアップを追加するには、次の手順を実行します。
Unreal Editor のコンテンツ ブラウザのアセット ツリーで、「Content」 > 「FirstPerson」 > 「Blueprints」 に移動します。
「Blueprints」フォルダに、ピックアップ クラスを格納するための「Pickups」という名前の新しい子フォルダを作成します。
アセット ツリーで、「C++ Classes」フォルダに移動します。 PickupBase クラスを右クリックし、そのクラスからブループリントを作成します。
「
BP_PickupBase」という名前を付けます。[Path (パス)] には、「Content/FirstPerson/Blueprints/Pickups」を選択し、[Create Pickup Base Class (ピックアップの基本クラスを作成)] をクリックします。
「Blueprints」 > 「Pickups」フォルダに戻ります。
BP_PickupBaseブループリントをレベルにドラッグします。 PickupBase のインスタンスがレベルに表示され、[Outliner (アウトライナー)] パネルで自動的に選択されます。 ただし、まだメッシュがありません。BP_PickupBaseアクタが選択されている状態で、[Details] パネルで次のプロパティを設定します。[Pickup Item ID (ピックアップアイテムID)] を
[pickup_001]に設定します。[Pickup Data Table (ピックアップデータテーブル)] を
[DT_PickupData]に設定します。[Should Respawn (リスポーンするか)] を [true] に設定します。
ゲームをテストするために [Play (プレイ)] をクリックすると、ピックアップはピックアップ アイテム ID を使用してデータ テーブルをクエリし、pickup_001 に関連付けられているデータを取得します。 ピックアップは、テーブル データと DA_Pickup_001 データ アセットへの参照を使用して、参照アイテムを初期化し、そのスタティックメッシュをロードします。
ピックアップの上を通過すると、ピックアップが消え、それから 4 秒後に再表示されるはずです。
異なるピックアップをロードする
[Pickup Item ID] を別の値に設定した場合、ピックアップはテーブルのその行からデータを取得します。
[Pickup Item ID] の切り替えを試すには、次の手順を実行します。
「DA_Pickup_002」という名前の新しいデータ アセットを作成します。 このアセットで次のプロパティを設定します。
[ID]:[pickup_002]
[Item Type (アイテムタイプ)]:[Consumable (消耗品)]
[Name (名前)]:Test Name 2
[Description (説明)]:Test Description 2
[World Mesh (ワールドメッシュ)]:
SM_ChamferCube
DT_PickupDataテーブルに新しい行を追加し、データ アセットの情報を新しい行のフィールドに入力します。BP_PickupBaseアクタで、[Pickup Item ID] を[pickup_002]に変更します。
ゲームをテストするために [Play] をクリックすると、ピックアップは代わりに DA_Pickup_002 からの値でスポーンします。
エディタでピックアップ アクタを更新する
ピックアップはゲーム内で機能しますが、デフォルト メッシュがないため、エディタでこれを視覚化するのが難しい場合があります。
これを修正するには、PostEditChangeProperty() 関数を使用します。 これは、エディタがプロパティを変更する場合に Unreal Engine が呼び出すインエディタの関数で、プロパティの変更時にビューポートでアクタを最新の状態に保つことができます。 たとえば、プレイヤーのデフォルトのヘルスを変更したときに UI 要素を更新したり、原点に近づけたり遠ざけたりして球体をスケーリングしたりできます。
このプロジェクトでは、ピックアップ アイテム ID が変更されるたびに、ピックアップの新しいスタティックメッシュに適用するために使用します。 こうすることで、[Play] をクリックしなくてもピックアップ タイプを変更してすぐにビューポートで更新を確認することができます。
ピックアップに変更を加えてすぐにエディタに表示するには、次の手順を実行します。
「
PickupBase.h」のprotectedセクションで、#if WITH_EDITORマクロを宣言します。 このマクロは、Unreal Header Tool 内のものはエディタ ビルドのみにパッケージし、ゲームのリリース バージョン用にはコンパイルしないように Unreal Header Tool に指示します。 このマクロを#endif文で終了します。C++#if WITH_EDITOR #endifこのマクロ内で、
PostEditChangeProperty()の仮想 void 関数のオーバーライドを宣言します。 この関数は、変更されたプロパティ、変更の型などに関する情報を含むFPropertyChangedEventへの参照を取得します。 ヘッダ ファイルを保存します。C++#if WITH_EDITOR // Runs whenever a property on this object is changed in the editor virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; #endif「
PickupBase.cpp」で、PostEditChangeProperty()関数を実装します。 まず、Super::PostEditChangeProperty()関数を呼び出し、親クラスのプロパティ変更を処理します。C++/** * Updates this pickup whenever a property is changed. * @param PropertyChangedEvent - contains info about the property that was changed. */ void APickupBase::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { // Handle parent class property changes Super::PostEditChangeProperty(PropertyChangedEvent); }「
ChangedProperty」という名前の新しいconst FName変数を作成し、変更したプロパティ名を格納します。C++// Handle parent class property changes Super::PostEditChangeProperty(PropertyChangedEvent); const FName ChangedPropertyName;PropertyChangedEventにプロパティが含まれていることを確認し、PropertyChangedEvent.Propertyを条件とする三項演算子を使用して、そのプロパティを保存します。 条件が true の場合、ChangedPropertyNameをPropertyChangedEvent.Property->GetFName()に設定し、false の場合は、NAME_Noneに設定します。C++// If a property was changed, get the name of the changed property. Otherwise use none. const FName ChangedPropertyName = PropertyChangedEvent.Property ? PropertyChangedEvent.Property->GetFName() : NAME_None;NAME_Noneは、FName型のグローバル Unreal Engine の定数であり、「有効な名前なし」または「null 名」を意味します。プロパティ名がわかったので、ID の変更を検出した場合に Unreal Engine にメッシュを更新させることができます。
if文で、ChangePropertyNameがGET_MEMBER_NAME_CHECKED()を呼び出した結果と等しいかどうかを確認し、このAPickupBaseクラスとPickupItemIDを渡します。 このマクロは、コンパイル時の確認を行い、渡したプロパティが渡されたクラス内に存在することを確認します。データ テーブルからも値を取得するため、
if文を入力する前にテーブルが有効であることも確認してください。C++// Verify that the changed property exists in this class and that the PickupDataTable is valid. if (ChangedPropertyName == GET_MEMBER_NAME_CHECKED(APickupBase, PickupItemID) && PickupDataTable) { }if文の中で、InitializePickup()で行ったのと同じ方法でFindRowを呼び出してこのピックアップに関連付けられたデータの行を取得します。今回は、操作を続行する前に
PickupItemIDがテーブル内にあることを確認するために、ネストされたif文のFindRow行を配置します。C++// Verify that the changed property exists in this class and that the PickupDataTable is valid. if (ChangedPropertyName == GET_MEMBER_NAME_CHECKED(APickupBase, PickupItemID) && PickupDataTable) { // Retrieve the associated ItemData for this pickup. if (const FItemData* ItemDataRow = PickupDataTable->FindRow<FItemData>(PickupItemID, PickupItemID.ToString())) { } }UE が行データを正常に見つけた場合、
ItemDataRowで参照される (新しいメッシュを含む) データ アセットを格納するためのTempItemDefinition変数を作成します。C++if (const FItemData* ItemDataRow = PickupDataTable->FindRow<FItemData>(PickupItemID, PickupItemID.ToString())) { UItemDefinition* TempItemDefinition = ItemDataRow->ItemBase;メッシュを更新するには、
PickupMeshComponentでSetStaticMeshを使用し、一時データ アセットのWorldMeshを渡します。C++// Set the pickup's mesh to the associated item's mesh PickupMeshComponent->SetStaticMesh(TempItemDefinition->WorldMesh.Get());SetSphereRadius(32.f)を使用して球体コンポーネントのコリジョン半径を設定します。C++// Set the sphere's collision radius SphereComponent->SetSphereRadius(32.f);コードを保存して Visual Studio からコンパイルします。
完成した PostEditChangeProperty() 関数は次のようになります。
/**
* Updates this pickup whenever a property is changed.
* @param PropertyChangedEvent - contains info about the property that was changed.
*/
void APickupBase::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
// Handle parent class property changes
Super::PostEditChangeProperty(PropertyChangedEvent);
// If a property was changed, get the name of the changed property. Otherwise use none.
エディタに戻り、アウトライナーで、BP_PickupBase アクタが選択されていることを確認します。 [Pickup Item ID] を [pickup_001] から [pickup_002] に変更し、それから元に戻します。 ID を変更すると、ビューポートでピックアップのメッシュが更新されます。
他のメッシュで試している場合は、エディタのビューポートで確認できるようになる前に、新しいメッシュを強制的に完全にロードさせるため、1 度ゲームをプレイする必要がある場合があります。
次の内容
次に、ピックアップ クラスをさらに拡張してカスタム ガジェットを作成し、それをキャラクターに装備します。
キャラクターを装備する
C++ を使用してカスタムの装備可能アイテムを作成し、それらをキャラクターにアタッチする方法について説明します。
完全なコード
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Components/SphereComponent.h"
#include "CoreMinimal.h"
#include "AdventureCharacter.h"
#include "GameFramework/Actor.h"
#include "PickupBase.generated.h"
// Copyright Epic Games, Inc. All Rights Reserved.
#include "PickupBase.h"
#include "ItemDefinition.h"
// Sets default values
APickupBase::APickupBase()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;