الفصل الأول: الأبواب والأقفال والمفاتيح

العب

سنتناول في هذا الفصل نوعين من الأبواب: الأبواب الدوّارة والأبواب المنزلقة. النوع الأول أقصد به الأبواب الشائعة التي نراها في معظم الأماكن، وهي تدور حول محورها العمودي الموجود في أقصى يمين أو يسار الباب. النوع الآخر – الأبواب المنزلقة – أعني به الأبواب المتحركة التي تفتح وتغلق بالحركة على بعد واحد كما في أبواب معظم المصاعد.

لتكن البداية مع الأبواب الدوّارة والتي سنقوم ببنائها مستخدمين مكوّنا فيزيائيا جديدا يسمى Hinge Joint وتعني بالعربية "المفصل الرزي". هذا النوع من المفاصل يسمح بحركة دورانية على مستوى واحد فقط فتحا وإغلاقا، تماما كما في مفصل ركبة الإنسان. نظرا لطبيعة حركة هذا المفصل يتضح لنا أنه مناسب ليستخدم مع الأبواب الدوّارة التي نرغب بعملها. سنبدأ أولا بعمل غرفة كبيرة نسبيا مكونة من أرضية وأربع جدران، ومن ثم نفصلها لقسمين بجدارين آخرين بنهما فتحة صغيرة بحجم الباب الذي سنضيفه. نقوم بعدها بإضافة كائن الباب كما في الشكل 76 ومن ثم نضيف له مكوّن الجسم الصلب ونقوم بضبطه كما في الشكل 77. من المهم هنا ملاحظة زيادة قيمة angular drag من أجل جعل الحركة الدورانية للباب معقولة، عدا عن ذلك سيكون الباب خفيفا جدا.

الشكل 76: الباب الدوّار

الشكل 76: الباب الدوّار

الشكل 77: ضبط الجسم الصلب الخاص بالباب الدوّار

الشكل 77: ضبط الجسم الصلب الخاص بالباب الدوّار

علينا الآن أن نقوم بإضافة مكوّن المفصل الرزي إلى كائن الباب وضبطه كما في الشكل 78. هذا المفصل هو عبارة عن مكوّن فيزيائي يتأثر بالقوى الفيزيائية المختلفة؛ لذا فإنّنا لن نحتاج لأي بريمج أو ضغط أي مفتاح لفتح هذا الباب، بل كا ما علينا هو تطبيق قوة دفع بمقدار كافٍ عليه حتى يفتح.

يمكن إضافة مكوّن المفصل الرزي عن طريق القائمة Component > Physics > Hinge Joint

الشكل 78: مكوّن المفصل الرزي والقيم اللازمة لعمل باب دوّار باستخدامه

الشكل 78: مكوّن المفصل الرزي والقيم اللازمة لعمل باب دوّار باستخدامه

