الفصل الخامس: المقذوفات الفيزيائية

العب

قمنا في الفصل الأول من الوحدة الثالثة بتطبيق نظام مقذوفات بسيط اعتمد حينها على تحريك الأجسام في اتجاه محدد بسرعة ثابتة. في هذا الفصل سنستفيد من إمكانات المحاكي الفيزيائي وتحديدا الأجسام الصلبة وقوة الاندفاع وذلك بهدف بناء نظام مقذوفات أكثر واقعية. ببساطة شديدة يمكن تحويل أي جسم صلب إلى مقذوف بمجرد إضافة قوة اندفاع بمقدار واتجاه محددين. ما سنقوم ببنائه في هذا الفصل هو جسم صلب على شكل كرة يتم قذفها على مجموعة من الصناديق المكدّسة فيما يشبه ميكانيكية اللعب في اللعبة الشهيرة Angry Birds. المشهد الذي سنحتاجه شبيه بالذي في الشكل 64.

الشكل 64: المشهد الخاص بتطبيق المقذوفات

الشكل 64: المشهد الخاص بتطبيق المقذوفات

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

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

الشكل 65: مكوّن Line Renderer الذي سنستخدمه لعرض اتجاه ومقدار قوة الاندفاع التي ستقذف الكرة

الشكل 65: مكوّن Line Renderer الذي سنستخدمه لعرض اتجاه ومقدار قوة الاندفاع التي ستقذف الكرة

يمكنك استخدام أي خامة تريدها لرسم هذا الخط، حيث استخدمت لهذا المثال خامة تحمل الاسم Line وهي عبارة عن تدريج لوني بين الأزرق والبرتقالي. عدد المواقع التي نحتاجها والتي يتم تحديدها عبر المصفوفة Positions هو 2: نقطة بداية للخط ونقطة نهاية له.سنترك القيم الافتراضية للمواقع كما هي حيث سنقوم لاحقا بتغييرها من خلال البريمج PhysicsProjectile والذي سنقوم أيضا بإضافته لكائن الكرة حتى نتمكن من التحكم بها. هذا البريمج موضح في السرد 54.

1. using UnityEngine;
2. using System.Collections;
3. 
4. public class PhysicsProjectile : MonoBehaviour {
5.  
6.  //عامل قوة الإطلاق الخاصةبالاندفاع
7.  public float launchPower = 300;
8.  
9.  //عدد الثواني التي سنبقي خلالها على المقذوف بعد إطلاقه و قبل تدميره
10.     //-1 = تعني أن يبقى المقذوف دائما ولا يتم تدميره
11.     public float lifeTime = 7;
12.     
13.     //هل قام اللاعب بالضغط بزر الفأرة على كائن المقذوف؟
14.     bool mousePressed = false;
15.     
16.     //هل تم إطلاق المقذوف حتى الآن؟
17.     bool launched = false;
18.     
19.     //الموقع الذي سيتم توليد قوة الاندفاع منه
20.     Vector3 launchPosition;
21.     
22.     //الخط الذي سيعرض اتجاه ومقدار الإطلاق
23.     LineRenderer line;
24.     
25.     //مرجع لكاميرا المشهد
26.     //سنحتاج للكاميرا لحساب موقع الإطلاق مستخدمين موقع مؤشر الفأرة
27.     Camera cam;
28.     
29.     void Start () {
30.         //قم بإيجاد كل من مكوّن رسم الخط والكاميرا
31.         line = GetComponent<LineRenderer>();
32.         cam = Camera.main;
33.     }
34.     
35.     void Update () {
36.         //سنقوم برسم الخط بعد أن يقوم اللاعب بالضغط بزر الفأرة على المقذوف لإطلاقه
37.         //وقبل أن يعاود إفلات الزر لإطلاق المقذوف
38.         if(!launched && mousePressed){
39.            //قم بتوليد شعاع يبدأ من موقع
40.            //الكاميرا ممتدا إلى داخل الشاشة، ويمر
41.            //عبر موقع مؤشر الفأرة
42.            Ray cameraRay = cam.ScreenPointToRay(Input.mousePosition);
43.             
44.            //قم بحساب المسافة بين موقع الكاميرا وموقع المقذوف
45.            float dist = Vector3.Distance(
46.                 cam.transform.position, transform.position);
47.             
48.            //ستكون نقطة الإطلاق بالتالي هي نقطة على الشعاع والتي تبعد
49.            //عن الكاميرا بنفس مقدار المسافة بين الكاميرا والمقذوف
50.            launchPosition = cameraRay.GetPoint (dist);
51.             
52.            //الآن علينا أن نحدد نقطتي البداية والنهاية للخط الذي سنقوم برسمه
53.            //نقطة البداية ستكون في نفس موقع المقذوف
54.            line.SetPosition(0, transform.position);
55.             
56.            //أما نقطة النهاية فهي نقطة الإطلاق التي قمنا بحسابها
57.            line.SetPosition(1, launchPosition);
58.             
59.            //الخاصة بنقطة الإطلاق z أخيرا علينا أن نقوم بتغيير القيمة
60.            //بحيث تصبح مساوية لنفس القيمة في موقع المقذوف
61.            launchPosition.z = transform.position.z;
62.         }
63.     }
64.     
65.     //يتم استدعاء هذه الدّالة عند الضغط بالفأرة على الكائن
66.     void OnMouseDown(){
67.         mousePressed = true;
68.     }
69.     
70.     //يتم استدعاء هذه الدّالة عندما يتم إفلات زر الفأرة
71.     //في حال إذا كان نفس الزر قد تم ضغطه على الكائن
72.     void OnMouseUp(){
73.         //يجب ألا يكون المقذوف قد تم إطلاقه مسبقا
74.         if(!launched){
75.             //ومن ثم قم بتدمير مكوّن الخط true إلى  launched قم بتغيير قيمة 
76.             launched = true;
77.             Destroy(line);
78.     
79.             //قم بتدمير الكائن بعد انتهاء مدة الإبقاء عليه
80.             Destroy(gameObject, lifeTime);
81.             
82.             //قم بتطبيق قوة الاندفاع في اتجاه متناسب
83.             //مع المسافة بين موقع الإطلاق وبين موقع المقذوف
84.             
85.             Vector3 forceDirection = 
86.                 transform.position - launchPosition;
87.             forceDirection = forceDirection * launchPower;
88.             rigidbody.AddForce(forceDirection, ForceMode.Impulse);
89.         }
90.     }
91. }

