الفصل السادس: التحكم بخصائص الكائنات

بعد أن قمنا ببناء المشهد وتحديد إحداثيات الكائنات الموجودة فيه، يمكننا الآن الدخول قليلا إلى البرمجة لنرى كيف يتم تغيير قيم هذه الإحداثيات للحصول على التأثيرات المرغوبة. لنبدأ مثلا بتغيير مواقع الأشياء ولتكن بدايتنا مع الكاميرا التي تقوم بتصيير المشهد للاعب. الخطوة الأولى ستكون إضافة بُرَيمِج (script) إلى المشروع، ومن الأفضل إنشاء مجلد خاص بالبريمجات باسم Scripts تماما كما فعلنا مع الإكساءات بإضافة المجلد Textures. بعد إضافة المجلد المذكور يمكننا أن نبدأ بإضافة البريمجات المطلوب إلى داخله، ولنبدأ مع البريمج الأول CameraMover

لإضافة بريمج جديد قم ببساطة بالنقر بزر الفأرة الأيمن على المجلد المطلوب (Scripts) ومن ثم Create > C# Script ثم قم بتسمية الملف باسم CameraMover

أود لفت الانتباه في هذه المرحلة إلى أن محرك Unity يدعم 3 لغات برمجة مختلفة، لكنني في هذا الكتاب سأتعامل مع لغة #C فقط. إن كنت مهتما بالكتابة بلغة أخرى يمكنك إعادة كتابة المنطق البرمجي بما يتلاءم مع اللغة التي تريدها. جدير بالذكر أيضا أنني أنصح باستخدام برنامج MonoDevelop لكتابة الأوامر البرمجية، وهو المحرر المضمّن مع Unity، بدلا من استخدام Visual Studio.
السرد 1 يمثل القالب الافتراضي للبريمج والذي سيقوم Unity بإنشائه عند إضافة البريمج الجديد للمشروع.

1. using UnityEngine;
2. using System.Collections;
3. 
4. public class CameraMover : MonoBehaviour {
5. 
6.       // Use this for initialization
7.       void Start () {}
8.  
9.      // Update is called once per frame
10.     void Update () {}
11. }

السرد 1: القالب الافتراضي للبريمجات في Unity3D

يختصر Unity3D علينا الطريق بإضافة الدّالتين الأكثر استخداما وهما ()Start و ()Update، حيث تستدعى الأولى مرة واحدة عند بداية التشغيل، لذا تستخدم لتعيين القيم الأولية قبل الدخول في دورة تحديث اللقطات، بينما تستدعى الثانية في كل مرة يتم فيها تصيير لقطة جديدة لتقوم بتنفيذ التغييرات المطلوبة على الكائن الذي تتبعه.

من المهم إدراك حقيقة أن البريمجات في Unity3D هي عبارة عن مكوّنات يتم التعامل معها داخل المحرك كما يتم التعامل المكوّنات الأخرى مثل Transform و Mesh Renderer التي تعرّفنا عليها في فصل سابق. من المهم أيضا ملاحظة أن كل بريمج في Unity3D يجب أن يكون وارثا من MonoBehavior حتى يتم التعرّف عليه كمكوّن ومعاملته على هذا الأساس. إذا لم تكن صاحب خبرة في البرمجة ولست متأكدا من دلالة مصطلح الوراثة هنا فلا بأس، يكفي أن نقول ببساطة أنك ينبغي أن تحافظ على البنية التركيبية للقالب أعلاه عند كتابة بريمجاتك حتى تعمل بالشكل الصحيح.

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

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

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

