لقد حان الوقت لكي نتحدث عن كشف التصادمات (Detection Collision). إنه يؤثر على الأجزاء الظاهرة في لعبتنا، مثل الجدران التي لا يمكننا المرور عبرها ومثل الأرضيات التي لا يمكننا السقوط من خلالها. كما أنه يؤثر على الأجزاء المخفية في لعبتنا مثل قذائف الأسلحة ونقاط التفتيش.
لن نصل إلى مفهوم الجاذبية بعد، ولن ننظر إلى صحة اللاعب أو استرداد حياته (الدم والأرواح). تعدّ الأرضيات والقذائف ونقاط التفتيش مثيرة للاهتمام، ولكنها تستحق أقسامًا خاصة بها. سنعمل على إنشاء كائنات غير نافذة (لا يمكن العبور من خلالها). سنتعلم طرقًا لمعرفة ما إذا كان جسمان يشغلان نفس الحيّز المكانيِّ.
لقد قضيت بعض الوقت أبحث في هذا الموضوع. يبدو أن هناك العديد من الطرق لحل مشكلة جسمين يشغلان نفس المكان. بعض هذه الطرائق يسهل شرحها وتنفيذها وهي التي سنلقي نظرة عليها، والطرائق الأخرى ليست سهلةً ولا تزال حديثة العهد رغم ذلك.
إليك ما سنعمل عليه في هذه المرحلة: *إنشاء أول صنف (class) غير متعلقة باللاعب.
- الكشف عن التصادمات المتعلقة بالدائرة.
- الكشف عن التصادمات المتعلقة بالمستطيل.
إنشاء صناديق
اللاعب هو كائنٌ من العديد من الكائنات التي سوف تتواجد على الشاشة في آنٍ واحدٍ. ولعبتنا هذه هي لعبة منصة، لذلك يمكننا أن نتوقع منصة واحدة على الأقل على الشاشة.
لدى المنصات بعض الخصائص المثيرة للاهتمام. فهي تسمح في بعض الأحيان للاعبين بالسقوط من خلالها كما هو الحال عندما تكون واقفًا على منصة وترجع للخلف وتقفز في الوقت ذاته. تأخذ بعض الألعاب هذه السلسلة على أنك تريد السقوط من خلال المنصّة.
تسمح بعض الألعاب للاعبين بالقفز في أسفل المنصات. وهذا يتيح الحركة العمودية دون وجود ثغرات في المنصات العلوية.
وفي بعض الأحيان، تكون المنصات متحرّكة!
تعتبر المنصّات فريدةً جدًا إلى حدّ أننا سنقضي بضعة أقسام في تنفيذ سلوكياتها المختلفة فقط. سوف نركز الآن على كائن مشترك آخر هو الصندوق الشامل (generic box).
فكّر في هذا الصّندوق كمولّد للمنصّة. قد يشترك في بعض وظائفه مع المنصّة، ولكن السبب الرئيسي لوجوده هو الأشياء التي تتصادم معها وبالخصوص الكائنات مثل اللاعب.
قد لا تبدو الصناديق التي سنُنشئها مثل الصناديق الحقيقية. عندما نصل إلى تطبيق الجاذبية، سنحتاج إلى صندوق عريضٍ ورفيعٍ لمنع اللاعب من السقوط خارج محيط اللعبة. وسنحتاج إلى صناديق طويلة ورفيعة لمنع اللاعب من الركض على جوانب أرضية اللعبة. سنصنع منها الجدران. وقد نصنع منها أيضًا صناديق فعلية كبيرة وخشبية تصلح للقفز عليها للوصول إلى أشياء أعلى.
حسنًا، لقد تحدّثت بما يكفي.
class Box { constructor(sprite, x, y, width, height) { this.sprite = sprite; this.x = x; this.y = y; this.width = width; this.height = height; this.sprite.x = this.x; this.sprite.y = this.y; } animate(state) { this.sprite.x = this.x; this.sprite.y = this.y; } } export default Box;
لإنشاء هذا الصّنف، نسخت ولصقت فئة "اللّاعب" وحذفت مجموعة من الأشياء. لقد كان عليّ أن أضيف إليها كذلك خاصيتي العرض والارتفاع. سوف نصل إلى ذلك بعد قليلٍ.
بعد ذلك، نحتاج إلى إضافة صندوقين إلى مسرح اللّعبة:
var player_sprite = new PIXI.Sprite.fromImage( "player.png" ); var player = new Player( player_sprite, window.innerWidth * 2/4, 200, 64, 64 ); var barrel_sprite1 = new PIXI.Sprite.fromImage( "barrel.png" ); var barrel1 = new Box( barrel_sprite1, window.innerWidth * 1/4, 152, 64, 64 ); var barrel_sprite2 = new PIXI.Sprite.fromImage( "barrel.png" ); var barrel2 = new Box( barrel_sprite2, window.innerWidth * 3/4, 248, 64, 64 ); stage.addChild(player_sprite); stage.addChild(barrel_sprite1); stage.addChild(barrel_sprite2); var state = { "renderer": renderer, "stage": stage, "keys": {}, "clicks": {}, "mouse": {}, "objects": [ player, barrel1, barrel2 ] };
هذا غريب، أليس كذلك! لقد أنشأتُ نسختين جديدتين من الصّندوق، وأطلقت عليهما اسم براميل. ذلك لأننا على وشك أن نلقي نظرة على التصادمات المتعلقة بالدائرة.
كشف التصادمات المتعلقة بالدائرة
أريدك أن تنشئ دوائر لهذه الصناديق القليلة الأولى لأننا سندرس أولاً كشف التصادمات المتعلقة بالدوائر رغم أنّ الصندوق يتميّز بالعرض والارتفاع بدلاً من الشّعاع الذي يُميّز الدائرة. ولكن، لن نستخدم هذا النوع من الكشف عن التصادمات في كثير من الأحيان ما لم تكن في منصة اللعبة الكثير من الدوائر.
لنر كيف يعمل هذا النوع من الكشف:
class Player { constructor(sprite, x, y, width, height) { this.sprite = sprite; this.x = x; this.y = y; this.width = width; this.height = height; this.velocityX = 0; this.maximumVelocityX = 12; this.accelerationX = 2; this.frictionX = 0.9; this.sprite.x = this.x; this.sprite.y = this.y; } animate(state) { if (state.keys[37]) { // left this.velocityX = Math.max( this.velocityX - this.accelerationX, this.maximumVelocityX * -1 ); } if (state.keys[39]) { // right this.velocityX = Math.min( this.velocityX + this.accelerationX, this.maximumVelocityX ); } this.velocityX *= this.frictionX; var move = true; state.objects.forEach((object) => { if (object === this) { return; } var deltaX = this.x - object.x; var deltaY = this.y - object.y; var distance = Math.sqrt( deltaX * deltaX + deltaY * deltaY ); if (distance < this.width / 2 + object.width / 2) { if (this.velocityX < 0 && object.x <= this.x) { move = false; } if (this.velocityX > 0 && object.x >= this.x) { move = false; } } }); if (move) { this.x += this.velocityX; } this.sprite.x = this.x; this.sprite.y = this.y; } } export default Player;
أول شيء يتعين علينا القيام به هو تحديد العرض والارتفاع. رغم أننا ندعي أن اللاعبين والصناديق عبارة عن دوائر، فإننا نحتاج فقط إلى نصف العرض كشعاع للدائرة.
بعد ذلك، نتحقق من كل كائن في الطبقة (state). يمكننا تجاهل اللاعب لأننا لسنا بحاجة لمعرفة متى يصطدم شيء ما بنفسه. ولكن سيكون علينا، مع ذلك، أن نتحقق من كلّ الكائنات الأخرى.
تصطدم دائرتان عندما تكون المسافة بين مركزيهما أقلّ من مجموع شعاعيهما معًا. تكون نقطتاهما الأصليتان متقاربتان جدًا بحيث تتداخل خطوطهما.
نفحص سريعًا لمعرفة ما إذا كان الاتجاه الذي يتحرك فيه اللاعب هو حيث يتواجد الصندوق. إذا كان الأمر كذلك، فإننا نمنع اللاعب من التحرك في هذا الاتجاه.
جرّبها، فمن الممتع جدًا أن ترى كيف تعيق الأشكال غير المربّعة بعضها بعضًا. بالطبع يجب أن تكون جميعها دوائر مثالية حتى تعمل هذه الخوارزمية البسيطة.
كشف التصادمات المتعلّقة بالمستطيل
يكون كشف التصادمات بالنسبة للمستطيلات سهلاً مثل الدوائر. امضِ قُدُمًا واستبدل صورة البرميل بصورة صندوق. يمكنك حتى ضبط شيفرة bootstrap لتعكس شكلا مربّعًا للصناديق.
هذه المرة، سنتعامل مع اللاعب باعتباره مستطيلًا. بدلاً من استعمال الشّعاع، سنحتاج إلى التحقق مما إذا كانت هناك فراغات بين المستطيل الذي يمثّل اللاعب وأيّ صندوق. نحن نسمي هذا الكشف عن التصادم في المربع المحيط المحاذي للمحور (axis-aligned bounding box) أو AABB للاختصار.
إذا لم يكن هناك فراغ، وكان اللاعب يريد التحرك في اتجاه الصندوق، فإننا نمنع حدوث ذلك:
var move = true; state.objects.forEach((object) => { if (object === this) { return; } if (this.x < object.x + object.width && this.x + this.width > object.x && this.y < object.y + object.height && this.y + this.height > object.y) { if (this.velocityX < 0 && object.x <= this.x) { move = false; } if (this.velocityX > 0 && object.x >= this.x) { move = false; } } }); if (move) { this.x += this.velocityX; }
هذه بعض الطرق البسيطة للكشف عن التصادمات، ولكن هناك طرق أخرى. هناك طريقة تعتمد على الإسقاط في الرياضيات المتجهية لتحديد التداخل. وهناك طريقة أخرى تفحص كل خطّ في زوجٍ من المضلّعات لمعرفة ما إذا كان هناك تقاطع للخطوط. إنّها طريقة غريبة.
ويمكنك كذلك تجربة مجموعات من الدوائر تتصادم معًا. قد يكون الأمر ممتعًا. سوف أشغّل قليلاً هذه الدائرة الخضراء الصغيرة لتتحرّك نحو هذه الصناديق الحمراء الصغيرة. نلتقي فيما بعد…
ترجمة -وبتصرف- للمقال Collision Detection لصاحبه Christopher Pitt
اقرأ أيضًا
- المقال التالي: الجاذبية
- المقال السابق: جلب المدخلات من اللاعب
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.