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

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

مثلًا قد يكون لديك شريط حياة health bar يمثل صحة اللاعب، تريد أن يتغير شريط الحياة عندما يتضرر اللاعب أو يستخدم عقار لزيادة الصحة، يجب استخدام الإشارات للقيام بذلك.

ملاحظة: الإشارات في جودو هي تطبيق لنمط المراقب Observer Pattern في البرمجة وهو نمط تصميم يُستخدم لتمكين كائن (المراقب) من الاستجابة لتغييرات تحدث في كائن آخر دون أن يكون هناك ارتباط مباشر بينهما لتقليل الترابط بين الأجزاء المختلفة من البرنامج وتسهيل إدارة التغييرات والتحديثات بشكل أكثر مرونة.، يمكنك التعلم المزيد عنه هنا وفي الفقرات التالية سنستخدم الإشارات لجعل أيقونة جودو التي حركناها في الدرس السابق تتحرك وتتوقف عن طريق ضغط الأزرار.

إعداد المشهد

لإضافة زر للعبة يجب إنشاء مشهد أساسي جديد يضم كلًّا من الزر والمشهد sprite_2d.tscn الذي أنشأناه في درس كتابة أول كود برمجي خاص بك في جودو.

أنشئ مشهد جديد في جودو عن طريق الذهاب إلى قائمة "مشهد" Scene ومن ثم اختر "مشهد جديد" New Scene

1

في قائمة "المشهد" Scene انقر زر 2D الذي سيضيف عقدة Node2D كجذر للمشهد.

2

انقر واسحب ملف sprite_2d.tscn المحفوظ سابقًا في Node2D في قائمة FileSystem لاستنساخه.

3

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

4

ابحث عن عقدة الزر Button وأضفها كما يلي:

5

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

6

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

7

انقر على الزر واسحبه لتقريبه أكثر من الأيقونة. يمكنك أيضًا كتابة عنوان للزر عن طريق تعديل خاصية النص الخاصة به في الفاحص Inspector، وادخل على Toggle motion.

8

يجب أن تكون شجرة المشهد وإطار الرؤية على النحو التالي:

9

احفظ المشهد باسم node_2d.tscn إن لم تقم بذلك حتى الآن، يمكنك تشغيله عن طريق F6 (أو Cmd+R على macOS)، إن الزر سيكون واضح، ولكن لن يحصل شيء إذا ضغطته.

ربط الإشارة بالمحرر

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

يمكن ربط الإشارات في قائمة العقدة عن طريق اختيار زر العقدة على يمين المحرر والنقر على التبويبة المسماة "عقدة Node" بجانب "الفاحص Inspector".

10

تظهر القائمة عدد من الإشارات المتوافرة للعقدة المختارة

11

انقر نقرة مزدوجة على إشارة "مضغوط pressed" لفتح نافذة ارتباط العقدة

12

يمكنك هنا ربط الإشارة مع عقدة Sprite2D التي تحتاج إلى تابع مستقبل أي إلى دالة تستدعيها جودو عندما يصدر الزر الإشارة، وسينشئ المحرر هذه الدالة لك تلقائيًا، تسمى هذه العقد اصطلاحًا "‎‏‏‎onnodenamesignalname" وقد أسميناها هنا "‎‎onbuttonpressed".

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

13

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

14

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

15

لنستبدل السطر الذي يتضمن الكلمة المفتاحية pass في الشيفرة بشيفرة فعلية تنفذ حركة العقدة. ستتحرك Sprite2D بفضل الشيفرة في دالة ‎_process()‎، إذ تقدم جودو تابعًا مضمنًا لتفعيل أو تعطيل المعالجة ويمكنك تفعيل أو تعطيل المعالجة المستمرة للدالة ‎_‎process()‎‎‎‎ على عقدة معينة فإذا مررت true للتابع Node.set_process()‎ سينفذ ‎_‎process()‎‎‎‎ في كل إطار مما يسمح لك بتحديث الموقع أو الحالة بمرور الوقت؛ وإذا مررت false فلن تنفذ وستتوقف التحديثات المستمرة للعقدة. يفيد تابع آخر لصنف العقدة is_processing‎()‎ في التحقق مما إذا كانت المعالجة المستمرة المنفذة في الدالة ‎_‎process()‎‎‎‎ مفعلة أم لا حيث تعيد القيمة true إذا كانت المعالجة الخاصة بالعقدة مفعلة والدالة ‎_‎‎process()‎‎‎‎ تنفذ في كل إطار، وتعيد false إذا كانت معالجة العقدة غير نشطة، ويمكننا استخدام الكلمة المفتاحية not لعكس القيمة. بلغة GDScript:

func _on_button_pressed():
    set_process(not is_processing())

بلغة C#‎:

private void OnButtonPressed()
{
    SetProcess(!IsProcessing());
}

