الفصل الثالث: صحة اللاعب والفرص والنقاط

العب

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

سنقوم في هذا الفصل بتجميع الأفكار الثلاث، وهي الصحة والفرص والنقاط في لعبة واحدة مكتملة. فكرة اللعبة تقوم على تحكم اللاعب بمكعب محصور داخل غرفة، وعلى جدران الغرفة قاذفات تطلق طلقات باتجاهات مختلفة. الهدف من اللعبة هو النجاة من هذه الطلقات أكبر وقت ممكن قبل استنفاذ الفرص جميعها. لنفترض مثلا أننا سنعطي اللاعب 3 فرص وصحة مقدارها 100 في كل فرصة. علاوة على ذلك سيكون لدينا نوعان من الطلقات أحدهما أحمر اللون وينقص من صحة اللاعب بمقدار 10 وحدات والآخر أخضر اللون وينقص 5 وحدات. بما أنّ اللاعب الأفضل سيتمكن من الصمود فترة أطول في هذه الغرفة قبل أن يستنفذ الفرص الثلاث وتنتهي اللعبة، من المنطقي أن نقوم بحساب هذا الوقت واعتماده كنقاط لقياس أداء اللاعب. الشكل 83 يظهر شكل الغرفة التي سنقوم بعملها لهذه اللعبة، حيث قاذفات الطلقات عبارة عن أسطوانات تم تدويرها بحيث يشير سطها العلوي (الاتجاه الموجب للمحور y) إلى داخل الغرفة.

الشكل 83: منظر علوي لغرفة اللعب والقاذفات التي تحيط بها من كل جانب

الشكل 83: منظر علوي لغرفة اللعب والقاذفات التي تحيط بها من كل جانب

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

1. using UnityEngine;
2. using System.Collections;
3. 
4. public class PhysicsShooter : MonoBehaviour {
5. 	
6. 	//مصفوفة تحتوي على قوالب الطلقات
7. 	public GameObject[] projectils;
8. 	
9. 	//أقصى وأدني عدد من الثواني التي يمكن انتظارها بين الطلقتين المتتاليتين
10. 	public float minTime = 1, maxTime = 6;
11. 
12. 	void Start () {
13. 		ShootRandomly();
14. 	}
15. 	
16. 	void Update () {
17. 	
18. 	}
19. 	
20. 	void ShootRandomly(){
21. 		//قم بإطلاق النار بعد عدد عشوائي من الثواني
22. 		float randomTime = Random.Range(minTime, maxTime);
23. 		Invoke("Shoot", randomTime);
24. 	}
25. 	
26. 	void Shoot(){
27. 		//قم باختيار طلقة عشوائية
28. 		int index = Random.Range(0, projectils.Length);
29. 		GameObject prefab = projectils[index];
30. 		GameObject projectile = (GameObject)Instantiate(prefab);
31. 		
32. 		//قم بإطلاق الطلقة التي تم اختيارها
33. 		projectile.transform.position = transform.position;
34. 		projectile.rigidbody.AddForce
35. 				(transform.up * 6, ForceMode.Impulse);
36. 		
37. 		//قم باستدعاء دالّة الإطلاق مرة أخرى
38. 		ShootRandomly();
39. 	}
40. }

السرد 71: بريمج أطلاق الطلقات العشوائية للقاذفات

كما تلاحظ فإنّ عملية الإطلاق تتم فعليا عبر إضافة قوة اندفاع للطلقة المقذوفة، وهي بدورها يتم اختيارها عشوائيا من المصفوفة projectiles. بعد كل عملية إطلاق يتم استدعاء ()ShootRandomly، وذلك حتى تقوم بتوليد رقم عشوائي لاستخدامه كوقت لتأخير عملية الإطلاق التالية. لاحظ أن القيمة العشوائية محصورة بين قيمتي المتغيرين minTime و maxTime، ويتم استخدامها مع الدّالة ()Invoke لاستدعاء ()Shoot لاحقا. الخطوة التالية هي بناء قالبين للطلقتين الحمراء والخضراء التين تحدثنا عنهما. ما تقوم به هاتان الطلقتان هو إنقاص صحة اللاعب بمجرد لمسه، لذلك نضيف إليهما بريمجا أسميناه PainfulProjectile وهو موضح في السرد 72.

