الفصل الرابع: التصويب باستخدام بث الأشعّة

العب

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

الشكل 61: بندقية بسيطة الشكل ومؤشر تصويب تمت إضافتهما كأبناء للكاميرا ليظهرا بمنظور الشخص الأول

الشكل 61: بندقية بسيطة الشكل ومؤشر تصويب تمت إضافتهما كأبناء للكاميرا ليظهرا بمنظور الشخص الأول

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

  1. يقوم البريمج ببث شعاع واحد فقط في المرة الواحدة التي يتم فيها إطلاق النار.
  2. هناك مسافة قصوى يمكن أن تصلها الأشعّة ويمكن تحديدها من نافذة الخصائص.
  3. هناك فجوة زمنية يمكن تحديدها يجب أن تفصل بين كل شعاعين متتاليين يتم بثهما، مما يعطينا القدرة على تحديد سرعة الإطلاق لكل سلاح.
  4. هناك هامش من عدم الدقة على المحورين الأفقي والعمودي لكل سلاح يستخدم هذا البريمج. هذا الهامش يمكن التعبير عنه بقياس زاوية بين الشعاع المستقيم الذي يمر من منتصف مؤشر التصويب وبين الشعاع الذي يتم بثه فعليا. قبل كل عملية بث للأشعة يتم حرف اتجاه الإطلاق أفقيا وعموديا بقيمة عشوائية بين القيمة الموجبة والسالبة للهامش، وذلك على المحورين x و y.
  5. يمكن تحديد قوة تدمير لكل طلقة يتم إطلاقها عن طريق بث الأشعة، بحيث تمثل هذه القوة مدى التأثير الذي ستحدثه الطلقة على الهدف عندما تصيبه من مسافة الصفر. بزيادة المسافة بين مصدر الإطلاق والهدف سنعمل على تقليل هذا التأثير بحيث يصل إلى صفر عند أقصى مسافة تصلها الطلقة، والتي ذكرناها في النقطة رقم 2.
1. using UnityEngine;
2. using System.Collections;
3. 
4. public class RaycastShooter : MonoBehaviour {
5. 
6.  //أقصى مسافة يمكن للطلقة أن تصلها
7.  public float maxRange = 100;
8.  
9.  //كم من الوقت يجب أن ينقضي بين كل عمليتي إطلاق متتاليتين
10.     public float shootRate = 0.1f;
11.     
12.     //درجة الانحراف العمودية لهامش عدم الدقة
13.     public float verticalInaccuracy = 1;
14.     
15.     //درجة الانحراف الأفقية لهامش عدم الدقة
16.     public float horizontalInaccuracy = 1;
17.     
18.     //قوة الطلقة التدميرية من مسافة الصفر
19.     public float power = 100;
20.     
21.     //كائن فوهة البندقية الخاص بتحديد موقع واتجاه بث الأشعّة
22.     public Transform muzzle;
23.     
24.     //وقت آخر عملية إطلاق تمت
25.     float lastShootTime = 0;
26.     
27.     //متغير لتخزين آخر قيمتي انحراف تم استخدامهما
28.     Vector2 inaccuracyVector;
29. 
30.     void Start () {
31.     
32.     }
33.     
34.     void Update () {
35.     
36.     }
37.     
38.     //قم بمحاولة إطلاق باستخدام بث الأشعّة
39.     //سيتم إرسال رسالة حال نجاح الأمر
40.     public void Shoot(){
41.         if(Time.time - lastShootTime > shootRate){
42.             //قم بتوليد قيم زوايا انحراف عشوائية لهامش عدم الدقة
43.             inaccuracyVector.y = Random.Range(
44.                                 -horizontalInaccuracy, 
45.                                 horizontalInaccuracy);
46.             
47.             inaccuracyVector.x = Random.Range(
48.                                 -verticalInaccuracy, 
49.                                 verticalInaccuracy);
50.             
51.             //قم بتدوير فوهة الإطلاق مستخدما قيم الانحراف التي تم توليدها
52.             muzzle.Rotate(inaccuracyVector.x, 0, 0);
53.             muzzle.Rotate(0, inaccuracyVector.y, 0);
54.             
55.             //متغير خاص بتخزين بيانات نتيجة بث الأشعّة
56.             RaycastHit hit;
57.             
58.             //قم ببث الأشعّة
59.             if(Physics.Raycast(
60.                     new Ray(muzzle.position, muzzle.forward), 
61.                     out hit, maxRange)){
62.                 
63.                 //hit.distance قم باستبدال قيمة
64.                 //بقيمة التدمير التي سنعمل على حسابها
65.                 
66.                 hit.distance = 
67.                     power * (1 - (hit.distance / maxRange));
68.                 hit.transform.SendMessage(
69.                         "OnRaycastHit", hit, 
70.                        SendMessageOptions.DontRequireReceiver);
71.                 
72.             }
73. 
74.             //قم بإرجاع دوران الفوهة إلى وضعه الأصلي عن طريق عكس قيم زوايا الانحراف
75.             muzzle.Rotate(-inaccuracyVector.x, 0, 0);
76.             muzzle.Rotate(0, -inaccuracyVector.y, 0);
77.             
78.             //قم بتسجيل آخر وقت تمت فيه عملية الإطلاق
79.             lastShootTime = Time.time;
80.             
81.             //قم بإبلاغ البريمجات الأخرى بحدوث عملية الإطلاق
82.             SendMessage("OnRaycastShoot",
83.                     SendMessageOptions.DontRequireReceiver);
84.         }
85.     }
86.     
87.     //تقوم هذه الدّالة بإرجاع قيمة آخر زاويتي انحراف لهامش عدم الدقة
88.     public Vector2 GetLastInaccuracyVector(){
89.         return inaccuracyVector;
90.     }
91. }

