الفصل الخامس: تطبيق نظام إدخال ألعاب منظور الشخص الثالث

العب

تضم ألعاب منظور الشخص الثالث مجموعة من أشهر سلاسل الألعاب مثل Tomb Rider و Hitman و Splinter Cell و Max Payne وغيرها الكثير. يعتمد نظام التحكم في هذه الألعاب على رؤية شخصية اللاعب من الخلف بمسافة وارتفاع محددين يمكن أن يتغيرا خلال اللعب حسب الحالة، مثل تقريب الكاميرا إلى السلاح الذي تحمله شخصية اللاعب عند التصويب وإبعادها عند الحركة بسرعة عالية.

سنتعلم في هذا الفصل كيفية بناء هذا النوع من نظام التحكم، مستفيدين من طرق إدخال لوحة المفاتيح والفأرة، إضافة إلى العلاقات بين الكائنات ووظائف الدوران مثل ()transform.RotateAround وكلها سبق وتعرفنا عليها في فصول سابقة. هناك تقنيات متعددة تتعلق بالعلاقة بين دوران الكاميرا وحركة الشخصية داخل اللعبة. وهذه التقنيات تختلف من لعبة لأخرى ومن نوع لآخر. على سبيل المثال، في لعبة مثل World of Warcraft يكون الاعتماد بشكل أساسي على مؤشر الفأرة للتعامل مع البيئة المحيطة بالتالي فإن دوران الكاميرا يعتمد على الحركة بالأسهم، بالإضافة لذلك، يمكن استخدام زر الفأرة الأيمن لإدارة الكاميرا. أما في ألعاب أخرى مثل Hitman فإن مؤشر الفأرة يكون أداة للتصويب، لذا فإن نظر الشخصية يتوجه دائما لموقع مؤشر الفأرة وتتم عملية التصويب في ذلك الاتجاه، في هذه الحالة تدور الكاميرا تلقائيا لتتبع اتجاه النظر.

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

الشكل 23: شخصية بسيطة لاستخدامها في نظام تحكم منظور الشخص الثالث

الشكل 23: شخصية بسيطة لاستخدامها في نظام تحكم منظور الشخص الثالث

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

1. using UnityEngine;
2. using System.Collections;
3. 
4. public class ThirdPersonControl : MonoBehaviour {
5.  
6.  //السرعة العمودية للاعب عند بداية القفز
7.  public float jumpSpeed = 1;
8.  
9.  //سرعة السقوط
10.     public float gravity = 3;
11.     
12.     //سرعة الحركة الأفقية على الأرض
13.     public float movementSpeed = 5;
14.     
15.     //تخزين سرعة اللاعب من أجل تنفيذ الحركة
16.     private Vector3 speed;
17.     
18.     void Start () {
19.     
20.     }
21.     
22.     void Update () {
23.         
24.         //تحديث الحركة
25.         if(Input.GetKey(KeyCode.A)){
26.             //لليسار
27.             speed.x = -movementSpeed * Time.deltaTime;
28.         } else if(Input.GetKey(KeyCode.D)){
29.             //لليمين
30.             speed.x = movementSpeed * Time.deltaTime;
31.         } else {
32.             speed.x = 0;
33.         }
34.         
35.         if(Input.GetKey(KeyCode.W)){
36.             //للأمام
37.             speed.z = movementSpeed * Time.deltaTime;
38.         } else if(Input.GetKey(KeyCode.S)){
39.             //للخلف
40.             speed.z = -movementSpeed * Time.deltaTime;
41.         } else {
42.             speed.z = 0;
43.         }
44.         
45.         //قراءة مدخل القفز
46.         if(Input.GetKeyDown(KeyCode.Space)){
47.             //تنفيذ القفز فقط في حال كون الشخصية تقف على الأرض
48.             if(transform.position.y == 2.0f){
49.                 speed.y = jumpSpeed;
50.             }
51.         }
52.         
53.         //تحريك الشخصية
54.         transform.Translate(speed);
55.         
56.         //تطبيق الجاذبية  في حالة القفز
57.         if(transform.position.y > 2.0f){
58.             speed.y = speed.y - gravity * Time.deltaTime;
59.         } else {
60.             speed.y = 0;
61.             Vector3 newPosition = transform.position;
62.             newPosition.y = 2.0f;
63.             transform.position = newPosition;
64.         }
65.     
66.     }
67. }

السرد 10: قراءة مدخلات الحركة لنظام تحكم منظور الشخص الثالث

جميع أن هذه الوظائف سبق وتطرقنا لها في الفصلين السابقين بالشرح المفصل تحديدا في السرد 6 والسرد 9. لاحظ أن طريقة التحكم مشابهة تماما لطريقة تحكم نظام منظور الشخص الأول، باستثناء ما يتعلق بقراءة مدخلات الفأرة وتحريك الكاميرا، وهي التي سنجدها في البريمج الثاني ThirdPersonCamera والذي سنضيفه على كائن الكاميرا. هذا البريمج موضح في السرد 11.