1. using UnityEngine;
2. using System.Collections;
3. 
4. public class PainfulProjectile : MonoBehaviour {
5. 	
6. 	//مقدار الصحة الذي ستنقصه الطلقة من اللاعب حين الإصابة
7. 	public int damage;
8. 
9. 	void Start () {
10. 	
11. 	}
12. 	
13. 	void Update () {
14. 	
15. 	}
16. 	
17. 	void OnCollisionEnter(Collision col){
18. 		//قم بإرسال رسالة إنقاص الصحة إلى الجسم الذي تم الاصطدام به
19. 		col.gameObject.SendMessage("OnPainfulHit", 
20. 						damage, 
21. 				     	   SendMessageOptions.DontRequireReceiver);
22. 		
23. 		//قم بتدمير كائن الطلقة
24. 		Destroy(gameObject);
25. 	}
26. }

السرد 72: بريمج الطلقة التي تنقص من صحة اللاعب

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

الشكل 84: يسارا: طلقة صغيرة تشع بلون أخضر وتنقص من صحة اللاعب بمقدار ، ويمينا: طلقة حمراء تنقص بمقدار 10

الشكل 84: يسارا: طلقة صغيرة تشع بلون أخضر وتنقص من صحة اللاعب بمقدار ، ويمينا: طلقة حمراء تنقص بمقدار 10

ما يتوجب عمله الآن هو إضافة قالبي هاتين الطلقتين إلى المصفوفة projectiles في قالب مدافع الإطلاق، مما يجعلهما تضافان تلقائيا إلى كافّة المدافع في المشهد. لإكمال بيئة اللعب علينا أخيرا أن نضيف اللاعب الذي سنتحكم به، وسيكون هنا عبارة عن مكعب مضاف إليه البريمج PhysicsCharacter إضافة إلى TopViewControl والموضح في السرد 73. هذا البريمج الأخير يسمح لنا بالتحكم بشخصية اللاعب من منظور علوي وتحريكه في الاتجاهات الأربع. إضافة لذلك سنمنع عملية القفز وذلك من خلال تعطيل حركة الجسم الصلب على المحور y إضافة لمنع دورانه على جميع المحاور.

1. using UnityEngine;
2. using System.Collections;
3. 
4. [RequireComponent(typeof(PhysicsCharacter))]
5. public class TopViewControl : MonoBehaviour {
6. 	
7. 	//متغير لتخزين شخصية اللاعب التي سيتم التحكم بها
8. 	PhysicsCharacter pc;
9. 	
10. 	void Start () {
11. 		//اعثر على بريمج شخصية اللاعب
12. 		pc = GetComponent<PhysicsCharacter>();
13. 	}
14. 	
15. 	void Update () {
16. 		//تحكم بالحركة باستخدام الأسهم
17. 		if(Input.GetKey(KeyCode.RightArrow)){
18. 			pc.StrafeRight();
19. 		} else if(Input.GetKey(KeyCode.LeftArrow)){
20. 			pc.StrafeLeft();
21. 		}
22. 		
23. 		if(Input.GetKey(KeyCode.UpArrow)){
24. 			pc.WalkForward();
25. 		} else if(Input.GetKey(KeyCode.DownArrow)){
26. 			pc.WalkBackwards();
27. 		}
28. 		
29. 	}
30. }

السرد 73: البريمج الخاص بالتحكم بشخصية اللاعب الفيزيائية من منظور علوي

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