السرد 54: البريمج الخاص بإطلاق المقذوف عن طريق إضافة قوة اندفاع ويتم التحكم به عن طريق الفأرة

يحتوي البريمج على متغيرين عامين هما launchPower و الذي يحدد مقدار قوة الاندفاع التي سنطبقها لإطلاق المقذوف بعد أن نضربها بالمسافة التي يتحكم بها اللاعب عند تحديد نقطة الإطلاق. المتغير الآخر هو lifeTime والذي يحدد عدد الثواني التي سنبقي فيها المقذوف في المشهد بعد أن يتم إطلاقه. بالإضافة لهذين المتغيرين لدينا متغيرا الحالة mousePressed والذي نقوم بتغيير قيمته إلى true بمجرد أن يقوم اللاعب بالضغط بالفأرة على المقذوف، إضافة إلى المتغير launched والذي تبقى قيمته false إلى أن يتم إطلاق المقذوف. تكمن أهمية المتغير launched في أنّه يضمن ألا نسمح للاعب بإطلاق المقذوف أكثر من مرة. المتغير الآخر المهم هو launchPosition والذي يمثل الطرف الآخر للخط الذي سنقوم برسمه ابتداء من موقع المقذوف، كما أنّه يحدد مقدار القوة التي يرغب اللاعب بتطبيقها على المقذوف. أخير لدينا مرجعان لكل من كاميرا المشهد ومكوّن رسم الخط Line Rendrer الذي قمنا بإضافته على كائن المقذوف. لاحظ هنا استخدامنا للمتغير Camera.main والذي يعطينا مرجعا مباشرا لكاميرا المشهد.

التنفيذ الفعلي لعملية إطلاق المقذوف تتم عبر الدّالتين ()OnMouseDown و ()OnMouseUp، فبعد أن يضغط اللاعب بالفأرة على الكرة ويبدأ بالسحب، تقوم الدّالة ()LateUpdate بتحديث الخط المرسوم عن طريق المكوّن Line Renderer. بما أن الدّالة ()OnMouseDown تُستدعى عند ضغط اللاعب على الفأرة فوق كائن المقذوف، فإنّنا نستخدمها لتغيير قيمة mousePressed إلى true. ما نتوقعه بعد ذلك هو أن يقوم اللاعب بتحريك الفأرة من أجل تحديد اتجاه الإطلاق، بالتالي نقوم في الدّالة ()LateUpdate بتحديث الخط المرسوم بين المقذوف ومؤشر الفأرة، وهو الأمر الذي يجب أن يتم فقط في حال تم الضغط على المقذوف ولم يتم إطلاقه بعد. لنتمكن من تحديد موقع النهاية الأخرى للخط فإننا نحتاج لأن نعرف موقع النقطة في فضاء المشهد التي يغطيها مؤشر الفأرة حاليا. من أجل ذلك نرسم خطا مستقيما (شعاع) ابتداء من موقع الكاميرا ومرورا بمؤشر الفأرة وانطلاقا داخل فضاء المشهد. من أجل إنشاء هذا الشعاع نحتاج لاستدعاء الدّالة ()camera.ScreenPointToRay.

