Advertisement
  1. Game Development

إنشاء كاسحة ألغام سداسية

Scroll to top
Read Time: 16 min

() translation by (you can also view the original English article)

Final product imageFinal product imageFinal product image
What You'll Be Creating

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

هذا سوف تحصل فقط مع استكشاف ألعاب المجلس سداسية بسيطة ولغز الألعاب وسوف يكون نقطة انطلاق جيدة للتعرف على نهج أكثر تعقيداً مثل أنظمة إحداثي سداسية المحوري أو مكعب.

1. بلاط سداسي وتخطيطات

ي الجيل الحالي من الألعاب العرضية ، لا نرى العديد من الألعاب التي تستخدم نهجًا قائمًا على أساس سداسية. عادةً ما تكون تلك التي نتلقاها هي ألعاب الألغاز أو ألعاب اللوحات أو الألعاب الإستراتيجية. أيضا ، يتم تلبية معظم متطلباتنا من خلال نهج الشبكة المربعة أو النهج متساوي القياس.  وهذا يؤدي إلى السؤال الطبيعي: "لماذا نحتاج إلى نهج سداسي مختلف ومعقد بشكل واضح؟" هيا نكتشف.

مزايا النهج السداسي

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

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

أود أن أقول أن السبب الأخير يجب أن يكون كافيا لإتقان هذا النهج الجديد. إضافة عنصر اللعبة الفريد من نوعه على منطق اللعبة الخاص بك يمكن أن يجعل كل الفرق وتمكنك من جعل لعبة رائعة.

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

تخطيطات 

السداسي هو مضلع ذو ستة جوانب ، ويطلق على السداسي مع جميع الجوانب التي لها نفس الطول اسم السداسي العادي. لأغراض نظرية ، سوف نعتبر أن البلاط السداسي الخاص بنا عبارة عن سداسيات منتظمة ، ولكن يمكن أن تكون مقلوبة أو ممدودة في الواقع العملي.

الشيء المثير للاهتمام هو أن السداسي يمكن وضعه بطريقتين مختلفتين: يمكن محاذاة الزوايا المتقلبة بشكل رأسي أو أفقي. عندما يتم محاذاة الرؤوس مدببة عموديا ، يطلق عليه تخطيط أفقي ، وعندما يتم محاذاة أفقيا ، يطلق عليه تخطيط عمودي . قد تعتقد أن الأسماء هي أسماء خاطئة فيما يتعلق بالتفسير المقدم.  توضح الصورة أدناه محاذاة البلاط المختلفة والتخطيطات المقابلة لها.

The vertical and horizontal hexagonal tile grid layoutThe vertical and horizontal hexagonal tile grid layoutThe vertical and horizontal hexagonal tile grid layout

يعتمد اختيار التنسيق تمامًا على صور وألعاب اللعبة. ومع ذلك ، لا ينتهي اختيارك هنا حيث يمكن تنفيذ كل من هذه التخطيطات بطريقتين مختلفتين.

دعونا النظر في تخطيط الشبكة الأفقية سداسية. يجب hexTileWidth/2 صفوف بديلة للشبكة بشكل أفقي بواسطة  هذا يعني أنه يمكننا اختيار تعويض الصفوف الفردية أو الصفوف المتساوية. إذا عرضنا أيضًا صف الصفوف ، قيم الأعمدة ، فإن هذه المتغيرات ستبدو مثل الصورة أدناه.

Horizontal hexagonal layout with even odd offsetsHorizontal hexagonal layout with even odd offsetsHorizontal hexagonal layout with even odd offsets

وبالمثل ، يمكن تنفيذ التخطيط العمودي في اثنين من الاختلافات أثناء تعويض الأعمدة البديلة بواسطة hexTileHeight/2 كما هو موضح أدناه.

Vertical hexagonal layout showing even odd variationsVertical hexagonal layout showing even odd variationsVertical hexagonal layout showing even odd variations

2. تنفيذ تخطيطات سداسية

من هنا فصاعدا، الرجاء البدء في إشارة إلى التعليمات البرمجية المصدر تقدم جنبا إلى جنب مع هذا البرنامج التعليمي لفهم أفضل.

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

