نركز في هذا المقال على شرح طريقة تحسين التفاعل بين اللاعب والأعداء في لعبتنا ثلاثية الأبعاد التي بدأنا تطويرها في مقال سابق، ونشرح كيف نجعل اللاعب قادرًا على تدمير العدو إذا هبط عليه من الأعلى. بالمقابل، سنجعل اللاعب يموت إذا اصطدم العدو به أثناء وجوده على الأرض.
التحكم في التفاعلات الفيزيائية
سنتحكم في التفاعلات الفيزيائية ونضبطها لتحديد كيفية تفاعل الكائنات المختلفة مع بعضها البعض ضمن اللعبة، لتحقيق ذلك يجب أن تكون على دراية بمفهوم طبقات الفيزياء في جودو. حيث تتمتع الأجسام الفيزيائية بميزات تسهّل التحكم في التفاعل بينها، وهي الطبقات Layers والأقنعة Masks.
- تُحدد الطبقات Layers الطبقة الفيزيائية التي ينتمي إليها الكائن، وتستخدم لتنظيم الكائنات الفيزيائية في اللعبة
- تُحدد الأقنعة Masks ما هي الطبقات التي يمكن للجسم رصدها أو اكتشافها والتفاعل معها
يؤثر هذا التفاعل بين الطبقات والأقنعة بشكل مباشر على كشف الاصطدام Collision Detection فعندما نرغب أن يتفاعل جسمان معًا، يجب أن يتوافق قناع أحدهما مع طبقة الآخر على الأقل. فإذا كنا نريد أن يتفاعل جسمان مثل اللاعب والعدو، يجب التأكد من إعداد الطبقات والأقنعة بحيث تكون طبقة أحدهما مدرجة ضمن قناع الآخر، والعكس، وإذا كنا نريد تجنب التفاعل بينهما، نجعل القناع الخاص بأحدهما لا يراقب الطبقة التي ينتمي إليها الكائن الآخر. ستتضح الفكرة لنا أكثر عند التطبيق العملي.
يكفي أن نعرف حاليًا أن الطبقات والأقنعة تساعدنا على التحكم بالتفاعلات الفيزيائية بين الأجسام بدقة، وتمكننا من تصفية التفاعلات غير الضرورية وتحقيق التفاعل للأجسام التي تحتاج للتفاعل فقط، وهي تحسن أداء اللعبة عن طريق تقليل العمليات الحسابية اللازمة لكشف الاصطدامات وتقلل حجم الشيفرة البرمجية وتزيل الحاجة لكتابة شروط إضافية فيها.
عند إنشاء الأجسام والمناطق في محرك الألعاب ستُعيّن افتراضيًا إلى الطبقة والقناع رقم 1
. هذا يعني أن جميع الأجسام التي عينت بهذا الرقم ستتفاعل وتتصادم مع بعضها تلقائيًا دون الحاجة إلى ضبط إعدادات إضافية.
تحديد أسماء الطبقات
دعونا الآن نعطي طبقات الفيزياء أسماء مميزة لتسهيل إدارة التفاعلات داخل لعبتنا، للقيام بذلك سنفتح محرك ألعاب جودو وننتقل إلى القائمة العلوية مشروع Project ومن ثم نختار إعدادات المشروع Project Settings.
ننتقل للقائمة اليسرى ونذهب للأسفل حتى نصل لقسم أسماء الطبقات، ثم نختار فيزياء 3D أو 3D Physics. ستظهر لنا قائمة بالطبقات وحقل نصي على يمين كل طبقة. يمكن من خلال هذا الحقل النصي تغيير اسم الطبقة، سنسمي الطبقات الثلاث الأولى بأسماء مناسبة وهي player
لتمثيل طبقة اللاعب، و enemies
لتمثيل طبقة الأعداء، و world
لتمثيل عالم اللعبة.
بعد تسمية الطبقات، دعونا نخصص كل كائن في اللعبة ليكون جزءًا من إحدى هذه الطبقات.
تعيين الطبقات والأقنعة
حان دور تعيين الطبقات والأقنعة للكائنات الموجودة في مشهد اللعبة، بداية سنحدد عقدة الأرضية Ground
في المشهد الرئيسي Main. ونوسّع قسم الاصطدام Collision في الفاحص Inspector. سنرى عندها طبقات العقدة وأقنعتها على شكل شبكة من الأزرار كما في الصورة التالية:
الأرضية جزء من عالم اللعبة، ونريدها أن تكون جزءًا من الطبقة الثالثة في مشهد لعبتنا. للقيام بذلك ليس علينا سوى تعطيل الزر المفعل رقم 1 الذي يمثل الطبقة الأولى، وتفعيل الزر رقم 3 الذي يمثل الطبقة الثالثة بالنقر فوقه، بعدها سنعطّل الزر رقم 1 للقناع بالنقر عليه أيضًا.
كما ذكرنا سابقًا، تتيح خاصية القناع للعقدة إمكانية التفاعل مع الكائنات الفيزيائية الأخرى دون الحاجة إلى تسجيل تصادمات فعلية. على سبيل المثال، لا تحتاج عقدة الأرضية إلى الاستماع إلى أي تصادمات، إذ يقتصر دورها على منع العناصر من السقوط.
لاحظ أن بإمكانك النقر فوق الثلاث نقاط الموجودة على الجانب الأيمن من الخصائص لعرض قائمة بمربعات الاختيار التي تمثل أسماء الطبقات. تسمح هذه المربعات بتخصيص الطبقات والأقنعة بدقة، وتمكنك من تحديد الطبقات التي تنتمي العقدة إليها، وتفعيل أو تعطيل التفاعلات مع الطبقات الأخرى.
سنعالج الآن عقدتي Player
و Mob
، وللقيام ذلك افتح مشهد اللاعب player.tscn
بالنقر المزدوج فوق ملفه الموجود أسفل نافذة نظام الملفات في جودو. وللتحكم في تفاعلات اللاعب مع الأعداء والعالم، حدد عقدة اللاعب وعيّن القناع Mask من قسم الاصطدام Collision لكل من الأعداء enemies والعالم world. يمكنك ترك خاصية الطبقة الافتراضية كما هي لأن الطبقة الأولى هي طبقة اللاعب player.
بعدها، لنفتح مشهد الأعداء Mob بالنقر المزدوج على الملف mob.tscn
ونحدد عقدة Mob
ونضبط طبقة تصادمه من قسم التصادم Collision الموجود داخل قسم الطبقة Layer ونجعل قيمته enemies لنحدد أنه سيصطدم فقط مع الأجسام التي تنتمي إلى طبقة الأعداء، ونلغي ضبط قناع التصادم من القسم تصادم Collision الموجود داخل قسم القناع Mask ونتركه فارغًا، لنمنعه من التفاعل مع الطبقات الأخرى كاللاعب أو البيئة.
بعد هذه الإعدادات، ستحدث تداخلات بين الأعداء أي أنهم قد يتصادمون أو يتداخلون مع بعضهم البعض. إذا كنت تريد أن ينزلق الأعداء بعيدًا عن بعضهم عندما يصطدمون، يجب تفعيل قناع الأعداء لضمان عدم تداخلهم وجعلهم يتفاعلون بطريقة تسمح لهم بالابتعاد عن بعضهم البعض عندما يحدث التصادم.
ملاحظة: لا يحتاج الأعداء إلى استخدام قناع mask لطبقة العالم world لأن حركتهم محصورة على المستوى XZ الذي يحدد الاتجاهين العرضي والطولي، ونحن لا نطبق عليهم أي جاذبية بشكل افتراضي.
تنفيذ القفز
في هذه الخطوة، سنكتب كود بسيط لتنفيذ القفز في اللعبة، تتطلب عملية القفز وحدها سطرين فقط من التعليمات البرمجية، ولكتابتها افتح السكربت الخاص بالعقدة Player
. سنحتاج إلى قيمة للتحكم في قوة القفزة وتحديث ()_physics_process
لبرمجة القفزة. لنضف jump_impulse
بعد السطر الذي يحدد fall_acceleration
في الجزء العلوي من السكربت.
#... # تطبيق الدفعة العمودية للشخصية عند القفر مقدرة بواحدة المتر @export var jump_impulse = 20
ثم أضف الشيفرة البرمجية التالية قبل كتلة التعليمات البرمجية move_and_slide()
داخل _physics_process()
.
func _physics_process(delta): #... # القفز if is_on_floor() and Input.is_action_just_pressed("jump"): target_velocity.y = jump_impulse #...
هذا كل ما تحتاجه للقفز!
يعد التابع is_on_floor()
أداة من الصنف CharacterBody3D
، فهو يعيد true
إذا اصطدم الجسم بالأرضية في هذا الإطار، ولهذا السبب نطبق الجاذبية على شخصية اللاعب فنجعله يصطدم بالأرض بدلاً من أن يطفو فوقها مثل شخصيات الأعداء.
عندما تكون الشخصية على الأرض ونضغط على زر القفز، نمنحها دفعة فورية وقوية في الاتجاه العمودي أي على المحور Y حتى تقفز بسرعة. تجعل هذه الطريقة استجابة التحكم سريعة وواقعية. وتجدر الملاحظة بأن المحور Y في الألعاب ثلاثية الأبعاد يكون موجبًا للأعلى أي أن القفز يجعل القيمة في Y تزداد، وهذا يختلف عن الألعاب ثنائية الأبعاد حيث يكون المحور Y موجبًا للأسفل.
تدمير الأعداء
دعنا نضف ميزة تدمير الأعداء للعبتنا، سنجعل من الشخصية تثب فوق الأعداء وتقتلهم في نفس الوقت. سنكون بحاجة إلى اكتشاف الاصطدامات مع العدو وتمييزها عن الاصطدامات بالأرضية. للقيام بذلك، يمكننا استخدام ميزة التصنيف بالوسوم الخاصة بـجودو.
افتح المشهد mob.tscn
مرة أخرى وحدد عقدة Mob
. انتقل إلى قائمة العقدة Node على اليمين لرؤية قائمة الإشارات. تحتوي قائمة العقدة Node تبوبين هما الإشارات Signals التي استخدمناها سابقًا، والمجموعات Groups التي تسمح لنا بإسناد وسوم للعُقد. انقر عليها لتكشف عن حقل مخصص لكتابة اسم الوسم، اكتب mob في هذا الحقل وانقر فوق زر إضافة Add.
سيظهر رمز في قائمة المشهد Scene للإشارة إلى أن العقدة جزء من مجموعة واحدة على الأقل.
يمكننا الآن استخدام هذه المجموعة في شيفرتنا البرمجية لتمييز الاصطدامات بالأعداء عن الاصطدامات بالأرض.
برمجة عملية تدمير اللاعب
لبرمجة عملية التدمير والارتداد في السكربت الخاص باللاعب، سنحتاج إلى إضافة خاصية جديدة تُسمى bounce_impulse
في الجزء العلوي من السكربت. لا نريد بالضرورة عند تدمير عدو أن ترتفع الشخصية إلى أعلى مستوى كما هو الحال عند القفز، هنا ستساعدنا هذه الخاصية في ضبط مقدار الارتداد بما يناسب الموقف.
# تطبيق الدفعة العمودية للشخصية عند القفر مقدرة بواحدة المتر @export var bounce_impulse = 16
والآن وبعد كتلة شيفرة القفز التي أضفناها أعلاه في _physics_process()
نضيف الحلقة التالية، إذ يجعل جودو الجسم يتحرك أحيانًا عدة مرات متتالية لتسهيل حركة الشخصية باستخدام move_and_slide()
، لذلك علينا أن نراجع جميع الاصطدامات التي قد تكون حدثت.
سنتحقق في في كل تكرار للحلقة فيما إذا كنا لامسنا عدو، وإذا كان الأمر كذلك، فإننا نقتله ونرتد عنه. لن تعمل الحلقة إذا لم يحدث أي تصادم في إطار معين. لاحظ الشيفرة التالية:
func _physics_process(delta): #... # كرّر خلال كل الاصطدامات التي تحصل خلال هذا الإطار for index in range(get_slide_collision_count()): # نحصل على واحد من الاصطدامات مع اللاعب var collision = get_slide_collision(index) # إذا كان الاصطدام مع الأرض if collision.get_collider() == null: continue # إذا كان الاصطدام مع العدو if collision.get_collider().is_in_group("mob"): var mob = collision.get_collider() # نتأكد من الضربة أنها من الأعلى if Vector3.UP.dot(collision.get_normal()) > 0.1: # إذا كان كذلك ندمره ونقفز mob.squash() target_velocity.y = bounce_impulse # تمنع أي استدعاءات مكررة break
تأتي الدالتان get_slide_collision_count()
وget_slide_collision()
كلاهما من الصنف CharacterBody3D
وهما مرتبطتان بالدالة move_and_slide()
.
تُعيد الدالة get_slide_collision()
كائن KinematicCollision3D
الذي يحتوي على معلومات حول مكان وكيفية حدوث التصادم، على سبيل المثال نستخدم الخاصية get_collider
الخاصة بها للتحقق مما إذا كنا قد اصطدمنا بـعدو mob عن طريق استدعاء is_in_group()
عليه بهذا الشكل:
collision.get_collider().is_in_group("mob")
ملاحظة: التابع is_in_group()
متاح في كل صنف عقدة Node
.
بعدها نستخدم جداء الأشعة النقطي vector dot product للتأكد من أننا هبطنا على العدو:
Vector3.UP.dot(collision.get_normal()) > 0.1
التصادم الافتراضي هو شعاع ثلاثي الأبعاد متعامد مع المستوي الذي حدث فيه الاصطدام، يتيح لنا الجداء النقطي مقارنته بالاتجاه الصاعد. في حالة الجداء النقطي؛ عندما تكون النتيجة أكبر من 0
يكون المتجهان بزاوية أقل من 90 درجة، والقيمة الأعلى من 0.1
تخبرنا أننا فوق العدو تقريبًا.
بعد التعامل مع منطق التدمير والارتداد، نخرج من الحلقة عبر عبارة Break
لمنع المزيد من الاستدعاءات المكررة إلى mob.squash()
، التي قد تؤدي بخلاف ذلك إلى أخطاء غير مقصودة مثل حساب النتيجة عدة مرات لقتل عدو واحد.
نستدعي هنا دالة واحدة غير معرّفة ألا وهي mob.squash()
، لذلك يتعين علينا إضافتها إلى صنف العدو Mob.
افتح السكربت Mob.gd
بالنقر المزدوج عليه في نافذة نظام الملفات. نريد تحديد إشارة جديدة في الجزء العلوي من السكربت تسمى squashed
. وفي الأسفل، يمكنك إضافة دالة التدمير، حيث نصدر الإشارة وندمر العدو.
# تنبثق عندما يقفز اللاعب على العدو signal squashed # ... func squash(): squashed.emit() queue_free()
لتجربة اللعبة، يمكن الضغط على F5
وتعيين main.tscn
كمشهد رئيسي للمشروع.
الخلاصة
بهذا نكون قد تعرفنا على كيفية إضافة خاصية القفز وتدمير الأعداء في الألعاب ثلاثية الأبعاد عبر محرك الألعاب جودو Godot، وسنتعلم في المقالات القادمة كيفية التعامل مع خصائص أخرى قد تحتاجها لتطوير ألعابك، مثل مفهوم الإشارة Signal الذي سنتعرف عليه في المقال التالي لإضافة نقاط لنتائج الألعاب عند تدمير الأعداء وجعل اللاعبين يفارقون الحياة عند اصطدامهم مع الأعداء.
ترجمة - وبتصرف - لقسم Jumping and squashing monsters من توثيق جودو الرسمي.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.