لنتمكن من فهم آلية عمل الدّالة ()ScreenPointToRay وكيف تفيدنا في تحديد نقطة الإطلاق، لنتخيل أن مؤشر الفأرة هو كائن موجود في فضاء المشهد، ويتواجد أمام الكاميرا مباشرة وأقرب إليها من أي كائن آخر. فإذا رسمنا خطا مستقيما يبدأ من موقع الكاميرا مرورا بالمؤشر، فإن امتداد هذا الخط سيكون داخل فضاء المشهد في الاتجاه الذي يراه اللاعب. ما نحتاجه الآن هو نقطة على هذا الخط تكون بعيدة عن الكاميرا بنفس مقدار بعد المقذوف، بالتالي يلزمنا أولا حساب المسافة بين الكاميرا والمقذوف وهي المسافة التي نخزنها في المتغير dist. في هذه الحالة تحديدا تساعدنا الدّالة
()cameraRay.GetPoint. عند استدعاء هذه الدّالة فإننا نزودها بمقدار مسافة معينة، وتعطينا بالمقابل إحداثيات نقطة على الشعاع تبعد بمقدار نفس المسافة عن مركز الشعاع. وبما أن مركز الشعاع هو الكاميرا نفسها، فإنّ هذه النقطة تبعد عن الكاميرا بنفس مسافة dist أي بنفس المسافة بين المقذوف والكاميرا، وهي النقطة التي نخزنها أخيرا في المتغير launchPosition. بقي علينا الآن أن نقوم بتحديث الخط المرسوم عن طريق المكوّن Line Renderer. في كل مرة نستدعي الدّالة ()line.SetPosition نحتاج لتزويدها بمتغيرين، الأول هو ترتيب النقطة على الخط والإحداثي الثاني هو موقع النقطة في فضاء المشهد. في حالتنا هذه لدينا نقطتان: الأولى ستكون في موقع المقذوف نفسه
transform.position والثانية ستكون في النقطة التي يقع فوقها مؤشر الفأرة والتي حسبناها سابقا وهي launchPosition. بعد تحديد هذين النقطتين سيظهر الخط ممتدا بين المقذوف ومؤشر الفأرة كما في الشكل 66. لاحظ أنه بعد رسم الخط نغير الإحداثي z في الموقع launchPosition ليصبح مساويا لمثيله في موقع المقذوف، مما يجعل اتجاه قوة الإطلاق صحيحا. لكن هذا الموقع لا يصلح لرسم الخط لأنه سيظهر طرف الخط بعيدا عن المؤشر.

الشكل 66: الخط الممتد بين موقع المقذوف وموقع مؤشر الفأرة

الشكل 66: الخط الممتد بين موقع المقذوف وموقع مؤشر الفأرة

بقي الآن أن نتعامل مع أهم حدث وهو إفلات اللاعب لزر الفأرة مما يؤدي لإطلاق المقذوف. هذا الحدث يتم التعامل معه عبر الدّالة ()OnMouseUp والتي تستدعى مرة واحدة فقط. إذا كانت قيمة متغير الحالة launched هي false، فهذا يعني أن المقذوف لم يتم إطلاقه بعد، لذا فأول ما نقوم به هو منع إعادة الإطلاق وذلك بتغيير قيمته إلى true. قبل أن نضيف قوة الاندفاع علينا أولا أن نقوم بإزالة الخط وذلك عن طريق تدمير المكوّن باستدعاء الدّالة ()Destroy تماما كما ندمر الكائن. بعدها نقوم أيضا باستدعاء ()Destroy لكن هذه المرة مع تأخير بمقدار lifeTime وذلك من أجل تدمير الكائن بعد انقضاء المدة المحددة. بعدها علينا أن نحسب اتجاه قوة الإطلاق التي سنستخدمها وهو بطبيعة الحال المتجه الممتد من موقع الإطلاق إلى موقع المقذوف والذي نحسبه بطرح الأول من الثاني ونخزنه في forceDirection. بطبيعة الحال فإن هذا المتجه يكون أطول أي مقدار أكبر كلما ابتعدت نقطة الإطلاق عن موقع المقذوف. قبل إضافة القوة للمقذوف نقوم بضرب المتجه بقيمة launchPower لتكبيرها بالقدر اللازم.

البريمج الآخر الذي سنحتاج إليه هو بريمج بسيط يقوم بإضافة مقذوف كلما أطلقنا آخر. هذا البريمج هو ProjectileGenerator وموضح في السرد 55. الشكل 67 يوضح المقذوف لحظة اصطدامه بالصناديق، ويمكنك مشاهدة النتيجة النهائية في المشهد scene18 في المشروع المرفق.

1. using UnityEngine;
2. using System.Collections;
3. 
4. public class ProjectileGenerator : MonoBehaviour {
5.  
6.  //قالب المقذوف الذي سنقوم بتوليده
7.  public GameObject projectile;
8.  
9.  void Start () {
10.         //قم بمحاولة توليد المقذوف مرة كل ثانية
11.         InvokeRepeating("Generate", 0, 1);
12.     }
13.     
14.     void Update () {
15.     
16.     }
17.     
18.     void Generate(){
19.         //إذا لم يكن هناك أي مقذوف في المشهد قم بتوليد واحد جديد
20.         PhysicsProjectile[] prjectiles = 
21.             FindObjectsOfType<PhysicsProjectile>();
22.         if(prjectiles.Length == 0){
23.             Instantiate(projectile, 
24.                         transform.position, 
25.                         transform.rotation);
26.         }
27.     }
28. }

السرد 55: البريمج الخاص بتوليد المقذوفات

الشكل 67: المقذوف لحظة إطلاقه واصطدامه بالصناديق

الشكل 67: المقذوف لحظة إطلاقه واصطدامه بالصناديق

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

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