السرد 49: بريمج التصويب باستخدام بث الأشعّة

المتغيرات الأولى تمثل الخصائص التي ذكرناها قبل الخوض في البريمج وهي maxRange, shootRange, verticalInaccuracy, horizontalInaccuracy, power. أمّا المتغير muzzle فهو يمثل موقع واتجاه الأشعّة التي سيتم بثها. من أجل الحفاظ على وتيرة إطلاق النار وتنفيذ الفجوة الزمنية بين العمليات المتتالية، سنحتاج لتخزين وقت آخر عملية إطلاق وهي الغاية من تعريف المتغير lastShootTime. في كل مرة تتم فيها عملية الإطلاق نقوم بتوليد قيم انحراف عشوائية من أجل تطبيق هامش عدم الدقة، ومن ثم نقوم بتخزين هذه القيم في المتغير inaccuracyVector لنقوم باستخدامها لاحقا.

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

بعد تنفيذ الانحراف المطلوب أصبحنا جاهزين لتنفيذ عملية بث الأشعة، حيث نقوم في السطر 56 بتعريف المتغير hit من نوع RaycastHit والذي سنقوم باستخدامه لتخزين بيانات عملية بث الأشعة بعد تنفيذها. نقوم في الأسطر 59 إلى 61 باستدعاء ()Physics.Raycast لتنفيذ عملية الإطلاق، بحيث تنطلق الأشعة ابتداء من موقع الكائن muzzle متجهة في الاتجاه الموجب لمحوره المحلي z. لاحظ هذه المرة أننا قمنا بتزويد الدّالة ()Physics.Raycast بالمتغير hit مستخدمين الكلمة المفتاحية out، والتي تعني باختصار أنّ هذا المتغير لا يحمل أي قيمة ليتم إدخالها على الدّالة، بل إننا نتوقع من الدّالة نفسها أن تقوم بتزويدنا بقيمة معينة تقوم بتخزينها في هذا المتغير. آخر متغير نحتاج لتزويده هو أقصى مسافة يجب أن تقطعها الأشعة قبل أن تتوقف وهي maxRange في حالتنا هذه. إذا اصطدمت الأشعّة بكائن ما قبل أن تصل إلى المسافة الأقصى لها فإنه يتم تنفيذ الأسطر 66 إلى 70، حيث يعطينا المتغير hit.distance مقدار المسافة بين موقع انطلاق الأشعة وبين الهدف الذي تمت إصابته، والذي نقوم باستخدامه لحساب مقدار التدمير الذي ستحدثه الطلقة للهدف بناء على قوة التدمير من مسافة الصفر وهي معروفة لنا عبر المتغير power.

