Unreal EngineでFPSゲームを作る:プロジェクト作成からAI敵まで
この記事では、Unreal Engine 5で一人称視点シューティング(FPS)を作る手順を説明します。Epic Games Launcherで最初のメニューをクリックするところから、プレイヤーを見つけて追いかける敵キャラクターを作るまでです。各部分は一つずつ実行できる手順になっており、コードファイルをどこに置くかも示します。図で全体の流れも確認できます。
必要なもの
Windows 10または11のパソコン、DirectX 12に対応したグラフィックスカード、100GB以上の空き容量、無料のEpic Gamesアカウントが必要です。C++コードをコンパイルするには、「C++によるゲーム開発」ワークロードを含むVisual Studio 2022をインストールしてください。Blueprintだけを使うならC++がなくても始められます。注意:この記事のコードはUnreal Engine 5のドキュメントに基づいて書いたもので、この記事を執筆した環境ではコンパイルとテストを行っていません。記載の結果は、手順を正しく行った場合に見えるはずのものです。
図解
1. インストールしてFPSプロジェクトを作成する
まずEpic Games LauncherからUnreal Engineをインストールし、用意されたテンプレートからプロジェクトを作成します。
1. Epic Games Launcherを開き、Epicアカウントでサインインする
2. 左のメニューからUnreal Engineを選び、Libraryタブを開く
3. + Add Versionをクリックし、最新のUnreal Engine 5を選んでInstallを押す
4. インストールが終わったら、そのバージョンのLaunchを押す
5. Unreal Project Browserの左パネルでGamesを選ぶ
6. First Personテンプレートを選ぶ
7. Project DefaultsでC++(コードを書く場合)またはBlueprint(ビジュアルで始める場合)を選ぶ
8. Starter Contentにチェックを入れ、基本的なモデルとマテリアルを入れる
9. プロジェクト名(例:FpsDemo)を付けてCreateを押す
**見えるはずの結果:** サンプルのレベルと一人称キャラクターを含むエディタが開きます。Playを押すと歩いたりカメラを回したりできます。
2. プロジェクトの構成とスクリプトの置き場所
プロジェクトの各部分の役割を確認します。C++コードはSourceフォルダに、BlueprintやモデルはContentにあります。
**注:** C++のスクリプトはすべてSource/FpsDemo/にあります。BlueprintはエディタでのみMake・編集します。Content/内のファイルは手で編集しないでください。
**コード: project-tree.txt**
FpsDemo/
├── FpsDemo.uproject ไฟล์โปรเจกต์ ดับเบิลคลิกเพื่อเปิดในเอดิเตอร์
├── Config/ ค่าตั้งต้นของเกม เช่น ปุ่มควบคุม
├── Content/ ไฟล์ของเกมที่สร้างในเอดิเตอร์ เช่น Blueprint โมเดล ฉาก เสียง
├── Source/
│ ├── FpsDemo/ โค้ด C++ ของเกม เขียนเป็นไฟล์ .h และ .cpp
│ │ └── FpsDemo.Build.cs ระบุว่าโมดูลนี้ใช้ไลบรารีอะไร เช่น AIModule
│ ├── FpsDemo.Target.cs
│ └── FpsDemoEditor.Target.cs
└── Binaries/, Intermediate/, Saved/ ไฟล์ที่ UE สร้างเอง ไม่ต้องแก้
3. 3Dモデルをプロジェクトに取り込む
多くのモデルはFBX形式で保存されています。Fabなど使用許可のあるところから入手し、ゲームで使う前にライセンスを確認してください。
1. 下のContent Browserで目的のフォルダを右クリックする(例:Content/Models)
2. Import to /Game/Modelsを選び、.fbxファイルを指定する
3. FBX Import Optionsで、骨格がある場合はSkeletal Mesh、固定物の場合はStatic Meshを選ぶ
4. Import Materials and Texturesにチェックを入れてImportを押す
5. Content Browserからモデルをレベルにドラッグして確認する
**見えるはずの結果:** モデルがContent Browserに表示され、レベルにドラッグすると シーン内に配置されます。
4. Enhanced Inputで操作を設定する
Unreal Engine 5はEnhanced Inputシステムで、キー入力と動作を分けています。そのためコードを変えずにキーを変更できます。
1. Content Browserで、テンプレートに含まれるInputフォルダを開く
2. IA_Fireを開き、射撃ボタン用のDigital(bool)のInput Actionであることを確認する
3. IMC_Defaultを開き、左マウスボタンがIA_Fireに割り当てられていることを確認する
4. キーを変えるにはIMC_Defaultの割り当てを編集してSaveを押す
5. Playを押し、左クリックで射撃が動くことを確認する
**見えるはずの結果:** 割り当てたボタンを押すたびに、キャラクターの射撃関数が呼ばれます。
5. ライントレースで射撃する
多くのFPSの銃は、カメラから前方へ見えない直線を飛ばし、何に当たったかを調べます。この例ではLineTraceSingleByChannelを呼び、当たったアクターにダメージを与えます。
1. Source/FpsDemo/FpsCharacter.hを開き、コードのとおりRangeとDamageの変数とFireの宣言を追加する
2. Source/FpsDemo/FpsCharacter.cppを開き、Fire関数を追加する
3. FpsCharacter.cppでテンプレートが作ったOnFire関数を探し、弾を生成する処理の代わりにFire()を呼ぶ
4. Ctrl+Alt+F11を押すか、エディタのCompileをクリックしてコードをビルドする
**コード: FpsCharacter_Fire.cpp**
// ไฟล์: Source/FpsDemo/FpsCharacter.h (เพิ่มในคลาสตัวละครผู้เล่นที่ template สร้างให้)
// UPROPERTY(EditAnywhere, Category = "Combat")
// float Range = 10000.f;
//
// UPROPERTY(EditAnywhere, Category = "Combat")
// float Damage = 25.f;
//
// void Fire();
//
// ไฟล์: Source/FpsDemo/FpsCharacter.cpp (ฟังก์ชัน Fire ด้านล่าง)
#include "FpsCharacter.h"
#include "Camera/CameraComponent.h"
#include "Engine/World.h"
#include "Kismet/GameplayStatics.h"
void AFpsCharacter::Fire()
{
if (!FirstPersonCameraComponent)
{
return;
}
// จุดเริ่มคือกล้อง และจุดสิ้นสุดคือระยะ Range ไปทางที่กล้องมอง
const FVector Start = FirstPersonCameraComponent->GetComponentLocation();
const FVector End = Start + FirstPersonCameraComponent->GetForwardVector() * Range;
FHitResult Hit;
FCollisionQueryParams Params;
Params.AddIgnoredActor(this); // ไม่ให้เส้นยิงชนตัวเอง
if (GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECC_Visibility, Params))
{
if (AActor* Target = Hit.GetActor())
{
UGameplayStatics::ApplyDamage(Target, Damage, GetController(), this, UDamageType::StaticClass());
}
}
}
**見えるはずの結果:** 左クリックで狙ったオブジェクトにダメージが入ります。射撃時にOutput Logにエラーが出なければ成功です。
6. 敵がダメージを受けて倒れる
Characterから敵のクラスを作ります。体力を持ち、TakeDamageでダメージを受け、体力がゼロになると消えます。
1. エディタでFile > New C++ Classを選び、親クラスにCharacterを指定して、名前をEnemyCharacterにする
2. EnemyCharacter.hを開き、コードのとおり宣言を追加する
3. EnemyCharacter.cppにコードを書く
4. コンパイルし、EnemyCharacterからBlueprintを作ってレベルに置く
**コード: EnemyCharacter.cpp**
// ไฟล์: Source/FpsDemo/EnemyCharacter.h
// UCLASS()
// class FPSDEMO_API AEnemyCharacter : public ACharacter
// {
// GENERATED_BODY()
// public:
// AEnemyCharacter();
// virtual float TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent,
// AController* EventInstigator, AActor* DamageCauser) override;
// protected:
// UPROPERTY(EditAnywhere, Category = "Combat")
// float Health = 100.f;
// };
//
// ไฟล์: Source/FpsDemo/EnemyCharacter.cpp
#include "EnemyCharacter.h"
#include "EnemyAIController.h"
AEnemyCharacter::AEnemyCharacter()
{
// ให้ AI Controller ของศัตรูเข้าควบคุมอัตโนมัติ เมื่อวางในฉากหรือสร้างขึ้นมา
AIControllerClass = AEnemyAIController::StaticClass();
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
}
float AEnemyCharacter::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent,
AController* EventInstigator, AActor* DamageCauser)
{
const float Applied = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
Health -= Applied;
if (Health <= 0.f)
{
Destroy(); // เมื่อพลังชีวิตหมด ศัตรูหายไปจากฉาก
}
return Applied;
}
**見えるはずの結果:** 敵の体力がゼロになるまで撃つと、レベルから消えます。
7. レベル、小物、NavMeshを追加する
敵は地面を歩く必要があるため、歩ける範囲の地図であるNavMeshが必要です。遮蔽物として小物も配置します。
1. Fabなど使用許可のある場所から小物を探し、Add to Projectを選ぶか、ダウンロードして取り込む
2. Content Browserから小物をレベルに置き、MoveとScaleのツールで位置と大きさを調整する
3. Place Actorsで Nav Mesh Bounds Volume を検索し、レベルにドラッグする
4. Volumeの大きさを、歩ける範囲を覆うように調整する
5. エディタでPキーを押し、NavMeshを表示する
**見えるはずの結果:** Pビューでは歩ける地面が緑で表示され、小物の下などは緑になりません。
8. プレイヤーを検知するAIコントローラー
AIコントローラーは敵の「頭脳」です。AI Perceptionでプレイヤーが見えるかを調べ、結果をBlackboardに書き込みます。
1. AIControllerから C++ クラスを作り、名前をEnemyAIControllerにする
2. FpsDemo.Build.csを開き、PublicDependencyModuleNamesに"AIModule"を追加する
3. .hと.cppのコードをプロジェクトに入れてコンパイルする
4. EnemyCharacterのコンストラクタがAIコントローラーを指定するので、敵を配置または生成すると自動で制御される
**コード: EnemyAIController.cpp**
// ไฟล์: Source/FpsDemo/EnemyAIController.h
// UCLASS()
// class FPSDEMO_API AEnemyAIController : public AAIController
// {
// GENERATED_BODY()
// public:
// AEnemyAIController();
// protected:
// virtual void BeginPlay() override;
// UFUNCTION()
// void OnTargetPerceptionUpdated(AActor* Actor, FAIStimulus Stimulus);
// UPROPERTY(VisibleAnywhere)
// UAIPerceptionComponent* Perception;
// UPROPERTY()
// UAISenseConfig_Sight* Sight;
// UPROPERTY(EditAnywhere, Category = "AI")
// UBehaviorTree* BehaviorTreeAsset;
// };
//
// ไฟล์: Source/FpsDemo/EnemyAIController.cpp
// ต้องเพิ่ม "AIModule" ใน PublicDependencyModuleNames ของ FpsDemo.Build.cs ก่อน
#include "EnemyAIController.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Perception/AIPerceptionComponent.h"
#include "Perception/AISenseConfig_Sight.h"
#include "Perception/AIPerceptionTypes.h"
AEnemyAIController::AEnemyAIController()
{
Perception = CreateDefaultSubobject<UAIPerceptionComponent>(TEXT("Perception"));
SetPerceptionComponent(*Perception);
Sight = CreateDefaultSubobject<UAISenseConfig_Sight>(TEXT("Sight"));
Sight->SightRadius = 1500.f; // มองเห็นได้ไกลเท่านี้
Sight->LoseSightRadius = 1800.f; // ต้องไกลเกินนี้จึงจะเสียการมองเห็น
Sight->PeripheralVisionAngleDegrees = 90.f;
Sight->DetectionByAffiliation.bDetectEnemies = true;
Sight->DetectionByAffiliation.bDetectNeutrals = true;
Sight->DetectionByAffiliation.bDetectFriendlies = true;
Perception->ConfigureSense(*Sight);
Perception->SetDominantSense(Sight->GetSenseImplementation());
Perception->OnTargetPerceptionUpdated.AddDynamic(this, &AEnemyAIController::OnTargetPerceptionUpdated);
}
void AEnemyAIController::BeginPlay()
{
Super::BeginPlay();
if (BehaviorTreeAsset)
{
RunBehaviorTree(BehaviorTreeAsset);
}
}
void AEnemyAIController::OnTargetPerceptionUpdated(AActor* Actor, FAIStimulus Stimulus)
{
// เห็นผู้เล่นเมื่อใด ให้เขียนลงช่อง TargetActor ใน Blackboard ที่ Behavior Tree ใช้อยู่
if (UBlackboardComponent* BB = GetBlackboardComponent())
{
BB->SetValueAsObject(TEXT("TargetActor"), Stimulus.WasSuccessfullySensed() ? Actor : nullptr);
}
}
**見えるはずの結果:** プレイヤーが敵の視界の距離と角度に入ると、Blackboardの TargetActor キーにプレイヤーが入ります。
9. Behavior TreeとBlackboard
Behavior Treeは行動を決める図です。例えば、目標があれば近づいて撃ち、なければ見回ります。Blackboardはその判断に使うデータを保存します。
1. Content Browserで右クリックし、Artificial Intelligence > Blackboardを選び、名前をBB_Enemyにする
2. BB_Enemyを開き、Object型のTargetActorというキーを追加する
3. もう一度右クリックし、Artificial Intelligence > Behavior Treeを選び、名前をBT_Enemyにする
4. BT_Enemyを開き、Blackboard AssetをBB_Enemyに設定する
5. ルートにSelectorを置き、TargetActorが設定されているかを調べるBlackboard条件付きのSequenceを作り、その後にTargetActorへ向かうMoveToタスクを置く
6. 敵のBlueprintの詳細で、AI ControllerのBehavior Treeの欄にBT_Enemyを指定する
**見えるはずの結果:** 敵がプレイヤーを見つけると近づいて追いかけ、プレイヤーが視界から外れると追うのをやめます。
10. ビルドとテスト
すべてが一緒に動くことを確認し、よくある問題を直します。
1. CompileまたはCtrl+Alt+F11を押して、C++コードをビルドする
2. エディタでPlayを押し、敵に近づく
3. 敵が消えるまで撃ち、ダメージが設定どおりか確認する
4. Window > Developer Tools > Output Logを開き、警告やエラーを読む
**見えるはずの結果:** ゲームが動きます。敵は見つけたプレイヤーの方を向き、撃たれると体力が減り、体力がゼロになると消えます。
次のステップ
次は、射撃音、スコア、スタートメニュー、敵のアニメーションを追加しましょう。主な参考資料は、Unreal Engine Learning Portal と dev.epicgames.com/documentation のUnreal Engine 5ドキュメントです。
まとめ
Epic Games LauncherのテンプレートからFPSプロジェクトを作り、モデルを取り込み、ライントレースで射撃と敵へのダメージを書き、AIコントローラーでプレイヤーを検知させ、Behavior TreeとBlackboardにつなぎます。各手順はメニューのクリックか、順番どおりのコードの貼り付けです。