الفصل الثالث: إمساك وإفلات الأشياء

العب

من المهم أحيانا إعطاء اللاعب القدرة على تحريك العناصر في المشهد، بحيث يمكنه – مثلا – تكديس بعض الصناديق لتسلقها أو إزالة بعض العقبات من الطريق وهلم جرا. هذا التحريك قد يتم بعدة أشكال، قد تكون بسيطة مثل دفع العناصر بمجرد التحرك باتجاهها، أو قد تكون عن طريق قوى خارقة أو أدوات خاصة مثل بندقية الجاذبية في لعبة Half-Life 2 المعروفة. في هذا الفصل سنعمل على استثمار العلاقات بين الكائنات بغرض بناء آلية لحمل الأشياء، بحيث يقوم اللاعب بحمل العنصر والمشي به ثم إفلاته في مكان آخر.

سنقوم في هذا الفصل بإعادة استعمال نظام إدخال منظور الشخص الأول الذي سبق وقمنا ببنائه في الفصل الرابع من الوحدة الثانية، ولذا يمكننا أن نبدأ العمل انطلاقا من المشهد scene5 في المشروع المرفق. التعديل البسيط الذي سنجريه على هذا النظام هو إعطاء اللاعب القدرة على حمل الصناديق عن طريق الضغط على مفتاح E وإفلاتها باستخدام نفس المفتاح، ومن أجل ذلك سنحتاج لإضافة بريمجين جديدين. البريمج الأول هو Holdable الموضح في السرد 33 والذي سنضيفه على كافة العناصر التي نريد أن نعطي اللاعب القدرة على حملها. في واقع الأمر سيكون هذا بريمجا فارغا تقريبا حيث لا يحتوي إلا على متغير واحد وهو نصف القطر radius والذي سنستخدمه لتحديد ما إذا كان اللاعب قريبا كفاية من الكائن ليقوم بحمله. وتذكر دائما أنه من الأفضل عمل قالب خاص بالصندوق القابل للحمل حيث أننا سنضيف عددا من هذه الصناديق للمشهد.

1. using UnityEngine;
2. using System.Collections;
3. 
4. public class Holdable : MonoBehaviour {
5.  
6.  //نصف قطر الكائن القابل للحمل
7.  public float radius = 1.5f;
8.  
9.  void Start () {
10.     
11.     }
12.     
13.     void Update () {
14.     
15.     }
16. }

السرد 33: البريمج الخاص بالكائنات القابلة للحمل

الجزء الأكبر من العمل سيقوم بإنجازه البريمج Holder والذي يجب أن نضيفه لكائن اللاعب (الأسطوانة). هذا البريمج موضح في السرد 34.

1. using UnityEngine;
2. using System.Collections;
3. 
4. public class Holder : MonoBehaviour {
5.  
6.  //نصف قطر كائن الحمل
7.  public float radius = 0.5f;
8.  
9.  Holdable objectInHand;
10.     
11.     void Start () {
12.     
13.     }
14.     
15.     void Update () {
16.         if(Input.GetKeyDown(KeyCode.E)){
17.             //إن لم يوجد في أيدينا كائن سلفا
18.             //علينا أن نبحث عن كائن مناسب لحمله
19.             if(objectInHand == null){
20.                 //البحث عن كافة الكائنات التي يمكن حملها
21.                 Holdable[] allHoldables = 
22.                     FindObjectsOfType<Holdable>();
23.                 foreach(Holdable holdable in allHoldables){
24.                     //حساب المسافة بين الكائن المحمول
25.                     //واللاعب الذي يحاول حمله
26.                     float distance = 
27.                         Vector3.Distance(
28.                                 transform.position,     
29.                             holdable.transform.position);
30.                     
31.                     //أولا يجب أن تكون المسافة قصيرة كفاية
32.                     bool close = 
33.                         distance < radius + holdable.radius;
34.                     
35.                     //ثانيا يجب أن يكون اللاعب مواجها للكائن المراد حمله
36.                     Vector3 dVector;
37.                     dVector = holdable.transform.position 
38.                                 - transform.position;
39.                     
40.                     float ang = 
41.                         Vector3.Angle(dVector, 
42.                             transform.forward);
43.                     
44.                     if(close && ang < 90){
45.                         //يمكننا الآن حمل الكائن
46.                         //holdableأولا نقوم بتخزين قيمته في 
47.                         objectInHand = holdable;
48.                         
49.                         //ثانيا نقوم بإضافة الكائن المحمول كابن
50.                         //لكائن اللاعب الذي يحمله
51.                          holdable.transform.parent = transform;
52.                         
53.                         return;
54.                     }
55.                     
56.                 }
57.             } else {
58.                 //يوجد بين أيدينا كائن سلفا
59.                 //لذا علينا الآن أن نقوم بإفلات هذا الكائن
60.                 objectInHand.transform.parent = null;
61.                 objectInHand = null;
62.             }
63.         }
64.     }
65. }