أول ما يمكن ملاحظته هنا هو أننا أمام مكوّن كبير الحجم نسبيا وفيه عدد لا بأس به من المتغيرات، إلاّ أننا سنتعامل مع بعضها فقط حتى نصل للسلوك المطلوب. البداية مع المتغيرين Anchor و Axis واللذان يحددان موقع واتجاه محور الدوران الخاص بهذا المفصل. بما أننا نقوم بعمل باب دوّار، علينا أن نضع المحور في أقصي يمين أو أقصى يسار الباب، كما يجب أن يكون اتجاه المحور عموديا أي باتجاه المحور y. بما أن قيمة الحجم z تمثل سماكة الباب وقيمة الحجم x تمثل عرضه، بالتالي فإنّ موقع المحور (أي المتغير Anchor) يجب أن يكون على أحد طرفي المحور المحلي x الخاص بكائن الباب وهو هنا (0 ,0 ,0.5) أي أقصى يمين الباب. كذلك الأمر بالنسبة لاتجاه المحور Axis والذي يجب أن يكون نحو الأعلى أي (0 ,1 ,0). التغيير الآخر الذي سنقيم بإجرائه هو تفعيل الخيار Use Spring والذي يعمل على تطبيق قوة دوران تعيد الباب إلى وضعه الأصلي حين لا يكون هناك أي قوى خارجية أكبر تؤثر عليه. بناء على هذا الخيار يجب أن يتم ضبط قيم كل من Spring و Damper بشكل مناسب بحيث لا تكون قوية جدا أو ضعيفة جدا. القيم الموضحة في الشكل 78 تم ضبطها لتتناسب مع الشخصية الفيزيائية التي قمنا بعملها في الفصل الثالث من الوحدة الرابعة. أخيرا علينا أن نقوم بتفعيل الخيار Use Limits والذي يسمح لنا بتحديد قيم دوران قصوى للباب في كلا الاتجاهين. في هذه الحالة قمنا بضبط قيم المتغيرين Min و Max على 90- و 90 بحيث نسمح بدفع الباب من كلا جانبيه ونسمح بدورانه 90 درجة كحد أقصى. يمكنك الآن أن تضيف شخصية فيزيائية للمشهد وتقوم بتجربة الباب، حيث يمكن فتحه بمجرد الاندفاع عبره.

سنقوم الآن بقفل هذا الباب ونطلب من اللاعب أن يمتلك مفتاحا ليتمكن من فك قفله والمرور عبره. المفتاح بطبيعة الحال سيكون كائنا قابلا للجمع وتتم إضافته لمحتويات حقيبة اللاعب حين جمعه. للتذكير فقد قمنا سابقا بنقاش موضوع الأشياء القابلة للجمع وتحديدا البريمج Collectable (السرد 26) إضافة إلى حقيبة اللاعب والبريمج InventoryBox (السرد 29). كل ما يلزمنا هو تعديل بسيط على البريمج InventoryBox بحيث نضيف له مساحة لتخزين المفاتيح التي يقوم اللاعب بجمعها. النسخة المعدلة من البريمج موضحة في السرد 61.

1. using UnityEngine;
2. using System.Collections.Generic;
3. 
4. public class InventoryBox : MonoBehaviour {
5. 	
6. 	//المبلغ المالي الذي يمتلكه اللاعب
7. 	public int money = 0;
8. 	
9. 	//ما هي المفاتيح التي يمتلكها اللاعب حاليا؟
10. 	public List<string> keys;
11. 	
12. 	void Start () {
13. 	
14. 	}
15. 	
16. 	void Update () {
17. 	
18. 	}
19. }

السرد 61: النسخة المعدّلة من البريمج InventoryBox

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

لنبدأ بالمفتاح القابل للجمع، والذي سيعتمد على آلية معدّلة لما قمنا به في الفصل الثاني من الوحدة الثالثة، وسيعتمد على اكتشاف التصادمات بدلا من المرور على كافّة الأشياء القابلة للجمع داخل المشهد. البداية مع البريمج CollectableKey والذي يستجيب للرسالّة Collect عن طريق تزويد اللاعب بالكلمة السرية المخزنة في المفتاح. هذا البريمج موضح في السرد 62.

1. using UnityEngine;
2. using System.Collections;
3. 
4. public class CollectableKey : MonoBehaviour {
5. 	
6. 	//الكلمة السرية الخاصّة بهذا المفتاح
7. 	public string key;
8. 	
9. 	void Start () {
10. 	
11. 	}
12. 	
13. 	void Update () {
14. 	
15. 	}
16. 	
17. 	//Collect استقبال الرسالة
18. 	public void Collect(GameObject owner){
19. 		//قم بإيجاد بريمج حقيبة اللاعب الخاص بالمالك
20. 		//ومن ثم قم بإضافة الكلمة السرية المخزنة في المفتاح
21. 		//إلى قائمة الكلمات السرية الموجودة في الحقيبة
22. 		InventoryBox box = owner.GetComponent<InventoryBox>();
23. 		if(box != null){
24. 			box.keys.Add(key);
25. 			//قم أخيرا بتدمير كائن المفتاح
26. 			Destroy (gameObject);
27. 		}
28. 	}
29. }

