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

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

آخر الأقسام الرئيسية التي علينا بناؤها في لعبتنا ثنائية الأبعاد هي واجهة المستخدم User Interface؛ إذ سنحتاج إلى طريقة لعرض نتيجة اللاعب وغيرها من المعلومات. ولتنفيذ الأمر، سنستخدم عقدةً مختلفة من النوع Control التي يزودنا بها محرك الألعاب جودو لبناء واجهات المستخدم.

مشهد واجهة المستخدم

سنبدأ المشهد بعقدة من النوع MarginContainer ونسميها UI، حيث تضمن الحاوية MarginContainer ألا يقترب العقد الأبناء كثيرًا من الحافة من خلال إضافة هوامش حولها.

سننقر الآن على العقدة UI وننتقل إلى نافذة الفاحص Inspector، ثم نضبط قيم الهوامش الأربعة على 10 ضمن الخاصية Theme Overrides>Constants.

ننتقل بعدها إلى نافذة العرض الثنائي وننقر على أيقونة تجهيزات المراسي Anchor Preset وأيقونة المرساة Anchor، ثم نختار موقع الحاوية ليكون في أعلى الشاشة وعلى اتساعها بالعرض بالأعلى Top Wide.

01 top wide

نضيف تاليًا عقدةً من النوع HBoxContainer، وهي نوع من الحاويات التي تنظم الأبناء أفقيًا. سنضيف ضمن هذه العقدة عقدة ابن من النوع TextureProgressBar، وهي شريط تقدم يمثل وضع درع المركبة الفضائية، وسنسمي هذه العقدة ShieldBar.

لا تضم مجموعة الصور التي نزّلناها في مشروعنا أية صورة مناسبة لشريط التقدم، لذلك سننزلّ الصورتين التاليتين ونحفظهما في مجلد اللعبة. ضع صورة الخلفية الخضراء 02 bar prog كقيمة للخاصية Texture>Progress، والصورة البيضاء 03 bar under كقيمة للخاصية Texture>Under.
 

سنلاحظ مباشرة أن الشريط صغير جدًا، لذلك سنغير قيمة الخاصية Minimum Custom Size لتصبح (80,60) وسنرى أن المستطيل البرتقالي قد كبر.

وكما هو واضح، لن يكون تمدد الصورة جميلًا، وقد يبدو سيئًا أيضًا، لهذا سنفعّل الخيار Range>Nine Patch Stretch ونضبط بعدها قيم الخاصية Stretch Margin الأربعة التي ستظهر على 3.

من المفترض أن نرى الآن شريطًا طويلًا فارغًا. ولمعرفة كيف سيبدو عندما يبدأ بالامتلاء، سنغير قيمة الخاصية Range>Value إلى أي قيمة بين 0 و 100.

04_level_bar.png

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

إنشاء عداد للنتيجة

سننشئ مشهدًا جديدًا أساسه عقدة من النوع HBoxContainer ونسيمها ScoreCounter. سنضبط هذه العقدة ليكون مركز ارتكاز العقدة بالعرض بالأعلى Top Wide، أي أن العقدة ستمتد أفقيًا وتتموضع عند الجزء العلوي من المشهد، ومحاذاتها نهاية الإنكماش End، أي ستكون محاذاة العقدة على الطرف الأيمن أو نهاية المساحة المتاحة باستخدام الزر المجاورة لأيقونة المرساة.

سنضبط أيضًا الخاصية Theme Overrides>Constants>Separation على القيمة 0، وهنا علينا تفعيل الخيار إلى جانب الخاصية.

سنضيف الآن مجموعةً من العقد النصية من النوع TextureRect ضمن الحاوية الأفقية لعرض الأرقام، وسنضيف عقدةً واحدة أولًا ثم نضاعف العدد.

