สร้างเกม FPS ด้วย Unreal Engine ตั้งแต่สร้างโปรเจกต์จนถึงศัตรู AI
บทความนี้พาคุณสร้างเกมยิงมุมมองบุคคลที่หนึ่ง (FPS) ด้วย Unreal Engine 5 ตั้งแต่คลิกเมนูแรกใน Epic Games Launcher จนถึงการสร้างตัวละครศัตรูที่มองเห็นผู้เล่นและไล่ตามได้ เนื้อหาแบ่งเป็นขั้นตอนที่ทำตามได้ทีละข้อ มีบอกว่าต้องวางไฟล์โค้ดไว้ตรงไหน และมีแผนผังช่วยให้เห็นภาพรวม
สิ่งที่ต้องเตรียม
ต้องมีคอมพิวเตอร์ที่ลง Windows 10 หรือ 11 การ์ดจอที่รองรับ DirectX 12 พื้นที่ว่างอย่างน้อย 100 GB และบัญชี Epic Games ฟรี ควรติดตั้ง Visual Studio 2022 พร้อมชุดพัฒนาเกม C++ เพื่อใช้คอมไพล์โค้ด C++ หากต้องการเขียนเฉพาะ Blueprint ก็เริ่มได้โดยไม่ต้องใช้ C++ หมายเหตุ: โค้ดในบทความเป็นตัวอย่างที่เขียนตามเอกสารของ Unreal Engine 5 แต่ยังไม่ได้คอมไพล์และทดสอบในเครื่องที่ใช้เขียนบทความนี้ ผลลัพธ์ที่ระบุคือสิ่งที่ควรเห็นเมื่อทำถูกต้อง
ภาพประกอบ
1. ติดตั้งและสร้างโปรเจกต์ FPS
ขั้นแรกติดตั้ง Unreal Engine ผ่าน Epic Games Launcher แล้วสร้างโปรเจกต์จากเทมเพลตพร้อมใช้งาน
1. เปิด Epic Games Launcher แล้วล็อกอินด้วยบัญชี Epic Games
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
**ผลที่ควรเห็น:** เอดิเตอร์เปิดขึ้นพร้อมฉากตัวอย่าง มีตัวละคร First Person ที่เดินและหมุนกล้องได้เมื่อกด Play
2. โครงสร้างโปรเจกต์และที่วางสคริปต์
ดูว่าไฟล์แต่ละส่วนของโปรเจกต์ทำหน้าที่อะไร และโค้ด C++ ต้องวางไว้ที่โฟลเดอร์ Source ส่วน Blueprint และโมเดลอยู่ใน Content
**หมายเหตุ:** สคริปต์ C++ ทั้งหมดอยู่ใน Source/FpsDemo/ ส่วน Blueprint สร้างและแก้ไขในเอดิเตอร์ ไฟล์ใน 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. นำโมเดล 3 มิติเข้าโปรเจกต์
โมเดลส่วนใหญ่บันทึกเป็นไฟล์ 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 เพื่อดูว่าเป็น Input Action ชนิด Digital (bool) สำหรับปุ่มยิง
3. เปิด IMC_Default แล้วดูว่าปุ่มเมาส์ซ้ายผูกกับ IA_Fire อยู่
4. ถ้าต้องการเปลี่ยนปุ่ม ให้แก้ค่าในช่องของ IMC_Default แล้วกด Save
5. ในหน้าต่างเกม กด Play แล้วทดสอบว่าคลิกซ้ายแล้วมีการทำงาน
**ผลที่ควรเห็น:** เมื่อกดปุ่มที่ผูกไว้ ฟังก์ชันยิงในตัวละครจะถูกเรียกทุกครั้ง
5. ยิงด้วย Line Trace
การยิงปืนในเกม FPS ส่วนใหญ่ใช้การยิงเส้นตรงจากกล้องไปข้างหน้า (Line Trace) แล้วดูว่าชนอะไร ตัวอย่างนี้เรียก 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 ใน Editor เพื่อคอมไพล์โค้ด
**โค้ดที่ใช้: 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 เป็น Parent Class ตั้งชื่อ EnemyCharacter
2. เปิดไฟล์ EnemyCharacter.h แล้วเพิ่มประกาศตามตัวอย่าง
3. วางโค้ดใน EnemyCharacter.cpp ตามตัวอย่าง
4. คอมไพล์โค้ด แล้วสร้าง Blueprint จาก EnemyCharacter เพื่อวางในฉาก
**โค้ดที่ใช้: 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 หรือดาวน์โหลดแล้ว Import
2. ลากวัตถุจาก Content Browser ลงในฉาก แล้วปรับตำแหน่งและขนาดด้วยเครื่องมือ Move และ Scale
3. ใน Place Actors ค้นหา Nav Mesh Bounds Volume แล้วลากลงฉาก
4. ปรับขนาดของ Volume ให้ครอบคลุมพื้นที่ที่เดินได้
5. กด P บนแป้นพิมพ์ในเอดิเตอร์ เพื่อดูพื้นที่สีเขียวที่แสดงว่า NavMesh ถูกสร้างแล้ว
**ผลที่ควรเห็น:** ในโหมด P จะเห็นพื้นสีเขียวบนพื้นที่ที่เดินได้ และไม่มีสีเขียวบนพื้นที่ที่วัตถุบังอยู่
8. AI Controller ตรวจจับผู้เล่น
AI Controller คือ 'สมอง' ของศัตรู ใช้ AI Perception ตรวจจับว่าเห็นผู้เล่นหรือไม่ แล้วเขียนผลลงใน Blackboard
1. สร้าง C++ Class จาก AIController ชื่อ EnemyAIController
2. เปิดไฟล์ FpsDemo.Build.cs แล้วเพิ่ม "AIModule" ในรายการ PublicDependencyModuleNames
3. วางโค้ด .h และ .cpp ตามตัวอย่าง แล้วคอมไพล์
4. ใน EnemyCharacter ให้ตั้งค่าตามที่โค้ดกำหนด เมื่อวางศัตรูในฉาก AI Controller จะเข้าควบคุมอัตโนมัติ
**โค้ดที่ใช้: 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);
}
}
**ผลที่ควรเห็น:** เมื่อผู้เล่นเดินเข้าไปในระยะและมุมมองของศัตรู ช่อง TargetActor ใน Blackboard จะมีค่าเป็นผู้เล่น
9. Behavior Tree และ Blackboard
Behavior Tree เป็นแผนผังการตัดสินใจ เช่น ถ้ามีเป้าหมายให้เดินเข้าไปและยิง ถ้าไม่มีให้เดินตรวจตรา Blackboard เก็บข้อมูลที่แผนผังใช้ตัดสินใจ
1. คลิกขวาใน Content Browser เลือก Artificial Intelligence > Blackboard แล้วตั้งชื่อ BB_Enemy
2. เปิด BB_Enemy แล้วเพิ่ม Key ชื่อ TargetActor ชนิด Object
3. คลิกขวาอีกครั้งเลือก Artificial Intelligence > Behavior Tree แล้วตั้งชื่อ BT_Enemy
4. เปิด BT_Enemy แล้วตั้ง Blackboard Asset เป็น BB_Enemy
5. เพิ่ม Selector ที่ราก แล้วเพิ่ม Sequence ที่มีเงื่อนไข Blackboard ตรวจว่า TargetActor ไม่ว่างแล้วให้ MoveTo ตาม TargetActor
6. ในคลาส EnemyAIController ตั้งค่า BehaviorTreeAsset เป็น BT_Enemy ในรายละเอียดของ Blueprint ศัตรู
**ผลที่ควรเห็น:** เมื่อศัตรูเห็นผู้เล่น จะเดินตามผู้เล่น และเมื่อผู้เล่นหลุดจากการมองเห็น ศัตรูจะหยุดตาม
10. Build และทดสอบ
ตรวจสอบว่าทุกอย่างทำงานร่วมกัน แล้วแก้ปัญหาที่พบบ่อย
1. กด Compile หรือ Ctrl+Alt+F11 เพื่อคอมไพล์โค้ด C++
2. กด Play ในเอดิเตอร์ แล้วเดินไปใกล้ศัตรู
3. ยิงศัตรูจนหายไป และดูว่ามีดาเมจตามที่ตั้งไว้
4. เปิด Window > Developer Tools > Output Log เพื่อดูข้อความแจ้งเตือนและข้อผิดพลาด
**ผลที่ควรเห็น:** เกมรันได้ ศัตรูหันหาผู้เล่นเมื่อเห็น ยิงแล้วพลังชีวิตลด และหายไปเมื่อพลังชีวิตหมด
ขั้นต่อไป
ขั้นต่อไปให้เพิ่มเสียงยิง ระบบคะแนน เมนูเริ่มเกม และแอนิเมชันศัตรู ใช้ Unreal Engine Learning Portal และเอกสาร Unreal Engine 5 ที่ dev.epicgames.com/documentation เป็นแหล่งอ้างอิงหลัก
สรุป
สร้างโปรเจกต์ FPS จากเทมเพลตใน Epic Games Launcher นำโมเดลเข้ามาใช้ เขียนการยิงด้วย Line Trace และดาเมจกับศัตรู ตั้งค่า AI Controller ให้ตรวจจับผู้เล่น แล้วเชื่อมกับ Behavior Tree และ Blackboard ทุกขั้นตอนเป็นการคลิกเมนูหรือวางโค้ดตามลำดับ