1. using UnityEngine;
2. using System.Collections;
3. 
4. public class PlayerHealth : MonoBehaviour {
5. 	
6. 	//القيمة الأولية لصحة اللاعب
7. 	public int initialHealth = 100;
8. 	
9. 	//الحد الأقصى للصحة
10. 	public int maxHealth = 100;
11. 	
12. 	//القيمة الحالية لصحة اللاعب
13. 	int health;
14. 	
15. 	//مؤشر داخلي للاستدلال موت اللاعب
16. 	bool dead = false;
17. 	
18. 	void Start () {
19. 		//تأكد من أ،ن القيمة الأولية للصحة صحيحة
20. 		health = Mathf.Min(initialHealth, maxHealth);
21. 		//لا يمكن أن يبدأ اللاعب اللعبة ميتا
22. 		if(health < 0){
23. 			health = 1;
24. 		}
25. 	}
26. 	
27. 	void Update () {
28. 		if(!dead){
29. 			if(health <= 0){
30. 				//يموت اللاعب في هذه الحالة
31. 				dead = true;
32. 				
33. 				//قم بإخبار البريمجات الأخرى بموت اللاعب
34. 				//مع تزويد القيمة النهائية لصحته
35. 				SendMessage("OnPlayerDeath", 
36. 					health, 
37. 					SendMessageOptions.DontRequireReceiver);
38. 			}
39. 		}
40. 	}
41. 	
42. 	//قم بإنقاص الصحة وإبلاغ البريمجات الأخرى بذلك
43. 	public void DecreaseHealth(int amount){
44. 		//لا حاجة لإنقاص الصحة إذا كان اللاعب ميتا أساسا
45. 		if(IsDead()) return;
46. 		
47. 		health -= amount;
48. 		SendMessage("OnHealthDecrement", 
49. 					health,
50. 					SendMessageOptions.DontRequireReceiver);
51. 	}
52. 	
53. 	//قم بزيادة صحة اللاعب وإعلام البريمجات الأخرى بذلك
54. 	public void IncreaseHealth(int amount){
55. 		//لا يمكن زيادة الصحة للاعب ميت
56. 		if(IsDead()) return;
57. 		
58. 		//قم بزيادة الصحة في حال كانت أقل من الحد الأقصى
59. 		if(health < maxHealth){
60. 			//لا تسمح للصحة بتجاوز الحد الأقصى
61. 			health = Mathf.Min(maxHealth, health + amount);
62. 			SendMessage("OnHealthIncrement", 
63. 					health,
64. 					SendMessageOptions.DontRequireReceiver);
65. 		}
66. 	}
67. 	
68. 	//هل اللاعب ميت أم لا؟
69. 	public bool IsDead(){
70. 		return dead;
71. 	}
72. 	
73. 	public int GetCurrentHealth(){
74. 		return health;
75. 	}
76. }

السرد 74: بريمج صحة اللاعب

عند إضافة البريمج لكائن اللاعب يمكننا تحديد القيمة الأولية للصحة عبر المتغير initialHealth والتي يقوم البريمج بالتأكد من أنها أكبر من الحد الأدنى وهو صفر وأقل من أو تساوي الحد الأقصى maxHealth. وهذا يمنع اللاعب من بدء اللعبة بمقدار صحة أكبر من الحد الأقصى كما يمنع أن يبدأ اللعبة ميتا. بطبيعة الحال فإن وصول قيمة صحة اللاعب إلى صفر أو أقل يعني موت اللاعب، آخذين بعين الاعتبار أن القيمة الفعلية لصحة اللاعب هي قيمة المتغير health والتي لا يمكن تعديلها مباشرة وإنما عبر الدّالتين ()InreaseHealth والتي تزيد من صحة اللاعب والدّالة ()DecreaseHealth التي تنقص من صحة اللاعب. استخدام هاتين الدّالتين ضروري من أجل التأكد من أنّ قيمة الصحة صالحة دائما أي ضمن الحدود وإرسال الرسائل المناسبة عند حدوث أي تغير حتى يتم إعلام البريمجات الأخرى التي تهمها قيمة صحة اللاعب بالتغييرات أولا بأول. إضافة لذلك فإنّ استخدام الدّالة
()DecreaseHealth ضروري لإعلام البريمجات الأخرى بموت اللاعب حال حدوثه وذلك عبر الرسالة OnPlayerDeath.

