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

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

01 level select

مراحل بناء القائمة

سنبني القائمة بتصميم شبكة قابلة للتمرير مكونة من صناديق لتحديد المستوى بحيث يمكن للاعب أن يختار فيما بينها. سنبدأ أولًا ببناء صندوق المستوى LevelBox بشكل مستقل.

بناء صندوق المستوى LevelBox

02 level box style

ستكون هيكلية العقدة اللازمة لبناء هذا الصندوق على النحو التالي:

LevelBox: PanelContainer
     Label
     MarginContainer
       TextureRect

استخدمنا العناصر التالية لتشكيل الصندوق:

  • حاوية PanelContainer لتنظيم وعرض العناصر داخلها
  • عقدة تسمية نصية Label لعرض رقم المستوى
  • حاوية MarginContainer لإضافة هوامش حول المحتوى 
  • عقدة LevelBox من نوع  TextureRect لعرض صورة قفل عندما يكون المستوى مغلق، ورقم عندما يكون مفتوح

 

نتأكد من ضبط الخاصية Layout  في القسم Custom Minimum Size للعقدة LevelBox  على القيمة (110,110) ويمكن اختيار أي حجم آخر مناسب لتخطيط القائمة.

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

@tool
extends PanelContainer  

signal level_selected  # إشارة تُطلق عند تحديد المستوى

@export var locked = true:  # يحدد إذا كان المستوى مغلقًا
    set = set_locked

@export var level_num = 1:  # رقم المستوى
    set = set_level

@onready var lock = $MarginContainer/Lock  # صورة القفل
@onready var label = $Label  #  رقم المستوى

# دالة لضبط حالة القفل وإظهارأو إخفاء العناصر
func set_locked(value):
    locked = value
    if not is_inside_tree():  # ننتظر حتى يكتمل تحميل العنصر داخل المشهد
      await ready
    lock.visible = value  # نعرض صورة القفل إذا كان المستوى مغلقًا
    label.visible = not value  # نظهر النص إذا كان المستوى غير مغلق

# دالة لضبط رقم المستوى وتحديث النص المعروض
func set_level(value):
    level_num = value
    if not is_inside_tree():  # ننتظر تحميل العنصر في المشهد
      await ready
    label.text = str(level_num)  # تحديث النص برقم المستوى

# دالة لمعالجة مدخلات المستخدم 
func _on_gui_input(event):
    if locked:  # إذا كان المستوى مغلقًا، نتجاهل النقر
      return
    if event is InputEventMouseButton and event.pressed:  # التحقق من النقر بزر الفأرة
      level_selected.emit(level_num)  # إطلاق الإشارة مع رقم المستوى
      print("Clicked level ", level_num)  # طباعة رسالة لاختبار النقر

نستخدم في شيفرتنا التوجيه tool@ حتى نتمكن من تغيير قيم الخاصيات عبر نافذة الفاحص inspector ونرى التأثير مباشرة دون الحاجة لتشغيل المشهد. لنجرب اﻵن النقر على الخاصية Locked ونتحقق من رؤية صورة القفل تظهر وتختفي. وطالما أن اللعبة لا تضم مستويات فعلية لتحميلها، ستساعدنا الدالة ()print على اختبار التقاط حدث النقر.

بناء الشبكة

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

03 level select grid

ننتقل للقسم Theme Overrides ثم Seperation ونضبط قيمتي الخاصيتين V Seperation و  H Seperation على 10، ونحفظ المشهد باسم LevelGrid. سنستخدم في القائمة عدة نسخ عن عقدة الشبكة لعرض العدد المطلوب من المستويات.

شاشة القائمة

بإمكاننا اﻵن تجميع القائمة النهائية. توضح الصورة التالية التخطيط اﻷولي الذي سننفذه:

04 level select screen menu

ننشئ المشهد انطلاقًا من العقد التالية:

LevelMenu: MarginContainer
     VBoxContainer
      Title: Label
       HBoxContainer
        BackButton: TextureButton
        ClipControl: Control
        NextButton: TextureButton

