الخرائط المصغرة هي عبارة عن واجهات رسومية صغيرة تظهر في زاوية شاشة اللعب، تعرض تمثيلًا مصغرًا لخريطة اللعبة الكاملة أو المنطقة المحيطة باللاعب. لتساعد على تحديد موقعنا داخل اللعبة وترينا أماكن الأعداء أو الأشياء المهمة من حولنا، فهي تعمل كنظام رادار لكشف الأعداء والأهداف المخفية وتوفر صورة عامة عن بيئة اللعب ككل. سنبني في هذا المقال خريطة مصغرة تعرض موقع اﻷشياء الواقعة خارج مجال رؤية اللاعب بشكل نقاط أو أيقونات صغيرة وسنحدث مواقع هذه النقاط كلما تحرك اللاعب.
إعداد المشروع
سننشئ لعبة تخطيطها من اﻷعلى إلى اﻷسفل وسنستخدم ميزة Autotile في محرك الألعاب جودو فهي تُسهّل كثيراً عملية رسم الخرائط باستخدام عناصر رقعة Tiles متداخلة وتمكننا من رسم الجدران أو الأرضيات بحرية، حيث يختار المحرك تلقائيًا الرقعة المناسبة من مجموعة الرقع حتى تتطابق الحواف والزوايا مع الرقع المجاورة، ويمكن تنزيل الصور المطلوبة لتطبيق المقال من مجلد assets من هذا الرابط أو منminimap_assets.zip
سيبدو المشهد الرئيسي للعبة كالتالي:
نستخدم العقدة CanvasLayer
لتجميع عناصر واجهة المستخدم بما في ذلك الخريطة المصغرة التي سننشؤها في هذا المقال، ونستخدم العقدة TileMap
لرسم الخريطة باستخدام الرقع tiles، بينما نستخدم العقدة Player
لتمثيل شخصية اللاعب.
تخطيط واجهة المستخدم
الخطوة اﻷولى في مثالنا هي بناء تخطيط للخريطة المصغرة. وللتعامل مع أية عناصر واجهة مستخدم تضمها اللعبة، لا بد من إعادة تحجيمها بشكل سلس ودمجها جيدًا في تخطيط يتلائم مع الحاوية. لهذا سنضيف أولًا العقدة MarginContainer
التي تساعدنا على وضع حواشي داخلية padding للعناصر داخل الحاوية، ونضبط الخاصية Constants
في القسم Theme Overrides
على 5
. يضم عنصر التحكم هذا بقية العقد ويضمن أن العناصر داخل الحاوية ستبقى بعيدًا عن حواف الحاوية نفسها بشكل متناسق. سنسمي هذه العقدة MiniMap
ثم نحفظ المشهد.
نضيف تاليًا العقدة NinePatchRect
وهي مشابهة للعقدة TextureRect
لكنها تتعامل مع تغيير اﻷبعاد بطريقة مختلفة، إذ لا تمدد الزوايا أو الحواف مما يحافظ على مظهر الصورة بشكل أفضل عند تغيير الأبعاد. نفلت الصورة panel_woodDetail_blank.png
من مجلد assets في لعبتنا في الخاصية Texture
، وهي صورة أبعادها 128x128 بكسل، لكن إن غيرنا أبعاد العقدة MarginContainer
ستصبح الصورة ممددة وسيئة المظهر.
لكن مع استخدام NinePatchRect
سنضمن أن اﻹطار سيحافظ على أبعاده فعند التمدد ستعمل العقدة على تقسيم الصورة إلى تسعة أجزاء بحيث تبقى الزوايا ثابتة وغير متمددة وتتوزع الحواف بطريقة تمدد سلسة بحيث لا تتشوه الصورة ويتمدد الوسط بطريقة مرنة لتعبئة المساحة.
بإمكاننا تعريف هذه الخاصيات رسوميًا في اللوحة TextureRegion
، لكن من اﻷسهل أحيانًا إدخال القيم مباشرة. نضبط الخاصيات اﻷربعة الموجودة في Patch Margin
على 64
ونغيّر اسم العقدة إلى Frame
. لنلاحظ اﻵن ما يحدث عند تغيير اﻷبعاد:
علينا تاليًا ملء الجزء الداخلي من اﻹطار بنمط يمثل شبكة وذلك باستخدام الصورة pattern_blueprintPaper.png
:
نريد اﻵن أن تملأ الصورة ما داخل اﻹطار تلقائيًا أيًا كانت أبعاده، وطالما أن المنطقة التي تغطيها الشبكة هي المكان الذي ستظهر فيه نقاط علام الخريطة، لا ينبغي أن تمتد الشبكة إذًا خارج حدود اﻹطار.
لهذا، نضيف عقدة جديدة MarginContainer
كابن للعقدة MiniMap
وشقيق للعقدة Frame
ثم نضبط خاصيات Constants
في القسم Theme Overrides
على القيمة 20
. نضيف بعد ذلك عقدة TectureRect
كابن للعقدة السابقة ثم نضبط قيمة الخاصية Texure
على نفس الصورة السابقة، والخاصية Strech Mode
على Tile
ونسمي العقدة أخيرًا Grid
. لنجرّب تغيير أبعاد العقدة اﻷصلية لرؤية تأثير ما فعلناه حتى اللحظة:
لنبق أبعاد الخريطة المصغرة حاليًا على 200x200 بكسل، وبإمكاننا التأكد من هذه اﻷبعاد من الخاصية Size
في القسم Layout
. ستبدو اﻵن شجرة المشهد كالتالي:
نقاط علام الخريطة
سنضيف للخريطة نقاط العلام Marker ترمز كل منها لأشياء معينة في اللعبة، نضيف أولًا عقدة من النوع Sprite2D
كابن للعقدة Grid
ونسميها PlayerMarker
لتمثل اللاعب ونمنحها الصورة minimapIcon_arrowA.png
، وننتبه إلى أن قيمة الخاصية Position
في القسم Transform
هي (0,0)
مما يجعلها في الزاوية العليا اليسارية من العقدة Grid
.
إن كانت أبعاد الشبكة (150,150)
سيكون مركزها عند (75,75)
، لنضبط إذًا موقع PlayerMarker
على تلك القيمة، ننوه أن هذه العملية ستكون آلية لاحقًا.
نضيف اﻵن عقدتين جديدتين من نوع Sprite2D
ونسميهما AlertMarker
و MobMarker
ونمنحهما الصورتين minimapIcon_jewelRed.png
التي تمثل جوهرة حمراء و minimapIcon_exclamationYellow.png
التي تمثل علامة تعجب صفراء كما يلي:
تمثل العقدتان السابقتان نوعين جديدن من نقاط العلام في عالم اللعبة. ننقر على الزر Toggle Visibility المجاور لكل منهما كي لا تظهر العقدة افتراضيًا.
كتابة سكريبت نقاط العلام
علينا اﻵن اتخاذ بعض القرارات. فطريقة نشر العلامات على الخريطة تتعلق كثيرًا بطريقة إعداد اللعبة. وطالما أن الهدف من المثال هو عرض الفكرة بسهولة، سنبقى العملية بسيطة ما أمكن، لكن في ألعاب أضخم يجب إيجاد نهج أقوى وأفضل.
لدينا في مثالنا كائنان اﻷول Mob
يتجول عشوائيًا في الخريطة والثاني Crate
يمكن للاعب التقاطه. يتبعثر العديد من هذه الكائنات ضمن المشهد الرئيسي، ولا بد من تمثيل كل منها بأحد أنواع نقاط العلام التي تعرضها الخريطة.
نضيف كل كائن نريده أن يظهر على الخريطة ضمن المجموعة minimap_objects
ثم نضبط المتغير minimap_icon
في سكريبت كل كائن على القيمة المناسبة من المجموعة:
# mob في سكريبت: var minimap_icon = "mob" # crate في سكريبت: var minimap_icon = "alert"
بإمكاننا اﻵن إضافة سكريبت إلى العقدة MiniMap
.
نرى أولًا في السكريبت مرجعًا إلى العقدة Player
لنعطي الخريطة معلومة عن موقع اللاعب، ويمكن تعيين هذا الموقع ضمن الفاحص عند إضافة الخريطة المصغرة إلى المشهد الرئيسي، كما نرى خاصية zoom
ومهمتها معايرة المقياس، أي إلى أي مدى نكبر أو نصغر العالم داخل الخريطة. كما أضفنا بعض المتغيرات يسبقها التوجيه Onready@
لجعل الوصول إلى العقد المطلوبة أكثر ملائمة فهو يعني إنشاء المتغير عندما تكون العقدة جاهزة أي بعد تحميلها داخل المشهد.
extends MarginContainer class_name Minimap @export var player: Player @export var zoom = 1.5 @onready var grid = $MarginContainer/Grid @onready var player_marker = $MarginContainer/Grid/PlayerMarker @onready var mob_marker = $MarginContainer/Grid/MobMarker @onready var alert_marker = $MarginContainer/Grid/AlertMarker
نستخدم تاليًا قاموسًا ونسميه minimap-icon
لربط الأنواع بالرموز، حيث نظهر الكائن mob
الذي يمثل العدو كنقطة حمراء على الخريطة، والكائن alert
الذي يمثل تحذير كعلامة صفراء:
@onready var icons = { "mob": mob_marker, "alert": alert_marker }
نحتاج أيضُا إلى متغير يخزّن نسبة حجم الخريطة إلى حجم عالم اللعبة. كما نستفيد من قاموس آخر لإسناد نقاط العلام الفعالة إلى كل كائن. وسيكون المفتاح Key في القاموس هو الكائن نفسه أي نسخة عن Mob
أو Crate
والقيمة value هي نقطة العلام المسندة إليه:
var grid_scale var markers = {}
نضبط موقع نقطة علام اللاعب في منتصف الخريطة ضمن الدالة ()ready_
، ونحسب عامل المقياس.
ملاحظة: علينا توصيل اﻷشارة resized
وتنفيذ خطوتي تحديد الموقع، وعامل المقياس ضمن دالة رد نداء callback إن كانت أبعاد واجهة المستخدم لدينا ديناميكية ومتغيرة الحجم وإلا فستظهر الرموز في أماكن خاطئة أو ستبدو بحجم غير مناسب.
func _ready(): await get_tree().process_frame player_marker.position = grid.size / 2 grid_scale = grid.size / (get_viewport_rect().size * zoom)
العقد الموجودة في الحاويات
نظرًا للطريقة التي تعامل فيها العقدة Container
أبناءها وتغير حجمهم أو موقعهم، لن نحصل على القيمة الصحيحة لأبعاد اﻷبناء وقت تنفيذ الدالة ()ready_
، لهذا علينا أن ننتظر حتى اﻹطار التالي لنحصل على أبعاد الشبكة.
ننشئ أيضًا نقطة علام لكل لكائن في اللعبة باستخدام المجموعة minimap_objects
بمضاعفة العقدة المطابقة لنقطة العلام وربط العلامة بالكائن بالاستفادة من القاموس markers
:
var map_objects = get_tree().get_nodes_in_group("minimap_objects") for item in map_objects: var new_marker = icons[item.minimap_icon].duplicate() grid.add_child(new_marker) new_marker.show() markers[item] = new_marker
بعد أن أنشأنا نقاط العلام وربطناها بالكائنات الموجودة، نستطيع اﻵن تحديث مواقعها ضمن الدالة ()process_
. وإن لم يُعين أي لاعب player
بعد، لا نفعل شيئًا:
func _process(delta): if !player: return
وﻹن كان هناك لاعب، ندور أولًا نقطة علام اللاعب لتطابق جهة حركته. وطالما أن نقطة العلامة PlayerMarker
تتجه إلى اﻷعلى وليس بالاتجاه اﻷفقي x
، لا بد من إضافة 90 درجة:
player_marker.rotation = player.rotation + PI/2
نبحث اﻵن عن موقع كل كائن بالنسبة إلى اللاعب ونستخدمه في إيجاد موقع نقطة العلام، لنتذكر إزاحة الموقع بمقدار grid.size/2
لأن نقطة المرجع هي الزاوية العليا اليسارية:
for item in markers: var obj_pos = (item.position - player.position) * grid_scale + grid.size / 2 markers[item].position = obj_pos
تبقى المشكلة إمكانية ظهور بعض نقاط العلام خارج الشبكة كما في الصورة التالية:
وﻹصلاح اﻷمر، نحصر موقع نقطة العلامة بمربع الشبكة باستخدام الدالة clamp
بعد حساب المتغير obj_pos
وقبل تحديد موقع العلامة:
obj_pos = obj_pos.clamp(Vector2.ZERO, grid.size)
بإمكاننا أيضًا معالجة العلامات التي تقع خارج الشاشة وخارج مربع الشبكة. باختيار أحد الحلين التاليين وقبل استخدام ()clamp
.
الخيار اﻷول هو كالتالي:
if grid.get_rect().has_point(obj_pos + grid.position): markers[item].show() else: markers[item].hide()
الخيار الثاني هو تغيير مظهر العلامات، بأن نجعلها أصغر لتدل على أنها أبعد مسافة:
if grid.get_rect().has_point(obj_pos + grid.position): markers[item].scale = Vector2(1, 1) else: markers[item].scale = Vector2(0.75, 0.75)
إزالة الكائنات
ستزدحم اللعبة وتتوقف إن قُتِل أي كائن Mob
أو التُقط كائن Crate
لأن نقاط العلام حينها لن تكون صحيحة. لهذا نحتاج إلى طريقة نتأكد من خلالها من إزالة نقاط العلام في حال إزالة الكائنات، وفيما يلي طريقة سريعة سنتبعها في هذا المقال:
نضيف signal removed
إلى أي كائن وضعناه ضمن المجموعة minimap_object
ثم نبث هذه الرسالة عندما يتدمر الكائن أو يُلتقط مع مرجع إلى الكائن نفسه كي تتعرف عليه الخريطة:
removed.emit(self)
نصل هذه اﻹشارات إلى الخريطة في الدالة ()ready_
للسكريبت الرئيسي:
func _ready(): for object in get_tree().get_nodes_in_group("minimap_objects"): object.removed.connect(minimap._on_object_removed)
نضيف اﻵن دالة استقبال اﻹشارات إلى سكريبت الخريطة المصغرة لتحرير نقطة العلام وإزالة المرجع:
func _on_object_removed(object): if object in markers: markers[object].queue_free() markers.erase(object)
تكبير وتصغير الخريطة المصغرة
لدينا ميزة أخيرة سنضيفها إلى مثالنا وهو تحديد مستوى التكبير والتصغير للخريطة. إذ يغير تدوير عجلة الفأرة فوق الخريطة مقياسها تكبيرًا أو تصغيرًا.
نضف بداية دالة تهيئة setter إلى الخاصية zoom
:
@export var zoom = 1.5: set = set_zoom func set_zoom(value): zoom = clamp(value, 0.5, 5) grid_scale = grid.size / (get_viewport_rect().size * zoom)
نصل في نافذة الفاحص اﻹشارة _gui_input
بالعقدة MiniMap
حتى نتمكن من معالجة أفعال تدوير عجلة الفأرة:
func _on_gui_input(event): if event is InputEventMouseButton and event.pressed: if event.button_index == MOUSE_BUTTON_WHEEL_UP: zoom += 0.1 if event.button_index == MOUSE_BUTTON_WHEEL_DOWN: zoom -= 0.1
فيما يلي نتيجة الشيفرة:
الخاتمة
شرحنا في هذا المقال طريقة إضافة خريطة مصغرة لعالم اللعبة وحاولنا أن نجعلها مرنًا بما فيه الكفاية حتى نتمكن من تضمينها في أي لعبة نعمل عليها في جودو، ويمكن تحسين هذه الخريطة بإضافة الأمور التالية إليها:
- أنواع أكثر من نقاط العلام.
- إضافة وحدات أخرى للخريطة عند توليدها باستعمال الإشارات كما فعلنا تمامًا عند إزالة الوحدات
- الحصول على معلومات عند النقر على نقطة العلام
- استخدام صورة للخريطة الفعلية بدلًا من استخدام صورة الشبكة
ترجمة -وبتصرف- للمقال: MinMap/Radar
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.