اذهب إلى المحتوى

ستتعلم في هذا المقال كيفية برمجة الأعداء في لعبة ثلاثية الأبعاد في محرك الألعاب جودو، حيث سنطلق على كل عدو اسم mob. ستتعرف أيضًا على كيفية توليدهم عشوائيًا في مواقع مختلفة حول منطقة اللعب.

إنشاء مشهد الأعداء

لنصمم الأعداء في لعبتنا نحتاج لإنشاء مشهد جديد وسيكون هيكل العقدة مشابهًا لمشهد اللاعب الذي أطلقنا عليه اسم player.tscn.

أنشئ مشهدًا يحتوي على عقدة CharacterBody3D لتكون بمثابة جذر المشهد فهذه العقدة توفر وظائف فيزياء جاهزة وتوفر حركات واقعية وأطلق عليها اسم Mob، وأضف عقدة فرعية Node3D وأطلق عليها اسم Pivot لتكون كنقطة ارتكاز لتحريك الشخصية، ثم اسحب وأفلت الملف mob.glb من قائمة نظام الملفات إلى Pivot لإضافة نموذج العدو ثلاثي الأبعاد إلى المشهد.

1

بعد سحب الملف ستنشأ عقدة جديدة تمثل الكائن الرسومي داخل اللعب، يمكنك إعادة تسميتها إلى Character

2

نحتاج إلى هيكل تصادم collision shape ليعمل الجسم بالشكل المتوقع ويتمكن من التفاعل مع الفيزياء أو الاصطدامات داخل اللعبة بطريقة صحيحة، لذا انقر بزر الفأرة الأيمن على عقدة جذر المشهد Mob ثم انقر فوق إضافة عقدة فرعية Add Child Node.

3

أضف العقدة CollisionShape3D

4

تحتوي العقدة CollisionShape3D على خاصية الشكل Shape.  اربط بهذه الخاصية عقدة BoxShape3D من قائمة الفاحص Inspector لإضافة شكل صندوق ثلاثي الأبعاد لتحديد حدود تصادم الكائنات كما في الصورة التالية:

5

لتحسين تجربة اللعبة، عليك تعديل حجم الصندوق BoxShape3D بحيث يتناسب بشكل أفضل مع النموذج ثلاثي الأبعاد، يمكنك القيام بذلك بسهولة عن طريق النقر على النقاط البرتقالية الظاهرة على الصندوق وسحبها لتغيير حجمه.

يجب أن يلمس الصندوق الأرض ويكون أقل ثخانة من النموذج بقليل، حيث تعمل محركات الفيزياء بطريقة تجعل الاصطدام يحدث إذا لامس مجسّم اللاعب الكروي حتى زاوية الصندوق، وقد تموت على مسافة من العدو إذا كان الصندوق كبيرًا جدًا مقارنة بالنموذج ثلاثي الأبعاد، مما يجعل اللعبة غير منصفة للاعبين ولا تحقق تجربة لعب صحيحة.

6

لاحظ أن الصندوق الذي يمثل منطقة التصادم الذي أضفناها للعدو أكبر من النموذج ثلاثي الأبعاد للعدو، لا بأس في هذا ضمن هذه اللعبة لأننا ننظر إلى المشهد من الأعلى ونستخدم منظور ثابت أي أن  الكاميرا تعرض المشهد من الأعلى ولا تتحرك، فلا يجب أن تتطابق أشكال التصادم تمامًا مع النموذج وإذا كانت منطقة التصادم الأكبر تجعل اللعبة أكثر سهولة أو متعة عند اختبارها، فلا بأس في ذلك.

إزالة شخصيات الأعداء الموجودين خارج الشاشة

سنعمل على توليد شخصيات الأعداء في اللعبة على فترات زمنية منتظمة في مرحلة اللعبة. لكن انتبه فقد يزداد عدد الأعداء في هذه الحالة إلى ما لا نهاية إذا لم نكن حذرين، وليس هذا ما نريده لأن كل نسخة من العدو لها تكلفة ذاكرة ومعالجة ولا حاجة لتحمل تكلفتها عندما يكون العدو خارج الشاشة.

فبمجرد أن يخرج عدو ما من نطاق الشاشة فإننا لم نعد بحاجته بعد ذلك ويجب حذفه. لحسن الحظ يوفر محرك الألعاب جودو عقدة تكتشف خروج الكائنات من حدود الشاشة تسمى VisibleOnScreenNotifier3D وسنستخدمها لتدمير الأعداء.

ملاحظة: عندما تستمر في استنساخ كائن ما ينصح باستخدام تقنية تسمى pooling وتعني تجميع الكائنات وإعادة استخدامها لتجنب تكلفة إنشاء وتدمير النسخ في كل مرة، فهي تعتمد على إنشاء مسبق لمصفوفة من الكائنات وحفظها في الذاكرة وإعادة استخدامها مرارًا وتكرارًا لتحسين الأداء. لكن لا داعي للقلق بشأن هذا الأمر عند العمل بلغة GDScript، فالسبب الرئيسي لاستخدام تقنية pooling هو تجنب التجمد في اللغات التي تعتمد على كنس المهملات garbage-collected مثل لغة C#‎، أما لغة GDScript فتستخدم تقنية عد المراجع مختلفة في ‎إدارة الذاكرة.

لمراقبة ما إذا كان الكائن قد خرج من الشاشة حدد عقدة Mob وأضف عقدة فرعية VisibleOnScreenNotifier3D، سيظهر صندوق آخر وردي اللون هذه المرة وستصدر العقدة إشارة عندما يخرج هذا المربع تمامًا عن الشاشة.

7

غيّر حجمه باستخدام النقاط البرتقالية حتى يغطي النموذج ثلاثي الأبعاد بالكامل.

8

برمجة حركة العدو

دعنا الآن نعمل على حركة الأعداء، وسننجز ذلك على مرحلتين، الأولى سنكتب سكريبت على Mob يعرّف دالة لتهيئة العدو، ثم نبرمج آلية الظهور العشوائي في مشهد main.tscn ونستدعي الدالة من هناك.

أرفق سكريبت بالعقدة Mob.

9

فيما يلي الشيفرة البرمجية التي ستنجز الحركة بلغة 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.

10

حدد العقدة VisibleOnScreenNotifier3D وانتقل إلى القائمة التي تعرض معلومات  العقدة في الجانب الأيمن من واجهة المحرر، ثم انقر نقرًا مزدوجًا فوق الإشارة screen_exited()التي تُرسل عندما يخرج الكائن من الشاشة.

11

صِل هذه الإشارة بالعقدة Mob

12

سيؤدي هذا إلى إعادتك إلى محرر النصوص وإضافة دالة جديدة ‎_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 من توثيق جودو الرسمي.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...