لاحظ أننا قمنا في السطر 67 بإنشاء تناسب خطي عكسي بين مقدار قوة التدمير ومقدار المسافة بين موقع إطلاق الشعاع والهدف، بحيث تصل هذه القوة إلى الصفر عند أقصى مسافة للشعاع. بعد أن نقوم بحساب قيمة التدمير فإنّ المسافة المخزنة في المتغير hit.distance لم تعد تلزمنا، لذا نقوم باستغلال هذا المتغير لتخزين قيمة التدمير التي قمنا بحسابها والتي هي أكثر أهمية بالنسبة لنا. أخيرا ينبغي أن نقوم بإعلام الهدف أنه تمت إصابته، وذلك عن طريق إرسال رسالة له تحمل الاسم OnRaycastHit ونقوم بإلحاق المتغير hit بما يحويه من بيانات إلى هذه الرسالة.

بعد اكتمال عملية الإطلاق يجب أن نقوم بإرجاع الفوهة إلى وضعها الصحيح قبل تنفيذ الانحراف، وذلك عن طريق تدويرها باستخدام القيم السالبة لقيم الانحراف الأصلية. بعد ذلك يجب أن نقوم بتسجيل الوقت الذي تمت فيه عملية الإطلاق باستخدام المتغير lastShootTime ومن ثم نقوم أخيرا بإرسال الرسالة OnRaycastShoot لإعلام البريمجات الأخرى باكتمال عملية الإطلاق. هذه الرسالة قد تنفع لتنفيذ مهام أخرى متعلقة بعملية الإطلاق مثل تشغيل صوت معين أو تشغيل تحريك ما. يمكننا أيضا الوصول لآخر قيمتين تم استخدامهما كزوايا للانحراف وذلك عن طريق الدّالة
()GetLastInaccuracyVector والتي قد تكون مفيدة في تنفيذ اهتزاز للكاميرا أو تحريك البندقية كما سنرى في البريمج RaycastShooterAnimator الموضح في السرد 50. وظيفة هذا البريمج هي القيام بتحريك كائن البندقية أثناء عملية الإطلاق وذلك باستخدام زوايا الانحراف، مما يعطي اللاعب علامة واضحة يعرف من خلالها أن الإطلاق تم بالفعل.

1. using UnityEngine;
2. using System.Collections;
3. 
4. public class RaycastShooterAnimator : MonoBehaviour {
5.  
6.  //مسافة الرجوع للخلف عند الإطلاق والتي تحاكي ردة فعل إطلاق الرصاصة
7.  public float zDistance = 0.15f;
8.  
9.  //متغير للوصول إلى بريمج الإطلاق الموجود على نفس الكائن
10.     RaycastShooter shooter;
11.     
12.     //الموقع الأصلي للبندقية قبل التحريك
13.     Vector3 originalPosition;
14.     
15.     //الدوران الأصلي للبندقية قبل التحريك
16.     Quaternion originalRotation;
17.     
18.     void Start () {
19.         shooter = GetComponent<RaycastShooter>();
20.         originalPosition = transform.localPosition;
21.         originalRotation = transform.localRotation;
22.     }
23.     
24.     void LateUpdate () {
25.         //قم بإرجاع البندقية ببطء وانسيابية نحو موقعها ودورانها الأصليين
26.         transform.localPosition = 
27.             Vector3.Lerp(transform.localPosition, 
28.                 originalPosition, Time.deltaTime * 10);
29.         
30.         transform.localRotation = 
31.             Quaternion.Lerp(transform.localRotation, 
32.                 originalRotation, Time.deltaTime * 10);
33.     }
34.     
35.     void OnRaycastShoot(){
36.         //تمت عملية الإطلاق، لذا نقوم بالتحريك بناء على قيمها
37.         Vector2 rotation = 
38.             shooter.GetLastInaccuracyVector();
39.         transform.Rotate(rotation.x, 0, 0);
40.         transform.Rotate(0, rotation.y, 0);
41.         transform.Translate(0, 0, -zDistance);
42.     }
43. }

السرد 50: البريمج الخاص بتحريك البندقية اعتمادا على عملية الإطلاق باستخدام بث الأشعّة