1
var levelData=
2
[[0,0,0,0,0],
3
[0,0,0,0,0],
4
[0,0,0,0,0],
5
[0,0,0,0,0],
6
[0,0,0,0,0]
7
]

لتسهيل عملية العرض ، سأظهر النتيجة المقصودة هنا في كل من الاختلافات الرأسية والأفقية.

simple hexagonal grid based on level data arraysimple hexagonal grid based on level data arraysimple hexagonal grid based on level data array

نبدأ بالتخطيط الأفقي ، وهي الصورة على الجانب الأيسر. في كل صف ، إذا تم التقاطها بشكل فردي ، hexTileWidth  يتم تعويض الأعمدة البديلة أفقياً بقيمة hexTileWidth/2 . فرق الارتفاع الرأسي بين كل صف هو hexTileHeight*3/4 .

لفهم كيفية وصولنا إلى هذه القيمة لإزاحة الارتفاع ، يجب أن نأخذ في الاعتبار حقيقة أن الأجزاء المثلثة العلوية والسفلية  الشكل الموضوعة أفقيًا هي بالضبط hexTileHeight/4 

هذا يعني أن السداسي يحتوي على جزء مستطيل hexTileHeight/2 في الوسط ، وهو جزء ثلاثي hexTileHeight/4 مثلث في الأعلى ، ومثلث hexTileHeight/4 جزء معكوس في الجزء السفلي. هذه المعلومات كافية لإنشاء التعليمات البرمجية اللازمة لوضع شبكة سداسية على الشاشة.

1
var verticalOffset=hexTileHeight*3/4;
2
var horizontalOffset=hexTileWidth;
3
var startX;
4
var startY;
5
var startXInit=hexTileWidth/2;
6
var startYInit=hexTileHeight/2;
7
    
8
var hexTile;
9
for (var i = 0; i < levelData.length; i++)
10
{
11
    if(i%2!==0){
12
        startX=2*startXInit;
13
    }else{
14
        startX=startXInit;
15
    }
16
    startY=startYInit+(i*verticalOffset);
17
    for (var j = 0; j < levelData[0].length; j++)
18
    {
19
        if(levelData[i][j]!=-1){
20
            hexTile= new HexTile(game, startX, startY, 'hex',false,i,j,levelData[i][j]);
21
            hexGrid.add(hexTile);
22
        }    
23
        startX+=horizontalOffset;
24
    }    
25
}

مع النموذج الأولي HexTile ، أضفت بعض الوظائف الإضافية إلى النموذج الأولي Phaser.Sprite الذي يمكّنها من عرض قيم i و j .  يضع الكود بشكل أساسي startX startY سداسية جديدة في startX و startY . يمكن تغيير هذا الرمز لعرض متغير إزاحة حتى فقط عن طريق إزالة عامل في حالة if مثل هذا: if(i%2===0) .

بالنسبة إلى المخطط العمودي (الصورة في النصف الأيمن) ، تتم hexTileHeight مربعات الجوار في كل عمود عموديًا بواسطة hexTileHeight .  يتم تعويض كل عمود بديل عموديًا بواسطة hexTileHeight/2 . بتطبيق المنطق الذي قمنا بتطبيقه على الإزاحة الرأسية للتخطيط الأفقي ، يمكننا أن نرى أن الإزاحة الأفقية للتخطيط العمودي بين مربعات الجوار في صف هي hexTileWidth*3/4 . الرمز المقابل هو أدناه.

1
var verticalOffset=hexTileHeight;
2
var horizontalOffset=hexTileWidth*3/4;
3
var startX;
4
var startY;
5
var startXInit=hexTileWidth/2;
6
var startYInit=hexTileHeight/2;  
7
var hexTile;
8
for (var i = 0; i < levelData.length; i++)
9
{
10
    startX=startXInit;
11
    startY=2*startYInit+(i*verticalOffset);
12
    for (var j = 0; j < levelData[0].length; j++)
13
    {
14
        if(j%2!==0){
15
            startY=startY+startYInit;
16
        }else{
17
            startY=startY-startYInit;
18
        }
19
        if(levelData[i][j]!=-1){
20
            hexTile= new HexTile(game, startX, startY, 'hex', true,i,j,levelData[i][j]);
21
            hexGrid.add(hexTile);
22
        }
23
        startX+=horizontalOffset;
24
    }    
25
}

