الفصل الثاني: الألغاز وتراكيب فك الأقفال

العب

هذا الفصل هو امتداد للفصل السابق حيث سنواصل العمل على الباب المنزلق الذي سبق وقمنا بعمله. سنقوم بإغلاق هذا الباب مستخدمين قفلا مركزيا نفترض أنه يعمل بآلة كهربائية معينة، وعلى اللاعب أن يقوم بحل لغز بسيط حتى يتمكن من فك القفل. ما نحتاج لعمله الآن هو إضافة البريمج SlidingDoor لكل طرف من أطراف الباب الموضح في الشكل الشكل 80 ونقوم بضبط اتجاه الانزلاق عبر المتغير slidingDirection بحيث تكون مثلا (0 ,0 ,1.2) للشق الأيمن و (0 ,0 ,1.2-) للشق الأيسر من الباب. عند أضافة البريمج SldingDoor سيقوم Unity تلقائيا بإضافة البريمج GeneralDoor ذلك أن الأول يعتمد على الثاني كما وضحنا في الفصل السابق. البريمج GeneralDoor يفترض أن الباب لا يحوي قفلا طالما أن قيمة المتغير unlockKey خالية، لذا علينا أن نقوم بتحديد قيمة ما ولتكن مثلا
"door2” لكل من شقي الباب المنزلق. علينا الآن أن نقوم بإضافة القفل المركزي وهو عبارة عن كائن خالِ يحتوي على البريمجات اللازمة للتحكم بالباب المنزلق فتحا وإقفالا. أول هذه البريمجات هو CentralLock والموضح في السرد 68.

1. using UnityEngine;
2. using System.Collections;
3. 
4. public class CentralLock : MonoBehaviour {
5. 	
6. 	//كائنات الأبواب التي سيتحكم بها هذا القفل
7. 	public GeneralDoor[] targetDoors;
8. 	
9. 	//مجموعة الكلمات السرية التي يحتاجها القفل لفك قفل الأبواب
10. 	public string[] keys;
11. 	
12. 	//هل سيفتح الباب تلقائيا عند فك القفل؟
13. 	public bool autoOpen = true;
14. 	
15. 	//هل سيغلق الباب تلقائيا قبل أن يتم قفله؟
16. 	public bool autoClose = true;
17. 	
18. 	void Start () {
19. 	
20. 	}
21. 	
22. 	void Update () {
23. 	
24. 	}
25. 	
26. 	//قم بقفل كافّة الأبواب
27. 	public void LockAll(){
28. 		foreach(GeneralDoor door in targetDoors){
29. 			if(autoClose){
30. 				door.Close();
31. 			}
32. 			door.Lock();			
33. 		}
34. 	}
35. 	
36. 	//قم بفك قفل كافّة الأبواب مستخدما الكلمات المتوفرة
37. 	public void UnlockAll(){
38. 		foreach(GeneralDoor door in targetDoors){
39. 			door.Unlock(keys);
40. 			if(autoOpen){
41. 				door.Open();
42. 			}
43. 		}
44. 	}
45. }

السرد 68: البريمج الخاص بالقفل المركزي

يحتوى هذا البريمج على مصفوفة من الأبواب targetDoors كما يحتوي على مصفوفة أخرى من الكلمات السرية اللازمة لفتح هذه الأبواب وهي keys. عند استدعاء الدّالة ()LockAll يقوم البريمج بالمرور على جميع أبواب المصفوفة targetDoors وقفلها عن طريق استدعاء الدّالة ()door.Lock في كل مرة. إضافة إلى ذلك سيقوم البريمج بإغلاق الأبواب قبل قفلها وذلك إذا كان الخيار autoClose مفعّلا. على الجانب الآخر فإنّ الدّالة ()UnlockAll تحاول فك قفل جميع الأبواب مستخدمة مصفوفة الكلمات السرية keys مع كل باب، كما أنها تقوم بفتح الباب بعد فك قفله في حال كان الخيار autoOpen مفعّلا. باستخدامنا لمجموعة من الأبواب ومجموعة من الكلمات السرية يمكننا استخدام هذا القفل المركزي للتحكم بمجموعة أبواب لا تمتلك بالضرورة نفس الكلمة السرية لفك قفلها. هذا مفيد لو تخيلت مثلا أنك تريد عمل غرفت تحكم ذات مدخل سري يمكن للاعب من داخلها فك قفل جميع الأبواب في المرحلة الحالية بضغطة زر، وإلا فإن عليه أن يقوم بالبحث عن مفتاح لكل باب لفتحه بشكل مستقل. لاحظ أيضا أن المصفوفة targetDoors هي من نوع GeneralDoor، مما يجعلها قابلة لاستيعاب أي نوع من الأبواب قد يلزمك إضافته، وليس فقط الأبواب المنزلقة.