إذن فإن البريمج PlayerHealth يعطينا القدرة على إدارة صحة اللاعب ومعرفة موته حال حدث ذلك، كما أنّه المكان الوحيد الذي يمكن من خلاله تغيير صحة اللاعب زيادة ونقصانا. على الرغم من ذلك فإنّه لا يحدد بشكل واضح ما الذي يمكن أن يسبب نقصا أو زيادة في صحة اللاعب، ولهذا السبب سنحتاج لبريمج آخر يمكنه أن يستجيب للمؤثرات الخارجية التي تؤثر على صحة اللاعب وتقوم بإنقاص مقدارها بقيمة محددة. هذا البريمج هو PainfulDamageTaker وهو موضح في السرد 75.

1. using UnityEngine;
2. using System.Collections;
3. 
4. [RequireComponent(typeof(PlayerHealth))]
5. public class PainfulDamageTaker : MonoBehaviour {
6. 
7. 	//متغير للوصول إلى بريمج صحة اللاعب
8. 	PlayerHealth playerHealth;
9. 	
10. 	void Start () {
11. 		playerHealth = GetComponent<PlayerHealth>();
12. 	}
13. 	
14. 	void Update () {
15. 	
16. 	}
17. 	
18. 	//استقبال رسالة اصطدام الطلقة وإنقاص الصحة بناء
19. 	//على قيمة الضرر المرفقة مع الرسالة
20. 	void OnPainfulHit(int amount){
21. 		playerHealth.DecreaseHealth(amount);
22. 	}
23. }

السرد 75: البريمج الخاص باستقبال الطلقات وإنقاص صحة اللاعب بناء عليها

لاحظ أن كل ما احتجنا لفعله في هذا البريمج هو استقبال الرسالة OnPainfulHit إضافة إلى قيمة إنقاص الصحة المرفقة معها. تذكر أنّ هذه الرسالة ترسلها الطلقة وتحديدا البريمج PainfulProjectile عند اصطدامها بجسم ما. يستخدم هذا البريمج ذات القيمة المرفقة مع الرسالة OnPainfulHit عندما يستدعي الدّالة ()DecreaseHealth من البريمج PlayerHealth، وهذا الأخير سبق وأعددناه ليقوم بكل الخطوات المطلوبة فيما يخص صحة اللاعب وتغير قيمتها. يمكنك الآن إضافة المكعب الذي يمثل اللاعب للمشهد وتحريكه محاولا تفادي الطلقات. سيبدو الشكل النهائي كما في الشكل 85.

الشكل 85: لقطة من اللعبة عند تشغيلها وتظهر المكعب الذي يتحكم به اللاعب إضافة للطلقات

الشكل 85: لقطة من اللعبة عند تشغيلها وتظهر المكعب الذي يتحكم به اللاعب إضافة للطلقات

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

1. using UnityEngine;
2. using System.Collections;
3. 
4. [RequireComponent(typeof(PlayerHealth))]
5. public class HealthColorChanger : MonoBehaviour {
6. 	
7. 	//اللون الذي يمثل الصحة الكاملة
8. 	public Color fullHealth = Color.green;
9. 	
10. 	//اللون الذي يمثل الموت
11. 	public Color zeroHealth = Color.red;
12. 	
13. 	//متغير للوصول إلى بريمج صحة اللاعب
14. 	PlayerHealth playerHealth;
15. 	
16. 	void Start () {
17. 		playerHealth = GetComponent<PlayerHealth>();
18. 		UpdateColor(playerHealth.GetCurrentHealth());
19. 	}
20. 	
21. 	void Update () {
22. 	
23. 	}
24. 	
25. 	void OnHealthIncrement(int amount){
26. 		UpdateColor(amount);
27. 	}
28. 	
29. 	void OnHealthDecrement(int amount){
30. 		UpdateColor(amount);
31. 	}
32. 	
33. 	//قم بتحديث اللون على مكوّن التصيير اعتمادا على مقدار
34. 	//القيمة الجديدة لصحة اللاعب
35. 	void UpdateColor(int newHealth){
36. 		//قم بتحويل مقدار الصحة من عدد صحيح إلى عدد كسري بحيث
37. 		//تكون قيمته بين 1 وتعني الصحة الكاملة وصفر وتعني موت اللاعب
38. 		float val = (float)newHealth / 
39. 			      (float) playerHealth.maxHealth;
40. 		
41. 		//قم بتطبيق اللون الجديد
42. 		renderer.material.color = 
43. 			Color.Lerp(zeroHealth, fullHealth, val);
44. 	}
45. }