بنفس الطريقة كما في التخطيط الأفقي ، يمكننا التبديل إلى متغير الإزاحة حتى فقط عن طريق إزالة ! عامل في الأعلى if الشرط. أستخدم مجموعة Phaser لجمع كافة hexTiles المسماة hexGrid .  للبساطة، وأنا باستخدام نقطة المركز لصورة البلاط سداسية كنقطة ارتكاز، وأﻻ فسوف نحتاج إلى النظر في إزاحة الصورة كذلك.

ملاحظة شيء واحد أن بلاط بلاط وعرض قيم الارتفاع في التخطيط الأفقي ليست مساوية لقيم الارتفاع العرض وبلاط بلاط في التخطيط العمودي. ولكن عند استخدام نفس الصورة لكل التخطيطات، يمكننا فقط استدارة تجانب الصورة 90 درجة ومبادلة قيم الارتفاع والعرض وبلاط بلاط.

3-إيجاد فهرس الصفيف البلاط سداسية

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

إذا اعتبرنا التخطيط الأفقي ، قد يبدو أن الجزء الأوسط المستطيل من البلاط السداسي يمكن أن يساعدنا على اكتشاف القيمة j بسهولة لأنها مجرد مسألة تقسيم القيمة x بواسطة hexTileWidth وأخذ قيمة عدد صحيح. ولكن ما لم نكن نعرف القيمة الأولى ، فإننا لا نعرف ما إذا كنا على خلاف أو حتى صف. يمكن إيجاد قيمة تقريبية لـ i بقسمة القيمة y على hexTileHeight*3/4 . ولكن ما لم نكن نعرف القيمة الأولى ، فإننا لا نعرف ما إذا كنا على خلاف أو حتى صف. يمكن إيجاد قيمة تقريبية لـ i بقسمة القيمة y على hexTileHeight*3/4

حان الآن أجزاء معقدة من البلاط سداسية: الثلاثي الأجزاء العلوية والسفلية. الصورة أدناه سوف تساعدنا على فهم المشكلة في متناول اليد.

a horizontally laid hexagonal tile split into regionsa horizontally laid hexagonal tile split into regionsa horizontally laid hexagonal tile split into regions

وتشكل المناطق 2، 3، 5، 6، 8 و 9 معا تجانب واحد. الجزء الأكثر تعقيداً لمعرفة إذا كان موقف الاستفادة منها في 1/2 أو 3/4 أو 7/8 أو 9/10. لهذا، نحن بحاجة إلى النظر في جميع المناطق الثلاثي الفردية وتحقق ضدهم باستخدام منحدر حافة مائلة.

يمكن العثور على هذا المنحدر من ارتفاع وعرض كل منطقة مثلثة ، والتي هي على التوالي hexTileHeight/4 و hexTileWidth/2 . دعني أظهر لك الوظيفة التي تقوم بذلك.

1
function findHexTile(){
2
    var pos=game.input.activePointer.position;
3
    pos.x-=hexGrid.x;
4
    pos.y-=hexGrid.y;
5
    var xVal = Math.floor((pos.x)/hexTileWidth);
6
    var yVal = Math.floor((pos.y)/(hexTileHeight*3/4));
7
    var dX = (pos.x)%hexTileWidth;
8
    var dY = (pos.y)%(hexTileHeight*3/4); 
9
    var slope = (hexTileHeight/4)/(hexTileWidth/2);
10
    var caldY=dX*slope;
11
    var delta=hexTileHeight/4-caldY;
12
    
13
    if(yVal%2===0){
14
       //correction needs to happen in triangular portions & the offset rows

15
       if(Math.abs(delta)>dY){
16
           if(delta>0){//odd row bottom right half

17
                xVal--;
18
                yVal--;
19
           }else{//odd row bottom left half

20
                yVal--;
21
           }
22
       }
23
    }else{
24
        if(dX>hexTileWidth/2){// available values don't work for even row bottom right half

25
            if(dY<((hexTileHeight/2)-caldY)){//even row bottom right half

26
                yVal--;
27
            }
28
        }else{
29
           if(dY>caldY){//odd row top right & mid right halves

30
               xVal--;
31
           }else{//even row bottom left half

32
               yVal--;
33
           }
34
        }
35
    }
36
   pos.x=yVal;
37
   pos.y=xVal;
38
   return pos;
39
}