بعد كتابة البريمج يمكننا أن نضيفه للكائن الفارغ الذي سنستخدمه كقفل مركزي، كما يتوجب علينا أن نضيف شقي الباب الأيمن والأيسر إلى المصفوفة targetDoors عبر نافذة الخصائص حتى يصبح هذان الشقان تحت تصرف القفل المركزي. بعدها علينا أن نضيف الكلمة السرية التي استخدمناها لقفل الباب المنزلق وهي door2 إلى المصفوفة keys حتى يكون القفل المركزي قادرا على فك قفل الباب حين يُطلب منه ذلك. سنبقي على الخيارين autoOpen و autoClose مفعّلين مما يوفر علينا إضافة آلية أخرى لفتح الباب المنزلق وإغلاقه بالتالي نكتفي بالقفل المركزي ليقوم بالمهمّة. لو تأملنا الوضع الحالي للمشهد، سنجد أننا أمام باب منزلق ذو شقين أيمن وأيسر، وهذا الباب مقفل باستخدام كلمة سرية وشقاه متصلان بقفل مركزي يحمل هذه الكلمة السرية ويمكنه فك القفل وفتح الباب بمجرد استدعاء الدّالة ()UnlockAll. إذن نحن أمام سؤال واحد أخير: متى سيتم استدعاء ()UnlockAll ومن سيقوم باستدعائها؟ الجواب هو نظام الألغاز الذي سنقوم ببنائه بعد قليل. قبل الخوض في التفاصيل البرمجية لهذا النظام لنتعرف على آلية عمله: سيحتوي هذا اللغز على أربعة أزرار، وسيكون لكل زر حالتان: أخضر وأحمر، وسيحتاج اللاعب لفك القفل إلى إيجاد التركيبة المناسبة بين ألوان الأزرار الأربعة، أي أنه أمام 16 احتمالا مختلفا (أي 24). سنقوم بترتيب هذه الأزرار الأربعة (وهي عبارة عن مكعبات صغيرة الحجم نسبيا) حول الباب كما في الشكل 81.

الشكل 81: أزرار لغز فك القفل الأربعة موزعة حول الباب المنزلق

الشكل 81: أزرار لغز فك القفل الأربعة موزعة حول الباب المنزلق

ما ينبغي علينا فعله الآن هو إعطاء اللاعب القدرة على التحكم بكل واحد من هذه الأزرار الأربعة وتغيير لونه من الأحمر للأخضر أو العكس. لأجل ذلك سنعيد استخدام بريمجات المحفّزات التي كنا قد كتبناها سابقا وهي SwitchableTrigger (السرد 35) وTriggerSwitcher (السرد 37) واللذين تم إنجازهما في الفصل الثالث من الوحدة الرابعة. بمراجعة سريعة لمحتوى البريمجين نتذكر وجود الدّالة ()SwitchState في البريمج SwitchableTrigger والتي تنقل المحفّز بين عدة حالات ويمكنها إرسال رسائل مختلفة عند الانتقال من حالة لأخرى. أمّا بالنسبة للبريمج TriggerSwitcher فهو يضاف لكائن شخصية اللاعب ليسمح له بتفعيل هذه المحفّزات عن طريق استدعاء ()SwitchState من المحفّز بالاقتراب من المحفّز والضغط على مفتاح E على لوحة المفاتيح. عند تحفيز اللاعب لأي من أزرار اللغز الأربعة، هناك ثلاث خطوات ينبغي القيام بها: الخطوة الأولى هي تغيير لون الزر نفسه من الأحمر للأخضر أو العكس، والخطوة الثانية هي التواصل مع بريمج مسؤول عن إدارة حالة اللغز وإعلامه بوجود تركيبة ألوان جديدة، مما يعني أنه يجب التأكد من أن اللاعب قد توصل للتركيبة الصحيحة لحل اللغز أم لا، والخطوة الثالثة هي محاولة فك قفل الباب باستخدام التركيبة الجديدة. وبما أننا اخترنا أن يفتح الباب تلقائيا عند فك قفله، فهذا يعني أنه بمجرد توصل اللاعب للتركيبة الصحيحة لحل اللغز سيفتح الباب تلقائيا دون الحاجة للاقتراب منه ومحاولة فتحه.