السرد 62: البريمج الخاص بالمفتاح القابل للجمع والذي يعطي اللاعب الكلمة السرية للمفتاح بمجرد جمعه

ما يقوم به هذا البريمج هو التأكد من كون المجمّع owner يحتوي على البريمج Inventory Box أي يمتلك حقيبة تمكنه من جمع المفاتيح. عن التأكد من وجودها يقوم بإضافة الكلمة السرية للمفتاح والموجودة في المتغير key إلى القائمة box.keys ومن ثم يقوم أخيرا بتدمير كائن المفتاح وحذفه من المشهد. عملية الحذف مهمة طبعا لإعطاء اللاعب انطباعا بأنه أخذ المفتاح فعلا. لو تساءلنا الآن: من أين يمكن للاعب أن يحصل على المفتاح؟ الجواب قد يكون بأكثر من طريقة: يمكن أن يتناوله من مكان ما على الأرض مثلا، كما يمكن أن يُعطى له من قبل شخصية أخرى في اللعبة وهلم جرا. المهم هو أن تنتهي العملية بإرسال الرسالة Collect إلى كان المفتاح مصحوبة بمرجع للاعب عبر المتغير owner. الطريقة التي سنستخدمها هي الجمع عن طريق اللمس، لكننا هذه المرة سنستفيد من المحاكي الفيزيائي وقدرته على اكتشاف التصادمات، بحيث نقوم بالجمع عند التصادم بين اللاعب وبين الكائن القابل للجمع. هذه العملية يقوم بها البريمج CollisionCollector والموضح في السرد 63.


1. using UnityEngine;
2. using System.Collections;
3. 
4. public class CollisionCollector : MonoBehaviour {
5. 
6. 	void Start () {
7. 	
8. 	}
9. 	
10. 	void Update () {
11. 	
12. 	}
13. 	
14. 	//قم بإرسال رسالة الجمع عند اكتشاف التصادم
15. 	void OnCollisionEnter(Collision col){
16. 		SendCollectMessage(col.gameObject);
17. 	}
18. 	
19. 	//قم بإرسال رسالة الجمع عند التصادم مع محفّز
20. 	void OnTriggerEnter(Collider col){
21. 		SendCollectMessage(col.gameObject);
22. 	}
23. 	
24. 	void SendCollectMessage(GameObject target){
25. 		//إلى الجسم الذي تم التصادم معه Collect قم بإرسال رسالة الجمع 
26. 		//قم بتزويد الكائن نفسه كمالك لما سيتم جمعه
27. 		target.gameObject.SendMessage("Collect", 
28. 			gameObject, //owner تذهب هذه القيمة للمتغير 
29. 			SendMessageOptions.DontRequireReceiver);
30. 	}
31. }

السرد 63: بريمج جمع الأشياء بناء على اكتشاف التصادم بينها وبين كائن اللاعب

يتعامل هذا البريمج مع النوعين المحتملين من التصادمات وهي التصادمات مع الأجسام الصلبة عن طريق ()OnCollisionEnter والتصادمات مع المحفّزات والتي تتعامل معها الدّالة ()OnTriggerEnter. بناء على التصادم المُكتشف يتم إرسال الرسالة Collect إلى الكائن الذي تم التصادم معه وتزويد الكائن الذي يحمل هذا البريمج (وهو هنا كائن اللاعب) عبر المتغير owner ليكون هو من يحصل على أي شيء قابل للجمع إن وُجد. بهذه الطريقة يمكننا ضمان جمع أي كائن يحتوي على البريمج Collectable طالما احتوى هذا الكائن على مكوّن تصادم Collider. لنلخص الآن ما الذي علينا فعله: نحتاج لكائن يمثل شخصية فيزيائية للاعب ويحتوي على الكاميرا مضافة كابن له. يجب أن يحتوي هذا الكائن على البريمجات PhysicsCharacter وFPSInput وinventoryBox وCollisionCollector. إضافة لذلك علينا أن نضيف كائنا يمثل المفتاح الذي سيتم التقاطه ونضيف إليه البريمج CollectableKey. يمكنك مثلا عمل مفتاح بسيط مستخدما الأشكال الأساسية كما في الشكل 79.

