ستتعلم في هذا المقال كيفية برمجة الأعداء في لعبة ثلاثية الأبعاد في محرك الألعاب جودو، حيث سنطلق على كل عدو اسم mob. ستتعرف أيضًا على كيفية توليدهم عشوائيًا في مواقع مختلفة حول منطقة اللعب.
إنشاء مشهد الأعداء
لنصمم الأعداء في لعبتنا نحتاج لإنشاء مشهد جديد وسيكون هيكل العقدة مشابهًا لمشهد اللاعب الذي أطلقنا عليه اسم player.tscn
.
أنشئ مشهدًا يحتوي على عقدة CharacterBody3D
لتكون بمثابة جذر المشهد فهذه العقدة توفر وظائف فيزياء جاهزة وتوفر حركات واقعية وأطلق عليها اسم Mob
، وأضف عقدة فرعية Node3D
وأطلق عليها اسم Pivot
لتكون كنقطة ارتكاز لتحريك الشخصية، ثم اسحب وأفلت الملف mob.glb
من قائمة نظام الملفات إلى Pivot
لإضافة نموذج العدو ثلاثي الأبعاد إلى المشهد.
بعد سحب الملف ستنشأ عقدة جديدة تمثل الكائن الرسومي داخل اللعب، يمكنك إعادة تسميتها إلى Character
نحتاج إلى هيكل تصادم collision shape ليعمل الجسم بالشكل المتوقع ويتمكن من التفاعل مع الفيزياء أو الاصطدامات داخل اللعبة بطريقة صحيحة، لذا انقر بزر الفأرة الأيمن على عقدة جذر المشهد Mob
ثم انقر فوق إضافة عقدة فرعية Add Child Node.
أضف العقدة CollisionShape3D
تحتوي العقدة CollisionShape3D
على خاصية الشكل Shape. اربط بهذه الخاصية عقدة BoxShape3D
من قائمة الفاحص Inspector لإضافة شكل صندوق ثلاثي الأبعاد لتحديد حدود تصادم الكائنات كما في الصورة التالية:
لتحسين تجربة اللعبة، عليك تعديل حجم الصندوق BoxShape3D
بحيث يتناسب بشكل أفضل مع النموذج ثلاثي الأبعاد، يمكنك القيام بذلك بسهولة عن طريق النقر على النقاط البرتقالية الظاهرة على الصندوق وسحبها لتغيير حجمه.
يجب أن يلمس الصندوق الأرض ويكون أقل ثخانة من النموذج بقليل، حيث تعمل محركات الفيزياء بطريقة تجعل الاصطدام يحدث إذا لامس مجسّم اللاعب الكروي حتى زاوية الصندوق، وقد تموت على مسافة من العدو إذا كان الصندوق كبيرًا جدًا مقارنة بالنموذج ثلاثي الأبعاد، مما يجعل اللعبة غير منصفة للاعبين ولا تحقق تجربة لعب صحيحة.
لاحظ أن الصندوق الذي يمثل منطقة التصادم الذي أضفناها للعدو أكبر من النموذج ثلاثي الأبعاد للعدو، لا بأس في هذا ضمن هذه اللعبة لأننا ننظر إلى المشهد من الأعلى ونستخدم منظور ثابت أي أن الكاميرا تعرض المشهد من الأعلى ولا تتحرك، فلا يجب أن تتطابق أشكال التصادم تمامًا مع النموذج وإذا كانت منطقة التصادم الأكبر تجعل اللعبة أكثر سهولة أو متعة عند اختبارها، فلا بأس في ذلك.
إزالة شخصيات الأعداء الموجودين خارج الشاشة
سنعمل على توليد شخصيات الأعداء في اللعبة على فترات زمنية منتظمة في مرحلة اللعبة. لكن انتبه فقد يزداد عدد الأعداء في هذه الحالة إلى ما لا نهاية إذا لم نكن حذرين، وليس هذا ما نريده لأن كل نسخة من العدو لها تكلفة ذاكرة ومعالجة ولا حاجة لتحمل تكلفتها عندما يكون العدو خارج الشاشة.
فبمجرد أن يخرج عدو ما من نطاق الشاشة فإننا لم نعد بحاجته بعد ذلك ويجب حذفه. لحسن الحظ يوفر محرك الألعاب جودو عقدة تكتشف خروج الكائنات من حدود الشاشة تسمى VisibleOnScreenNotifier3D
وسنستخدمها لتدمير الأعداء.
ملاحظة: عندما تستمر في استنساخ كائن ما ينصح باستخدام تقنية تسمى pooling وتعني تجميع الكائنات وإعادة استخدامها لتجنب تكلفة إنشاء وتدمير النسخ في كل مرة، فهي تعتمد على إنشاء مسبق لمصفوفة من الكائنات وحفظها في الذاكرة وإعادة استخدامها مرارًا وتكرارًا لتحسين الأداء. لكن لا داعي للقلق بشأن هذا الأمر عند العمل بلغة GDScript، فالسبب الرئيسي لاستخدام تقنية pooling هو تجنب التجمد في اللغات التي تعتمد على كنس المهملات garbage-collected مثل لغة C#، أما لغة GDScript فتستخدم تقنية عد المراجع مختلفة في إدارة الذاكرة.
لمراقبة ما إذا كان الكائن قد خرج من الشاشة حدد عقدة Mob
وأضف عقدة فرعية VisibleOnScreenNotifier3D
، سيظهر صندوق آخر وردي اللون هذه المرة وستصدر العقدة إشارة عندما يخرج هذا المربع تمامًا عن الشاشة.
غيّر حجمه باستخدام النقاط البرتقالية حتى يغطي النموذج ثلاثي الأبعاد بالكامل.
برمجة حركة العدو
دعنا الآن نعمل على حركة الأعداء، وسننجز ذلك على مرحلتين، الأولى سنكتب سكريبت على Mob
يعرّف دالة لتهيئة العدو، ثم نبرمج آلية الظهور العشوائي في مشهد main.tscn
ونستدعي الدالة من هناك.
أرفق سكريبت بالعقدة Mob
.
فيما يلي الشيفرة البرمجية التي ستنجز الحركة بلغة GDScript، كما تلاحظ عرفنا خاصيتين min_speed
و max_speed
لتحديد نطاق سرعة عشوائي والذي سنستخدمه لاحقًا لضبط قيمة سرعة الحركة CharacterBody3D.velocity
.
extends CharacterBody3D # سرعة العدو الدنيا مقدرة بالمتر في الثانية. @export var min_speed = 10 # سرعة العدو القصوى مقدرة بالمتر في الثانية. @export var max_speed = 18 func _physics_process(_delta): move_and_slide()
سنحرك العدو على غرار تحريك اللاعب في كل إطار عن طريق استدعاء الدالة CharacterBody3D.move_and_slide()
. لكن لا نعمل هذه المرة على تحديث السرعة في كل إطار بل نريد أن يتحرك العدو بسرعة ثابتة ويخرج من الشاشة حتى لو اصطدم بعائق.
نحتاج إلى تعريف دالة أخرى لحساب CharacterBody3D.velocity
حيث تحوّل هذه الدالة العدو باتجاه اللاعب وتضفي طابعًا عشوائيًا على كل من زاوية حركته وسرعته.
ستأخذ الدالة موقع ظهور العدو أول مرة start_position
، وموقع اللاعب player_position
كوسيط. نضع العدو في الموقع start_position
ونوجهه نحو اللاعب باستخدام الدالةlook_at_from_position()
، ولجعل الحركة أكثر طبيعية نعطي العدو زاوية عشوائية عن طريق الدوران بمقدار عشوائي حول المحور Y بحيث لا يكون دائمًا موجهًا بشكل مباشر نحو اللاعب. تعطينا الدالة randf_range()
في الكود التالي قيمة عشوائية بين -PI/4
راديان و PI/4
راديان وسنستخدمها لتدوير العدو.
# يتم استدعاء هذه الدالة من المشهد الأساسي func initialize(start_position, player_position): # نحدد واحد من الأعداء بوضعه في start_position # ونديره باتجاه player_position ليواجه اللاعب look_at_from_position(start_position, player_position, Vector3.UP) # دوّر هذا العدو تلقائيًا بين -45 و +45 درجة # لكي لا تتحرك نحو اللاعب بشكل مباشر. rotate_y(randf_range(-PI / 4, PI / 4))
لقد حصلنا على موقع عشوائي، والآن نحتاج إلى تحديد سرعة عشوائية باستخدام random_speed
. سنستخدم الدالة randi_range()
للحصول على قيمة عدد صحيح عشوائي حيث سنحدد الحد الأدنى min_speed
والحد الأقصى max_speed
للسرعة.أما random_speed
فهو مجرد عدد صحيح نستخدمه لمضاعفة سرعة الحركة CharacterBody3D.velocity
. بعد ذلك، سنوجه شعاع CharacterBody3D.velocity
نحو اللاعب مع تطبيق random_speed
لتحديد السرعة.
func initialize(start_position, player_position): # ... # نحسب السرعة العشوائية (عدد صحيح) var random_speed = randi_range(min_speed, max_speed) # نحسب سرعة أمامية تمثّل السرعة velocity = Vector3.FORWARD * random_speed # ثم ندوّر شعاع السرعة على اتجاه دوران العدو حول Y # لكي يتحرك في اتجاه نظر العدو velocity = velocity.rotated(Vector3.UP, rotation.y)
مغادرة الشاشة
ما زلنا بحاجة إلى تدمير الأعداء عندما يخرجون من الشاشة، لذلك سنربط إشارة screen_exited
الخاصة بعقدة VisibleOnScreenNotifier3D
بالعقدة Mob
.
عد إلى نافذة العرض لاثي الأبعاد، اضغط على التسمية ثلاثي الأبعاد 3D أعلى المحرر أو يمكنك الضغط على Ctrl+F2
أو Alt+2
على نظام التشغيل macOS.
حدد العقدة VisibleOnScreenNotifier3D
وانتقل إلى القائمة التي تعرض معلومات العقدة في الجانب الأيمن من واجهة المحرر، ثم انقر نقرًا مزدوجًا فوق الإشارة screen_exited()
التي تُرسل عندما يخرج الكائن من الشاشة.
صِل هذه الإشارة بالعقدة Mob
سيؤدي هذا إلى إعادتك إلى محرر النصوص وإضافة دالة جديدة _on_visible_on_screen_notifier_3d_screen_exited()
ثم استدعِ من خلالها تابع queue_free()
حيث تعمل هذه الدالة على تدمير النسخة التي يتم استدعاؤها عليها.
func _on_visible_on_screen_notifier_3d_screen_exited(): queue_free()
بهذا أصبح العدو جاهزًا للدخول إلى منطقة اللعب وسنشرح في الدرس التالي كيفية توليد الأعداء داخل مستوى اللعبة.
إليك الشيفرة البرمجية الكاملة للملف Mob.gd
للرجوع إليها:
extends CharacterBody3D # سرعة العدو الدنيا مقدرة بالمتر في الثانية @export var min_speed = 10 # سرعة العدو القصوى مقدرة بالمتر في الثانية @export var max_speed = 18 func _physics_process(_delta): move_and_slide() # يتم استدعاء هذه الدالة من المشهد الأساسي func initialize(start_position, player_position): # نحدد واحد من العدو بوضعه في start_position # ونديره باتجاه player_position, ليواجه اللاعب.. look_at_from_position(start_position, player_position, Vector3.UP) # دوّر هذا العدو تلقائيًا بين -45 و +45 درجة, # لكي لا تتحرك نحو اللاعب بشكل مباشر rotate_y(randf_range(-PI / 4, PI / 4)) # نحسب السرعة العشوائية (عدد صحيح) var random_speed = randi_range(min_speed, max_speed) # نحسب سرعة أمامية تمثّل السرعة velocity = Vector3.FORWARD * random_speed # ثم ندوّر شعاع السرعة على اتجاه دوران العدو حول Y # لكي يتحرك في اتجاه نظر العدو velocity = velocity.rotated(Vector3.UP, rotation.y) func _on_visible_on_screen_notifier_3d_screen_exited(): queue_free()
الخلاصة
تعلمنا في هذا المقال تصميم وضبط مشهد الأعداء في لعبتنا ثلاثية الأبعاد في محرك جودو، كما شرحنا كيفية إزالة شخصيات الأعداء بعد تخطيهم لحدود الشاشة، بهذا أصبح مشروعنا جاهزًا لبرمجة وتصميم التفاعلات بين اللاعب والأعداء، وهو ما سنفعله في الدرس التالي.
ترجمة -وبتصرف- لقسم Designing the mob scene من توثيق جودو الرسمي.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.