لنبدأ مع الخطوة الأولى والأسهل وهي تغيير لون الزر من الأحمر للأخضر وبالعكس. هذه المهمة يقوم بها البريمج ColorCycler والموضح في السرد 69. هذا البريمج يعتمد في عمله على تغيير لون الخامة الرئيسية لمكوّن التصيير renderer الخاص بالكائن.

1. using UnityEngine;
2. using System.Collections;
3. 
4. public class ColorCycler : MonoBehaviour {
5. 	
6. 	//مصفوفة الألوان التي سيتم التبديل بينها
7. 	public Color[] colors;
8. 	
9. 	//الموقع الخاص باللون الحالي
10. 	public int currentColor = 0;
11. 	
12. 	void Start () {
13. 		renderer.material.color = colors[currentColor];
14. 	}
15. 	
16. 	void Update () {
17. 	
18. 	}
19. 	
20. 	//قم بالتبديل للون التالي في المصفوفة أو العودة للبداية حين الوصول لنهايتها
21. 	public void CycleColor(){
22. 		if(colors.Length > 0){
23. 			currentColor++;
24. 			
25. 			if(currentColor == colors.Length){
26. 				currentColor = 0;
27. 			}
28. 			
29. 			renderer.material.color = colors[currentColor];
30. 		}
31. 	}
32. }

السرد 69: بريمج يقوم بتغيير لون الكائن بين عدة قيم مخزنة في مصفوفة

ما يتوجب علينا فعله الآن هو إضافة البريمج SwitchableTrigger إلى الأزرار الأربعة التي قمنا بإضافتها حول الباب (الأفضل طبعا هو عمل قالب خاص بالأزرار ونسخه أربع مرات) ومن ثم تحديد عدد الحالات التي يمكن الانتقال بينها إلى حالتين. عند الانتقال للحالة الأولى أو الثانية ستكون النتيجة دائما هي إرسال الرسالة CycleColor والتي سيقوم البريمج ColorCycler - والذي يجب أن يكون مضافا للزر أيضا ومضاف إليه اللونان الأحمر والأخضر – باستقبالها وتغيير اللون بناء عليها. الآن علينا تنفيذ الخطوة الأخيرة وهي حلقة الوصل بين الأزرار الأربعة والبريمج CentralLock والذي يمتلك إمكانية فك قفل الباب وفتحه، وهي العملية التي يجب أن تتم بناء على حالة اللغز. بما أننا نتحدث عن أربعة أزرار متفرقة ينبغي علينا أن نمتلك آلية لمعرفة ألوان هذه الأزرار ومقارنتها بحل اللغز الصحيح الذي نختاره، وبناء على التطابق بين حالة الأزرار الأربعة والحل الصحيح نقوم بإرسال الرسالة UnlockAll للبريمج CentralLock حتى يفك قفل الباب ويقوم بفتحه. هذا البريمج هو ColorCodePuzzle الموضح في السرد 70، وهو المكان الفعلي الذي يحتوي على منطق اللغز وكيفية حله. عند تطابق حل اللاعب مع الحل الصحيح سنحتاج لإرسال الرسالة UnlockAll، أما في حال عدم التطابق علينا إرسال الرسالة LockAll.

1. using UnityEngine;
2. using System.Collections;
3. 
4. public class ColorCodePuzzle : MonoBehaviour {
5. 	
6. 	//تركيبة فك القفل أو الحل الصحيح لهذا اللغز
7. 	public Color[] unlockCode;
8. 	
9. 	//مصادر الحصول على مدخلات اللاعب لحل اللغز
10. 	public Renderer[] colorSources;
11. 	
12. 	//الرسائل التي سيتم إرسالها عند حل اللغز حلا صحيحا
13. 	public TriggerMessage[] matchMessages;
14. 	
15. 	//الرسائل التي سيتم إرسالها عند الحل الخاطئ للغز أي التراكيب غير المتوافقة مع الحل
16. 	public TriggerMessage[] mismatchMessages;
17. 	
18. 	void Start () {
19. 	}
20. 	
21. 	void Update () {
22. 	}
23. 	
24. 	//قم بمقارنة تركيبي اللاعب والحل الصحيح
25. 	public void CompareCodes(){
26. 		//نفترض أن التركيبين متطابقان
27. 		bool match = true;
28. 		
29. 		//قم بالمرور على المدخلات ومقارنتها مع الحل الصحيح
30. 		for(int i = 0; i < colorSources.Length; i++){
31. 			//مجرد وجود اختلاف في مدخل واحد كافِ لنفي التطابق بين التركيبين
32. 		   if(!colorSources[i].material.color.Equals(unlockCode[i])){
33. 				match = false;
34. 			}
35. 		}
36. 		
37. 		TriggerMessage[] toSend;
38. 		
39. 		//في حال التطابق بين تركيب مدخل اللاعب وتركيب الحل الصحيح
40. 		//علينا أن نقوم بإرسال الرسائل الخاصة بالتطابق
41. 		if(match){
42. 			toSend = matchMessages;
43. 		} else {
44. 			//عدا ذلك علينا إرسال الرسائل الخاصة بعدم التطابق
45. 			toSend = mismatchMessages;
46. 		}
47. 		
48. 		//قم بإرسال الرسائل
49. 		foreach(TriggerMessage msg in toSend){
50. 			if(msg.messageReceiver != null){
51. 				msg.messageReceiver
52. 						.SendMessage(
53. 						    msg.messageName,
54. 						 SendMessageOptions.RequireReceiver);
55. 				
56. 			}
57. 		}
58. 	}
59. }