الشكل 79: شكل مفتاح بسيط تم إنشاؤه باستخدام الأشكال الأساسية

الشكل 79: شكل مفتاح بسيط تم إنشاؤه باستخدام الأشكال الأساسية

بعد إضافة البريمج CollectableKey لهذا الكائن علينا أن نقوم باختيار الكلمة السرية التي تحدد ما هي الأقفال التي يمكن لهذا المفتاح أن يفتحها ومن ثم نقوم بكتابة هذه الكلمة في الخانة key عن طريق نافذة الخصائص. لنستخدم مثلا الكلمة "door1” لتمييز هذا المفتاح، بالتالي عندما يقوم اللاعب بالتقاطه فإن الكلمة door1 ستضاف إلى القائمة keys الموجودة في البريمج InventoryBox والذي يمثل حقيبة اللاعب. كل ما تبقى علينا الآن هو كتابة بريمج القفل وإضافة لكان الباب الدوّار الموجود لدينا. بما أننا قمنا باستخدام مكوّن المفصل الرزي لعمل الباب وهو بطبيعة الحال مكوّن يقع تحت تحكم المحاكي الفيزيائي، فإن عملية قفل هذا الباب ستكون ببساطة عبارة عن تجميد موقعه ودورانه من خلال مكوّن الجسم الصلب، بالتالي لن يستجيب الباب لأي قوى خارجية تؤثر عليه ولن يتحرك من مكانه. البريمج PhysicsDoorLock الموضح في السرد 64 يقوم بمهمة قفل الأبواب ذات الخصائص الفيزيائية.

1. using UnityEngine;
2. using System.Collections.Generic;
3. 
4. public class PhysicsKeyLock : MonoBehaviour {
5. 	
6. 	//الكلمة السرية اللازمة لفك هذا القفل
7. 	public string unlockKey;
8. 	
9. 	void Start () {
10. 		Lock ();
11. 	}
12. 	
13. 	void Update () {
14. 	
15. 	}
16. 	
17. 	//true إلى rigidbody.isKinematic قم بقفل الباب عن طريق تغيير قيمة 
18. 	public void Lock(){
19. 		rigidbody.isKinematic = true;
20. 	}
21. 	
22. 	//قم بمحاولة فك القفل مستخدما قائمة من الكلمات السرية
23. 	public void Unlock(ICollection<string> keys){
24. 		
25. 		if(!rigidbody.isKinematic){
26. 			return;
27. 		}
28. 		
29. 		//إذا تطابقت إحدى الكلمات مع كلمة فك القفل يتم فكه تلقائيا
30. 		foreach(string key in keys){
31. 			if(unlockKey.Equals(key)){
32. 				//قم بإبلاغ البريمجات الأخرى بنجاح عملية فك القفل
33. 				SendMessage("OnUnlock", 
34. 					SendMessageOptions.DontRequireReceiver);	
35. 				
36. 				rigidbody.isKinematic = false;
37. 				return;
38. 			}
39. 		}
40. 		
41. 		//قم بإبلاغ البريمجات الأخرى بفشل عملية فك القفل
42. 		SendMessage("OnUnlockFail", 
43. 			SendMessageOptions.DontRequireReceiver);
44. 	}
45. }

السرد 64: بريمج يعمل على قفل الأبواب الفيزيائية مستخدما كلمة سرية للقفل