السرد 76: بريمج يقوم باستيفاء لون كائن اللاعب بين قيمتين مختلفتين بناء على مقدار صحته

ما يقوم به هذا البريمج هو استقبال الرسالتين OnHealthIncrement و OnHealthDecrement التين يقوم PlayerHealth بإرسالهما عند تغير قيمة الصحة، ومن ثم يقوم بتحويل القيمة إلى نسبة مئوية بين صفر و 1. بعد ذلك يقوم باستدعاء الدالة ()Color.Lerp من أجل تغيير لون الكائن الحالي إلى القيمة الصحيحة بين لوني القيمة العظمى والقيمة الدنيا. إذا قمت بتجربة اللعبة بعد إضافة هذا البريمج للمكعب ستلاحظ أنه يبدأ أخضر اللون ومن ثم يميل للاحمرار مع تلقي الضربات ونقصان الصحة.

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

1. using UnityEngine;
2. using System.Collections;
3. 
4. public class LivesManager : MonoBehaviour {
5. 	
6. 	//عدد الفرص عند بداية اللعبة
7. 	public int startLives = 3;
8. 	
9. 	//المتغير الداخلي لإحصاء عدد الفرص
10. 	int lives;
11. 	
12. 	void Start () {
13. 		//يلزم على الأقل فرصة واحدة عند البداية
14. 		if(startLives > 0){
15. 			lives = startLives;
16. 		} else {
17. 			lives = 1;
18. 		}
19. 	}
20. 	
21. 	void Update () {
22. 	}
23. 	
24. 	public void GiveLife(){
25. 		lives++;
26. 		SendMessage("OnLifeGained",
27. 					lives, //العدد الجديد للفرص
28. 					SendMessageOptions.DontRequireReceiver);
29. 	}
30. 	
31. 	public void TakeLife(){
32. 		lives--;
33. 		if(lives == 0){
34. 			//تم فقدان الفرصة الأخيرة
35. 			//يجب إعلام البريمجات الأخرى ليقوم أحدها بالتعامل مع هذا الأمر
36. 			SendMessage("OnAllLivesLost", 
37. 				SendMessageOptions.DontRequireReceiver);
38. 		} else {
39. 			//تم فقدان فرصة لكن ما زال هناك غيرها
40. 			//قم بإرسال رسالة ليتم التعامل مع هذا الأمر
41. 			SendMessage("OnLifeLost", 
42. 				lives, //عدد الفرص المتبقية
43. 				SendMessageOptions.DontRequireReceiver);
44. 		}
45. 	}
46. }

السرد 77: البريمج الخاص بالتحكم بعدد الفرص المتبقية للاعب