أولاً ، نجد xVal و yVal بنفس الطريقة التي yVal لشبكة مربعة. ثم نجد القيم المتبقية ( dX ) والأفقية ( dX ) dY بعد إزالة تخالف مُضاعِف البلاط. باستخدام هذه القيم ،  نحاول معرفة ما إذا كانت النقطة في أي من المناطق الثلاثية المعقدة.

إذا تم العثور على ذلك ، xVal تغييرات مقابلة على القيم المبدئية لـ xVal و yVal . وكما قلت من قبل ، فإن المدونة ليست جميلة وليست بسيطة.  أسهل طريقة لفهم هذا هي استدعاء findHexTile على تحريك الماوس ، ثم وضع console.log داخل كل من هذه الشروط وتحريك الماوس فوق مناطق مختلفة داخل إطار واحد سداسية.  بهذه الطريقة ، يمكنك أن ترى كيف يتم التعامل مع كل منطقة داخل سداسية.

يتم عرض التغييرات البرمجية للتخطيط العمودي أدناه.

1
function findHexTile(){
2
    var pos=game.input.activePointer.position;
3
    pos.x-=hexGrid.x;
4
    pos.y-=hexGrid.y;
5
    var xVal = Math.floor((pos.x)/(hexTileWidth*3/4));
6
    var yVal = Math.floor((pos.y)/(hexTileHeight));
7
    var dX = (pos.x)%(hexTileWidth*3/4);
8
    var dY = (pos.y)%(hexTileHeight); 
9
    var slope = (hexTileHeight/2)/(hexTileWidth/4);
10
    var caldX=dY/slope;
11
    var delta=hexTileWidth/4-caldX;
12
    if(xVal%2===0){
13
        if(dX>Math.abs(delta)){// even left

14
            
15
        }else{//odd right

16
            if(delta>0){//odd right bottom

17
                xVal--;
18
                yVal--;
19
            }else{//odd right top

20
                xVal--;
21
            }
22
        }
23
    }else{
24
        if(delta>0){
25
            if(dX<caldX){//even right top

26
                xVal--;
27
            }else{//odd mid

28
               yVal--; 
29
            }
30
        }else{//current values wont help for even right bottom

31
           if(dX<((hexTileWidth/2)-caldX)){//even right bottom

32
                xVal--;
33
           }
34
        } 
35
    }
36
   pos.x=yVal;
37
   pos.y=xVal;
38
   return pos;
39
}

4-إيجاد الجيران

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

odd and even rows of horizontal layout when a middle tile has ij set to 0odd and even rows of horizontal layout when a middle tile has ij set to 0odd and even rows of horizontal layout when a middle tile has ij set to 0

توضح الصورة أعلاه الصفوف الفردية وحتى الصفرية للشبكة سداسية الشكل أفقياً عندما يكون للقرص الأوسط قيمة 0 لكل من i و j . من الصورة ، يصبح من الواضح أنه إذا كان الصف غريبًا ، ففي حالة وجود بلاطة عند i,j يكون الجيران i, j-1 ، i-1,j-1 ، i-1,j ، i,j+1 و i+1,j و i+1,j-1 .  عندما يكون الصف متساويًا ، فسيكون الجيران في i, j-1 و i-1,j و i-1,j+1 و i,j+1 و i+1,j+1 و i+1,j . هذا يمكن أن يحسب يدويا بسهولة.

دعونا نحلل صورة مشابهة للأعمدة الفردية والزوجية في شبكة سداسية بمحاذاتها عمودياً.

odd and even columns of vertical layout when a middle tile has ij set to 0odd and even columns of vertical layout when a middle tile has ij set to 0odd and even columns of vertical layout when a middle tile has ij set to 0