1. using UnityEngine;
2. using System.Collections;
3. 
4. public class ThirdPersonCamera : MonoBehaviour {
5.  
6.  //سرعة الدوران الأفقي لشخصية اللاعب
7.  public float horizontalSpeed = 0.4f;
8.  
9.  //سرعة الحركة العمودية للكاميرا
10.     public float verticalSpeed = 5;
11.     
12.     //القيمة القصوى العليا والدنيا المسموح بها
13.     //لارتفاع الكاميرا
14.     public float minCameraHeight = 0.25f;
15.     public float maxCameraHeight = 15;
16.     
17.     //متغيرات التحكم بالتقريب
18.     public float maxZoom = -10;
19.     public float minZoom = -30;
20.     public float zoomSpeed = 3;
21.     
22.     //هل يجب أن تتحرك الكاميرا للأسفل 
23.     //عند تحريك الفأرة للأعلى؟ أي عكس الحركة العمودية
24.     public bool invertYMovement = true;
25.     
26.     //موقع الفأرة خلال الإطار السابق
27.     //ضروري لحساب كمية الإزاحة
28.     private Vector3 lastMousePosition;
29.     
30.     //متغير لتخزين كائن شخصية اللاعب
31.     Transform playerBody;
32.     
33.     void Start () {
34.         lastMousePosition = Input.mousePosition;
35.         //من الضروري أن يكون كائن اللاعب أبا لكائن الكاميرا
36.         playerBody = transform.parent;
37.     }
38.     
39.     void Update () {
40.         Vector3 mouseDelta = Input.mousePosition - lastMousePosition;
41.         
42.         //الإزاحة  الأفقية للفأرة يتم تحويلها إلى
43.         //دوران أفقي لكائن اللاعب
44.         playerBody.RotateAround(
45.                 Vector3.up, //محور الدوران
46.                 mouseDelta.x * 
47.                 horizontalSpeed * 
48.                 Time.deltaTime);//زاوية الدوران
49.         
50.         //الإزاحة العمودية للفأرة تتم ترجمتها إلى
51.         //إزاحة عمودية للكاميرا
52.         float yDelta = 0;
53.         if(invertYMovement){
54.              //عكس اتجاه الحركة العمودية: الفأرة للأعلى = الكاميرا للأسفل
55.              yDelta = -mouseDelta.y * verticalSpeed * Time.deltaTime;
56.         } else {
57.              //استخدام الاتجاه الأصلي للحركة العمودية: الفأرة للأعلى = الكاميرا للأعلى
58.             yDelta = mouseDelta.y * verticalSpeed * Time.deltaTime;
59.         }
60.         
61.         //تنفيذ الحركة العمودية للكاميرا
62.         transform.Translate(0, yDelta, 0, Space.World);
63.         
64.         //فحص إذا ما تجاوزت الكاميرا حدود الحركة المسموح بها عموديا
65.         Vector3 newCameraPos = transform.localPosition;
66.         if(newCameraPos.y > maxCameraHeight){
67.             newCameraPos.y = maxCameraHeight;
68.         } else if(newCameraPos.y < minCameraHeight){
69.             newCameraPos.y = minCameraHeight;
70.         }
71.         
72.         //تغيير موقع الكاميرا للموقع الجديد بعد تصحيح الموقع العمودي
73.         transform.localPosition = newCameraPos;
74.         
75.         //إبقاء الكاميرا تنظر إلى كائن شخصية اللاعب
76.         transform.LookAt(playerBody);
77.         
78.         //تخزين موقع الفأرة الحالي استعدادا للإطار المقبل
79.         lastMousePosition = Input.mousePosition;
80.         
81.         //تنفيذ التقريب 
82.         float wheel = Input.GetAxis("Mouse ScrollWheel");
83.         //تقريب الكاميرا
84.         if(wheel > 0 && transform.localPosition.z < maxZoom){
85.             //Zتحريك الكاميرا للأمام على محورها المحلي 
86.             //zoomSpeed باستخدام متغير السرعة
87.             transform.Translate(0, 0, zoomSpeed);
88.         } else //إبعاد الكاميرا
89.         if(wheel < 0 && transform.localPosition.z > minZoom){
90.             //z تحريك الكاميرا للخلف على محورها المحلي
91.             //zoomSpeed باستخدام القيمة السالبة للمتغير
92.             transform.Translate(0, 0, -zoomSpeed);
93.         }
94.     }
95. }

السرد 11: آلية تحريك الكاميرا في نظام تحكم منظور الشخص الثالث