تفعل هذه السيفرة دالة المعالجة والتي بدورها تفعل أو تعطل حركة الأيقونة عند ضغط الزر. نحتاج قبل تجربة اللعبة لتبسيط دالة ‎_Process()‎ لنقل العقدة تلقائيًا والانتظار لمدخلات المستخدم، استبدلها بالشيفرة التالية التي شاهدناها منذ درسين سابقين.

بلغة GDScript:

func _process(delta):
    rotation += angular_speed * delta
    var velocity = Vector2.UP.rotated(rotation) * speed
    position += velocity * delta

بلغة C#‎:

public override void _Process(double delta)
{
    Rotation += _angularSpeed * (float)delta;
    var velocity = Vector2.Up.Rotated(Rotation) * _speed;
    Position += velocity * (float)delta;
}

يجب أن تكون شيفرة sprite_2d.gd كالتالي:

بلغة GDScript:

extends Sprite2D

var speed = 400
var angular_speed = PI


func _process(delta):
    rotation += angular_speed * delta
    var velocity = Vector2.UP.rotated(rotation) * speed
    position += velocity * delta


func _on_button_pressed():
    set_process(not is_processing())

بلغة C#‎:

using Godot;

public partial class MySprite2D : Sprite2D
{
    private float _speed = 400;
    private float _angularSpeed = Mathf.Pi;

    public override void _Process(double delta)
    {
        Rotation += _angularSpeed * (float)delta;
        var velocity = Vector2.Up.Rotated(Rotation) * _speed;
        Position += velocity * (float)delta;
    }

    private void OnButtonPressed()
    {
        SetProcess(!IsProcessing());
    }
}

شغل المشهد الآن وانقر على الزر لترى كيفية تحرك وتوقف الأيقونة مع النقر.

ربط الإشارة باستخدام الشيفرة

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

لنستخدم عقدة مختلفة، إن لجودو عقدة مؤقت زمني Timer تفيد في تحديد أوقات المهارات أو أوقات إعادة تلقيم الأسلحة النارية في الألعاب.

عد إلى مساحة العمل عن طريق النقر على "2D" أعلى الشاشة أو الضغط على Ctrl+F1 (أو Ctrl+Cmd+1على macOS)

انقر بالزر الأيمن على عقدة Sprite2D في قائمة "المشهد" Scene وأضف عقدة ابن جديدة، وابحث عن المؤقت Timer وأضف العقدة المرافقة، يجب أن يكون المشهد كالتالي

16

اذهب إلى "الفاحص" Inspector بعد اختيار عقدة المؤقت لتفعيل خاصية البدء التلقائي.

17

انقر على أيقونة السكريبت بجانب Sprite2D للانتقال إلى مكان عمل البرنامج النصي

18

سنحتاج إلى إجراء عمليتين لربط العقد عن طريق الشيفرة البرمجية:

  • الحصول على مرجع من المؤقت إلى Sprite2D
  • استدعاء التابع connect()‎ على إشارة المؤقت "timeout"

ملاحظة: تحتاج استدعاء تابع connect()‎ للإشارة التي تريد أن تستمع إليها لربط الإشارة باستخدام الشيفرة، وفي حالتنا نريد الاستماع لإشارة "timeout" الخاصة بالمؤقت.

نريد ربط الإشارة عند استنساخ المشهد، ويمكنن القيام بذلك باستخدام الدالة المضمّنة Node._ready()‎ التي يتم استدعائها تلقائيًا عن طريق المحرك عندما يتم استنساخ المشهد بالكامل.

نستخدم الدالة Node.get_node()‎ للحصول على مرجع بالنسبة للعقدة الحالية، ويمكننا تخزين المرجع في متغير.

بلغة GDScript:

func _ready():
    var timer = get_node("Timer")

بلغة C#‎:

public override void _Ready()
{
    var timer = GetNode<Timer>("Timer");
}

تنظر الدالة get_node()‎ إلى أبناء Sprite2D وتحصل على العقد بأسمائها، مثلًا إذا أعدت تسمية عقدة المؤقت إلى "BlinkingTimer" في المحرر، فيجب عليك تغيير الاستدعاء إلى get_node("BlinkingTimer")‎

يمكننا الآن ربط المؤقت إلى Sprite2D في دالة ‎_ready()‎

بلغة GDScript:

func _ready():
    var timer = get_node("Timer")
    timer.timeout.connect(_on_timer_timeout)

بلغة C#‎:

public override void _Ready()
{
    var timer = GetNode<Timer>("Timer");
    timer.Timeout += OnTimerTimeout;
}

يُقرأ السطر كالتالي: ربطنا إشارة المؤقت "timeout" للعقدة التي يرتبط بها السكربت، وعندما يصدر المؤقت timeout نريد استدعاء الدالة ‎_on_timer_timeout()‎ التي نحتاج لتعريفها، لنضف ذلك إلى أسفل الشيفرة البرمجية ونستخدمه لتفعيل شفافية الأيقونة.