يقوم هذا البريمج بالتعامل مع عدد الفرص بطريقة مشابهة لما يقوم به البريمج PlayerHealth مع صحة اللاعب، حيث لدينا متغير يحدد العدد الأولي للفرص ومن ثم فإنّ التحكم بعددها زيادة أو نقصانا يتم فقط عبر الدّالتين ()GiveLife و ()TakeLife وهذه الأخيرة تتميز بأنها يمكن أن ترسل رسالتين وهما OnLifeLost والتي تدل على فقدان فرصة مع وجود غيرها لدى اللاعب و OnAllLivesLost والتي تدل على فقدان جميع الفرص. ما علينا فعله الآن هو أن نقوم باستدعاء الدّالة ()TakeLife في كل مرّة تصل فيها صحة اللاعب لقيمة أقل من أو مساوية للصفر. أي أننا نحتاج لاستقبال الرسالة OnPlayerDeath وإرسال الرسالة TakeLife إلى البريمج LivesManager. من سيتولى هذه المهمة هو البريمج PlayerDeathReporter والموضح في السرد 78.

1. using UnityEngine;
2. using System.Collections;
3. 
4. public class PlayerDeathReporter : MonoBehaviour {
5. 	
6. 	//LivesManager متغير للوصول إلى الكائن المحتوي على البريمج
7. 	LivesManager manager;
8. 	
9. 	void Start () {
10. 		manager = FindObjectOfType<LivesManager>();
11. 	}
12. 	
13. 	void Update () {
14. 	
15. 	}
16. 	
17. 	//قم بإنقاص فرص اللاعب في كل مرة يموت فيها
18. 	void OnPlayerDeath(int deathHealth){
19. 		if(manager != null){
20. 			manager.TakeLife();
21. 		}
22. 	}
23. }

السرد 78: البريمج الخاص بإرسال الرسالة TakeLife في كل مرة يموت فيها اللاعب

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

1. using UnityEngine;
2. using System.Collections;
3. 
4. public class PlayerSpawn : MonoBehaviour {
5. 	
6. 	//القالب الخاص بكائن اللاعب
7. 	public GameObject playerPrefab;
8. 	
9. 	//الثواني التي يجب انتظارها ما بين تدمير الكائن وإعادة إنتاجه في الفرصة التالية
10. 	public int spawnDelay = 3;
11. 	
12. 	//متغير لتخزين كائن اللاعب الحالي
13. 	GameObject currentInstance;
14. 	
15. 	void Start () {
16. 		SpawnPlayer();
17. 	}
18. 	
19. 	void Update () {
20. 	
21. 	}
22. 	
23. 	//تمت خسارة فرصة
24. 	void OnLifeLost(int remainingLives){
25. 		//قم بتدمير كائن اللاعب واستدعاء إعادة الإنشاء بعد التأخير المطلوب
26. 		Destroy(currentInstance);
27. 		Invoke("SpawnPlayer", spawnDelay);
28. 	}
29. 	
30. 	//انتهت اللعبة: قم بتدمير كائن اللاعب ولا تنشئ غيره
31. 	void OnAllLivesLost(){
32. 		Destroy(currentInstance);
33. 	}
34. 	
35. 	void SpawnPlayer(){
36. 		currentInstance = 
37. 			(GameObject) Instantiate(playerPrefab);
38. 	}
39. }

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

ما يحتاجه هذا البريمج ليعمل هو قالب لإنشاء كائن اللاعب من خلاله بالإضافة إلى عدد من الثواني التي يجب انتظارها قبل إعادة إنشاء كائن اللاعب مرة أخرى. إنشاء اللاعب يتم من خلال استدعاء الدّالة ()SpawnPlayer والتي تقوم بعمل نسخة من قالب اللاعب وتحتفظ بمرجع لها عبر قيمة المتغير currentInstance. أهمية هذا المرجع تكمن في الحاجة لتدمير كائن اللاعب في وقت لاحق حين يموت، وبالتالي سنحتاج لاستقبال رسائل موت اللاعب وفقدانه للفرص من البريمج LivesManager والذي يفترض وجوده على نفس الكائن. الرسالتان المهمتان بالنسبة لهذا البريمج هما OnLifeLost والتي يقوم بناء عليها بتدمير كائن اللاعب الحالي وإنشاء واحد آخر جديد عبر استدعاء ()SpawnPlayer وذلك مع تأخير يعادل spawnDelay من الثواني. الرسالة الأخرى المهمة هي OnAllLivesLost والتي يقوم بناء عليها بتدمير كائن اللاعب دون إنشاء واحد جديد، أي دون استدعاء ()SpawnPlayer.

