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

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

إضافة عقدة واجهة المستخدم

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

نضيف عقدة Label ونسميها ScoreLabel

01 عقدة scorelabel

ثم نضبط الخاصية Text ضمن قائمة الفاحص Inspector للتسمية النصية بقيمة افتراضية مثل Score: 0

02 الخاصية text

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

03 لون الخط font color

أخيرًا، ننقر ونسحب النص في نافذة العرض لتحريكه بعيدًا عن الزاوية العلوية اليسرى.

04 سحب النص

تتيح لنا عقدة UserInterface تجميع عناصر واجهة المستخدم الخاصة بنا في فرع من شجرة المشهد واستخدام مورد مظهر theme resource يتاح توارثه من قبل كل عناصرها الفرعية والذي سنستخدمه لتعيين خط لعبتنا.

إنشاء مظهر واجهة المستخدم

نحدد عقدة UserInterface مرة أخرى، وننشئ في لوحة الفاحص Inspector مورد مظهر theme resource جديد عبر الذهاب إلى Theme ومن ثم الحقل Theme كالتالي:

05 مظهر الواجهة theme

ننقر فوقه لفتح محرر المظهر في اللوحة السفلية لنستطيع معاينة كيفية ظهور جميع أدوات واجهة المستخدم المضمنة مع مورد المظهر الخاص بنا.

06 محرر المظهر

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

يتوقّع حقل ملف الخط ملفات كملفات خطوط الكتابة الموجودة على حاسوبنا، فهناك صيغتان شائعتان لملفات الخطوط هما TTF و OTF

من قائمة نظام الملفات FileSystem، نوسّع دليل الخطوط fonts وننقر ونسحب ملف Montserrat-Medium.ttf الذي أضفناه إلى المشروع على حقل الخط الافتراضي Default Font حتى يظهر النص مرة أخرى في معاينة المظهر.

سنلاحظ أن النص صغير قليلاً، لذا نضبط حجم الخط الافتراضي Default Font Size على قيمة 22 بكسل لزيادة حجم النص.

07 حجم الخط الافتراضي

زيادة قيمة النتيجة

للعمل على نظام النقاط، نرفق سكريبت جديد بعقدة ScoreLabel ونعرّف فيه متغير النتيجة score

extends Label

var score = 0

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

نفتح السكربت الرئيسي main.gd، ويمكن النقر فوق اسمه في العمود الأيسر لمحرر النصوص البرمجية إذا كان لا يزال مفتوحًا.

08 السكربيت الرئيسي 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 في توثيق جودو الرسمي.

يمكننا الآن تشغيل اللعبة وتدمير بعض الأعداء لمشاهدة زيادة قيمة النتيجة.

9 تشغيل اللعبة

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

إعادة اللعب

الآن سنضيف القدرة على إعادة اللعب بعد موت اللاعب، فعندما يموت اللاعب، سنعرض رسالة على الشاشة وننتظر إدخالًا منه.

لتعد إلى المشهد الرئيسي main.tscn ونحدد عقدة UserInterface ثم نضيف عقدة فرعية جديدة من نوع ColorRect ونسميها Retry تُملأ هذه العقدة مستطيل بلون موحد وتُستخدم كطبقة تظليل لإعتام الشاشة.

لاستخدامها على كامل نافذة العرض viewport، نستخدم قائمة Anchor Preset في شريط الأدوات.

10 قائمة anchor preset

نفتحها ونطبق أمر مستطيل كامل Full Rect

11 مستطيل كامل

لم يحدث شيء لكن تتحرك المقابض الخضراء الأربعة فقط إلى زوايا مربع التحديد.

12 مقابض الحركة

هذا لأن عقد واجهة المستخدم التي تحتوي على أيقونة خضراء تعمل مع نقاط الربط والهوامش بالنسبة إلى مربع إحاطة العنصر الأب، كما أن عقدة UserInterface هنا لها حجم صغير وعقدة Retry محدودة بها.

نحدد UserInterface ونطبّق الأمر مستطيل كامل Full Rect عليها أيضًا من Anchor Preset، ويجب أن تغطي عقدة Retry الآن نافذة العرض بأكملها.

دعنا نغيّر لونها لتعتيم منطقة اللعبة، سنحدد Retry وفي لوحة الفاحص Inspector، نضبط لون Color إلى لون غامق وشفاف في نفس الوقت. للقيام بذلك، نسحب مؤشر A إلى اليسار في مُحدّد اختيار اللون. يتحكم هذا المؤشر بقناة ألفا Alpha للون، أي معامل العتامة أو الشفافية.

13 لون العنصر

بعدها نضيف عقدة Label كعقدة ابن لعقدة Retry ونكتب فيها نص مفاده اضغط على مفتاح Enter لإعادة المحاولة ومن ثم نطبق الأمر Center من Anchor Preset لنقلها وتثبيتها في مركز الشاشة.

14 مركز الشاشة

برمجة خيار إعادة المحاولة

يمكننا الآن الانتقال إلى الشيفرة لإظهار وإخفاء عقدة 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 أو باستخدام الرمز + بجوار المشهد المفتوح حاليًا.

15 مشهد جديد

ننقر فوق الزر عقدة أخرى Other Node لإنشاء AudioStreamPlayer ونعيد تسميتها إلى MusicPlayer.

16 عقدة التأثيرات الصوتية

أضفنا مقطع صوتي إلى المسار art/‎ وهو House In a Forest Loop.ogg. لسحب هذا المقطع إلى اللعبة، نضغط عليه ثم نسحبه لخاصية Stream في لوحة الفاحص Inspector. بعد ذلك، نفعّل خيار التشغيل التلقائي Autoplay لتشغيل المؤثرات الصوتية تلقائيًا عند بدء اللعبة.

17 خيار التشغيل التلقائي

نحفظ المشهد باسم MusicPlayer.tscn، بعدها علينا تسجيله كمشهد تحميل تلقائي، ولفعل ذلك نتوجه إلى قائمة مشروع Project ومن ثم إعدادات المشروع Project Settings وتنقر على تبويبة التحميل التلقائي Autoload.

نحتاج إلى إدخال المسار إلى المشهد في حقل المسار Path، ولفعل ذلك ننقر فوق أيقونة المجلد لفتح مستعرض الملفات وننقر نقرًا مزدوجًا فوق MusicPlayer.tscn، ثم ننقر فوق الزر إضافة على اليمين لتسجيل العقدة.

18 تسجيل العقدة

يجري الآن تحميل MusicPlayer.tscn في أي مشهد نفتحه أو نشغله. لذا، إذا شغلنا اللعبة الآن، تعمل المؤثرات الصوتية تلقائيًا في أي مشهد.

قبل أن ننهي هذا الدرس، لنلقي نظرة سريعة على كيفية عمل الميزة، عند تشغيل اللعبة، تتغير نافذة Scene وتمنحنا تبويبتين هما عن بعد Remote ومحلي Local.

19 تبويب remote

يتيح لنا تبويب Remote تصوّر شجرة عقد اللعبة التي نعمل عليها. سنرى هناك العقدة الرئيسية Main وكل ما يشتمل عليه المشهد والأعداء التي استنسخناها في الأسفل.

20 شجرة عقد اللعبة

في الأعلى توجد العقدة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 من توثيق جودو الرسمي.

اقرأ أيضًا


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

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

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



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

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

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

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   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.


×
×
  • أضف...