عندما يكون لدينا عمود فردي ، سيكون للقرنة في i,j i,j-1 و i-1,j-1 و i-1,j و i-1,j+1 و i,j+1 و i+1,j كجيران. وبالمثل ، بالنسبة للعمود المتساوي ، يكون الجيران i+1,j-1 و i,j-1 و i-1,j و i,j+1 و i+1,j+1 و i+1,j .

5-سداسية لعبة كانسة الألغام

مع المعرفة أعلاه، يمكن أن نحاول جعل سداسية "كانسة الألغام" لعبة في تخطيطات مختلفة اثنين. دعونا كسر ميزات لعبة كانسة الألغام.

  1. سيكون هناك عدد N من الألغام المخفية داخل الشبكة.
  2. إذا أننا اضغط على أحد المربعات منجم، أن اللعبة قد انتهت.
  3. إذا نحن اضغط على أحد المربعات التي لديه المجاورة للأعمال المتعلقة بالألغام، فإنه سيتم عرض عدد الألغام حوله فورا.
  4. إذا نحن برفق في منجم دون أي الألغام المجاورة، فإنه سيؤدي إلى كشف كافة المربعات المتصلة التي لا تملك الألغام.
  5. يمكننا الاستفادة وعقد بمناسبة بلاط كلغم.
  6. انتهاء اللعبة عندما تكشف لنا كل البلاط خال من الألغام.

يمكننا بسهولة تخزين قيمة في صفيف levelData للإشارة إلى لغم. يمكن استخدام نفس الطريقة لتجميع قيمة مناجم قريبة على فهرس مجموعة المربعات المجاورة

عند بدء اللعبة ، سنقوم بملء مصفوفة levelData مع عدد N من الألغام بشكل عشوائي. بعد ذلك ، سنقوم بتحديث القيم لجميع المربعات المجاورة . سوف نستخدم طريقة متكررة لسلسلة تكشف عن كل البلاطات الفارغة المتصلة عندما ينقر اللاعب على بلاط لا يحتوي على لغم كجار.

البيانات على مستوى

نحن بحاجة إلى إنشاء شبكة سداسية تبحث لطيفة، كما هو موضح في الصورة أدناه.

horizontal hexagonal minesweeper gridhorizontal hexagonal minesweeper gridhorizontal hexagonal minesweeper grid

يمكن القيام بذلك عن طريق عرض جزء من صفيف levelData . إذا استخدمنا -1 كقيمة levelData غير قابل للاستعمال و 0 كقيمة levelData قابل للاستخدام ، فإن مستوى بياناتنا لتحقيق النتيجة السابقة سيبدو levelData .

1
//horizontal tile shaped level

2
var levelData=
3
[[-1,-1,-1,0,0,0,0,0,0,0,-1,-1,-1],
4
[-1,-1,0,0,0,0,0,0,0,0,-1,-1,-1],
5
[-1,-1,0,0,0,0,0,0,0,0,0,-1,-1],
6
[-1,0,0,0,0,0,0,0,0,0,0,-1,-1],
7
[-1,0,0,0,0,0,0,0,0,0,0,0,-1],
8
[0,0,0,0,0,0,0,0,0,0,0,0,-1],
9
[0,0,0,0,0,0,0,0,0,0,0,0,0],
10
[0,0,0,0,0,0,0,0,0,0,0,0,-1],
11
[-1,0,0,0,0,0,0,0,0,0,0,0,-1],
12
[-1,0,0,0,0,0,0,0,0,0,0,-1,-1],
13
[-1,-1,0,0,0,0,0,0,0,0,0,-1,-1],
14
[-1,-1,0,0,0,0,0,0,0,0,-1,-1,-1],
15
[-1,-1,-1,0,0,0,0,0,0,0,-1,-1,-1]];

أثناء التكرار خلال الصفيف ، نضيف فقط القراميد السداسية عندما يكون مستوى levelData له قيمة 0 . بالنسبة إلى المحاذاة الرأسية ، يمكن استخدام نفس levelData ، لكننا سنحتاج إلى تبديل الصفيف. هنا طريقة أنيقة يمكن أن تفعل ذلك من أجلك.

1
levelData=transpose(levelData);
2
//...

3
function transpose(a) {
4
    return Object.keys(a[0]).map(
5
        function (c) { return a.map(function (r) { return r[c]; }); }
6
        );
7
}