كما سبق واحتجنا لتمثيل مرئي لمقدار صحة اللاعب وذلك عن طريق لون المكعب، سنحتاج أيضا لتمثيل مرئي لعدد الفرص المتبقية. الطريقة الأبسط هي إظهار عددها كنص على الشاشة، وهذا يقودنا لاستخدام كائن جديد هو 3D Text والذي يمكننا وضعه في أي مكان في المشهد ويظهر لنا نصا مكتوبا بحروف ثلاثية الأبعاد. ما سيلزمنا في حالتنا هذه وهو وضع هذا الكائن أمام الكاميرا مباشرة.

لإضافة كائن نص ثلاثي الأبعاد 3D Text إلى المشهد اذهب إلى Game Object > Create Other > 3D Text. يمكنك بعدها تحريك النص وتحجيمه كأي كائن آخر في المشهد حيث ينبغي وضعه أمام الكاميرا مباشرة ليكون مرئيا للاعب

الشكل 86 يظهر أهم متغيرات هذا الكائن كما تظهر في شاشة الخصائص.

الشكل 86: المتغيرات الخاصة بكائن النص ثلاثي الأبعاد كما تظهر في شاشة الخصائص

الشكل 86: المتغيرات الخاصة بكائن النص ثلاثي الأبعاد كما تظهر في شاشة الخصائص

معظم الخصائص تبدو واضحة ومعتادة لمن يتعامل مع نصوص الحاسب في برامج أخرى. سيكون تركيزنا منصبا على الخانة Text والتي سنحتاج لتغيير قيمتها عن طريق بريمج ما بحيث يعرض هذا الكائن عدد الفرص المتبقية للاعب. لنقم أولا بوضع الكائن في مكان مناسب وليكن أعلى الشاشة مثلا، ومن الجيد أن نختار له قيمة أولية كالرقم صفر مثلا. علينا بعدها أن نكتب بريمجا يقرأ عدد الفرص المتبقية للاعب ويعرضها كنص على هذا الكائن. هذا البريمج هو LivesCounterHandler الموضح في السرد 80.

1. using UnityEngine;
2. using System.Collections;
3. 
4. [RequireComponent(typeof(LivesManager))]
5. public class LivesCounterHandler : MonoBehaviour {
6. 	
7. 	//مرجع لكائن النص ثلاثي الأبعاد
8. 	public TextMesh display;
9. 	
10. 	//مرجع لبريمج إدارة الفرص
11. 	LivesManager lManager;
12. 	
13. 	void Start () {
14. 		lManager = GetComponent<LivesManager>();
15. 		//مبدئيا سنقوم بإظهار عدد الفرص الأولي الذي تم تحديده
16. 		display.text = lManager.startLives.ToString();
17. 	}
18. 	
19. 	void Update () {
20. 	
21. 	}
22. 	
23. 	void OnLifeGained(int newLives){
24. 		//ازداد عدد الفرص، بالتالي علينا تحديث النص
25. 		display.text = newLives.ToString();
26. 	}
27. 	
28. 	void OnLifeLost(int remainingLives){
29. 		//قل عدد الفرص، بالتالي علينا تحديث النص
30. 		display.text = remainingLives.ToString();
31. 	}
32. 	
33. 	void OnAllLivesLost(){
34. 		//خسر اللاعب كل الفرص
35. 		//"لذلك سنكتب عبارة "انتهت اللعبة
36. 		display.text = "Game Over";
37. 	}
38. }

السرد 80: البريمج الخاص بالتحكم بعرض الفرص المتبقية للاعب على شكل نص