السرد 34: بريمج الحمل الخاص باللاعب

الفكرة وراء هذا البريمج بسيطة وتتلخص في أنه عندما يضغط اللاعب على المفتاح E فإنه يقوم بالتحقق من وجود كائن حاليا في يدي اللاعب. فإذا لم يكن هناك كائن يتم البحث عن كائن مناسب ليتم حمله. وتتم عملية الإمساك والحمل فعليا إذا وُجِد هذا الكائن المناسب. أما إذا كان هناك كائن موجود سلفا بين يدي اللاعب فإن هذا الكائن سيتم إفلاته. هذه المعلومة يمكن معرفتها عن طريق قيمة المتغير objectInHand والذي يخزن قيمة الكائن الذي يحمله اللاعب حاليا، وبالطبع فإن القيمة null تعني أن اللاعب لا يحمل أي شيء كما نلاحظ في السطر 19.

عملية إمساك وحمل الكائنات تتم عبر عدة مراحل، وتبدأ بالبحث عن جميع كائنات المشهد التي تحمل البريمج Holdable ومن ثم المرور عبرها جميعا كما في الأسطر من 21 إلى 23. خلال عملية المرور هذه نقوم بمقارنة المسافة بين الكائن واللاعب ومقارنتها بمجموع أنصاف الأقطار للكائنين. إذا تحقق هذا الشطر فإننا نقوم بتغيير قيمة close إلى true كما في الأسطر 26 إلى 33. إلاّ أن شرط قرب المسافة وحده لا يكفي إذ لابد أن يكون اللاعب مواجها للصندوق أو الكائن الذي يريد أن يحمله، وهذا يتم عن طريق قياس الزاوية اتجاه نظر اللاعب transform.forward والخط المستقيم الواصل بين موقع اللاعب وموقع الكائن الذي يحاول أن يحمله، وذلك في الأسطر من 36 إلى 42. بالدخول قليلا إلى حساب المتجهات، نتعلم أنه يمكننا الحصول على المتجه الواصل بين موقعي الكائن واللاعب عن طريق طرح الثاني من الأول. إذا كانت الزاوية التي قمنا بحسابها أقل من 90 درجة فإننا نعتبر بذلك أن اللاعب يواجه الصندوق وبالتالي يمكنه حمله كما في السطر 44.

بعد التحقق من كافّة الشروط الواجب توفرها يمكن أن تتم عملية الإمساك بالصندوق، وهي عملية بسيطة جدا لا تعدو تخزين قيمة الكائن الذي تم حمله في المتغير objectInHands، مما يجعل التأثير المقبل للمفتاح E هو إفلات الكائن. بالإضافة لذلك علينا أن نضيف الكائن المحمول كابن لكائن اللاعب وذلك حتى يتحرك ويستدير معه وهو المغزى من عملية الإمساك كما ترى في الأسطر 44 إلى 51. لاحظ أننا في السطر 53 نقوم باستخدام return من أجل إيقاف تنفيذ الدّالة ()Update بعد أن حققنا مبتغانا في حمل الكائن، وهذا يؤدي إلى تحسين الأداء من جهة بمنع أي مقارنات غير ضرورية كما أنه يمنع حمل أكثر من كائن في المرة الواحدة. أخيرا نلاحظ الأسطر 57 إلى 62 والتي تنطبق في حال الضغط على مفتاح E أثناء حمل كائن ما، في هذه الحالة يجب أن نقوم بإفلات الكائن وذلك بإزالته من أبناء اللاعب عن طريق تغيير قيمة transform.parent الخاصة به إلى null، ولا ننسى أيضا إعادة قيمة objectInHand إلى null من أجل تحرير يدي اللاعب ليتمكن لاحقا من الإمساك بكائن آخر. يمكنك مشاهدة النتيجة النهائية في المشهد scene11 في المشروع المرفق.

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

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