إضافة الألغام وتحديثها الجيران

بشكل افتراضي ، يحتوي levelData لدينا على levelData فقط ، -1 و 0 ، لن نستخدم منها سوى المنطقة 0 . للإشارة إلى أن البلاطة تحتوي على لغم ، يمكننا استخدام قيمة 10 .

يمكن للبلاط السداسي الفارغ أن يكون بحد أقصى ستة ألغام بالقرب منه حيث أنه يحتوي على ستة قرميدات متجاورة. يمكننا تخزين هذه المعلومات أيضًا في المستوى levelData بعد إضافة جميع الألغام. وبشكل أساسي ، levelData مؤشر مستوى levelData يحتوي على قيمة 10 على لغم ، وإذا كان يحتوي على أي قيم من 0 إلى 6 ، فإن ذلك يشير إلى عدد من المناجم المجاورة.  بعد ملء الألغام وتحديث الجيران ، إذا كان عنصر الصفيف لا يزال 0 ، فإنه يشير إلى أنه بلاطة فارغة بدون أي ألغام مجاورة.

يمكننا استخدام الأساليب التالية لأغراضنا.

1
function addMines(){
2
    var tileType=0;
3
    var tempArray=[];
4
    var newPt=new Phaser.Point();
5
    for (var i = 0; i < levelData.length; i++)
6
    {
7
        for (var j = 0; j < levelData[0].length; j++)
8
        {
9
            tileType=levelData[i][j];
10
            if(tileType===0){
11
                newPt=new Phaser.Point();
12
                newPt.x=i;
13
                newPt.y=j;
14
                tempArray.push(newPt);
15
            }
16
        }
17
    }
18
    for (var i = 0; i < numMines; i++)
19
    {
20
        newPt=Phaser.ArrayUtils.removeRandomItem(tempArray);
21
        levelData[newPt.x][newPt.y]=10;//10 is mine

22
        updateNeighbors(newPt.x,newPt.y);
23
    }
24
}
25
function updateNeighbors(i,j){//update neighbors around this mine

26
    var tileType=0;
27
    var tempArray=getNeighbors(i,j);
28
    var tmpPt;
29
    for (var k = 0; k < tempArray.length; k++)
30
    {
31
        tmpPt=tempArray[k];
32
        tileType=levelData[tmpPt.x][tmpPt.y];
33
        levelData[tmpPt.x][tmpPt.y]=tileType+1;
34
    }
35
}

لكل منجم تمت إضافته في addMines ، نقوم بزيادة قيمة الصفيف المخزنة في جميع جيرانها. لن تقوم طريقة getNeighbors بإرجاع البلاط الذي يقع خارج منطقتنا الفعالة أو إذا كان يحتوي على لغم.

اضغط على منطق

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

1
function onTap(){
2
    var tile= findHexTile();
3
    
4
     if(!checkforBoundary(tile.x,tile.y)){
5
        if(checkForOccuppancy(tile.x,tile.y)){
6
            if(levelData[tile.x][tile.y]==10){
7
                //console.log('boom');

8
                var hexTile=hexGrid.getByName("tile"+tile.x+"_"+tile.y);
9
                if(!hexTile.revealed){
10
                    hexTile.reveal();
11
                    //game over

12
                }
13
            }
14
        }else{
15
            var hexTile=hexGrid.getByName("tile"+tile.x+"_"+tile.y);
16
                    
17
            if(!hexTile.revealed){
18
                if(levelData[tile.x][tile.y]===0){
19
                    //console.log('recursive reveal');

20
                    recursiveReveal(tile.x,tile.y);
21
                }else{
22
                    //console.log('reveal');

23
                    hexTile.reveal();
24
                    revealedTiles++;
25
                }
26
                
27
            }
28
        }
29
    }
30
    infoTxt.text='found '+revealedTiles +' of '+blankTiles;
31
}

نقوم بتتبع العدد الإجمالي للبلاط الفارغ باستخدام blankTiles المتغيرة وعدد المربعات التي تم الكشف عنها باستخدام blankTiles . بمجرد أن يكونوا متساوين ، فقد فزنا باللعبة.

