حان وقت البرمجة في سلسلتنا التي تشرح تطوير الألعاب ثلاثية الأبعاد باستخدام محرك الألعاب جودو، إذ سنستخدم إجراءات الإدخال التي أنشأناها في الدرس الماضي لتحريك الشخصية في هذا الدرس باستخدام الشيفرة البرمجية، إذ ستساعدنا الشيفرة بضبط تسارع حركة اللاعب وسرعته القصوى بالإضافة لسرعة الجاذبية التي تحدد مقدار تأثير الجاذبية الافتراضية على حركة الشخصية في اللعبة وغيرها من المفاهيم المفيدة.
انقر بزر الفأرة الأيمن على عقدة Player
وحدد خيار إضافة سكريبت Attach Script لإضافة سكربت جديد إليها، ثم اضبط القالب Template في النافذة المنبثقة على Empty قبل الضغط على زر إنشاء Create.
سنعرّف خصائص الصنف class مثل سرعة الحركة movement speed وتسارع السقوط fall acceleration الذي يمثل الجاذبية، والسرعة التي سنستخدمها لتحريك الشخصية.
extends CharacterBody3D # سرعة اللاعب بواحدة المتر في الثانية @export var speed = 14 # التسارع نحو الأسفل في الهواء بوحدة متر في الثانية للتربيع @export var fall_acceleration = 75 var target_velocity = Vector3.ZERO
هذه الخصائص مشتركة لجسم متحرك حيث أن target_velocity
هو شعاع ثلاثي الأبعاد 3D vector
يجمع بين السرعة والاتجاه. وقد عرفناه هنا كخاصية لأننا نريد تحديث قيمته وإعادة استخدامه عبر الإطارات.
ملاحظة: القيم هنا مختلفة تمامًا عما اعتدت عليه في شيفرات الألعاب ثنائية الأبعاد، وذلك لأن المسافات تقاس هنا بالمتر، أي قد توافق ألف وحدة بالبكسل مع نصف عرض شاشتك فقط في الفضاء ثنائي الأبعاد أما في الفضاء ثلاثي الأبعاد فيكون ذلك مساويًا إلى كيلومتر واحد بما أن كل وحدة مساوية إلى واحد متر، لذا ستحتاج إلى التكيف مع النظام الثلاثي الأبعاد حيث يمكن أن تمثل القيم أبعادًا أكبر وأكثر واقعية في المسافات والسرعات.
لتحديد اتجاه الحركة نبدأ بحساب شعاع اتجاه الإدخال باستخدام الكائن العام input
في _physics_process()
لبرمجة الحركة كما في الشيفرة التالية:
func _physics_process(delta): # أنشأنا متغير محلي لتخزين اتجاه الإدخال var direction = Vector3.ZERO # نتحقق من كل خطوة ونحدّث الاتجاه حسب ذلك if Input.is_action_pressed("move_right"): direction.x += 1 if Input.is_action_pressed("move_left"): direction.x -= 1 if Input.is_action_pressed("move_back"): # لاحظ أننا نعمل مع محاور أشعة x و z # إن المسطح xz في ثلاثي الأبعاد هو مستوي الأرض direction.z += 1 if Input.is_action_pressed("move_forward"): direction.z -= 1
سنجري هنا جميع الحسابات باستخدام الدالة الافتراضية _physics_process()
وهي على غرار الدالة _process()
التي تتيح لنا تحديث العقدة في كل إطار ولكنها مصممة خصيصًا للشيفرة المتعلقة بالفيزياء مثل تحريك جسم حركي kinematic يتفاعل مع محيطه من خلال الكود البرمجي، أو جسم صلب rigid يعتمد على محرك الفيزياء في جودو للتحرك والتفاعل بشكل واقعي مع البيئة المحيطة به بناءً على القوى المؤثرة عليه مثل الجاذبية أو التصادمات.
ولتعلّم المزيد حول الفرق بين الدالتين _process()
و _physics_process()
راجع توثيق جودو حول معالجة الفيزياء والسكون ومعرفة كيفية استخدام هذه الدوال لضبط الحركات الفيزيائية بطريقة مستقرة وواقعية.
نبدأ بتهيئة قيمة المتغير direction
إلى Vector3.ZERO
ليكون متجهًا فارغًا (أي أن قيمته تكون صفرًا في المحاور الثلاثة x و y و z) ثم نتحقق مما إذا كان اللاعب يضغط على إدخال move_*
واحد أو أكثر ونغيّر مكوني x
وz
للشعاع وفقًا لذلك بحيث تتوافق هذه مع محاور مستوى الأرض.
توفر لنا هذه الحالات الأربعة ثمانية احتمالات، وثمانية اتجاهات ممكنة.
سيكون طول الشعاع في حالة ضغط اللاعب مثلًا على كل من W و D في وقت واحد حوالي 1.4
، ولكن إذا ضغط على مفتاح واحد سيكون طوله 1
. نستدعي تابع normalized()
ليكون طول الشعاع ثابتًا ولا يتحرك بشكل أسرع قطريًا.
وفيما يلي شيفرة تحريك أو تدوير الكائن في اتجاه حركة اللاعب بناءً على المدخلات.
#func _physics_process(delta): #... if direction != Vector3.ZERO: direction = direction.normalized() # تهيئة خاصية basis سيؤثر على دوارن العقدة. $Pivot.basis = Basis.looking_at(direction)
لاحظ أننا نعالج الشعاع فقط إذا كان طوله أكبر من الصفر، مما يعني أن اللاعب يضغط على مفتاح اتجاه، مما يؤدي إلى تحديث direction
،بحيث يعكس الاتجاه الذي يريد اللاعب التحرك فيه.
الآن سنحسب الاتجاه الذي ينظر إليه $Pivot
عن طريق إنشاء Basis
ينظر في اتجاه direction
، ومن ثم نحدّث قيمة السرعة حيث يتعين علينا حساب سرعة الأرض أو السرعة الأفقية وسرعة السقوط أو السرعة العمودية بشكل منفصل، حيث تُدمج السرعات معًا وتُحرّك الشخصية باستخدام الدالة move_and_slide()
لتطبيق الحركة الفعلية مع الفيزياء.
تأكد من الضغط على مفتاح Tab مرة واحدة بحيث تكون الأسطر داخل دالة _physics_process()
ولكن خارج الشرط الذي كتبناه للتو أعلاه.
func _physics_process(delta): #... if direction != Vector3.ZERO: #... # السرعة الأرضية target_velocity.x = direction.x * speed target_velocity.z = direction.z * speed # السرعة العمودية if not is_on_floor(): # إذا كان في الهواء، فسيسقط على الأرض target_velocity.y = target_velocity.y - (fall_acceleration * delta) # تحريك الشخصية velocity = target_velocity move_and_slide()
تعيد دالة CharacterBody3D.is_on_floor()
قيمة true
إذا تصادم الجسم مع الأرض في هذا الإطار، لهذا السبب نطبق الجاذبية على اللاعب Player
فقط عندما يكون في الهواء.
بالنسبة للسرعة العمودية، نطرح تسارع السقوط مضروبًا في وقت دلتا في كل إطار. سيؤدي هذا السطر من الشيفرة إلى سقوط شخصيتنا في كل إطار طالما أنها ليست على الأرض أو لم تصطدم بها.
يمكن لمحرك الفيزياء اكتشاف التفاعلات فقط مع الجدران أو الأرض أو أجسام أخرى خلال إطار معين إذا حدثت الحركة والاصطدامات. وسنستخدم هذه الخاصية لاحقًا لبرمجة القفز.
نستدعي في السطر الأخير التابع CharacterBody3D.move_and_slide()
وهو تابع ذو قدرات عظيمة لصنف CharacterBody3D
إذ يسمح لك بتحريك الشخصية بسلاسة، حيث سيحاول محرك جودو إصلاح الحركة إذا اصطدمت بحائط في منتصفها باستخدام قيمة السرعة الأصلية CharacterBody3D
.
هذه هي كل الشيفرة التي تحتاجها لتحريك الشخصية على الأرض. فيما يلي شيفرة Player.gd
الكاملة لاستخدامها كمرجع:
extends CharacterBody3D # سرعة اللاعب بواحدة المتر في الثانية @export var speed = 14 # التسارع نحو الأسفل في الهواء بوحدة متر في الثانية للتربيع. @export var fall_acceleration = 75 var target_velocity = Vector3.ZERO func _physics_process(delta): var direction = Vector3.ZERO if Input.is_action_pressed("move_right"): direction.x += 1 if Input.is_action_pressed("move_left"): direction.x -= 1 if Input.is_action_pressed("move_back"): direction.z += 1 if Input.is_action_pressed("move_forward"): direction.z -= 1 if direction != Vector3.ZERO: direction = direction.normalized() $Pivot.basis = Basis.looking_at(direction) # السرعة الأرضية target_velocity.x = direction.x * speed target_velocity.z = direction.z * speed # السرعة العمودية if not is_on_floor(): # إذا كان في الهواء، فيسقط على الأرض target_velocity.y = target_velocity.y - (fall_acceleration * delta) # تحريك الشخصية velocity = target_velocity move_and_slide()
اختبار حركة اللاعب
نحتاج إلى استنساخ اللاعب ثم إضافة كاميرا من أجل وضع اللاعب في المشهد Main
واختباره. لن ترى أي شيء في الفضاء ثلاثي الأبعاد إذا لم تحوي نافذة العرض كاميرا موجهة نحو شيء ما، على عكس الفضاء ثنائي الأبعاد.
احفظ المشهد Player
وافتح المشهد Main
من خلال النقر على تبويب Main أعلى المحرر.
إذا أغلقت المشهد من قبل، فتوجه إلى نافذة نظام الملفات FileSystem Dock وانقر نقرًا مزدوجًا فوق main.tscn
لإعادة فتحه.
انقر بزر الفأرة الأيمن على العقدة الرئيسية Main
وحدد Instantiate Child Scene لاستنساخ المشهد Player
.
الآن انقر نقرًا مزدوجًا فوق player.tscn
في النافذة المنبثقة لتظهر الشخصية في وسط نافذة العرض.
إضافة كاميرا
سننشئ إعدادًا أساسيًا تمامًا كما فعلنا مع `Pivot. انقر بزر الفأرة الأيمن على عقدة المشهد الرئيسي
Mainمرة أخرى وحدد خيار "إضافة عقدة فرعية Add Child Node"، ثم أنشئ عقدة
Marker3Dجديدة وسمها
CameraPivot، ومن ثم حدد
CameraPivotوأضف إليها عقدة فرعية
Camera3D` لتبدو شجرة المشهد على هذا النحو
ستلاحظ مربع اختيار معاينة Preview يظهر في الزاوية العلوية اليسرى عند تحديد الكاميرا حيث يمكنك النقر فوقه لمعاينة إسقاط الكاميرا داخل اللعبة.
سنستخدم المحور Pivot لتدوير الكاميرا كما لو كانت على رافعة، لذا دعنا أولاً نقسم نافذة العرض ثلاثي الأبعاد 3D view لنتمكن من التنقل بحرية في المشهد ورؤية ما تراه الكاميرا. في شريط الأدوات أعلى النافذة مباشرةً، انقر فوق View ثم 2 Viewports. يمكنك أيضًا الضغط على Ctrl + 2
(أو Cmd + 2
على نظام التشغيل macOS).
حدد Camera3D
في النافذة السفلية، وشغّل معاينة الكاميرا بالنقر فوق مربع الاختيار Preview.
حرك الكاميرا في النافذة العلوية حوالي 19
وحدة على المحور Z ذي اللون الأزرق.
هنا نرى ثمرة عملنا، حدد CameraPivot ودوره بمقدار -45
درجة حول محور X باستخدام الدائرة الحمراء لترى الكاميرا تتحرك كما لو كانت متصلة برافعة.
يمكنك تشغيل المشهد بالضغط على F6
ثم الضغط على مفاتيح الأسهم لتحريك الشخصية.
يمكننا رؤية مساحة فارغة حول الشخصية بسبب الإسقاط المنظوري perspective projection، لذلك سنستخدم بدلاً منه في هذه اللعبة إسقاطًا متعامدًا orthographic projection لتأطير منطقة اللعب بشكل أفضل وتسهيل قراءة المسافات على اللاعب.
حدد الكاميرا مرة أخرى ومن قائمة الفاحص Inspector اضبط الإسقاط على القيمة "متعامد Orthogonal" والحجم على 19
. يجب أن تبدو الشخصية مسطحة أكثر ومنسجمة مع الأرضية.
ملاحظة: تعتمد جودة الظل الاتجاهي directional shadow على قيمة Far للكاميرا عند استخدام كاميرا متعامدة فإعداد Far يحدد المسافة الأبعد التي يمكن للكاميرا رؤيته، وكلما زادت قيمة البعد زادت المسافة التي ستتمكن الكاميرا من الرؤية فيها. بالرغم من ذلك، فإن قيم البعد الأعلى ستقلل أيضًا من جودة الظل حيث يجب أن يغطي عرض الظل مسافة أكبر.
قلل خاصية البعد للكاميرا إلى قيمة أقل مثل 100
إذا بدت الظلال الاتجاهية ضبابية جدًا بعد التبديل إلى كاميرا متعامدة فلا تقلل من خاصية البعد هذه كثيرًا وإلا ستبدأ الكائنات في البعد بالاختفاء.
اختبر مشهدك ويجب أن تكون قادرًا على التحرك في جميع الاتجاهات الثمانية (الأمام، الخلف، اليمين، اليسار، بالإضافة إلى الزوايا القطرية) دون أن تخترق الأرضية، إذا تحقق هذا فذلك يشير لأن الأمور تعمل بشكل صحيح وأن الشخصية تتحرك بالشكل المطلوب.
الخلاصة
شرحنا في هذا المقال طريقة ضبط حركة اللاعب باستخدام الشيفرة البرمجية وتحديد سرعته، بالإضافة لإعداد الكاميرا وكيفية عرض المشهد في لعبتنا ثلاثية الأبعاد. سننتقل في الدرس التالي إلى كيفية برمجة الجزء الثاني من اللعبة ألا وهو الأعداء!
ترجمة - وبتصرف - لقسم Moving the player with code من توثيق جودو الرسمي.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.