لاحظ أن هذا البريمج وبالرغم من كونه مضافا على كائن الكاميرا، إلا أن له تأثيرا على كل من كائن الكاميرا وكائن شخصية اللاعب كما سنرى. في البداية قمنا بتعريف بعض المتغيرات التي ستساعدنا على التحكم بحركة الكاميرا، وذلك في الأسطر 7 إلى 24. تساعدنا هذه المتغيرات على التحكم بسرعة الدوران الأفقي للاعب والحركة العمودية للكاميرا نحو الأعلى والأسفل، بالإضافة إلى حدود الحركة العمودية ومدى وسرعة تقريب أو إبعاد الكاميرا عن كائن اللاعب. لاحظ في السطرين 18 و19 أن القيم العليا والدنيا لتقريب وإبعاد الكاميرا هي قيم سالبة؛ ذلك لأن الكاميرا سواء كانت قريبة أو بعيدة لا بد وأن تكون خلف اللاعب أي نحو الخارج، وهو الاتجاه السالب للمحور z حسب قاعدة اليد اليسرى التي نتبعها.

في الدّالّة ()Start وتحديدا في السطر 36 قمنا باستخدام المتغير playerBody لتخزين مكوّن Transform الخاص بكائن شخصية اللاعب، وتمكنّا من الوصول إليه عبر transform.parent والتي تعطينا الأب الحالي للكائن. وبما أننا قمنا بإضافة البريمج على كائن الكاميرا الذي هو بالأصل ابن لكائن شخصية اللاعب، فإن transform.parent ستوصلنا إلى كائن اللاعب.

في الأسطر من 44 إلى 45 نقوم بتحويل الإزاحة الأفقية للفأرة إلى دوران لكائن اللاعب playerBody على المحور العمودي y، بطريقة شبيهة بما قمنا به عند بناء نظام تحكم منظور الشخص الأول. في الأسطر 52 إلى 62 قمنا بتعريف المتغير yDelta والذي سنستخدمه لحساب الحركة العمودية للفأرة بناء على قيمة المتغير invertYMovement، والذي تحدد قيمته ما إذا كانت إزاحة الكاميرا العمودية ستكون في نفس اتجاه إزاحة الفأرة أم في الاتجاه المعاكس لها، وبعد ذلك نقوم بتنفيذ الحركة العمودية للكاميرا على محاور فضاء المشهد، وذلك في السطر 62.

بعد تحريك الكاميرا عموديا نقوم في الأسطر 65 إلى 70 بالتحقق من كون الموقع العمودي للكاميرا داخل حدود القيم القصوى العليا والدنيا المسموح بها، والتي قمنا بتحديدها عن طريق المتغيرين maxCameraHeight و minCameraHeight. لاحظ هنا أننا فحصنا قيمة transform.localPosition بدلا من transform.position لتحديد الموقع. الهدف من ذلك هو جعل حدود الحركة هذه نسبية إلى موقع اللاعب، وليس إلى فضاء المشهد أو سطح الأرض؛ وذلك حتى تكون حساباتنا صحيحة في كل الأحوال حتى لو تغير الموقع العمودي للاعب كما في حالة القفز مثلا. نقوم بتخزين هذه القيمة في المتغير newCameraPos ومن ثم نقوم بفحص القيمة العليا والدنيا للعضو y حتى نتأكد من بقاء قيمته ضمن الحدود المطلوبة ونعدلها إذا لزم ذلك، وأخيرا نقوم بتخزين القيمة الجديدة التي تم تعديلها في transform.localPosition كما في السطر 73.

بعد الانتهاء من تحريك الكاميرا يبقى أن نتأكد من أنها تنظر دائما إلى كائن اللاعب، لذا نقوم في السطر 76 باستدعاء ()transform.LookAt ونمرر لها المتغير playerBody والذي يمثل كائن اللاعب كما ذكرنا. أخيرا في الأسطر 81 إلى 93 نقوم بقراءة مدخل عجلة الفأرة، ونترجم حركة العجلة للأعلى إلى تقريب للكاميرا نحو كائن شخصية اللاعب، وحركتها للأسفل إلى إبعاد للكاميرا. حركة الكاميرا نحو الأمام أو الخلف محكومة بالسرعة zoomSpeed ، كما أنها مشروطة بعدم تجاوزها الحدود المعرفة في maxZoom و minZoom وهي تمثل الموقعين الأقرب والأبعد المسموح بهما. مرة أخرى قمنا هنا باستخدام transform.localPosition؛ وذلك لأن القرب أو البعد هو نسبي لكائن شخصية اللاعب وليس لنقطة الأصل في المشهد.

يمكنك بناء مشهد بسيط كما في الشكل 24 لتقوم بتجربة نظام التحكم الذي قمت ببنائه، كما يمكنك الاطلاع على المشهد scene6 في المشروع المرفق لمشاهدة النتيجة النهائية.

الشكل 24: المشهد الخاص بتجربة نظام تحكم منظور الشخص الثالث

الشكل 24: المشهد الخاص بتجربة نظام تحكم منظور الشخص الثالث

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

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