سنسمي العقدة النصية Digit0 ثم ننتقل إلى الفاحص، فالخاصية Texture، ونختار إنشاء واحدة جديدة New AtlasTexture. سننقر بعد ذلك على هذا الخيار لتظهر لنا نافذة أسفلها الخاصية Atlas. نسحب الآن الصورة Number_font (8 x 8).png إلى هذه الخاصية ثم نضبط قيم الخاصية Region على (32, 8, 8, 😎. بعد ذلك ننقر مجددًا على الخاصية Texture لتغلق النافذة، ثم اضبط قيمة الخاصية Stretch Mode على Keep Aspect Centered.

سنعود الآن إلى نافذة المشهد ونختار العقدة Digit0، ثم نضغط على المفتاحين Ctrl+D سبع مرات لإنشاء سبع عقد أخرى مماثلة للأولى من ناحية الشكل والخاصيات. حيث ستبدو نافذة العرض بعد هذه الخطوات كما يلي:

05 digits nodes

على الرغم من إنشاء ثمانية عقد TextureRect للأرقام، إلا أن قيمة الخاصية Texture تبقى نفسها، وهذه مشكلة، فعند تغيير الخاصية Region لعرض الأرقام المختلفة ستعرض كل العقد صورة الرقم نفسه.

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

06 make unique

نضيف الآن سكريبت إلى العقدة ScoreCounter، ونختار فيها المنطقة الصحيحة Region من الصورة لكل رقم نريد عرضه:

extends HBoxContainer

var digit_coords = {
    1: Vector2(0, 0),
    2: Vector2(8, 0),
    3: Vector2(16, 0),
    4: Vector2(24, 0),
    5: Vector2(32, 0),
    6: Vector2(0, 8),
    7: Vector2(8, 8),
    8: Vector2(16, 8),
    9: Vector2(24, 8),
    0: Vector2(32, 8)
}

func display_digits(n):
    var s = "%08d" % n
    for i in 8:
      get_child(i).texture.region = Rect2(digit_coords[int(s[i])],
          Vector2(8, 8))

تبدأ الشيفرة بتشكيل قائمة من الإحداثيات التي تمثل أماكن من الصورة يحدد كل منها مكان وجود رقم، ثم تنسّق الدالة ()display_digits العدد المطلوب ليكون من 8 أرقام، بحيث لو كان أمامنا الرقم 285 مثلًا، فسيكتب بالشكل 00000258. بعد ذلك، سنضع الرقم المناسب في كل منزلة من العدد السابق اعتمادًا على مصفوفة الإحداثيات.

إضافة سكريبت واجهة المستخدم UI

نعود الآن إلى المشهد ui ثم نضيف المشهد ScoreCounter إلى العقدة HBoxContainer. نضيف بعد ذلك السكريبت التالي إلى العقدة UI:

extends MarginContainer

@onready var shield_bar = $HBoxContainer/ShieldBar
@onready var score_counter = $HBoxContainer/ScoreCounter

func update_score(value):
    score_counter.display_digits(value)


func update_shield(max_value, value):
    shield_bar.max_value = max_value
    shield_bar.value = value

سنستدعي هاتين الدالتين في المشهد الرئيسي Main في كل مرة نحتاج فيها إلى تغيير النتيجة أو قوة درع السفينة.

إضافة المشهد UI إلى المشهد الرئيسي Main

سنضيف الآن عقدةً من النوع CanvasLayer إلى المشهد الرئيسي Main، ثم منسخ إليها المشهد UI كعقدة ابن. ستُنشئ العقدة CanvasLayer طبقة رسم جديدة، ولهذا ستُرسم واجهة المستخدم فوق جميع مكونات اللعبة؛ ولحل هذه المشكلة سنغيّر التابع التالي في السكريبت main.gd كما يلي:

func _on_enemy_died(value):
    score += value
    $CanvasLayer/UI.update_score(score)

درع اللاعب

يمكننا إضافة الدرع إلى سكريبت اللاعب، عبر إضافة الأسطر التالية إلى الملف player.gd:

signal died
signal shield_changed

@export var max_shield = 10
var shield = max_shield:
    set = set_shield

سنستدعي الدالة ()set_shield من خلال عملية الإسناد = set في كل مرة يُضبط فيها المتغير shield الذي يمثل قيمة درع المركبة كما يلي:

func set_shield(value):
    shield = min(max_shield, value)
    shield_changed.emit(max_shield, shield)
    if shield <= 0:
      hide()
      died.emit()

نستطيع أيضًا وصل إشارة المركبة area_entered كي نلتقط اصطدام العدو بالمركبة:

func _on_area_entered(area):
    if area.is_in_group("enemies"):
      area.explode()
      shield -= max_shield / 2

لنضف الآن بعض الضرر إلى درع المركبة عندما تُصاب في سكريبت قذائف العدو enemy_bullet.gd، وذلك على النحو الآتي:

func _on_area_entered(area):
    if area.name == "Player":
      queue_free()
      area.shield -= 1

في الأخير، علينا وصل إشارة اللاعب shield_changed إلى الدالة التي تحدّث شريط الدرع في واجهة المستخدم، ولهذا سننتقل إلى الفاحص بعد اختيار عقدة اللاعب Player في المشهد الرئيسي. سننقر بعد ذلك في نافذة العقدة Node نقرًا مزدوجًا على الإشارة، وذلك لفتح نافذة توصل إشارة إلى دالة Connect a Signal.

سنختار بعد ذلك العقدة UI ثم نكتب update_shield في صندوق الدالة المتلقية Receiver Method.

07 connect signal

يمكننا الآن تشغيل اللعبة والتأكد من أن طاقة الدرع تنخفض عندما تصيبه قذيفة أو مركبة معادية.

ختامًا

بهذا نكون قد تعرفنا على كيفية بناء واجهة المستخدم وعارض النتيجة في الألعاب ثنائية الأبعاد عبر محرك الألعاب جودو، وسنتعرف على كيفية تطوير اللعبة أكثر بالمقالات الموالية. ترجمة -وبتصرف- للمقال: UI and Score.

اقرأ أيضًا


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

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

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



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

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

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

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


×
×
  • أضف...