ملاحظة: تسمى دالة رد النداء callback اصطلاحًا باسم GDScript "OnNodeNameSignalName"‎ وستكون هنا باسم "_on_timer_timeout" في كود GDScript وباسم "()OnTimerTimeout" في كود #C.

بلغة GDScript:

func _on_timer_timeout():
    visible = not visible

بلغة C#‎:

private void OnTimerTimeout()
{
    Visible = !Visible;
}

إن الخاصية visible بوليانية وتتحكم بشفافية العقدة، يتفعل السطر visible = not visible وإذا كانت القيمة visible هي true تصبح false والعكس صحيح سترى الأيقونة الآن تظهر وتختفي كل ثانية إذا شغلت المشهد

البرنامج النصي الكامل

لقد انتهينا من أيقونة جودو المتحركة التي تومض والملف التالي هو ملف sprite_2d.gd كاملًا كمرجع. بلغة GDScript:

extends Sprite2D

var speed = 400
var angular_speed = PI


func _ready():
    var timer = get_node("Timer")
    timer.timeout.connect(_on_timer_timeout)


func _process(delta):
    rotation += angular_speed * delta
    var velocity = Vector2.UP.rotated(rotation) * speed
    position += velocity * delta


func _on_button_pressed():
    set_process(not is_processing())


func _on_timer_timeout():
    visible = not visible

بلغة C#‎:

using Godot;

public partial class MySprite2D : Sprite2D
{
    private float _speed = 400;
    private float _angularSpeed = Mathf.Pi;

    public override void _Ready()
    {
        var timer = GetNode<Timer>("Timer");
        timer.Timeout += OnTimerTimeout;
    }

    public override void _Process(double delta)
    {
        Rotation += _angularSpeed * (float)delta;
        var velocity = Vector2.Up.Rotated(Rotation) * _speed;
        Position += velocity * (float)delta;
    }

    private void OnButtonPressed()
    {
        SetProcess(!IsProcessing());
    }

    private void OnTimerTimeout()
    {
        Visible = !Visible;
    }
}

الإشارات المخصصة

هذا القسم هو مرجع لكيفية تعريف واستخدام الإشارات الخاصة بك، ولا يبني على المشروع المُنشئ في الدروس السابقة

يمكنك تعريف إشارات مخصصة في برنامج نصي. مثلًا تريد أن تظهر شاشة "انتهت اللعبة" عندما تصل حياة اللاعب للصفر، يمكنك تعريف إشارة اسمها "died" أو "health_depleted" للقيام بذلك عندما تصل حياتهم للصفر.

إليك الشيفرة بلغة GDScript:

extends Node2D

signal health_depleted

var health = 10

وبلغة C#‎:

using Godot;

public partial class MyNode2D : Node2D
{
    [Signal]
    public delegate void HealthDepletedEventHandler();

    private int _health = 10;
}

ملاحظة: تمثل الإشارات أحداثًا حصلت للتو، ويمكننا استخدام أفعال بالزمن الماضي في تسميتها.

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

19

لِبَثّ إشارة في برنامج النصي تحتاج لاستدعاءemit()‎ على الإشارة

بلغة GDScript:

func take_damage(amount):
    health -= amount
    if health <= 0:
        health_depleted.emit()

بلغة C#‎:

public void TakeDamage(int amount)
{
    _health -= amount;

    if (_health <= 0)
    {
        EmitSignal(SignalName.HealthDepleted);
    }
}

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

بلغة GDScript:

extends Node

signal health_changed(old_value, new_value)

var health = 10

بلغة C#‎:

using Godot;

public partial class MyNode : Node
{
    [Signal]
    public delegate void HealthChangedEventHandler(int oldValue, int newValue);

    private int _health = 10;
}

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

لبث القيم باستخدام الإشارة أضف بعض الوسائط الإضافية إلى الدالة emit()‎

بلغة GDScript:

func take_damage(amount):
    var old_health = health
    health -= amount
    health_changed.emit(old_health, health)

بلغة C#‎:

public void TakeDamage(int amount)
{
    int oldHealth = _health;
    _health -= amount;
    EmitSignal(SignalName.HealthChanged, oldHealth, _health);
}

الخلاصة

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

على سبيل المثال إذا كان هناك عنصر في اللعبة (مثل قطعة نقدية) يمكن للاعب التقاطها أو جمعها ممثلة بعقدة Area2D فإنها ستصدر أو تَبُثّ إشارة body_entered عندما يدخل جسم اللاعب شكل الاصطدام الخاص بها مما يسمح لك بمعرفة إذا ما تم التقطها من قبل اللاعب أم لا.

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

ترجمة - وبتصرف - لقسم Using signals من توثيق جودو الرسمي.

اقرأ أيضًا


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

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

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



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

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

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

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


×
×
  • أضف...