من المهم هنا الحديث – ولو بشكل مختصر - عن الآلية التي يتم بها تشغيل اللعبة عن طريق محرك Unity. عند بداية التشغيل يقوم Unity بتهيئة البريمجات الفعّالة في المشهد وذلك عن طريق استدعاء الدّالّة ()Start المسؤولة عن تنفيذ الخطوات الأولية التي تؤدي لتشغيل البريمج بشكل صحيح وتنفيذ الوظيفة المطلوبة منه (مثل إعطاء القيم الأولية للمتغيرات). يتم استدعاء هذه الوظيفة مرة واحدة فقط عندما يتم تشغيل البريمج للمرة الأولى. بعد ذلك تبدأ حلقة تكرارية من بناء وتصيير اللقطات. يتم في كل دورة من دورات هذه الحلقة تنفيذ منطق اللعبة: ابتداء من قراءة مدخلات اللاعب، وتشغيل الذكاء الاصطناعي، والتحريك، وغيرها، حتى ينتهي بتصيير اللقطة على الشاشة ومن ثم الدخول في دورة تصيير جديدة. تتكرر هذه العملية بمعدل يجب أن لا يقل عن 25 إلى 30 مرة في الثانية حتى تظهر الحركة بشكل انسيابي للاعب. يقوم Unity في كل دورة باستدعاء الدّالّة ()Update مرة واحدة من كل البريمجات الفعّالة في المشهد، ويستمر الاستدعاء حتى يتم إيقاف تشغيل اللعبة.

1. using UnityEngine;
2. using System.Collections;
3. 
4. public class CameraMover : MonoBehaviour {
5.  
6.      public float speed = 1;
7.  
8.      // تستدعى هذه الدّالّة مرة واحدة عند بداية التشغيل
9.      void Start () {
10.         
11.     }
12.     
13.     // تستدعى هذه الدّالّة مرة واحدة في كل لقطة
14.     void Update () {
15.         transform.Translate(0, speed * Time.deltaTime, 0);
16.     }
17. }

السرد 2: تحريك الكائن لأعلى بسرعة ثابتة

في السطر 6 قمنا بتعريف متغير (أي وحدة برمجية لتخزين البيانات) من نوع float - أي عدد كسري – وأعطينا هذا المتغير القيمة 1. بعد ذلك استخدمنا المتغير في السطر 15 لتحريك الكائن باستدعاء الدّالّة ()transform.Translate والتي وظيفتها إزاحة الكائن في الفضاء على المحاور الثلاث x, y, z تبعا للقيم التي يتم تزويدها عند الاستدعاء. لاحظ أننا حددنا قيمة للإزاحة على المحور العمودي y فقط.

في السطر 15 نحتاج لأن نقوم بتحريك الكائن نحو الأعلى بمسافة محددة في كل إطار، وعلينا أن نحسب مقدار هذه المسافة. جميعنا نعرف القانون الفيزيائي الذي ينص على أن السرعة تساوي المسافة المقطوعة في زمن معين، بالتالي لكي نحسب المسافة علينا أن نضرب السرعة (وهي قيمة المتغير speed) بالزمن المنقضي. لكن ما هو هذا الزمن؟ بما أن الإزاحة ستتكرر عند تصيير كل إطار، فإن الزمن هنا هو الزمن المنقضي منذ أن تم تصيير الإطار الماضي، والذي نحصل عليه عبر المتغير Time.deltaTime. فعندما نضرب هذا الزمن المنقضي بالسرعة نحصل على المسافة المطلوبة للإزاحة، وهذا تحديدا ما نفعله. قم الآن بتشغيل اللعبة لترى تأثير هذا البريمج على حركة الكاميرا في المشهد.

لتشغيل اللعبة أو إيقاف تشغيلها استخدم الزر1d

الشكل 13 يوضح كيف يظهر مكوّن البريمج في نافذة الخصائص وكيف أن المتغيرات العامّة (أي تلك التي يتم تعريفها باستخدام كلمة public) تظهر على شكل خانات يمكن تغيير قيمها الأولية مباشرة دون الحاجة لتغيير القيمة داخل الكود في كل مرة. يمكنك الآن تغيير سرعة حركة الكاميرا بسهولة عن طريق تغيير القيمة في الخانة Speed. جرب أن تغير السرعة إلى قيمة سالبة وشاهد النتيجة التي تتوقعها.

الشكل 13: مكوّن البريمج كما يظهر في نافذة الخصائص

الشكل 13: مكوّن البريمج كما يظهر في نافذة الخصائص

عند تشغيل اللعبة ستلاحظ أن الكاميرا تبدأ مباشرة في الارتفاع حسب السرعة المحددة مما سيؤدي لاختفاء المشهد عن ناظر اللاعب بعد فترة بسيطة. لنحاول تركيز نظر الكاميرا على وسط المشهد، وذلك عن طريق تغيير استدارتها بعد التحريك لتنظر إلى نقطة الأصل. لحسن الحظ يريحنا Unity من عناء حساب الاستدارة المطلوبة بتوفير الدّالّة ()transform.LookAt والتي نقوم باستدعائها وإعطائها نقطة معينة في الفضاء لتنظر إليها. قم بإضافة السطر التالي بعد السطر 15 في السرد 2

