التنبيهات في جودو
يطبّق كل كائن في جودوت تابع _notification
، وهدفه هو السماح للكائن بالاستجابة لأي رد نداء callback على مستوى المحرك المتعلق به. مثلًا، إذا طلب المحرك من CanvasItem
"الرسم" سيستدعي _notification(NOTIFICATION_DRAW)
.
بعض هذه التنبيهات مثل الرسم مهمة في إعادة تعريف البرامج النصية وبالتالي تقدّم جودو عددًا منهم بشكل توابع مخصصة:
-
_ready()
:NOTIFICATION_READY
_enter_tree()
:NOTIFICATION_ENTER_TREE
-
_exit_tree()
:NOTIFICATION_EXIT_TREE
-
_process(delta)
:NOTIFICATION_PROCESS
-
_physics_process(delta)
:NOTIFICATION_PHYSICS_PROCESS
_draw()
:NOTIFICATION_DRAW
ما لا يدركه المستخدمين أن التنبيهات موجودة أيضًا للأنواع الأخرى وغير مقتصرة على العقد فقط مثل:
Object::NOTIFICATION_POSTINITIALIZE
: رد نداء يُفعل أثناء تهيئة الكائن ولا يمكن للبرامج النصية الوصول إليهObject::NOTIFICATION_PREDELETE
: رد نداء يُفعل قبل أن يحذف المحرك كائن أي "حاذف"
وهناك العديد من ردود النداء الموجودة في العقد وليس لديها أي تابع مخصص ولكنها مفيدة
Node::NOTIFICATION_PARENTED
: رد نداء يُفعل كل مرة يُضاف عقدة ابن لعقدة أخرىNode::NOTIFICATION_UNPARENTED
: رد نداء بُفعل كل مرة يُزال عقدة ابن من عقدة أخرى.
يمكن الوصول إلى كل هذه التنبيهات الخاصة من التابع العام _notification()
.
ملاحظة: التوابع المعنونة في هذا المستند كـ "افتراضية" معدة لكي يُعاد تعريفها عن الطريق النصوص البرمجية. مثال على ذلك هو تابع
_init
في الكائن. حيث ليس لديها ما يعادلNOTIFICATION_*
يستدعي المحرك التابع. تعتمد معظم اللغات البرمجية (عدا C#) عليه كباني.
لذا في أي حالة يجب عليك استخدام أي نوع من هذه التنبيهات أو التوابع الافتراضية؟
_procces أو _physics_process أو *_input
استخدم _process()
عند الحاجة إلى فعل شيء يعتمد على الوقت بين إطارات اللعبة delta time، هذه هي الحالة الأفضل إذا كانت الشيفرة التي تحدث بيانات الكائن تحتاج للتحديث أكثر عدد مرات ممكنة. يُنفذ تحقق المنطق المتكرر وتخزين البيانات المؤقت هنا، ولكن يعود الأمر إلى كم مرة يُراد تحديث البيانات. إذا لم ترد التحقق عند كل إطار فيكون تنفيذ حلقة Timer-timeout هو خيار أخر.
بلغة جي دي سكربت:
# يسمح بالعمليات المتكررة التي لا تستدعي منطق السكربت في كل إطار
func _ready():
var timer = Timer.new()
timer.autostart = true
timer.wait_time = 0.5
add_child(timer)
timer.timeout.connect(func():
print("This block runs every 0.5 seconds")
)
استخدم _physics_process()
عندما تحتاج فرق توقيت مستقل عن معدل الإطارات بين الإطارات. هذا هو المكان المناسب إذا احتاجت الشيفرة تحديث دائم لفترة من الزمن بغض النظر عن كيفية مرور الوقت. يجب تنفيذ العمليات الحركية و تحولات الكائن المتكررة هنا.
يجب تفادي تحققات الإدخال عند ردود النداء هذه للحصول على أفضل أداء أينما أمكن. تصبح كل من _process()
و _physics_process()
فعالتان في كل فرصة (لا "تهدأ" بشكل افتراضي) وعلى العكس يصبح رد نداء *_input()
فعالًا في الإطارات التي يشعر فيها المحرك بإدخال.
يمكن التحقق من نتيجة الدخل داخل رد نداء الدخل بنفس الطريقة. إذا أردت استخدام فرق الوقت يمكن جلبها من فرق الوقت المتعلق بالتابع وقت الحاجة.
بلغة جي دي سكربت:
# يُستدعى كل إطار حتى عندما لا يجد المحرك أي دخل
func _process(delta):
if Input.is_action_just_pressed("ui_select"):
print(delta)
# يُستدعى عند كل حدث إدخال
func _unhandled_input(event):
match event.get_class():
"InputEventKey":
if Input.is_action_just_pressed("ui_accept"):
print(get_process_delta_time())
بلغة سي شارب:
using Godot;
public partial class MyNode : Node
{
// تُستدعى كل إطار، حتى عندما لا يجد المحرك أي دخل
public void _Process(double delta)
{
if (Input.IsActionJustPressed("ui_select"))
GD.Print(delta);
}
// تُستدعى كل حدث إدخال، وهي مساوية للقيمة true بالنسبة للتابع _input()
public void _UnhandledInput(InputEvent @event)
{
switch (@event)
{
case InputEventKey:
if (Input.IsActionJustPressed("ui_accept"))
GD.Print(GetProcessDeltaTime());
break;
}
}
}
_init أو التهيئة أو التصدير
إذا هيأ البرنامج النصي عقدة الشجرة الفرعية الخاصة به دون مشهد يجب على الشيفرة أن تُنفذ هنا، كما يجب أن تُنفذ الخاصيات الأخرى أو التهييئات المستقلة عن شجرة المشهد هنا أيضًا. يُفعل ذلك قبل _ready()
أو _enter_tree()
ولكن بعد ما يُنشئ السكربت ويهيئ خاصياته
لدى السكربتات ثلاثة أنواع من الإسنادات التي تحصل وقت النسخ:
بلغة جي دي سكربت:
# القيمة "one" هي القيمة الأولية، وهي لا تتسبب باستدعاء الضابط
# إذا ضبط أحدهم القيمة إلى "two" من المحرر، فهذه ستكون بمثابة قيمة مصدّرة، مما يتسبب باستدعاء الضابط
export(String) var test = "one" setget set_test
func _init():
# القيمة "three" هي قيمة إسناد أولية
# هذه القيم لا تتسبب باستدعاء الضابط
test = "three"
# هذه تستدعي الضابط (لاحظ self الموجودة كسابقة)
self.test = "three"
func set_test(value):
test = value
print("Setting: ", test)
بلغة سي شارب:
using Godot;
public partial class MyNode : Node
{
private string _test = "one";
// تغيير القيمة من المحرر يتسبب باستدعاء التابع الضابط في سي شارب
[Export]
public string Test
{
get { return _test; }
set
{
_test = value;
GD.Print($"Setting: {_test}");
}
}
public MyNode()
{
// يتسبب هذا باستدعاء الضابط أيضًا
Test = "three";
}
}
عند نسخ المشهد ستُضبط قيم الخاصية حسب الترتيب التالي:
- إسناد القيمة الأولية: سيعين النسخ إما قيمة تهيئة أو قيمة إسناد init وتكون الأولوية لإسناد init على قيم التهيئة
- إسناد القيمة المُصدّرة: تعين جودو القيمة المُصدرة لتستبدل القيمة الأولية المحددة في البرنامج النصي في حال كان النسخ من مشهد بدلًا من سكربت
نتيجة لذلك سيؤثر نسخ السكربت والمشهد على كلّ من التهيئة وعدد المرات التي يستدعي فيها المحرك الضابط.
_ready أو _enter_tree أو NOTIFICATION_PARENTED
ستنسخ جودو العقد في الشجرة (عن طريق إجراء باستدعاءات _init()
) وتبني الشجرة بدءًا من الجذر عند نسخ مشهد متصل مع مع المشهد المُنفذ أولًا. هذا يجعل استدعاءات _enter_tree()
لتنتقل على طول الشجرة، وتستدعي عقد الأوراق ()_ready
عندما تكتمل الشجرة. تستدعي العقدة هذه التابع عندما ينتهي كل العقد الأولاد من استدعائهم يسبب هذا انتقال عكسي يصعد على جذر الشجرة
عند نسخ سكربت أو مشهد مستقل، لا تُضاف العقد إلى شجرة المشهد عند الإنشاء، لذا لا يُفعل رد نداء _enter_tree()
بدلًا عن ذلك يحصل فقط استدعاء _init()
وعندما يضاف المشهد إلى شجرة المشهد يحصل استدعائي _enter_tree()
و _ready()
.
إذا أردت تفعيل سلوك يحصل من عقدة أب لأُخرى بغض النظر إذا كان جزءًا من المشهد الأساسي/الفعال أو لا يمكن استخدام تنبيه PARENTED. مثلًا هذا جزء من شيفرة تصل تابع عقدة مع إشارة مخصصة على عقدة أب دون أن تفشل. إن هذا مفيد في عقد مركزية البيانات التي نحتاج إليها وقت التنفيذ.
بلغة جي دي سكربت:
extends Node
var parent_cache
func connection_check():
return parent_cache.has_user_signal("interacted_with")
func _notification(what):
match what:
NOTIFICATION_PARENTED:
parent_cache = get_parent()
if connection_check():
parent_cache.interacted_with.connect(_on_parent_interacted_with)
NOTIFICATION_UNPARENTED:
if connection_check():
parent_cache.interacted_with.disconnect(_on_parent_interacted_with)
func _on_parent_interacted_with():
print("I'm reacting to my parent's interaction!")
بلغة سي شارب:
using Godot;
public partial class MyNode : Node
{
private Node _parentCache;
public void ConnectionCheck()
{
return _parentCache.HasUserSignal("InteractedWith");
}
public void _Notification(int what)
{
switch (what)
{
case NotificationParented:
_parentCache = GetParent();
if (ConnectionCheck())
{
_parentCache.Connect("InteractedWith", Callable.From(OnParentInteractedWith));
}
break;
case NotificationUnparented:
if (ConnectionCheck())
{
_parentCache.Disconnect("InteractedWith", Callable.From(OnParentInteractedWith));
}
break;
}
}
private void OnParentInteractedWith()
{
GD.Print("I'm reacting to my parent's interaction!");
}
}