الخطوة الأولى كما تلاحظ هي تخزين الموقع والدوران الأصليين للكائن وذلك حتى نكون قادرين على إعادته لمكانه بعد انتهاء عملية التحريك. المتغير المهم الآخر هو zDistance والذي يحدد مقدار الحركة على المحور z نحو الخلف عند الإطلاق. عندما يقوم اللاعب بعملية الإطلاق فإن البندقية ترتد نحو الخلف بسرعة كبيرة، وهي التي ننفذها فعليا بشكل لحظي عن طريق وضع البندقية في موقع يبعد عن موقعها الأصلي بمقدار zDistance نحو الخلف. إضافة لإرجاعها نحو الخلف، فإننا نقوم بتدوير البندقية نحو أفقيا وعموديا حول محاورها المحلية بمقدار يساوي زوايا الانحراف العشوائية التي قرأناها من البريمج RaycastShooter عن طريق الدّالة ()GetLastInaccuracyVector. بعد نقوم عبر الدّالة
()LateUpdate بإرجاع البندقية لموقعها ودورانها الأصلي بشكل سلس عن طريق استدعاء كل من
()Vector3.Lerp و ()Quaternion.Lerp. لتسريع عملية رجوع البندقية لوضعها الأصلي والتي نحتاجها في حالة إطلاق النار المتتابع، نقوم بضرب قيمة الوقت المنقضي بـ 10.

أصبح لدينا الآن شخصية لاعب فيزيائية يمكننا التحكم بها بالإضافة لامتلاك هذه الشخصية لبندقية تطلق النار عن طريق بث الأشعّة. ما تبقى علينا عمله الآن هو أن نعطي اللاعب القدرة على إطلاق النار مستخدما الفأرة وتحديدا الزر الأيسر، وهي في الواقع مهمة بسيطة ننفذها عبر البريمج GunInput والذي يقوم ببساطة بقراءة مدخل زر الفأرة الأيسر ويستدعي الدّالة ()Shoot من البريمج RaycastShooter بناء على ذلك. هذا البريمج موضح في السرد 51.

1. using UnityEngine;
2. using System.Collections;
3. 
4. public class GunInput : MonoBehaviour {
5.  
6.  //فإننا لا نشترط trueبضبط هذا المتغير على
7.  //على اللاعب أن يفلت الزر بعد كل عملية إطلاق
8.  public bool continuous = true;
9.  
10.     void Start () {
11.     
12.     }
13.     
14.     void Update () {
15.         //بناء على مدخل زر الفأرة الأيسر Shoot قم بإرسال الرسالة 
16.         if(continuous){
17.             if(Input.GetMouseButton(0)){
18.                 SendMessage("Shoot");
19.             }
20.         } else {
21.             if(Input.GetMouseButtonDown(0)){
22.                 SendMessage("Shoot");
23.             }
24.         }
25.     }
26. }

السرد 51: بريمج إطلاق النار عن طريق قراءة زر الفأرة الأيسر

يمكننا باستخدام المتغير continuous أن نتحكم بنوع إطلاق النار، بأن نسمح للاعب بالضغط المستمر أو نجبره على إفلات زر الفأرة قبل الإطلاق مرة أخرى. من المثير للاهتمام في هذا البريمج هو استقلاليته الكاملة عن البريمج RaycastShooter مما يجعله قابلا لإعادة الاستخدام مع أنواع أخرى من البريمجات التي تمثل الأسلحة، والتي بدورها لا تحتاج إلّا إلى القدرة على استقبال الرسالة ()Shoot. بعد الانتهاء من كتابة البريمجات الثلاثة RaycastShooter و RaycstShooterAnimator و GunInput يجب أن تتم إضافتها إلى كائن الكبسولة الذي يتحكم به اللاعب. بعد إضافة البريمجات يجب أن يتم تعيين كائن ما كفوهة للإطلاق في المتغير muzzle داخل RaycastShooter وفي حالتنا هذه يمكن أن يكون هذا الكائن هو مؤشر التصويب. أخيرا يمكنك بناء مشهد كالذي في الشكل 62 وإضافة بعض الكائنات إليه وذلك حتى تتمكن من اختبار عملية التصويب.

الشكل 62: مشهد خاص باختبار التصويب باستخدام بث الأشعّة

الشكل 62: مشهد خاص باختبار التصويب باستخدام بث الأشعّة

الخطوة التالية هي تحديد ما الذي سيحصل حين تتم إصابة الهدف. ردة فعل الهدف على الإصابة قد تختلف من هدف لآخر، فمثلا إصابة جدار ليست كإصابة لوح خشبي أو زجاجي، وهذه بدورها ليست كإصابة لاعب آخر أو حتى برميل متفجرات. هذا التنوع يترجم عن طريق الاستجابات المختلفة للرسالة OnRaycastHit والتي يتم إرسالها لكل هدف تتم إصابته حتى يستجيب لها بالطريقة المناسبة. سوف أستعرض هنا مثالين مختلفين على طريقة الاستجابة للإصابة وسأبدأ بالبريمج BulletHoleMaker الموضح في السرد 52. مهمة هذا البريمج هي رسم فتحة صغيرة (ثقب) في المكان الذي أصابته الطلقة على سطح الجسم، هذا الثقب في الحقيقة هو كائن سنستعمل له قالبا خاصا نقوم بنسخه في المكان المناسب مع كل عملية إصابة. هذا القالب يتكون ببساطة من كائن مربع quad مضاف إليه إكساء على شكل ثقب.