نضبط خواص العقد كالتالي:

  • العقدة LevelMenu: نضبط Theme Overrides ثم Constants ثم Margins على القيمة 20
  • العقدة VBoxContainer:نضبط Theme Overrides ثم Constants ثم Margins على القيمة 50
  • العقدة Title: ننسقها بالطريقة التي نريدها
  • نضبط العقدتين BackButton و NextButton كالتالي:
    • Ignore Texture Size: On لضبط حجم الزر وفقًا لحجمه داخل واجهة المستخدم
    • Stretch Mode: Keep Centered للحفاظ على محاذاة المحتوى داخل الأزرار
    • Layout/Container: On  ليعمل الزر كحاوية
    • Sizing/Horizontal/Expand: On لتوسيع الأزرار أفقيًا داخل المساحة المتاحة
  • نضبط العقدة ClipControl كما يلي:
    • Layout/Clip Contents:On لاقتصاص أي محتوى يتجاوز حجم الإطار
    • Layout>Custom Minimum Size:(710, 350) لتحديد الحجم الأدنى للعقدة  

بعد ذلك نضع الشبكة ضمن العقدة ClipControl، بما أننا  تمكين الخاصية Clip Content سيقتص محتوى العقدة إن كانت أكبر من عنصر التحكم. وسنتمكن اﻵن من بناء شبكة من صناديق المستويات قابلة للتمرير، لهذا نضيف عقدة من النوع HBoxContainer تُدعى GridBox إلى ClipControl إضافة إلى ثلاث نسخ من العقدة LevrlGrid أو أكثر إن أردنا. ونتأكد من ضبط الخاصية Theme Overrides ثم Constants ثم Separation على القيمة 0.

ينبغي أن يبدو تخطيط المشهد اﻵن كما في الشكل التالي تقريبًا، مع العلم أننا عطلنا الخاصية Clip content لنعرض ما يحدث بشكل أوضح:

05 level select complete

الشبكات الثلاثة موجودة ضمن Clip Content لكن لا يمكن للمتحكم ClipControl عرض سوى شبكة واحدة كل مرة. لهذا ولكي ننتقل إلى الشبكتين الباقيتين عن طريق التمرير، لا بد من إزاحة GridBox مقدار 710 بكسل يمينًا أو يسارًا

110 (width of each LevelBox)
    * 6 (grid columns)
    + 10 (grid spacing) * 5
    == 710

قد يتبادر للذهن سؤال عن عدم استخدام العقدة ScrollCointainer. هنا، بالتأكيد يمكن ذلك، لكننا لا نريد التنقل بين الشبكات باستمرار، ولا نريد أيضًا رؤية شريط تمرير.

نضيف السكربت التالي إلى العقدة LevelMenu لوصل إشارتي pressed الخاصتين بكل زر:

extends MarginContainer

var num_grids = 1
var current_grid = 1
var grid_width = 710

@onready var gridbox = $VBoxContainer/HBoxContainer/ClipControl/GridBox

func _ready():

    # ترقيم جميع صناديق المستويات وإلغاء قفلها   
    # يمكن استبداله بما يتناسب مع نظام المستويات في اللعبة
    # يمكن أيضًا ربط إشارات "level_selected" هنا 
    num_grids = gridbox.get_child_count()
    for grid in gridbox.get_children():
      for box in grid.get_children():
        var num = box.get_position_in_parent() + 1 + 18 * grid.get_position_in_parent()
        box.level_num = num
        box.locked = false

func _on_BackButton_pressed():
    if current_grid > 1:
      current_grid -= 1
      gridbox.rect_position.x += grid_width

func _on_NextButton_pressed():
    if current_grid < num_grids:
      current_grid += 1
      gridbox.rect_position.x -= grid_width

ننقر على زر التالي Next والسابق Back عند تشغيل المشهد ونتأكد من تمرير العناصر كما هو متوقع. من المفترض أن يطبع النقر على صندوق المستوى شيئًا في شاشة الطرفية.

الخاتمة

بهذا نكون قد انتهينا من مقالنا الذي يشرح طريقة بناء شبكة قابلة للتمرير يمينًا ويسارًا تضم صناديق لتحديد مرحلة أو مستوى اللعبة، ويمكن تحميل المثال بالكامل لرؤية كل شيء يعمل كما يجب بما في ذلك اﻹجراءات وبعض عمليات توليد اﻷطر البينية tweens لتجميل عملية التمرير.

ترجمة -وبتصرف- للمقال: Level Select Menu

اقرأ أيضًا


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

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

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



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

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

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

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


×
×
  • أضف...