صناعة لعبة FPS بمحرك Unreal Engine: من إنشاء المشروع إلى عدو بالذكاء الاصطناعي
تشرح هذه المقالة كيفية صناعة لعبة تصويب بمنظور الشخص الأول (FPS) باستخدام Unreal Engine 5، بدءًا من أول نقرة في قائمة Epic Games Launcher وصولًا إلى عدو يرى اللاعب ويطارده. تتكون كل مرحلة من خطوات يمكنك تنفيذها واحدة تلو الأخرى، مع توضيح مكان وضع ملفات الكود، والرسوم التوضيحية تعرض المسار العام.
ما الذي تحتاجه
تحتاج إلى حاسوب يعمل بنظام Windows 10 أو 11، وبطاقة رسوميات تدعم DirectX 12، ومساحة خالية لا تقل عن 100 غيغابايت، وحساب Epic Games مجاني. لتجميع كود C++ ثبّت Visual Studio 2022 مع حزمة "تطوير الألعاب بلغة 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. استيراد نموذج ثلاثي الأبعاد إلى المشروع
تُحفظ معظم النماذج بصيغة 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 خطًا مستقيمًا غير مرئي من الكاميرا إلى الأمام وتتحقق مما يصطدم به. يستدعي هذا المثال 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. بعد التجميع أنشئ 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 أو حمّلها واستوردها
2. اسحب الأغراض من Content Browser إلى المرحلة وضعها باستخدام أدوات Move وScale
3. في Place Actors ابحث عن Nav Mesh Bounds Volume واسحبه إلى المرحلة
4. غيّر حجم الـ Volume بحيث يغطي المنطقة القابلة للمشي
5. اضغط المفتاح P في المحرر لعرض NavMesh
**ما يجب أن تراه:** في عرض P تظهر الأرض القابلة للمشي باللون الأخضر، ولا يظهر اللون الأخضر تحت الأغراض.
8. متحكم ذكاء اصطناعي يكتشف اللاعب
متحكم الذكاء الاصطناعي هو "دماغ" العدو. يستخدم AI Perception للتحقق من رؤية اللاعب، ثم يكتب النتيجة في Blackboard.
1. أنشئ صنف C++ من AIController واسمه EnemyAIController
2. افتح FpsDemo.Build.cs وأضف "AIModule" إلى PublicDependencyModuleNames
3. ضع ملفي .h و.cpp في المشروع ثم جمّعهما
4. يحدد المُنشئ في EnemyCharacter متحكم الذكاء الاصطناعي، لذلك يتولى التحكم تلقائيًا عند وضع العدو في المرحلة أو إنشائه
**الكود: 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
شجرة السلوك مخطط لاتخاذ القرار، مثلًا: إذا كان هناك هدف فاقترب منه واطلق النار، وإلا فاتجه في دورية. أما Blackboard فيخزن البيانات التي تعتمد عليها الشجرة في القرار.
1. انقر بزر الفأرة الأيمن في Content Browser واختر Artificial Intelligence > Blackboard وسمّه BB_Enemy
2. افتح BB_Enemy وأضف مفتاحًا باسم TargetActor من نوع Object
3. انقر بزر الفأرة الأيمن مرة أخرى واختر Artificial Intelligence > Behavior Tree وسمّه BT_Enemy
4. افتح BT_Enemy واجعل Blackboard Asset هو BB_Enemy
5. أضف Selector في الجذر، ثم Sequence بشرط Blackboard يتحقق من أن TargetActor محدد، يتبعه مهمة MoveTo تستخدم TargetActor
6. في تفاصيل Blueprint العدو اضبط Behavior Tree في متحكم الذكاء الاصطناعي ليكون BT_Enemy
**ما يجب أن تراه:** عندما يرى العدو اللاعب يتجه نحوه، وعندما يخرج اللاعب من مجال رؤيته يتوقف عن المطاردة.
10. البناء والاختبار
تحقق من أن كل الأجزاء تعمل معًا، ثم عالج المشكلات الشائعة.
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 والضرر على العدو، واضبط متحكم الذكاء الاصطناعي ليكتشف اللاعب، ثم اربطه بشجرة السلوك وBlackboard. كل خطوة إما نقرة في القائمة أو لصق للكود بالترتيب.