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

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

يُعَد معامل دلتا delta أو "زمن دلتا" مفهومًا يُساء فهمه كثيرًا في تطوير الألعاب، لذا سنشرح في هذا المقال كيفية استخدامه وأهمية الحركة المستقلة عن معدل الإطارات وأمثلة عملية لاستخدامه في محرّك الألعاب جودو Godot.

ليكن لدينا عقدة Sprite تتحرك عبر الشاشة. إذا كان عرض الشاشة 600 بكسل ونريد أن نعبر الشخصية الرسومية Sprite الشاشة خلال 5 ثوانٍ، فيمكننا استخدام العملية الحسابية التالية لإيجاد السرعة اللازمة لذلك:

600 pixels / 5 seconds = 120 pixels/second

سنحرّك الشخصية الرسومية في كل إطار باستخدام الدالة ‎_process()‎، بحيث إذا شُغِّلت اللعبة بمعدل 60 إطارًا في الثانية، فيمكننا إيجاد الحركة لكل إطار باستخدام العملية الحسابية التالية:

120 pixels/second * 1/60 second/frame = 2 pixels/frame

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

تكون الشيفرة البرمجية الضرورية كما يلي:

extends Node2D

# الحركة المطلوبة بالبكسلات لكل إطار
var movement = Vector2(2, 0)

func _process(delta):
    $Sprite.position += movement

نشغّل الشيفرة البرمجية السابقة وسنجد أن الصورة تعبر الشاشة خلال 5 ثوانٍ.

عبور صورة اللعبة خلال 5 ثوانٍ في محرك الألعاب جودو

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

إذا تخيلنا مثلًا أن معدل الإطارات انخفض إلى النصف بحيث يستغرق كل إطار 1/30 بدلًا من 1/60 من الثانية. فمعنى هذا أن الأمر سيستغرق ضعف الوقت حتى تصل الشخصية الرسومية إلى طرف الشاشة عند التحرك بمعدل 2 بكسل لكل إطار.

عبور صورة اللعبة خلال 10 ثوانٍ في محرك الألعاب جودو

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

إصلاح مشكلة معدل الإطارات

تتضمن الدالة ‎_process()‎ تلقائيًا عند استخدامها معاملًا بالاسم delta يمرّره المحرّك كما في الدالة ‎_physics_process()‎ التي تُستخدَم في الشيفرة البرمجية المتعلقة بالفيزياء.

المعامل delta هو قيمة عشرية تمثل الوقت المستغرق منذ الإطار السابق، والذي سيكون 1/60 أو 0.0167 ثانية تقريبًا. يمكننا التوقف عن القلق بشأن مقدار تحريك كل إطار من خلال استخدام المعامل delta، إذ سنحتاج للاهتمام فقط بالسرعة المطلوبة بالبكسلات في الثانية، والتي هي 120 من العمليات الحسابية السابقة.

سيعطينا ضرب قيمة delta الخاصة بالمحرك بهذا العدد عددَ البكسلات التي يجب تحريكها في كل إطار، وسيُعدَّل هذا العدد تلقائيًا عند تقلّب زمن الإطار.

# ‫60 إطار في الثانية
120 pixels/second * 1/60 second/frame = 2 pixels/frame

# ‫30 إطار في الثانية
120 pixels/second * 1/30 second/frame = 4 pixels/frame

وكما نلاحظ، يبدو أنه إذا انخفض معدل الإطارات إلى النصف (أي تضاعف زمن الإطار)، فيجب أن تتضاعف أيضًا الحركة لكل إطار للحفاظ على السرعة المطلوبة، ولهذا سنعدّل الشيفرة البرمجية كما يلي:

extends Node2D

# الحركة المطلوبة بالبكسلات لكل إطار
var movement = Vector2(120, 0)

func _process(delta):
    $Sprite.position += movement * delta

يكون زمن الانتقال متناسقًا عند التشغيل بمعدل 30 إطارًا في الثانية كما يلي:

زمن انتقال اللعبة متناسق عند التشغيل بمعدل 30 إطارًا في الثانية

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

تعثر حركة اللعبة في محرك جودو في مدة محددة

استخدام معامل دلتا مع معادِلات الحركة

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

ملاحظة: يُعَد التعامل بالبكسلات والثواني أسهل بكثير لأنه يتعلق بكيفية قياس هذه الكميات في العالم الحقيقي، فمثلًا الجاذبية Gravity هي 100 بكسل/ثانية/ثانية، لذا ستتحرك الكرة بسرعة 200 بكسل/ثانية بعد سقوطها لمدة ثانيتين، وإذا استخدمنا وحدة الإطارات، فيجب استخدام التسارع Acceleration بوحدة البكسل/إطار/إطار، ولكننا سنجد أن هذه الوحدة غير مألوفة.

إذا طبّقنا الجاذبية مثلًا، فإنها تمثّل التسارع، بحيث ستزيد السرعة بمقدارٍ معين في كل إطار، وستغير السرعة موضع العقدة كما هو الحال في المثال السابق؛ وهنا سنضبط قيم delta و target_fps في الشيفرة البرمجية التالية لمعرفة النتائج:

extends Node2D

# التسارع بالبكسل/ثانية/ثانية
var gravity = Vector2(0, 120)
# التسارع بالبكسل/إطار/إطار
var gravity_frame = Vector2(0, .033)

# السرعة بالبكسل/ثانية أو بالبكسل/إطار
var velocity = Vector2.ZERO

var use_delta = false
var target_fps = 60

func _ready():
    Engine.target_fps = target_fps

func _process(delta):
    if use_delta:
        velocity += gravity * delta
        $Sprite.position += velocity * delta
    else:
        velocity += gravity_frame
        $Sprite.position += velocity

وكما هو ظاهر، فقد ضربنا القيمة المحدثة في الخطوة الزمنية لكل إطار لتحديث السرعة velocity والموضع position، إذ يجب ضرب أي كمية مُحدَّثة في كل إطار بقيمة delta لضمان تغيرها بحيث تكون مستقلة عن معدل الإطارات.

استخدام الدوال الحركية Kinematic

استخدمنا Sprite للتبسيط في الأمثلة السابقة، مع تحديث الموضع position في كل إطار.

إذا استخدمنا جسمًا حركيًا Kinematic ثنائي الأبعاد أو ثلاثي الأبعاد، فسنحتاج لاستخدام أحد توابع الحركة الخاصة به بدلًا من ذلك، خاصةً في حالة استخدام التابع move_and_slide()‎؛ إذ قد يحدث بعض الارتباك لأنه يستخدم متجه السرعة وليس الموضع، وهذا يعني أننا لن نضرب السرعة بقيمة delta لإيجاد المسافة، إذ تنجز الدالة ذلك نيابةً عنا، ولكن يجب تطبيقها على أيّ عمليات حسابية أخرى مثل التسارع كما في المثال التالي:

# الشيفرة البرمجية لحركة الشخصية الرسومية‫:
velocity += gravity * delta
position += velocity * delta

# الشيفرة البرمجية لحركة الجسم الحركي‫:
velocity += gravity * delta
move_and_slide()

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

ملاحظة: يجب أيضًا تطبيق قيمة delta على أي كميات أخرى مثل الجاذبية والاحتكاك وغير ذلك عند استخدام التابع move_and_slide()‎.

ختامًا

بهذا نكون قد تعرفنا على مفهوم delta في مجال تطوير الألعاب وكيفية استخدامه، وسنتعرف في المقال التالي على كيفية حفظ واسترجاع البيانات المحلية بين جلسات اللعب.

ترجمة -وبتصرّف- للقسم Understanding delta من توثيقات Kidscancode.

اقرأ أيضًا


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

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

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



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

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

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

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


×
×
  • أضف...