1. using UnityEngine;
2. using System.Collections;
3. 
4. public class BulletHoleMaker : MonoBehaviour {
5.  
6.  //قالب كائن الثقب الذي سنقوم باستخدامه
7.  public GameObject holePrefab;
8.  
9.  //عدد الثواني التي يجب انتظارها قبل تدمير كائن الثقب
10.     public float holeLife = 15;
11.     
12.     void Start () {
13.     
14.     }
15.     
16.     void Update () {
17.     
18.     }
19.     
20.     //ومن ثم إنشاء ثقب في مكان الإصابة OnRaycastHit قم باستقبال الرسالة
21.     void OnRaycastHit(RaycastHit hit){
22.         GameObject hole = (GameObject) Instantiate(holePrefab);
23.         hole.transform.position = hit.point;
24.         //إذا كان هناك مكوّن جسم فيزيائي صلب يجب أن نضيف الثقب كابن للهدف
25.         //مما يجعل الثقب يتحرك ويستدير مع الهدف إذا ما تحرك
26.         if(hit.rigidbody != null){
27.             hole.transform.parent = hit.transform;
28.         }
29.         //فإننا نحتاج لأن نديره 180 درجة quad بما أننا نستخدم كائنا من نوع
30.         //نحو الداخل ليظهر سطحه المرئي، هذه حالة خاصة قد لا تنطبق بالضرورة على كائنات أخرى
31.         hole.transform.LookAt(hit.point - hit.normal);
32.         //قم بإزاحة كائن الثقب بمقدار ضئيل نحو الخارج بحيث
33.         //نضمن أن يكون ظاهرا فوق السطح المصاب
34.         hole.transform.Translate(0, 0, -0.0125f);
35.         
36.         //قم باستدعاء التدمير بعد فترة محددة
37.         Destroy(hole, holeLife);
38.         
39.         //قم بطباعة بعض المعلومات
40.         print (name + " took damage of " + hit.distance);
41.     }
42. }

السرد 52: البريمج الخاص بإحداث ثقب في مكان إصابة الطلقة

عند إضافة هذا البريمج لكائن ما فإنه يقوم بالاستجابة للرسالة OnRaycastHit عن طريق عمل ثقب في مكان الإصابة. الخطوة الأولى في الدّالة ()OnRaycastHit هي أن تقوم بعمل نسخة من القالب holePrefab ومن ثم وضعه في مكان الإصابة hit.point والذي يمثل موقع اصطدام الشعاع بالكائن في فضاء المشهد. إذا كان الكائن نشطا فيزيائيا (أي أنّه يحمل مكوّن الجسم الصلب rigid body) فإنه ينبغي علينا إضافة كائن الثقب كابن للهدف لأنه من الممكن أن يتحرك أو يستدير، ونريد أن نضمن أن يبقى الثقب في مكانه بالنسبة للهدف. السؤال الذي يطرح نفسه الآن، ما هي الوجهة الصحيحة لإدارة الثقب حتى يظهر بالشكل المطلوب؟ الإجابة المباشرة هي نحو الخارج بالنسبة للسطح الذي تمت إصابته. يسهل علينا المتغير hit العملية عن طريق توفير المتجه hit.normal، وهو متجه بطول وحدة واحدة يمتد من موقع الإصابة على سطح الهدف نحو الخارج في اتجاه عمودي على السطح. بإضافة هذا المتجه إلى موقع الإصابة فإن المتجه الناتج سيدلنا على اتجاه الدوران الصحيح لكائن الثقب. الاختلاف البسيط هنا هو أننا نستخدم كائنا من نوع quad وهو ذو وجه مرئي واحد فقط، وهذا الوجه هو الذي تراه عندما تنظر من الاتجاه السالب للمحور z. بكلمات أخرى، حتى نظهر هذا الوجه بالطريقة الصحيحة للاعب يجب أن ندير المتجه الأمامي للكائن نحو الداخل في اتجاه متعامد على سطح الهدف المصاب كما في الشكل 63، وبالتالي فبدلا من أن نجمع المتجهين hit.point و hit.normal فإننا نقوم بطرحهما كما في السطر 31.