عندما نضغط على بلاط به قيمة صفيف من 0 ، نحتاج أن نكشف المنطقة بشكل متكرر مع جميع البلاطات الفارغة المتصلة. يتم ذلك عن طريق الدالة recursiveReveal ، والتي تستقبل مؤشرات البلاط من القرميد الذي تم النقر عليه.

1
function recursiveReveal(i,j){
2
    var newPt=new Phaser.Point(i,j);
3
    var hexTile;
4
    var tempArray=[newPt];
5
    var neighbors;
6
    while (tempArray.length){
7
        newPt=tempArray[0];
8
        var neighbors=getNeighbors(newPt.x,newPt.y);
9
        
10
        while(neighbors.length){
11
            newPt=neighbors.shift();
12
            hexTile=hexGrid.getByName("tile"+newPt.x+"_"+newPt.y);
13
            if(!hexTile.revealed){
14
                hexTile.reveal();
15
                revealedTiles++;
16
                if(levelData[newPt.x][newPt.y]===0){
17
                    tempArray.push(newPt);
18
                }
19
            }
20
        }
21
        newPt=tempArray.shift();//it seemed one point without neighbor sometimes escapes the iteration without getting revealed, catch it here

22
        hexTile=hexGrid.getByName("tile"+newPt.x+"_"+newPt.y);
23
        if(!hexTile.revealed){
24
            hexTile.reveal();
25
            revealedTiles++;
26
        }
27
    }
28
}

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

وسم والكشف عن البلاط

يجب أن تكون قد لاحظت أنني أستخدم hexTile.reveal() ، والذي أصبح ممكنا عن طريق إنشاء نموذج HexTile الذي يحافظ على معظم السمات المتعلقة بالبلاط السداسي الخاص بنا.  أنا استخدم الدالة reveal لعرض نص قيمة البلاط وتعيين لون البلاط.وبالمثل ، يتم استخدام وظيفة toggleMark لوضع علامة على البلاط على أنه منجم عند النقر مع الاستمرار HexTile أيضًا على سمة revealed تتعقب ما إذا كان يتم HexTile أم لا.

1
HexTile.prototype.reveal=function(){
2
    this.tileTag.visible=true;
3
    this.revealed=true;
4
    if(this.type==10){
5
        this.tint='0xcc0000';
6
    }else{
7
        this.tint='0x00cc00';
8
    }
9
}
10
HexTile.prototype.toggleMark=function(){
11
    if(this.marked){
12
       this.marked=false; 
13
       this.tint='0xffffff';
14
    }else{
15
        this.marked=true;
16
        this.tint='0x0000cc';
17
    }
18
}

تحقق من كاسحة ألغام سداسية مع الاتجاه الأفقي أدناه. انقر للكشف عن البلاط ، واضغط مع الاستمرار لوضع علامة على الألغام. لا يوجد أي لعبة أكثر من الآن ، ولكن إذا كنت تكشف عن قيمة 10 ، ثم هو طفل لا فيستا فيستا!

التغييرات للإصدار الرأسي

عندما أستخدم نفس صورة البلاط سداسية الاتجاه لكلا الاتجاهين ، أقوم بتدوير Sprite للمحاذاة الرأسية. التعليمات البرمجية أدناه في النموذج الأولي HexTile القيام بذلك.

1
if(isVertical){
2
    this.rotation=Math.PI/2;
3
}

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

تحقق من الإصدار الرأسي أدناه.

باقي التعليمات البرمجية في المصدر بسيط ومباشر. أود أن أبلغكم في محاولة لإضافة إعادة المفقودين وين لعبة لعبة أكثر من وظيفة.

الاستنتاج

هذا النهج من لعبة سداسية على البلاط باستخدام مجموعة ثنائية الأبعاد هو أكثر من نهج المواطن العادي. تتضمن المناهج الوظيفية والأكثر إثارة للاهتمام تغيير نظام الإحداثيات لأنواع مختلفة باستخدام المعادلات.

أهمها الإحداثيات المحورية والإحداثيات المكعبة. سيكون هناك سلسلة من البرامج التعليمية للمتابعة التي ستناقش هذه الأساليب. في هذه الأثناء ، أوصيك بقراءة مقالة عميت الشاملة حول شبكات سداسية .

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Game Development tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.