في هذا الدرس من سلسلة تطوير لعبة ثلاثية الأبعاد في جودو، سنضيف للعبتنا نظام النقاط وتشغيل المؤثرات الصوتية وإمكانية اللعب مجددًا. سنتعلم كيفية تتبّع النتيجة الحالية بواسطة متغير وعرضها على الشاشة باستخدام واجهة مستخدم بسيطة عن طريق كتابة نصية.
إضافة عقدة واجهة المستخدم
سنضيف عقدة فرعية جديدة في المشهد الرئيسي من نوع Control
إلى العقدة الرئيسية Main
ونطلق عليها اسم UserInterface
وهذا سينقلنا تلقائيًا إلى واجهة الفضاء ثنائي الأبعاد، حيث يمكننا تعديل واجهة المستخدم UI.
نضيف عقدة Label
ونسميها ScoreLabel
ثم نضبط الخاصية Text ضمن قائمة الفاحص Inspector للتسمية النصية بقيمة افتراضية مثل Score: 0
يكون لون النص أبيضًا بشكل افتراضي مثل خلفية لعبتنا، لذا نحتاج إلى تغيير لونه لرؤيته أثناء تشغيل اللعبة. ننتقل للأسفل إلى Theme Overrides ونفتح الألوان ونفعّل لون الخط Font Color لتلوين النص بالأسود لأنه يظهر بشكل جيد مع المشهد ثلاثي الأبعاد الأبيض.
أخيرًا، ننقر ونسحب النص في نافذة العرض لتحريكه بعيدًا عن الزاوية العلوية اليسرى.
تتيح لنا عقدة UserInterface
تجميع عناصر واجهة المستخدم الخاصة بنا في فرع من شجرة المشهد واستخدام مورد مظهر theme resource يتاح توارثه من قبل كل عناصرها الفرعية والذي سنستخدمه لتعيين خط لعبتنا.
إنشاء مظهر واجهة المستخدم
نحدد عقدة UserInterface
مرة أخرى، وننشئ في لوحة الفاحص Inspector مورد مظهر theme resource جديد عبر الذهاب إلى Theme ومن ثم الحقل Theme كالتالي:
ننقر فوقه لفتح محرر المظهر في اللوحة السفلية لنستطيع معاينة كيفية ظهور جميع أدوات واجهة المستخدم المضمنة مع مورد المظهر الخاص بنا.
لا يحتوي المظهر إلا على خاصية واحدة بشكل افتراضي، وهي الخط الافتراضي Default Font، ويمكننا أيضًا إضافة المزيد من الخصائص إلى مورد المظهر لتصميم واجهات مستخدم معقدة، ولكن هذا خارج نطاق مقالنا الحالي.
يتوقّع حقل ملف الخط ملفات كملفات خطوط الكتابة الموجودة على حاسوبنا، فهناك صيغتان شائعتان لملفات الخطوط هما TTF و OTF
من قائمة نظام الملفات FileSystem، نوسّع دليل الخطوط fonts
وننقر ونسحب ملف Montserrat-Medium.ttf
الذي أضفناه إلى المشروع على حقل الخط الافتراضي Default Font حتى يظهر النص مرة أخرى في معاينة المظهر.
سنلاحظ أن النص صغير قليلاً، لذا نضبط حجم الخط الافتراضي Default Font Size على قيمة 22
بكسل لزيادة حجم النص.
زيادة قيمة النتيجة
للعمل على نظام النقاط، نرفق سكريبت جديد بعقدة ScoreLabel
ونعرّف فيه متغير النتيجة score
extends Label var score = 0
يجب أن تزداد قيمة النتيجة بمقدار 1
في كل مرة ندمّر عدو، ويمكننا الاستفادة من إشارة squashed
الخاصة بالأعداء لمعرفة متى يحدث ذلك، ولكن بما أننا نستنسخ الأعداء من خلال الشيفرة البرمجية، فلا يمكننا توصيل إشارة العدو ScroeLabel
عبر المحرر، ويتعين علينا بدلاً من ذلك إنشاء الاتصال من الشيفرة نفسها في كل مرة نولّد فيها عدو.
نفتح السكربت الرئيسي main.gd
، ويمكن النقر فوق اسمه في العمود الأيسر لمحرر النصوص البرمجية إذا كان لا يزال مفتوحًا.
أو بدلاً من ذلك يمكن النقر نقرًا مزدوجًا فوق ملف main.gd
في نافذة نظام الملفات FileSystem، وإضافة السطر التالي أسفل دالة
onmobtimertimeout():
func _on_mob_timer_timeout(): #... # نربط العدو مع قائمة النتيجة لتحديث النتيجة عند تدمير عدو mob.squashed.connect($UserInterface/ScoreLabel._on_mob_squashed.bind())
يعني هذا السطر أنه عندما يصدر العدو إشارة squashed
، ستستقبل عقدة ScoreLabel
الإشارة وتستدعي الدالة _on_mob_squashed ()
، ننتقل إلى السكربت ScoreLabel.gd
لتعريف دالة رد النداء callback function المسماة _on_mob_squashed()
، ونزيد هناك قيمة النتيجة ونحدث النص المعروض.
func _on_mob_squashed(): score += 1 text = "Score: %s" % score
يستخدم السطر الثاني قيمة متغير النتيجة score
لاستبدال الموضع المؤقت %s
، ويحوّل محرك جودو القيم تلقائيًا إلى نص عند استخدام هذه الميزة، وهو أمر مفيد حين إخراج النص في التسميات النصية أو حين استخدام دالة print()
.
لمزيد من المعلومات حول التعامل مع النصوص يمكن مراجعة تنسيقات النصوص في GDScript في توثيق جودو الرسمي.
يمكننا الآن تشغيل اللعبة وتدمير بعض الأعداء لمشاهدة زيادة قيمة النتيجة.
ملاحظة: من المحبذ فصل واجهة المستخدم تمامًا عن عالم اللعبة في الألعاب المعقدة، وفي هذه الحالة لن تتابع النتيجة على التسمية النصية بل قد ترغب في تخزينها في كائن منفصل مخصص لذلك الغرض ولكن عند إنشاء نموذج أولي أو عندما يكون مشروعك بسيطًا، فلا بأس بالحفاظ على الشيفرة البرمجية بسيطة لأن البرمجة عملية تسىعى للموازنة دائمًا.
إعادة اللعب
الآن سنضيف القدرة على إعادة اللعب بعد موت اللاعب، فعندما يموت اللاعب، سنعرض رسالة على الشاشة وننتظر إدخالًا منه.
لتعد إلى المشهد الرئيسي main.tscn
ونحدد عقدة UserInterface
ثم نضيف عقدة فرعية جديدة من نوع ColorRect
ونسميها Retry
تُملأ هذه العقدة مستطيل بلون موحد وتُستخدم كطبقة تظليل لإعتام الشاشة.
لاستخدامها على كامل نافذة العرض viewport، نستخدم قائمة Anchor Preset في شريط الأدوات.
نفتحها ونطبق أمر مستطيل كامل Full Rect
لم يحدث شيء لكن تتحرك المقابض الخضراء الأربعة فقط إلى زوايا مربع التحديد.
هذا لأن عقد واجهة المستخدم التي تحتوي على أيقونة خضراء تعمل مع نقاط الربط والهوامش بالنسبة إلى مربع إحاطة العنصر الأب، كما أن عقدة UserInterface
هنا لها حجم صغير وعقدة Retry
محدودة بها.
نحدد UserInterface
ونطبّق الأمر مستطيل كامل Full Rect عليها أيضًا من Anchor Preset، ويجب أن تغطي عقدة Retry
الآن نافذة العرض بأكملها.
دعنا نغيّر لونها لتعتيم منطقة اللعبة، سنحدد Retry
وفي لوحة الفاحص Inspector، نضبط لون Color إلى لون غامق وشفاف في نفس الوقت. للقيام بذلك، نسحب مؤشر A إلى اليسار في مُحدّد اختيار اللون. يتحكم هذا المؤشر بقناة ألفا Alpha للون، أي معامل العتامة أو الشفافية.
بعدها نضيف عقدة Label
كعقدة ابن لعقدة Retry
ونكتب فيها نص مفاده اضغط على مفتاح Enter لإعادة المحاولة ومن ثم نطبق الأمر Center من Anchor Preset لنقلها وتثبيتها في مركز الشاشة.
برمجة خيار إعادة المحاولة
يمكننا الآن الانتقال إلى الشيفرة لإظهار وإخفاء عقدة Retry
عند موت اللاعب وإعادة اللعب. نفتح السكربت الرئيسي main.gd
.
أولاً، نريد إخفاء التراكب في بداية اللعبة. لذا نضيف هذا السطر إلى الدالة _ready()
:
func _ready(): $UserInterface/Retry.hide()
بعدها عندما يتعرض اللاعب للاصطدام، نُظهر عنصر واجهة المستخدم Retry كما يلي:
func _on_player_hit(): #... $UserInterface/Retry.show()
أخيرًا، عندما تكون عقدة Retry
مرئية، نحتاج إلى الاستماع إلى دخل اللاعب من أجل إعادة تشغيل اللعبة إذا ضغط على زر Enter. للقيام بذلك، نستخدم دالة رد النداء _unhandled_input()
، والتي يجري تشغيلها عند أي إدخال.
إذا ضغط اللاعب على زر الإدخال المحدد مسبقًا ui_accept
وكانت عقدة Retry
مرئية، فإننا نعيد تحميل المشهد الحالي.
func _unhandled_input(event): if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible: # هذا يعيد تشغيل المشهد الحالي get_tree().reload_current_scene()
تمنحنا دالة get_tree()
الوصول إلى كائن SceneTree العام الذي يسمح لنا بإعادة تحميل وتشغيل المشهد الحالي.
إضافة المؤثرات الصوتية
سنستخدم الآن ميزة أخرى في جودو لإضافة مؤثرات صوتية تعمل بشكل مستمر في الخلفية ألا وهي التحميل التلقائي autoloads.
لتشغيل الصوت، كل ما علينا فعله هو إضافة عقدة AudioStreamPlayer
إلى المشهد الخاص بنا وإرفاق ملف صوت بها. عند بدء تشغيل المشهد، يمكن أن يعمل الصوت تلقائيًا. ومع ذلك، عند إعادة تحميل المشهد، كما نفعل لإعادة التشغيل نعيد تعيين عقد الصوت، وتبدأ المؤثرات صوتية من البداية.
يمكننا الاستفادة من ميزة التحميل التلقائي Autoload في جودو لتمكين المحرك من تحميل عقدة أو مشهد معين تلقائيًا عند بدء اللعبة، حتى لو كانت خارج المشهد الحالي. هذه الميزة مفيدة أيضًا لإنشاء كائنات عامة يمكن الوصول إليها بسهولة من أي مكان في المشروع، مما يسهم في تحسين تنظيم الكود وإدارة الموارد المشتركة بين المشاهد المختلفة.
ننشئ مشهدًا جديدًا بالانتقال إلى قائمة مشهد Scene والنقر على مشهد جديد New Scene أو باستخدام الرمز + بجوار المشهد المفتوح حاليًا.
ننقر فوق الزر عقدة أخرى Other Node لإنشاء AudioStreamPlayer
ونعيد تسميتها إلى MusicPlayer
.
أضفنا مقطع صوتي إلى المسار art/
وهو House In a Forest Loop.ogg
. لسحب هذا المقطع إلى اللعبة، نضغط عليه ثم نسحبه لخاصية Stream في لوحة الفاحص Inspector. بعد ذلك، نفعّل خيار التشغيل التلقائي Autoplay لتشغيل المؤثرات الصوتية تلقائيًا عند بدء اللعبة.
نحفظ المشهد باسم MusicPlayer.tscn
، بعدها علينا تسجيله كمشهد تحميل تلقائي، ولفعل ذلك نتوجه إلى قائمة مشروع Project ومن ثم إعدادات المشروع Project Settings وتنقر على تبويبة التحميل التلقائي Autoload.
نحتاج إلى إدخال المسار إلى المشهد في حقل المسار Path، ولفعل ذلك ننقر فوق أيقونة المجلد لفتح مستعرض الملفات وننقر نقرًا مزدوجًا فوق MusicPlayer.tscn
، ثم ننقر فوق الزر إضافة على اليمين لتسجيل العقدة.
يجري الآن تحميل MusicPlayer.tscn
في أي مشهد نفتحه أو نشغله. لذا، إذا شغلنا اللعبة الآن، تعمل المؤثرات الصوتية تلقائيًا في أي مشهد.
قبل أن ننهي هذا الدرس، لنلقي نظرة سريعة على كيفية عمل الميزة، عند تشغيل اللعبة، تتغير نافذة Scene وتمنحنا تبويبتين هما عن بعد Remote ومحلي Local.
يتيح لنا تبويب Remote تصوّر شجرة عقد اللعبة التي نعمل عليها. سنرى هناك العقدة الرئيسية Main وكل ما يشتمل عليه المشهد والأعداء التي استنسخناها في الأسفل.
في الأعلى توجد العقدةMusicPlayer
التي جرى تحميلها تلقائيًا، والعقدة الجذر root هي نافذة عرض لعبتنا.
شيفرة المشهد الرئيسي
فيما يلي سكربت main.gd
الكامل بلغة GDScript للرجوع إليه
extends Node @export var mob_scene: PackedScene func _ready(): $UserInterface/Retry.hide() func _on_mob_timer_timeout(): # إنشاء نسخة جديدة من مشهد العدو var mob = mob_scene.instantiate() # اختيار مكان عشوائي على SpawnPath # نخزن المرجع في عقدة SpawnLocation var mob_spawn_location = get_node("SpawnPath/SpawnLocation") # ونعطيه انزياح عشوائي mob_spawn_location.progress_ratio = randf() var player_position = $Player.position mob.initialize(mob_spawn_location.position, player_position) # توليد العدو عن طريق إضافته للمشهد الرئيسي add_child(mob) # نربط العدو مع قائمة النتيجة لتحديث النتيجة عند تدمير عدو mob.squashed.connect($UserInterface/ScoreLabel._on_mob_squashed.bind()) func _on_player_hit(): $MobTimer.stop() $UserInterface/Retry.show() func _unhandled_input(event): if event.is_action_pressed("ui_accept") and $UserInterface/Retry.visible: # هذا يعيد تشغيل المشهد get_tree().reload_current_scene()
الخلاصة
استطعنا في هذا المقال جعل لعبتنا ثلاثية الأبعاد في جودو شيّقة وممتعة أكثر عن طريق إضافة ميزة إحراز النقاط، كما أدخلنا تحسينات على عملية اللعب بالسماح للاعب باللعب من جديد بشكل سريع في حال خسارته. سنعمل في الجزء التالي على إضافة المزيد من الميزات لجعل تجربة اللعب تفاعلية بشكل أكبر.
ترجمة - وبتصرف - لقسم Score and replay من توثيق جودو الرسمي.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.