السرد 70: بريمج لغز تراكيب الألوان

قمنا في هذا البريمج بإعادة استخدام TriggerMessage الذي قمنا بكتابته في الفصل الثالث من الوحدة الرابعة (السرد 35) وقمنا بإنشاء مصفوفتين من هذا النوع. المصفوفة الأولى هي matchMessages وهي الرسائل التي سنقوم بإرسالها في حال فحص الحل وحدوث التوافق بين مُدخل اللاعب والحل الصحيح للغز والمصفوفة الثانية هي mismatchMessages وهي الرسائل التي سنرسلها في حال فحص الحل وعدم اكتشاف توافق بين مُدخل اللاعب والحل الصحيح. بدوره فإن الحل الصحيح يتم تحديده مباشرة من نافذة الخصائص وذلك عبر المصفوفة unlockCode والتي تحتوي على الترتيب الصحيح للألوان والذي سيعمل على حل اللغز وبالتالي إرسال رسالة لفك قفل الباب. بعد ذلك علينا أن نربط الأزرار الأربعة بهذا البريمج وذلك عن طريق المصفوفة colorSources والتي هي عبارة عن مصفوفة لمكونات من نوع renderer وهي التي سنعمل على استخراج الألوان منها. ما علينا فعله الآن هو إضافة كل زر من الأزرار الأربعة عبر مكوّن التصيير renderer الخاص به إلى المصفوفة colorSources وليكن ترتيبها من اليسار لليمين. لدينا الآن أربعة مصادر للألوان تأتي من مكوّنات تصيير الأزرار الأربعة إضافة لأربع ألوان تحدد الحل الصحيح موجودة في المصفوفة unlockCode. بالتالي عند تفعيل المحفّز الخاص بكل زر من الأزرار يجب أن نعاود استدعاء ()CompareCodes وذلك حتى يقوم البريمج ColorCodePuzzle بمقارنة تركيبة الألوان الجديدة وفتح الباب إن كانت صحيحة. من المهم ذكره هنا هو أن عملية مقارنة الألوان تتم برمجيا عن طريق مقارنة القيم الرقمية الخاصة بدرجات الأخضر والأحمر والأزرق والشفافية، لذا من المهم أن تكون الألوان متطابقة تماما رقميا فلن ينفع مثلا وجود درجتين مختلفتين من الأخضر بين القيم في colorSources و unlockCode لأن ذلك سيعني برمجيا أنه لا تطابق لونيا بينهما.

بقي علينا أن نذكر أن البريمج ColorCodePuzzle يجب أن تتم إضافته لنفس الكائن الذي أضفنا له البريمج CentralLock. لتلخيص كل هذه التفاصيل انظر الشكل 82 والذي يوضح مكوّنات بريمجات فك القفل عن طريق الألغاز إضافة للمتغيرات والقيم الخاصة بهذه البريمجات. يمكنك أيضا تجربة النتيجة النهائية في المشهد scene20 في المشروع المرفق.

الشكل 82: ضبط قيم المتغيرات الخاصة ببريمجات أزرار لغز فك القفل إضافة للبريمجين ColorCodePuzzle و CentralLock وذلك لصناعة لغز لفك القفل عن طريق ترتيب الألوان

الشكل 82: ضبط قيم المتغيرات الخاصة ببريمجات أزرار لغز فك القفل إضافة للبريمجين ColorCodePuzzle و CentralLock وذلك لصناعة لغز لفك القفل عن طريق ترتيب الألوان

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

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