開始する前に
前のセクションで次の目標が完了していることを確認してください。 一人称視点のアドベンチャー ゲームをコーディングする:
「入力アクションを使用してプレイヤー キャラクターを作成する」で C++ の一人称視点プレイヤー キャラクターをビルドする。
「アイテムとデータを管理する」でアイテム データを管理するためのデータ駆動のゲームプレイ要素を設定する。
「リスポーンするピックアップ アイテムを作成する」でピックアップ アイテムを作成し、レベルに追加する。
新しい CreateItemCopy 関数で参照アイテムを作成する
新しい装備可能なピックアップ アイテムを作成する前に、まず ItemDefinition クラスと PickupBase クラスを変更して、さまざまなアイテム タイプからの参照アイテムのキャプチャをサポートさせる必要があります。
PickupBase クラスの InitializePickup() 関数で、UItemDefinition 型の ReferenceItem を設定します。 これは制限が厳しすぎます。参照アイテムをこのように設定すると、UItemDefinition から派生した新しい特化したアイテム クラスに追加するプロパティは含まれません。
これを解決するには、ItemDefinition に新しい仮想関数を作成して、そのアイテムのコピーを作成して返すようにします。 これは仮想関数であるため、UItemDefinition から継承するどのクラスでもオーバーライドできます。 PickupBase が関数を呼び出すと、コンパイラは呼び出し元のクラスに基づいて、呼び出す適切な関数を決定します。
この関数を親の ItemDefinition クラスに追加することで、UItemDefinition から継承するアイテム タイプを増やすためにプロジェクトの拡張を続けることを決定した場合に、確実に利用可能になります。
参照アイテムを作成するための新しい CreateItemCopy() 関数を定義するには、次の手順を実行します。
「
ItemDefinition.h」を開きます。publicセクションで、UItemDefinitionポインタを返す「CreateItemCopy()」という名前の新しい仮想 const 関数を宣言します。C++// Creates and returns a copy of the item. virtual UItemDefinition* CreateItemCopy() const;「
ItemDefinition.cpp」で、CreateItemCopy()関数を実装します。 この内部で、StaticClass()を使用して、「ItemCopy」という名前の新しいUItemDefinitionオブジェクト ポインタを作成します。C++UItemDefinition* UItemDefinition::CreateItemCopy() const { UItemDefinition* ItemCopy = NewObject<UItemDefinition>(StaticClass()); }Visual Studio は
UItemDefinition::StaticClass()とStaticClass()をはっきり区別します。ItemCopyの各フィールドをこのクラスのフィールドに割り当て、ItemCopyを返します。C++/** * Creates and returns a copy of this Item Definition. * @return a copy of the item. */ UItemDefinition* UItemDefinition::CreateItemCopy() const { UItemDefinition* ItemCopy = NewObject<UItemDefinition>(StaticClass()); ItemCopy->ID = this->ID; ItemCopy->ItemType = this->ItemType;
次に、ReferenceItem を手動で設定するコードを削除し InitializePickup() 関数をリファクタリングし、そのコードを CreateItemCopy() 呼び出しに置き換えます。
InitializePickup() を新しい CreateItemCopy() 関数で更新するには、次の手順を実行します。
「
PickupBase.cpp」を開き、InitializePickup()に移動します。ReferenceItemを定義し設定する次の 5 行を削除します。C++ReferenceItem = NewObject<UItemDefinition>(this, UItemDefinition::StaticClass()); ReferenceItem->ID = ItemDataRow->ID; ReferenceItem->ItemType = ItemDataRow->ItemType; ReferenceItem->ItemText = ItemDataRow->ItemText; ReferenceItem->WorldMesh = ItemDataRow->ItemBase->WorldMesh;ReferenceItemで、次のようにTempItemDefinition->CreateItemCopy()を呼び出します。C++// Create a copy of the item with the class type ReferenceItem = TempItemDefinition->CreateItemCopy();
「PickupBase.cpp」を保存します。 InitializePickup() 関数は次のようになります。
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());
UItemDefinition* TempItemDefinition = ItemDataRow->ItemBase.Get();
// Create a copy of the item with the class type
ReferenceItem = TempItemDefinition->CreateItemCopy();
}装備可能なツール データを定義する
前のセクションでは、テーブル データを具体的に表現した、インタラクト可能なピックアップ オブジェクトをレベル内に作成する方法を学びました。 このセクションでは、キャラクターが装備するツールをビルドする方法について説明します。
新しい装備可能なツールを設定するには、次を作成します。
EquippableToolDefinition:ツールのデータを格納するItemDefinitionから派生したデータ アセット クラス。EquippableToolBase:インゲームでツールを表すアクタ クラス。 キャラクターがツールを持って操作できるように、アニメーション、入力マッピング、メッシュをキャラクターに提供します。
キャラクターがツールを拾ったり装備したりできるようにするには、次の要素を追加します。
アイテムを格納する場所。
インベントリにある各アイテムのタイプを知る方法。
ツールを装備する方法。
EquippableToolBase アクタはキャラクターが保持および使用しているツールを表し、PickupBase アクタはレベル内のピックアップ アイテムを表すことに注意してください。 キャラクターがピックアップ アイテムを装備する前に、アイテムと衝突する必要があるため、コリジョンが成功した後に、PickupBase を変更し、アイテムをキャラクターに付与します。
次に、新しいツール クラスをビルド済みのピックアップおよびデータ テーブルと組み合わせ、カスタム ダーツ ランチャーを作成してキャラクターにアタッチします。
まず、新しい ItemDefinition クラスにツールのデータを定義します。
新しい EquippableToolDefinition クラスを作成するには、次の手順を実行します。
Unreal Editor で、[Tools] > [New C++ Class] に移動します。 [All Classes] に移動して、「ItemDefinition」を検索して親クラスとして選択し、[Next] をクリックします。
クラスに「
EquippableToolDefinition」という名前を付けて [Create Class] をクリックします。Visual Studio で、「
EquippableToolDefinition.h」の先頭に、”ItemDefinition.h”のインクルードを追加し、次の前方宣言を追加します。class UInputMappingContext:装備可能な各ツールは、そのツールを操るキャラクターに適用する入力マッピング コンテキストへの参照を保持する必要があります。class AEquippableToolBase:インゲームのツールを表すアクタ。 次の手順でこれを作成します。C++#pragma once #include "CoreMinimal.h" #include "ItemDefinition.h" #include "EquippableToolDefinition.generated.h" class AEquippableToolBase; class UInputMappingContext; UCLASS(BlueprintType, Blueprintable)
publicセクションで、「ToolAsset」という名前のAEquippableToolBase型のTSubclassOfプロパティを追加します。 これに、EditDefaultsOnlyのUPROPERTY()マクロを追加します。C++// The tool actor associated with this item UPROPERTY(EditDefaultsOnly) TSubclassOf<AEquippableToolBase> ToolAsset;TSubclassOf<AEquippableToolBase>は、型安全性を確保しながらAEquippableToolBaseのブループリント サブクラスを参照可能にする、UClassのテンプレート ラッパーです。 さまざまなタイプのアクタを動的にスポーンするゲームプレイのシナリオで役立ちます。ToolAssetを使用して、キャラクターに装備されたときにツール アクタを動的にスポーンします。UItemDefinitionで宣言したCreateItemCopy()関数のオーバーライドを宣言します。 このオーバーライドにより、UEquippableToolDefinitionクラスのコピーが作成され、返されます。完成した
EquippableToolDefinition.hファイルは、次のようになります。C++// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "ItemDefinition.h" #include "EquippableToolDefinition.generated.h" class AEquippableToolBase; class UInputMappingContext;「
EquippableToolDefinition.cpp」内でCreateItemCopy()関数を実装します。 これは、「ItemDefinition.cpp」のCreateItemCopy()関数に似ていますが、ToolAssetもコピーする必要があります。C++// Copyright Epic Games, Inc. All Rights Reserved. #include "EquippableToolDefinition.h" UEquippableToolDefinition* UEquippableToolDefinition::CreateItemCopy() const { UEquippableToolDefinition* ItemCopy = NewObject<UEquippableToolDefinition>(StaticClass()); ItemCopy->ID = this->ID; ItemCopy->ItemType = this->ItemType;
両方の EquippableToolDefinition クラス ファイルを保存します。
装備可能なツール アクタを設定する
次に、装備可能なツール アクタの作成を開始します。 これは、ツールのアニメーション、制御、メッシュをキャラクターに追加するインゲームの表現です。
新たに基本装備可能なツール アクタを作成し、設定するには、次の手順を実行します。
Unreal Editor で、[Tools] > [New C++ Class] に移動します。 親クラスとして [Actor] を選択し、クラスに「EquippableToolBase」という名前を付けます。
[Create Class] をクリックします。 Unreal Engine が、VS で新しいクラスのファイルを自動的に開きます。
「
EquippableToolBase.h」の先頭で、AAdventureCharacterクラスとUInputActionクラスを前方宣言します。 装備可能なツールは、ツール固有の入力アクションをそのキャラクターにバインドするために、どのキャラクターに装備されているかを把握している必要があります。クラスの宣言の
UCLASSマクロ内で、BlueprintType指定子とBlueprintable指定子を追加し、このクラスをブループリントに公開します。C++UCLASS(BlueprintType, Blueprintable) class ADVENTUREGAME_API AEquippableToolBase : public AActor
ツール アニメーションを宣言する
「EquippableToolBase.h」の public セクションで、「FirstPersonToolAnim」と「ThirdPersonToolAnim」という名前の UAnimBlueprint プロパティに 2 つの TObjectPtr を追加します。 これは、このツールを装備している場合にキャラクターが使用する一人称視点と三人称視点のアニメーションです。
これらのプロパティに EditAnywhere と BlueprintReadOnly の UPROPERTY() マクロを追加します。
// First Person animations
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TObjectPtr<UAnimBlueprint> FirstPersonToolAnim;
// Third Person animations
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TObjectPtr<UAnimBlueprint> ThirdPersonToolAnim;ツールのメッシュを作成する
「EquippableToolBase.h」の public セクションで、「ToolMeshComponent」という名前の USkeletalMeshComponent に TObjectPtr を追加します。 これは、装備時にキャラクターに見えるツールのスケルタルメッシュです。 EditAnywhere と BlueprintReadOnly の UPROPERTY() マクロを追加します。
// Tool Skeletal Mesh
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TObjectPtr<USkeletalMeshComponent> ToolMeshComponent;「EquippableToolBase.cpp」で、AEquippableToolBase() コンストラクタ関数を変更して、デフォルトの USkeletalMeshComponent を作成し、ToolMeshComponent に割り当てます。 次に、ToolMeshComponent が null でないことを確認し、ロードされたときにツールにモデルが存在することを確認します。
AEquippableToolBase::AEquippableToolBase()
{
// 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 tool's mesh component
ToolMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("ToolMesh"));
check(ToolMeshComponent != nullptr);
}
ツールの所有者を宣言する
「EquippableToolBase.h」の public セクションで、「OwningCharacter」という名前のキャラクター クラスのインスタンスに TObjectPtr を作成します。 EditAnywhere と BlueprintReadOnly の UPROPERTY() マクロを追加します。
これは、このツールが現在装備されているキャラクターです。
// The character holding this tool
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TObjectPtr<AAdventureCharacter> OwningCharacter;入力とツール使用時の関数を宣言する
ツールには、キャラクターに設定する必要がある入力マッピング コンテキストと入力アクションが備わっています。
入力マッピング コンテキストを追加するには、public セクションで、「ToolMappingContext」という名前の UInputMappingContext に TObjectPtr を宣言します。 EditAnywhere と BlueprintReadOnly の UPROPERTY() マクロを追加します。
// The input mapping context associated with this tool
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TObjectPtr<UInputMappingContext> ToolMappingContext;移動コントロールを実装した場合と同様に、ツール使用時のアクションを実装する関数と、入力アクションをその関数にバインドする新しい関数を追加します。
「EquippableToolBase.h」の public セクションで、「Use()」と「BindInputAction()」という名前の 2 つの仮想 void 関数を宣言します。
キャラクター移動の制御を実装した際は、ターゲット関数の正確な名前を渡す必要がある InputComponent の BindAction() 関数を使用しました。 関数の完全な名前がまだ分からないため、[ToolChildClass]::Use を渡し、BindAction を呼び出すための、各 EquippableToolBase サブクラスに実装できるカスタムの BindInputAction() 関数が必要です。
BindInputAction() 関数は、const UInputAction ポインタを受け取り、指定された入力アクションをキャラクターの Use() にバインドします。
// Use the tool
UFUNCTION()
virtual void Use();
// Binds the Use function to the owning character
UFUNCTION()
virtual void BindInputAction(const UInputAction* ActionToBind);「EquippableToolBase.cpp」に、Use() 関数と BindInputAction() 関数を実装します。 これらは親クラスでは何も実行しないため、現時点では空白のままにできます。 EquippableToolBase サブクラスを作成するとき、これらにロジックを追加します。たとえば、Use() 関数には、発射物の発射やドアの開閉など、ツール固有のアクションを含む必要があります。
void AEquippableToolBase::Use()
{
}
void AEquippableToolBase::BindInputAction(const UInputAction* ActionToBind)
{
}
コードを保存して VS からコンパイルします。
「EquippableToolBase.h」ファイルは次のようになります。
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "EquippableToolBase.generated.h"
class AAdventureCharacter;
class UInputAction;
「EquippableToolBase.cpp」は次のようになります。
// Copyright Epic Games, Inc. All Rights Reserved.
#include "EquippableToolBase.h"
#include "AdventureCharacter.h"
AEquippableToolBase::AEquippableToolBase()
{
// 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;
キャラクターにアイテムを付与する
キャラクターが使用できるツールを定義しましたが、まだ装備することはできません。 次に、キャラクターがアイテムを拾ったときに保管および装備できるように、インベントリ システムを追加します。
インベントリ コンポーネントをビルドする
キャラクターのインベントリは、キャラクターに機能を追加するものの、ゲーム ワールドには存在しない必要があるため、キャラクターが所有するアイテムを把握し、ツールを交換したり、同じツールを 1 つ以上取得できないようにしたりできるインベントリを定義する Actor Components クラスを使用します。
Unreal Editor で、[Tools] > [New C++ Class] に移動します。 親クラスとして [Actor Component (アクタコンポーネント)] を選択し、クラスに「InventoryComponent」という名前を付けます。
[Create Class] をクリックします。
VS で、「InventoryComponent.h」の先頭にUEquippableToolDefinition を前方宣言します。 これは、インベントリに格納するクラスです。
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "InventoryComponent.generated.h"
class UEquippableToolDefinition;
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class ADVENTUREGAME_API UInventoryComponent : public UActorComponent
{
GENERATED_BODY()
public セクションで、「ToolInventory」という名前の UEquippableToolDefinition ポインタの新しい TArray を宣言します。 これに、VisibleAnywhere、BlueprintReadOnly、Category = ツールの UPROPERTY() マクロを追加します。
public:
// Sets default values for this component's properties
UInventoryComponent();
// The array of tools stored in this inventory.
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Tools)
TArray<UEquippableToolDefinition*> ToolInventory;これは、ツールのみを格納するインベントリですが、拡張して任意のタイプのアイテムを含めるようにすることができます。 より汎用的な実装では、UItemDefinition または TSubclassOf<UItemDefinition> の値のみを格納し、UI、アイコン、サウンド エフェクト、コスト、および他のアイテム プロパティを含む、より複雑なインベントリをビルドします。
完成した「InventoryComponent.h」ファイルは次のようになります。
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "InventoryComponent.generated.h"
class UEquippableToolDefinition;
ツールとインベントリの宣言をキャラクターに追加する
アイテムの格納場所が確保されたため、アイテムを付与するロジックでキャラクターをアップグレードする準備ができました。
まず、キャラクターの .h ファイルの先頭で、AEquippableToolBase クラス、UEquippableToolDefinition クラス、UInventoryComponent クラスを前方宣言します。
#include "CoreMinimal.h"
#include "Camera/CameraComponent.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "GameFramework/Character.h"
#include "InputActionValue.h"
#include "AdventureCharacter.generated.h"
class AEquippableToolBase;
class UAnimBlueprint;
protected セクションで、「UseAction」という名前の UInputAction に TObjectPtr を宣言します。 これが、ツールの Use() 関数にバインドする「ツールを使用する」入力アクションです。
// Use Input Actions
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
TObjectPtr<UInputAction> UseAction;キャラクターのインベントリ コンポーネントを作成する
キャラクターの .hファイルの、public セクションで、「InventoryComponent」という名前の UInventoryComponent に TObjectPtr を宣言します。 VisibleAnywhere および Category = インベントリの UPROPERTY() マクロを追加します。
// Inventory Component
UPROPERTY(VisibleAnywhere, Category = Inventory)
TObjectPtr<UInventoryComponent> InventoryComponent;キャラクターのコンストラクタ関数で、メッシュ コンポーネント サブオブジェクトを作成後、「InventoryComponent」という名前のデフォルトの UInventoryComponent サブオブジェクトを作成します。 これにより、キャラクターがスポーンされたときに、インベントリが適切に設定されます。
// Create an inventory component for the owning player
InventoryComponent = CreateDefaultSubobject<UInventoryComponent>(TEXT("InventoryComponent"));既存のインベントリを確認する
ツールをアタッチする前に、複数回装備しないように、プレイヤーがすでにツールを所有しているかどうかを確認します。
キャラクターの .hファイルの public セクションで、UEquippableToolDefinition ポインタを受け取り、プレイヤーのインベントリにツールがすでに存在する場合は true を返す、「IsToolAlreadyOwned()」という名前の関数を宣言します。
// Returns whether or not the player already owns this tool
UFUNCTION()
bool IsToolAlreadyOwned(UEquippableToolDefinition* ToolDefinition);キャラクターの .cppファイルである「AdventureCharacter.cpp」で、IsToolAlreadyOwned() 関数を実装します。 この内部の for ループで、InventoryComponent->ToolInventory 配列にアクセスすることでキャラクターのインベントリにある各ツールを取得します。
bool AAdventureCharacter::IsToolAlreadyOwned(UEquippableToolDefinition* ToolDefinition)
{
// Check that the character does not yet have this particular tool
for (UEquippableToolDefinition* InventoryItem : InventoryComponent->ToolInventory)
{
}
}
次に、if 文で、この関数に渡されるツールの ToolDefinition->ID が InventoryItem->ID と一致するかどうかを確認します。 一致する場合、キャラクターはすでにこのツールを所有するため、true を返します。 そうでない場合、for ループ後、ToolDefinition がインベントリに存在するアイテムと一致せず、したがって新しいアイテムであるため、false を返します。
bool AAdventureCharacter::IsToolAlreadyOwned(UEquippableToolDefinition* ToolDefinition)
{
// Check that the character does not yet have this particular tool
for (UEquippableToolDefinition* InventoryItem : InventoryComponent->ToolInventory)
{
if (ToolDefinition->ID == InventoryItem->ID)
{
return true;
}
}
ツールをアタッチする
キャラクターの .hファイルの public セクションで、UEquippableToolDefinition ポインタを受け取る「AttachTool()」という名前の関数を宣言します。 この関数は ToolDefinition 内のツールをプレイヤーに装備しようと試みます。
// Attaches and equips a tool to the player
UFUNCTION()
void AttachTool(UEquippableToolDefinition* ToolDefinition);protected セクションで、「EquippedTool」という名前の AEquippableToolBase に TObjectPtr を宣言します。 VisibleAnywhere 指定子、BlueprintReadOnly 指定子、Category = ツール UPROPERTY() 指定子を追加します。
// The currently-equipped tool
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Tools)
TObjectPtr<AEquippableToolBase> EquippedTool;キャラクターの .cppファイルで、AttachTool() を実装します。 初めに、if 文内で、IsToolAlreadyOwned() を呼び出してキャラクターがそのツールをすでに所有するかどうかを確認します。
void AAdventureCharacter::AttachTool(UEquippableToolDefinition* ToolDefinition)
{
// Only equip this tool if it isn't already owned
if (not IsToolAlreadyOwned(ToolDefinition))
{
}
}
アイテムをスポーンする
ToolDefinition 内に格納されている AEquippableToolBase ツールはアクタであるため、AttachTool() が呼び出されたときにロードされない場合があります。 これを処理するため、SpawnActor() 関数を使用して、ツールの新しいインスタンスをスポーンします。
SpawnActor() は、UWorld オブジェクトの一部であり、マップとそこにあるアクタを表すコア オブジェクトです。 任意のアクタから GetWorld() 関数を呼び出してアクセスします。
if 文内で、「ToolToEquip」という名前の新しい AEquippableToolBase ポインタを宣言します。 これを GetWorld()->SpawnActor() の呼び出し結果と等しく設定し、スポーンするアクタとして ToolDefinition->ToolAsset を、スポーンする位置として this->GetActorTransform() を渡します。
// Only equip this tool if it isn't already owned
if (not IsToolAlreadyOwned(ToolDefinition))
{
// Spawn a new instance of the tool to equip
AEquippableToolBase* ToolToEquip = GetWorld()->SpawnActor<AEquippableToolBase>(ToolDefinition->ToolAsset, this->GetActorTransform());
}ToolDefinition->ToolAsset を SpawnActor に渡す際、UE は ToolAsset のクラスタイプを調べ、そのタイプのアクタをスポーンすることを認識します。 (ToolAsset は、その ToolDefinition に関連付けられている EquippableToolBase アクタです。)
キャラクターにアイテムをアタッチする
スポーンしたツールをキャラクターにアタッチするには、「AttachementRules」という名前の新しい FAttachementTransformRules を宣言します。
FAttachementTransformRules は、アタッチするときに位置、回転、スケールを処理する方法を定義する構造体です。 末尾にEAttachmentRules とブール型の InWeldSimulatedBodies を受け取り、UE に物理が関与しているかどうかを伝えます。 true の場合、UE は動き回るときに両方のボディを 1 つとして相互作用できるように結合します。 よく使用されるアタッチメントルールには、KeepRelative (親への相対的なトランスフォームを維持)、KeepWorld (ワールド トランスフォームを維持)、SnapToTarget (親のトランスフォームへのスナップ) があります。
AttachmentRoles 定義に EAttachmentRule::SnapToTarget と true を追加します。
// Attach the tool to the First Person Character
FAttachmentTransformRules AttachmentRules(EAttachmentRule::SnapToTarget, true);次に、ToolToEquip->AttachToActor() を呼び出し、ツールをキャラクターにアタッチします。続いて、ToolToEquip->AttachToComponent() を呼び出し、ツールを一人称視点メッシュ コンポーネントの右手のソケットにアタッチします。
AttachToActor は、アクタをターゲット親アクタにアタッチし、AttachToComponent は、アクタのルート コンポーネントをターゲット コンポーネントにアタッチします。 その構文は以下のとおりです。
MyActor->AttachToActor(ParentActor, AttachmentRules, OptionalSocketName)
// Attach the tool to this character, and then the right hand of their first-person mesh
ToolToEquip->AttachToActor(this, AttachmentRules);
ToolToEquip->AttachToComponent(FirstPersonMeshComponent, AttachmentRules, FName(TEXT("HandGrip_R")));アイテムのアニメーションをキャラクターに追加する
SetAnimInstanceClass() を使用して、ツールの一人称視点と三人称視点アニメーションを渡し、一人称視点メッシュと三人称視点メッシュにアニメーションを設定します。
// Set the animations on the character's meshes.
FirstPersonMeshComponent->SetAnimInstanceClass(ToolToEquip->FirstPersonToolAnim->GeneratedClass);
GetMesh()->SetAnimInstanceClass(ToolToEquip->ThirdPersonToolAnim->GeneratedClass);SetAnimInstanceClass は、ランタイム時にスケルタルメッシュのアニメーション ブループリントを動的に変更します。異なるアニメーション セットのアイテムや武器を装備する際によく使用されます。 GeneratedClass は、ブループリントから生成された実際の AnimInstance クラスを取得します。
インベントリにアイテムを追加する
ToolInventory.Add() を使用して、キャラクターのインベントリにツールを追加します。
// Add the tool to this character's inventory
InventoryComponent->ToolInventory.Add(ToolDefinition);
ツールがアタッチされたため、ToolToEquip->OwningCharacter をこのキャラクターに設定します。
ToolToEquip->OwningCharacter = this;キャラクターへの新しいツールのアタッチが完了したため、EquippedTool を ToolToEquip に設定します。
EquippedTool = ToolToEquip;アイテムの制御をキャラクターに追加する
次に、ツールの入力アクションと入力マッピング コンテキストをキャラクターに追加します。
これは、「キャラクターの移動を設定する」の「キャラクターに入力マッピングをバインドする」のセクションで AAdventureCharacter::BeginPlay() を設定した方法と同じように実装します。PlayerController を取得し、if 文を使用して null ポインタを確認しながら拡張入力ローカル サブシステムを取得します。
// Get the player controller for this character
if (APlayerController* PlayerController = Cast<APlayerController>(Controller))
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
{
Subsystem->AddMappingContext(ToolToEquip->ToolMappingContext, 1);
}
}
今回は、ツールの入力マッピング コンテキストをプレイヤー サブシステムに追加するときに、優先度を 1 に設定します。 プレイヤーのメインのマッピング コンテキスト (FirstPersonContext) の優先度はより低い (0) ため、両方のマッピング コンテキストに同じキー バインディングがある場合、ToolToEquip->ToolMappingContext の入力バインディングは、FirstPersonContext よりも優先されます。
マッピング コンテキストを追加したあと、ToolToEquip->BindInputAction() を呼び出して UseAction を渡し、キャラクターの入力アクションをツールの Use() 関数にバインドします。
// Get the player controller for this character
if (APlayerController* PlayerController = Cast<APlayerController>(Controller))
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
{
Subsystem->AddMappingContext(ToolToEquip->ToolMappingContext, 1);
}
ToolToEquip->BindInputAction(UseAction);
}
完成した AttachTool() 関数は次のようになります。
void AAdventureCharacter::AttachTool(UEquippableToolDefinition* ToolDefinition)
{
// Only equip this tool if it isn't already owned
if (not IsToolAlreadyOwned(ToolDefinition))
{
// Spawn a new instance of the tool to equip
AEquippableToolBase* ToolToEquip = GetWorld()->SpawnActor<AEquippableToolBase>(ToolDefinition->ToolAsset, this->GetActorTransform());
// Attach the tool to the First Person Character
GiveItem() でさまざまなアイテム タイプをサポートする
ツールをアタッチする方法がありますが、ピックアップとそのアイテムの定義にはツール以上のものを含めることができるため、AttachTool() を呼び出す前にキャラクターがインタラクトしているアイテムの種類を知る方法が必要です。
GiveItem() 関数を作成し、渡された ItemDefinition の型に基づいて異なるアクションを実行するようにします。 「ItemData.h」で EItemType 列挙型でさまざまなアイテムのタイプを宣言しました。そのデータを使用して、異なるアイテムの定義を区別することができます。
「AdventureCharacter.h」のpublic セクションで、UItemDefinition() ポインタを受け取る「GiveItem()」という名前の関数を宣言します。 他のクラスでは、プレイヤーにアイテムを与えようとするときに、この関数を呼び出します。
// Public function that other classes can call to attempt to give an item to the player
UFUNCTION()
void GiveItem(UItemDefinition* ItemDefinition);「AdventureCharacter.cpp」で、GiveItem() を実装します。 まず、この関数に渡されるアイテムの ItemType に基づいてケースを切り替える switch 文を宣言します。
void AAdventureCharacter::GiveItem(UItemDefinition* ItemDefinition)
{
// Case based on the type of the item
switch (ItemDefinition->ItemType)
{
}
}
switch 文内で、EItemType::Tool、EItemType::Consumable、デフォルトのケースを宣言します。 このチュートリアルでは、ツールタイプのアイテムのみを実装するため、消耗品とデフォルトのケースでアイテムのタイプをログ記録し、スイッチ ケースから抜け出します。
// Case based on the type of the item
switch (ItemDefinition->ItemType)
{
case EItemType::Tool:
{
}
case EItemType::Consumable:
{
// Not yet implemented
break;
ツール ケース内で、ItemDefinition を「ToolDefinition」という名前の UEquippableToolDefinition ポインタにキャストします。
次に、ToolDefinition が null かを確認し、キャストが成功したことを確認します。 null ではない場合、AttachTool() を呼び出してキャラクターにツールをアタッチします。 そうでない場合、エラーを出力し、中断します。
case EItemType::Tool:
{
// If the item is a tool, attempt to cast and attach it to the character
UEquippableToolDefinition* ToolDefinition = Cast<UEquippableToolDefinition>(ItemDefinition);
if (ToolDefinition != nullptr)
{
AttachTool(ToolDefinition);
}
完成した GiveItem() 関数は次のようになります。
void AAdventureCharacter::GiveItem(UItemDefinition* ItemDefinition)
{
// Case based on the type of the item
switch (ItemDefinition->ItemType)
{
case EItemType::Tool:
{
// If the item is a tool, attempt to cast and attach it to the character
最後に、アイテム付与ロジックを実行するために設定する、インゲームのトリガーが必要です。 キャラクターがピックアップと衝突するとき、ピックアップはキャラクターの GiveItem() 関数を呼び出し、ピックアップの ReferenceItem をキャラクターに付与する必要があります。
これを行うには、「PickupBase.cpp」を開きます。
OnSphereBeginOverlap() で、キャラクターが有効かを確認したあと、Character->GiveItem(ReferenceItem) を呼び出してアイテムをキャラクターに付与します。
// Checking if it is a First Person Character overlapping
AAdventureCharacter* Character = Cast<AAdventureCharacter>(OtherActor);
if (Character != nullptr)
{
// Give the item to the character
Character->GiveItem(ReferenceItem);
// Unregister from the Overlap Event so it is no longer triggered
SphereComponent->OnComponentBeginOverlap.RemoveAll(this);
ツール データとアクタを設定したため、これらを使用して、キャラクターがインゲームで装備するリアルなツールをビルドすることができるようになりました。
ダーツ ランチャーを実装する
最初の装備可能なツールとして、発射物を発射できるダーツ ランチャーを作成します。 このセクションでは、まず、キャラクターが保持し、使用するツールを作成します。 このチュートリアルの次のセクションでは、発射物のロジックを実装します。
新しい DartLauncher クラスを設定する
Unreal Editor で、[Tools] > [New C++ Class] に移動します。
[All Classes] に移動して、「EquippableToolBase」を検索して親クラスとして選択し、クラスに「DartLauncher」と名前を付けます。 ツールのコードを格納するための「Tools」という名前の新しいフォルダを作成します。 [Create Class] をクリックします。
Visual Studio で、「DartLauncher.h」の先頭に次の手順を実行します。
”[ProjectName]/EquippableToolBase.h”の include 文を追加します。UCLASS()マクロにBlueprintType指定子とBlueprintable指定子を追加します。publicセクションで、AEquippableToolBaseからのUse()関数とBindInputAction()関数の両方にオーバーライドを宣言します。
完成した「DartLauncher.h」クラスは次のようになります。
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "AdventureGame/EquippableToolBase.h"
#include "DartLauncher.generated.h"
UCLASS(BlueprintType, Blueprintable)
class ADVENTUREGAME_API ADartLauncher : public AEquippableToolBase
「DartLauncher.cpp」で、ファイルの先頭に ”[ProjectName]/AdventureCharacter.h” の include 文を追加します。 これは、BindInputAction() 関数で必要になります。
#include "DartLauncher.h"
#include "AdventureGame/AdventureCharacter.h"ツール制御を実装する
これで特定のツールで作業し、どの関数がバインディングするかを把握したため、BindInputAction() を実装できます。
初めに、Use() 関数を実装します。 Use() 内にプレイヤーが関数をトリガーした場合に通知するデバッグ メッセージを追加します。
#include "DartLauncher.h"
#include "AdventureGame/AdventureCharacter.h"
void ADartLauncher::Use()
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Using the dart launcher!"));
}
次に、BindInputAction() 関数を実装します。 関数内の if 文内で、GetController() を使用して OwningCharacter のプレイヤー コントローラーを取得し、APlayerController にキャストします。 これは、AAdventureCharacter::BeginPlay() 関数にマッピング コンテキストを追加した方法と同様です。
void ADartLauncher::BindInputAction(const UInputAction* InputToBind)
{
// Set up action bindings
if (APlayerController* PlayerController = Cast<APlayerController>(OwningCharacter->GetController()))
{
}
}
「キャラクターの移動を設定する」の「移動アクションをバインディングする」のセクションで移動アクションをバインドした方法と同じように、別の if 文で、「EnhancedInputComponent」という名前の新しい UEnhancedInputComponent ポインタを宣言します。 これをこの関数に渡された PlayerInputComponent での Cast() の呼び出しの結果と同じにすると同時に、UEnhancedInputComponent にキャストします。
最後に、BindAction を使用して、BindAction() を使用してこの関数に渡された InputToBind アクションにADartLauncher::Use アクションをバインドします。 これは、指定されたアクションが発生すると Use() を呼び出すために、InputAction を Use(); にバインドします。
// Set up action bindings
if (APlayerController* PlayerController = Cast<APlayerController>(OwningCharacter->GetController()))
{
if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerController->InputComponent))
{
// Fire
EnhancedInputComponent->BindAction(InputToBind, ETriggerEvent::Triggered, this, &ADartLauncher::Use);
}
}
キャラクターの移動を設定するときに、失敗するとゲームがクラッシュする CastChecked<> を使用しました。 ここでは、ピックアップ制御が適切に初期化されない場合にゲームを停止させないようにするため、Cast<> を使用します。 失敗したキャストが重大なバグを示す場合にのみ、CastChecked<> を使用します。
コードを保存してコンパイルします。
BindInputAction() 関数と完成した「DartLauncher.cpp」クラスは次のようになります。
// Copyright Epic Games, Inc. All Rights Reserved.
#include "DartLauncher.h"
#include "AdventureGame/AdventureCharacter.h"
void ADartLauncher::Use()
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Using the dart launcher!"));
}
アニメーション ブループリントをキャラクターに適合させる
一人称視点テンプレートには、武器タイプのアイテムのサンプル アニメーション ブループリントが含まれますが、ダーツ ランチャーで機能するようにするにはブループリントにいくつかの変更を加える必要があります。
既存のアニメーション ブループリントをキャラクターに適合させるには、次の手順を実行します。
コンテンツ ブラウザで、「Content」 > 「Variant_Shooter」 > 「Anim」フォルダに移動し、「
ABP_FP_Pistol」アニメーション ブループリントを右クリックして、[Duplicate (複製)] を選択しますこのコピーに「ABP_FP_DartLauncher」という名前を付け、「Content」 > 「FirstPerson」 > 「Anims」 フォルダにドラッグし、[Move Here (ここに移動)] を選択します。
ABP_TP_Pistolは、BP_FPShooterキャラクターに固有のロジックを使用しません。キャラクターに合わせて変更する必要はありません。[Event Graph (イベントグラフ)] の上部近くで、Event Blueprint Begin Play ノードから始まる一連のノードにズームインします。
このブループリントは、
BP_FPShooterから 一人称視点メッシュ変数と一人称視点カメラ変数を取得するため、これを代わりにキャラクター ブループリントに変更します (このチュートリアルはBP_AdventureCharacterを使用します)。次の各ノードをクリックし [Delete (削除)] を押します。
Cast To BP_FPShooter
First Person Mesh
First Person Camera
Event Graph を右クリックして、Cast To BP_AdventureCharacter ノードを検索し、選択します。
Event Blueprint Begin Play から新しいノードに実行ピンを接続し、続いて Set First Person Mesh ノードに接続します。
Try Get Pawn Owner ノードの Return Value ピンを Cast To ノードの Object ピンに接続します。
Cast To BP_AdventureCharacter から新しいノードを作成するには、As BP My Adventure Character ピンをクリックし、グラフの空白部分にドラッグします。
「Get Mesh」を検索し、[Variables (変数)] > [Character (キャラクター)] から選択します。 新しいノードの Mesh ピンを Set First Person Mesh ノードに接続します。
カメラについては、As BP My First Person Character ピンから別のノートをドラッグして、「Get Component by Class」を検索し、選択します。
必ず「by」が小文字の Get Component by Class ノードを作成してください。
新しいノードで、[Component Class (コンポーネントクラス)] を [Camera Component (カメラコンポーネント)] に設定します。 次に、Return Value ピンを Set First Person Camera ノードに接続します。
ABP_FP_DartLauncher ブループリントを保存してコンパイルします。
ダーツ ランチャー制御を定義する
キャラクターがツールから発射物を発射できるように、ダーツ ランチャーには入力アクションと入力マッピング コンテキストが必要です。
ダーツ ランチャー ツールのプレイヤー制御を作成するには、次の手順を実行します。
コンテンツ ブラウザで、「Input」 > 「Actions」フォルダに移動します。
「ツールを使用する」入力アクションを作成し、次の手順を実行して設定します。
[Add (追加)] をクリックして [Input (入力)] に移動し、[Input Action (入力アクション)] を選択します。 「IA_UseTool」という名前を付けます。
「IA_UseTool 」をダブルクリックして開きます。 [Details (詳細)] パネルで、[Value Type (値の種類)] が [Digital (bool) (デジタル (ブール型))] であることを確認します。
[Triggers (トリガー)] の横の [+] ボタンをクリックし、トリガーのリストから [Pressed (押下)] を選びます。
入力アクションを保存して閉じます。
コンテンツ ブラウザに戻り、「Input」フォルダに移動します。
マウスの左ボタンと、ゲームパッドの右トリガーをダーツ ランチャーの Use アクションにマッピングする新しい入力マッピング コンテキストを作成して設定します。
「IMC_DartLauncher」という名前の新しい入力マッピング コンテキストを作成します。
「IMC_DartLauncher」を開きます。 [Mappings (マッピング)] の横にある [+] ボタンをクリックします。
ドロップダウン リストで「
IA_UseTool」を選択します。矢印をクリックしてマッピングを展開します。 キーボード ボタンをクリックし、マウスの左ボタンを押してそのボタンを
IA_UseToolにバインドします。IA_UseTool の横の [+] ボタンをクリックして別のバインディングを追加します。 ドロップダウンリストで、[Gamepad (ゲームパッド)] を展開し、[Gamepad Right Trigger Axis (ゲームパッド右トリガー軸)] を選択します。
入力マッピング コンテキストを保存して閉じます。
DartLauncher ブループリントを作成する
メイン エディタに戻り、コンテンツ ブラウザのアセット ツリーで、「C++ Classes」フォルダに移動し、DartLauncher クラスを右クリックして新しいブループリント クラスを作成します。
「BP_DartLauncher」という名前を付けます。 「FirstPerson」 > 「Blueprints」フォルダで、装備可能なアイテムを格納するための「Tools」という名前の新しいフォルダを作成し、ブループリントの作成を終了します。
ブループリントの[Details] パネルで、次のデフォルト プロパティを設定します。
[First Person Tool Anim (一人称視点ツールアニメーション)] を
[ABP_FP_DartLauncher]に設定します。[Third Person Tool Anim (三人称視点ツールアニメーション)] を
[ABP_TP_Pistol]に設定します。[Tool Mapping Context (ツールマッピングコンテキスト)] を
[IMC_DartLauncher]に設定します。
[Components (コンポーネント)] タブで、[Tool Mesh Component (ツールメッシュコンポーネント)] を選択し、[Skeletal Mesh Asset (スケルタルメッシュアセット)] を [SKM_Pistol] に設定します。
ダーツ ランチャー データ アセットを作成する
このブループリントのデータを格納するデータ アセットを作成するには、次の手順を実行します。
コンテンツ ブラウザの「FirstPerson」 > 「Data」フォルダで、新しいデータ アセットを作成し、データ アセット インスタンスとして [Equippable Tool Definition (装備可能なツールの定義)] を選択します。 このアセットに「
DA_DartLauncher」という名前を付けます。DA_DartLauncher内の [Details] パネルで、次のプロパティを設定します。[Tool Asset (ツールアセット)] を
[BP_DartLauncher]に設定します。[ID] を [tool_001] に設定します。
[Item Type (アイテムタイプ)] を [Tool (ツール)] に設定します。
[World Mesh (ワールドメッシュ)] を
[SM_Pistol]に設定します。
[Name (名前)] と [Description (説明)] を入力します。
データ アセットを保存します。
ツールのデータ テーブルを作成する
このツールは DT_PickupData テーブルに配置できますが、特定のものをトラックするためにテーブルを整理する場合に便利です。 たとえば、特定のクラスが装備できるアイテム用に異なるテーブルを作成したり、異なる敵が倒されたときにドロップするアイテムのテーブルを個別に作成したりできます。 このチュートリアルでは、消耗品用のテーブルとツール用のテーブルを作成します。
ツール アイテムを追跡するための新しいデータ テーブルを作成するには、次の手順を実行します。
コンテンツ ブラウザで、「FirstPerson > Data」フォルダに移動し、新しいデータ テーブルを作成します。
行の構造体として [ItemData] を選択します。
テーブルに「DT_ToolData」と名前を付けて、ダブルクリックして開きます。
DT_ToolData 内で [Add] をクリックし、ダーツ ランチャー用の新しい行を作成します。
新しい行を選択した状態で、次のフィールドを設定します。
[Row Name (行名)] と ID を「tool_001」に設定します。
[Item Type] を [Tool] に設定します。
[Item Base (アイテムベース)] を
[DA_DartLauncher]に設定します。
データ テーブルを保存して閉じます。
ゲーム内でダーツ ランチャーのピックアップをテストする
これで、ユーザーにアイテムを付与するようにピックアップ クラスを変更し、プレイヤーに新しいメッシュ、アニメーション、制御を提供する装備可能なアイテム クラスを作成して、ダーツ ランチャー ツールを設定できました。 すべてを統合して、チュートリアルのこの部分で設定した装備可能なアイテム ロジックをすべてトリガーする、インゲームのピックアップを作成します。
コンテンツ ブラウザで、「Content」 > 「FirstPerson」 > 「Blueprints」に移動して、新しい BP_PickupBase をレベルにドラッグします。 [Pickup Item ID (ピックアップアイテムID)] を tool_001 に、[Pickup Data Table (ピックアップデータテーブル)] を [DT_ToolData] に設定します。
[Play] をクリックしてゲームをテストします。 ゲームが開始したら、ピックアップはダーツ ランチャーに初期化されます。 ピックアップの上を通過すると、キャラクターがツールを保持し始めるはずです。
次の内容
最後のセクションでは、ダーツ ランチャーに発射物の物理を実装し、フォーム ダーツを発射するようにします。
発射物を実装する
C++ を使用して発射物を実装し、ゲームプレイ時にスポーンする方法について説明します。
完全なコード
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "ItemData.h"
#include "ItemDefinition.generated.h"
/**
* Defines a basic item with a static mesh that can be built from the editor.
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "ItemDefinition.h"
#include "EquippableToolDefinition.generated.h"
class AEquippableToolBase;
class UInputMappingContext;
// Copyright Epic Games, Inc. All Rights Reserved.
#include "EquippableToolDefinition.h"
UEquippableToolDefinition* UEquippableToolDefinition::CreateItemCopy() const
{
UEquippableToolDefinition* ItemCopy = NewObject<UEquippableToolDefinition>(StaticClass());
ItemCopy->ID = this->ID;
ItemCopy->ItemType = this->ItemType;
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "InventoryComponent.generated.h"
class UEquippableToolDefinition;
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "AdventureGame/EquippableToolBase.h"
#include "DartLauncher.generated.h"
UCLASS(BlueprintType, Blueprintable)
class ADVENTUREGAME_API ADartLauncher : public AEquippableToolBase
// Copyright Epic Games, Inc. All Rights Reserved.
#include "DartLauncher.h"
#include "AdventureGame/AdventureCharacter.h"
void ADartLauncher::Use()
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Using the dart launcher!"));
}
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "EquippableToolBase.generated.h"
class AAdventureCharacter;
class UInputAction;
// Copyright Epic Games, Inc. All Rights Reserved.
#include "EquippableToolBase.h"
#include "AdventureCharacter.h"
AEquippableToolBase::AEquippableToolBase()
{
// 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;
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Camera/CameraComponent.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "GameFramework/Character.h"
#include "InputActionValue.h"
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AdventureCharacter.h"
#include "EquippableToolBase.h"
#include "EquippableToolDefinition.h"
#include "ItemDefinition.h"
#include "InventoryComponent.h"
// Sets default values
AAdventureCharacter::AAdventureCharacter()