بتغييرنا للقيمة rigidbody.isKeinematic إلى true، فإننا نخبر المحاكي الفيزيائي أن القوى الخارجية غير مسموح لها التأثير على موقع ودوران الباب، إلا أن الأجسام الأخرى تستمر في التصادم معه ويمكنه إيقاف حركتها. يبدأ البريمج باستدعاء الدّالة ()Lock والتي تقوم بتغيير قيمة rigidbody.isKinematic إلى true. عندما يحاول أي كائن آخر أو بريمج آخر فك القفل فإنّه يتوجب عليه تزويد الدّالة ()Unlock بقائمة من الكلمات السرية (أي المفاتيح)، فإذا تطابق أحد عناصر هذه القائمة مع الكلمة المخزنة في المتغير unlockKey يتم فتح القفل. لاحظ أننا قمنا باستخدام نوع المتغير ICollection للتعبير عن القائمة، وهو نوع عام يشمل كثيرا من أنواع المجموعات المختلفة. نتيجة لذلك، فإنّ الدّالة ()Unlock قادرة على استقبال قائمة من الكلمات 1. using UnityEngine; 2. using System.Collections; 3. 4. public class TouchUnlocker : MonoBehaviour { 5. 6. void Start () { 7. 8. } 9. 10. void Update () { 11. 12. } 13. 14. //إلى الجسم الذي يتم التصادم معه Unlock قم بإرسال الرسالة 15. void OnCollisionEnter(Collision col){ 16. //استخرج مرجعا لبريمج حقيبة اللاعب 17. InventoryBox box = GetComponent<InventoryBox>(); 18. 19. //قم بتجربة كافّة المفاتيح الموجودة في الحقيبة 20. col.gameObject.SendMessage("Unlock", 21. box.keys, //مجموعة المفاتيح المستخدمة في محاولة فك القفل 22. SendMessageOptions.DontRequireReceiver); 23. 24. } 25. 26. }

السرد 65: البريمج الخاص بمحاولة فك قفل الباب بمجرد ملامسة اللاعب له
[/caption]

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

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

1. using UnityEngine;
2. using System.Collections.Generic;
3. 
4. public class GeneralDoor : MonoBehaviour {
5. 	
6. 	//هل الباب مفتوح ابتداء؟
7. 	public bool initiallyOpen = false;
8. 	
9. 	//الكلمة السرية لفك قفل الباب
10. 	public string unlockKey;
11. 	
12. 	//متغير لتخزين حالة الباب داخليا
13. 	bool isOpen;
14. 	
15. 	//متغير لتخزين حالة القفل داخليا
16. 	bool locked;
17. 	
18. 	void Start () {
19. 		//قم بقفل الباب إن كان هناك كلمة سرية لقفله
20. 		locked = !string.IsNullOrEmpty(unlockKey);
21. 		//قم بضبط الحالة الابتدائية للباب
22. 		isOpen = initiallyOpen;
23. 	}
24. 	
25. 	void Update () {
26. 	
27. 	}
28. 	
29. 	//قم بفتح الباب إن لم يكن القفل يمنع ذلك
30. 	public void Open(){
31. 		if(!locked){
32. 			isOpen = true;
33. 		}
34. 	}
35. 	
36. 	//قم بإغلاق الباب إن لم يكن القفل يمنع ذلك
37. 	public void Close(){
38. 		if(!locked){
39. 			isOpen = false;
40. 		}
41. 	}
42. 	
43. 	//قم بقفل الباب
44. 	public void Lock(){
45. 		locked = true;
46. 	}
47. 	
48. 	//حاول فك القفل مستخدما مجموعة من الكلمات السرية
49. 	public void Unlock(ICollection<string> keys){
50. 		//قم بفحص حالة القفل الحالية أولا
51. 		if(!IsLocked()){
52. 			return;
53. 		}
54. 		//جرب جميع الكلمات السرية في القائمة لمحاولة فك القفل
55. 		foreach(string key in keys){
56. 			if(key.Equals(unlockKey)){
57. 			      //قم بإبلاغ البريمجات الأخرى بنجاح عملية فك القفل
58. 				SendMessage("OnUnlock", 
59. 					SendMessageOptions.DontRequireReceiver);
60. 				
61. 				locked = false;
62. 				return;
63. 			}
64. 		}
65. 		//قم بإبلاغ البريمجات الأخرى بفشل محاولة فك القفل
66. 		SendMessage("OnUnlockFail", 
67. 			SendMessageOptions.DontRequireReceiver);
68. 	}
69. 	
70. 	//هل الباب مقفل حاليا؟
71. 	public bool IsLocked(){
72. 		return locked;
73. 	}
74. 	
75. 	//هل الباب مفتوح حاليا؟
76. 	public bool IsOpen(){
77. 		return isOpen;
78. 	}
79. 	
80. 	//قم بتبديل حالة الباب بين الفتح والإغلاق
81. 	public void Switch(){
82. 		if(IsOpen()){
83. 			Close();
84. 		} else {
85. 			Open();
86. 		}
87. 	}
88. }

السرد 66: البريمج الخاص بالوظائف الأساسية للأبواب

لعلك لاحظت أن جميع الدّوال في هذا البريمج تتعامل مباشرة مع الحالة الداخلية للباب ويمكن من خلالها تغيير هذه الحالة من حيث الفتح والإغلاق أو القفل وفك القفل. جميع هذه الدّوال تسمح لك بتغيير الحالة الداخلية للباب بشكل مباشر بمجرد استدعائها، مع مراعاة وجوب تزويد الكلمة السرية الصحيحة لفك القفل في حالة الدّالة ()Unlock. ففي هذه الحالة ستبقى قيمة المتغير locked على حالها إن كانت true إذا لم يتم تزويد الدّالة بالكلمة السرية الصحيحة لفك القفل. السؤال الآن هو كيف نستفيد من هذا البريمج الذي يمثل حالة الباب في عمل باب منزلق؟ البريمج SlidingDoor يجيب على هذا السؤال حيث يقوم بشكل مستمر بفحص حالة الباب عن طريق استدعاء ()IsOpen وتحريك الباب فعليا بناء على قيمتها. هذا البريمج موضح في السرد 67.

1. using UnityEngine;
2. using System.Collections;
3. 
4. [RequireComponent(typeof(GeneralDoor))]
5. public class SlidingDoor : MonoBehaviour {
6. 	
7. 	//الموقع النسبي للباب عند الفتح
8. 	public Vector3 slidingDirection = Vector3.up;
9. 	//سرعة حركة الباب عند الفتح والإغلاق
10. 	public float speed = 2;
11. 	//متغيرات داخلية لتخزين مواقع الفتح والإغلاق
12. 	Vector3 originalPosition, slidingPosition;
13. 	//GeneralDoorمرجع لبريمج الباب 
14. 	GeneralDoor door;
15. 	//تخزين حالة الباب المنزلق
16. 	SlidingDoorState state;
17. 	
18. 	void Start () {
19. 		//تعيين القيم الأولية للمتغيرات
20. 		door = GetComponent<GeneralDoor>();
21. 		originalPosition = transform.position;
22. 		slidingPosition = transform.position + slidingDirection;
23. 		state = SlidingDoorState.close;
24. 	}
25. 	
26. 	void Update () {
27. 		if(door.IsOpen()){
28. 			//يجب أن يكون الباب مفتوحا
29. 			if(state != SlidingDoorState.open){
30. 				//الباب ليس مفتوحا مما يعني أنه يجب أن نحركه
31. 				//بشكل سلس باتجاه موقع الفتح
32. 				transform.position =
33. 							Vector3.Lerp(
34. 								transform.position,
35. 								slidingPosition, 
36. 								Time.deltaTime * speed);
37. 				
38. 				float remaining = 
39. 					Vector3.Distance(
40. 						transform.position, slidingPosition);
41. 
42. 				//التحقق من وصول الباب لموقع الفتح
43. 				if(remaining < 0.01f){
44. 					//Open position reached: 
45. 					//change state of the door
46. 					state = SlidingDoorState.open;
47. 					transform.position = slidingPosition;
48. 					//قم بإبلاغ البريمجات الأخرى باكتمال عملية فتح الباب
49. 					SendMessage("OnOpenComplete", 
50. 					   SendMessageOptions.DontRequireReceiver);
51. 					
52. 				} else if(state != SlidingDoorState.openning){
53. 					//الباب بدأ يفتح للتو
54. 					//قم بإرسال رسالة تبلغ البريمجات الأخرى بالأمر
55. 					SendMessage("OnOpenStart", 
56. 					   SendMessageOptions.DontRequireReceiver);
57. 					
58. 					state = SlidingDoorState.openning;
59. 				}
60. 			}
61. 		} else {
62. 			//يجب أن يكون الباب مغلقا
63. 			if(state != SlidingDoorState.close){
64. 				//الباب ليس مغلقا، لذا يجب أن يتم تحريكه
65. 				//بشكل سلس باتجاه موقع الإغلاق
66. 				transform.position = 
67. 					Vector3.Lerp(
68. 							transform.position, 
69. 							originalPosition, 
70. 							Time.deltaTime * speed);
71. 				float remaining = 
72. 					Vector3.Distance(
73. 						transform.position, slidingPosition);
74. 				
75. 				//التحقق من وصول الباب لموقع الإغلاق
76. 				if(remaining < 0.01f){
77. 					//تم الوصول لموقع الإغلاق
78. 					//قم بتغيير حالة الباب
79. 					state = SlidingDoorState.close;
80. 					transform.position = originalPosition;
81. 					//قم بإبلاغ البريمجات الأخرى باكتمال عملية الإغلاق
82. 					SendMessage("OnCloseComplete", 
83. 					   SendMessageOptions.DontRequireReceiver);
84. 					
85. 				} else if(state != SlidingDoorState.closing){
86. 					//بدأ الباب بالإغلاق للتو
87. 					//أرسل رسالة تبلغ البريمجات الأخرى بهذا الأمر
88. 					SendMessage("OnCloseStart", 
89. 					   SendMessageOptions.DontRequireReceiver);
90. 					
91. 					state = SlidingDoorState.closing;
92. 				}
93. 			}
94. 		}
95. 	}
96. 	
97. 	void OnCollisionEnter(Collision col){
98. 		if(state == SlidingDoorState.closing){
99. 			//شيء ما أعاق إغلاق الباب
100. 			//قم بإبلاغ البريمجات الأخرى بحدوث هذا الأمر
101. 			SendMessage("OnCloseInterruption", 
102. 				col.gameObject, 
103. 				SendMessageOptions.DontRequireReceiver);
104. 		}
105. 	}
106. 	
107. 	//مُعَدَّد خاص بالحالات المختلفة للباب المنزلق
108. 	enum SlidingDoorState{
109. 		open, close, openning, closing
110. 	}
111. }

السرد 67: البريمج الخاص بالباب المنزلق

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

بالعودة الآن لتفاصيل البريمج، نلاحظ وجود المتغير slidingDirection والذي يمثل متجه المسافة التي يقطعها الباب حين يتحرك من موقع الإغلاق لموقع الفتح. فإذا كانت قيمته مثلا (0 ,2 ,0) فإنّ الباب سيتحرك مترين نحو الأعلى عند فتحه. سرعة حركة الباب يتحكم بها المتغير speed، كما أنّ حركة الفتح والإغلاق نفسها هي عبارة عن انتقال سلس عن طريق الاستيفاء بين نقطتي الإغلاق والفتح المخزنتين في المتغيرين originalPosition و slidingPosition. المتغير الأول originalPosition يأخذ قيمته عند بداية التشغيل من الموقع الحالي للباب، وهذا يعني أنّ الباب يجب أن يوضع في المشهد في حالة الإغلاق دائما. أمّا المتغير الآخر slidingPosition فهو عبارة عن ناتج جمع الموقع الأصلي مع المتجه slidingDirection. سبق وعرفنا أن حالة الباب تتم إدارتها عن طريق البريمج GeneralDoor، إضافة لذلك نقوم بإدارة الحالات الانتقالية للباب المنزلق عن طريق المُعَدَّد SlidingDoorState والذي نحفظ فيه قيمة الحالة الحالية للباب المنزلق (مفتوح opened، مغلق closed، يفتح opening، يغلق closing) حيث يتم إسناد القيمة الأولية بناء على قيمة المتغير door.initiallyOpen. الشكل 80 يوضح بابا منزلقا ذو مصراعين يتحركان في اتجاهين متعاكسين عند الفتح والإغلاق.

الشكل 80: باب منزلق ذو مصراعين يتحرك كل منهما في اتجاه السهم حين فتحه

الشكل 80: باب منزلق ذو مصراعين يتحرك كل منهما في اتجاه السهم حين فتحه

أثناء تنفيذ الدّالة ()Update يقوم البريمج باستدعاء ()door.IsOpen وفحص القيمة التي ترجعها. فإذا كانت القيمة true يعني أن الباب يجب أن يكون مفتوحا، وإن لم يكن كذلك يجب فتحه. لأجل ذلك نقوم بفحص الحالة الحالية للباب المنزلق state والتأكد من أنها تساوي SlidingDoorState.open. فإن لم تكن كذلك فهي واحدة من الثلاث الأخريات: إمّا أن الباب مغلق SlidingDoorState.closed أو أنّه يتم إغلاقه SlidingDoorState.closing أو أنّه يتم فتحه SlidingDoorState.opening. في الحالتين الأوليين وهما حالتا الإغلاق علينا أن نقوم بتغيير الحالة إلى الفتح SlidingDoorState.opening، أمّا إذا كانت القيمة تساوي الحالة الثالثة فإننا نقوم بتحريك الباب بشكل سلس باتجاه موقع الفتح slidingPosition. هذه الحركة السلسة تتم عن طريق الدّالة ()Vector3.Lerp والتي سبق شرح طريقة عملها. آخذين بعين الاعتبار أن المنطقة الميتة لفتح الباب أو إغلاقه هي 0.01 أي سنتيمترا واحدا، فإننا نقوم بوضع الباب مباشرة في الموقع slidingPosition إذا قلت المسافة بين موقع الباب الحالي وهذه النقطة عن مقدار المنطقة الميتة. وبعدها نقوم بتغيير حالة الباب إلى الحالة الجديدة وهي "مفتوح"
SlidingDoorState.open. وإرسال رسالة تعلم البريمجات الأخرى بالحالة الجديدة للباب. الأمر نفسه يتم ولكن في الاتجاه المعاكس إذا كانت القيمة المُرجعة من ()door.IsOpen هي false. حيث نقوم بفحص ما إذا كانت حالة الباب هي الإغلاق SlidingDoorState.closed وتغييرها للحالة المناسبة إذا كانت غير ذلك. أخيرا يمكننا اكتشاف تصادم الباب مع أي كائن آخر أثناء الإغلاق، وفي هذه الحالة نقوم بإرسال الرسالة OnCloseInterruption مزودين معها مرجعا للكائن الذي يعيق إغلاق الباب. هذه الرسالة يمكن التعامل معها مثلا عن طريق تدمير الكائن المعيق مما يجعل الباب سلاحا يمكن للاعب استخدامه ضد الأعداء، خاصّة إن كان يمكنه التحكم بالباب من مكان بعيد.

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

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