transform.LookAt(Vector3.zero);

ستلاحظ أن الكاميرا تبقى مركزة على وسط المشهد وهو نقطة الأصل بينما تواصل ارتفاعها. لعلك لاحظت أيضا أنها تقترب شيئا فشيئا من وسط المشهد على المحورين x و z إلى أن تصبح فوقه تماما، عندها يحدث اضطراب في الصورة بسبب تغير استدارة الكاميرا بسرعة.

السبب في السلوك أعلاه هو كون الإزاحة التي تقوم بها دالّة ()transform.Translate تخضع لفضاء الكائن المحلي وليس فضاء المشهد أو الفضاء العالمي. فضاء الكائن أو الفضاء المحلي يُقصد به محاور الكائن نفسه، فلو تخيلت الكائن طائرة بجناحين كالتي في الشكل 14، فإن مقدمة الطائرة تشير للاتجاه الموجب للمحور z، وجناحها الأيمن يشير للاتجاه الموجب للمحور x، والاتجاه الموجب للمحور y يمتد من سطح الطائرة العلوي بشكل متعامد عليه نحو الأعلى، بالتالي كيفما تحركت هذه الطائرة أو مالت واستدارت فإن هذه المحاور تتحرك معها وتستدير. (وعلى سبيل التحفيز، فإننا سنقوم ببناء هذه الطائرة وجعلها تطير في الوحدة التالية إن شاء الله).

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

الشكل 14: محاور الفضاء المحلي لنموذج طائرة

الشكل 14: محاور الفضاء المحلي لنموذج طائرة

الشكل 15: الفرق بين فضاء المشهد والفضاء المحلي للكائن، لاحظ التغير في محاور فضاء الكاميرا بعد استدارتها نحو الأسفل لتنظر لمنتصف المشهد

الشكل 15: الفرق بين فضاء المشهد والفضاء المحلي للكائن، لاحظ التغير في محاور فضاء الكاميرا بعد استدارتها نحو الأسفل لتنظر لمنتصف المشهد

لحل هذه المشكلة، علينا ببساطة أن نخبر الدّالة ()transform.Translate أن تستخدم فضاء المشهد للإزاحة بدلا من فضاء الكائن. لذا علينا أن نقوم بتغيير السطر 15 في السرد 2 ليصبح كما يلي.

 transform.Translate(0, speed * Time.deltaTime, 0, Space.World);

بعد هذا التعديل ستتحرك الكاميرا للأعلى فقط بغض النظر عن دورانها، ذلك أن محاور فضاء المشهد ثابتة دائما. لنقم الآن بشيء أكثر إثارة من مجرد تحريك الكاميرا. ماذا لو غيرنا دوران الضوء الاتجاهيّ الذي يضيء المشهد؟ كيف سيؤثر ذلك على ظلال الأجسام؟ لعمل ذلك علينا أن نضيف بريمجا جديدا للمشروع ليقوم بمهمة إدارة هذا الضوء، ولنسمه مثلا LightRotator. يمكنك مشاهدة هذا البريمج في السرد 3.

1. using UnityEngine;
2. 
3. public class LightRotator : MonoBehaviour {
4.  
5.      public float speed = 10;
6.  
7.      void Update () {
8.            transform.Rotate(speed * Time.deltaTime, 0, 0);
9.      }
10. }

السرد 3: البريمج الخاص بتدوير الضوء الاتجاهيّ

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

تحدثنا في هذه الوحدة عن كيفية بناء مشهد أساسي مؤلّف من كائنات متوزعة في الفضاء، وخصاص تصيير هذه الكائنات. كما تناولنا كيفية ربط هذه الكائنات ببعضها البعض وأنواع الضوء التي يمكننا استخدامها في بناء المشهد. كما تناولنا بشكل أولي كيفية كتابة بريمجات بلغة #C و استخدام هذه البريمجات لتغيير خصائص الكائنات أثناء اللعب.

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

السابقالتالي

تعليقات واستفسارات