لاحظ أننا نصل لكائن النص ثلاثي الأبعاد عن طريق متغير من نوع TextMesh، لاحظ أيضا أن هذا البريمج يحتاج لوجود البريمج LivesManager وذلك لأنه سيكون المصدر الرئيسي للمعلومات التي سيقوم بعرضها على شكل نص ثلاثي الأبعاد. بعد إضافة هذا البريمج لكائن الكاميرا (وهو الكائن الذي أفترض أنك أضفت البريمج LivesManager إليه) علينا أن نقوم بسحب كائن النص ثلاثي الأبعاد من الهرمية إلى داخل خانة المتغير display في شاشة خصائص هذا البريمج. لاحظ أنه يمكننا تغيير النص المعروض عن طريق المتغير display.text وبالتالي نقوم في البداية داخل الدّالة ()Start بعرض قيمة startLives. بما أن المتغير display.text هو عبارة عن متغير نصّي string بينما startLives هو عدد صحيح int، ينبغي علينا استخراج قيمة هذا الرقم على شكل نص حتى نتمكن من إسنادها للمتغير النصي. هذه العملة تتم عبر استدعاء الدّالة ()ToString كما في السطر 16. بعد عرض القيمة الأولية كل ما يتبقى لنا هو أن نتابع أي تغيير على عدد الفرص عن طريق استقبال الرسالتين OnLifeGaind و OnLifeLost وذلك لتغيير النص المعروض. أخيرا علينا أن ننتبه للرسالة OnAllLivesLost وذلك حتى نعرض نصا يخبر اللاعب بنهاية اللعبة وهو Game Over.

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

1. using UnityEngine;
2. using System.Collections;
3. 
4. [RequireComponent(typeof(LivesManager))]
5. public class ScoreCounter : MonoBehaviour {
6. 	
7. 	//متغير اختياري لعرض عدد النقاط على شكل نص
8. 	public TextMesh display;
9. 	
10. 	LivesManager lManager;
11. 	
12. 	//العداد الداخلي للنقاط
13. 	int score = 0;
14. 	
15. 	void Start () {
16. 		lManager = GetComponent<LivesManager>();
17. 		//قم بزيادة النقاط بمقدار نقطة كل ثانية
18. 		InvokeRepeating("IncrementScore", 1, 1);
19. 	}
20. 	
21. 	void Update () {
22. 	
23. 	}
24. 	
25. 	void IncrementScore(){
26. 		score++;
27. 		if(display != null){
28. 			display.text = score.ToString();
29. 		}
30. 	}
31. 	
32. 	void OnAllLivesLost(){
33. 		//انتهت اللعبة، إذن علينا التوقف عن إحصاء النقاط
34. 		CancelInvoke("IncrementScore");
35. 	}
36. }

السرد 81: بريمج يعمل على زيادة نقاط اللاعب كل ثانية وعرضها على كائن نص ثلاثي الأبعاد

يعتمد هذا البريمج على استدعاء الدّالة ()IncrementScore مرة واحدة كل ثانية وذلك عن طريق استدعاء ()InvokeRepeating عند بداية اللعبة. بالتالي فإنه عند كل ثانية تمر من وقت اللعب تتم زيادة عدد النقاط إضافة إلى تحديث النص الذي يعرضها على الشاشة. عند استقبال الرسالة OnAllLivesLost يجب التوقف عن إحصاء النقاط وذلك لأن اللعبة انتهت. إيقاف الاستدعاء المتكرر للدّالة ()IncrementScore يتم عبر استدعاء ()CancelInvoke وإعطائها اسم الدالة التي نرغب بإيقاف استدعائها كما في السطر 34. يمكنك مشاهدة النتيجة النهائية للعبة التي أنشأناها في هذا الفصل في الشكل 87، كما يمكنك تجربتها في المشهد scene21 في المشروع المرفق.

الشكل 87: المشهد النهائي للعبة حيث يظهر عدد الفرص في النص أعلى الشاشة والنقاط في النص أسفل الشاشة

الشكل 87: المشهد النهائي للعبة حيث يظهر عدد الفرص في النص أعلى الشاشة والنقاط في النص أسفل الشاشة

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

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