الشكل 63: كائن ثقب تم بناؤه باستخدام الشكل quad، بالتالي يجب أن تتم إدارته نحو الداخل ليظهر الوجه المرئي نحو الخارج

الشكل 63: كائن ثقب تم بناؤه باستخدام الشكل quad، بالتالي يجب أن تتم إدارته نحو الداخل ليظهر الوجه المرئي نحو الخارج

بعد إدارة الكائن في الاتجاه الصحيح، علينا التأكد من أنه سيظهر فوق سطح الهدف دائما. من أجل هذا علينا أن نحرك كائن الثقب نحو الخارج قليلا لا أن نجعله ملاصقا تماما لسطح الهدف المصاب. مسافة التحريك يجب أن تكون ضئيلة جدا بحيث لا يمكن للاعب بأي حال من الأحوال ملاحظة وجود فراغ بين الهدف والثقب الذي يفترض أنه على سطحه. في حالتنا يمكن أن نزيح الثقب نحو الخلف قليلا بمقدار 0.0125. إضافة لذلك علينا أن نبقي عدد كائنات الثقوب محدودا حتى لا يؤثر ذلك على الأداء. من أجل هذا قمنا بتحديد عمر افتراضي لكل كان ثقب تتم إضافته بحيث يتم تدميره تلقائيا بعد انقضاء مدة زمنية محددة. أخيرا تذكر أننا قمنا باستغلال العضو hit.distance من أجل تخزين قيمة قوة التدمير التي قمنا بحسابها بناء على بعد الهدف عن مركز الإطلاق. ما نقوم به في السطر 40 هو عملية طباعة لاسم الهدف ومقدار القوة التدميرية التي تلقاها من الطلقة التي أصابته، وبملاحظة هذه الأرقام ستتكون لديك فكرة عن تأثير المسافة على قوة إصابة الطلقة للهدف.

البريمج الثاني الذي سنقوم بكتابته كمستجيب للرسالة OnRaycastHit هو البريمج BulletForceReceiver والذي يختص بالكائنات التي تحتوي على جسم فيزيائي صلب. هذا البريمج موضح في السرد 53.

1. using UnityEngine;
2. using System.Collections;
3. 
4. public class BulletForceReceiver : MonoBehaviour {
5. 
6.  void Start () {
7.  
8.  }
9.  
10.     void Update () {
11.     
12.     }
13.     
14.     //وترجمها إلى قوة اندفاع فيزيائية لتحريك الهدف OnRaycastHit قم باستقبال الرسالة
15.     void OnRaycastHit(RaycastHit hit){
16.         rigidbody.AddForceAtPosition(
17.            -hit.normal * hit.distance, hit.point, ForceMode.Impulse);
18.     }
19. }

السرد 53: بريمج خاص باستقبال إصابة الأشعة وتحويلها لقوة اندفاع فيزيائية تحرك كائن الهدف

عندما يتم استقبال الرسالة OnRaycastHit من قبل هذا البريمج فإنّه يقوم بتطبيق قوة اندفاع حركة فيزيائية على الجسم الصلب الخاص بالكائن، وذلك باستخدام الدّالة ()AddForceAtPoint. ما يميز هذه الدّالة هو إمكانية تحديد نقطة معينة على الكائن لتطبيق القوة عليها، وليس بالضرورة على مركز ثقل الكائن. فمثلا عندما تكون الإصابة لصندوق ما في الجهة العلوية من أحد جوانبه، يجب أن تتركز معظم قوة الإصابة في تلك النقطة، مما يجعل الصندوق يستدير قليلا من قوة الإصابة وربما ينقلب على جانبه. الموقع الذي سنختاره لتطبيق القوة عليه هو بطبيعة الحال موقع الإصابة hit.point ومقدار القوة هو hit.distance حيث قمنا بتخزين مقدار التدمير الذي حسبناه تبعا للمسافة بين الهدف ومركز الإطلاق. أخيرا بقي علينا تحديد اتجاه القوة التي سنطبقها وهو هنا hit.normal- حيث أنّ hit.normal متعامدة على سطح الهدف نحو الخارج، بينما نحتاج لتطبيق قوة نحو الداخل لتحريك الجسم بعيدا عن اتجاه مصدر الطلقة. يمكنك مشاهدة النتيجة النهائية في المشهد scene17 في المشروع المرفق.

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

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