آموزش ساخت بازی Minesweeper در گیم میکر

در این آموزش سایت ای تاتس قصد داریم یک آموزش و توضیح کاملا مفصل در مورد ساخت بازی Minesweeper بدیم. این یک بازی جدانشدنی از سیستم عامل ویندوز است. آموزش این بازی بر روی انجین گیم میکر انجام میشه. یعنی ما میخوایم روی گیم میکر این بازی رو بسازیم. با ما در ساخت این بازی محبوب در گیم میکر همراه باشید.
Minesweeper یکی از بازی های جذاب و جدا نشدنی سیستم عامل ویندوز است.2306246-minesweeperpromoimage_558x756

منابع مورد نیاز برای ساخت این بازی تنها اسپرایت 16 فریمی زیر است:

ابتدا آبجکتی برای ایجاد و کنترل خانه های بازی می سازیم.

آبجکتی با نام cont_MinePanel بسازید.

در رویداد Create آن کد زیر را وارد کنید:

//Dimension and mines
Rows = 9;
Columns = 9;
Mines = 10;
Flags = Mines;

Rows تعداد سطرها و Columns تعداد ستون های بازی را مشخص می کند. Mines تعداد مین های موجود در بازی را مشخص می کند. دقت نمایید که تعداد آن ها متناسب با ابعاد بازی باشد.

متغیر Flags نیز تعداد پرچم های مورد استفاده برای مین های یافته شده در بازی را مشخص می کند. دقت نمایید، زمانی که بازیکن اطمینان داشت خانه ای حاوی مین می باشد، با پرچم آن را مشخص می کند که اشتباها کلیک نکند.

در ادامه کد زیر را اضافه نمایید:

//Time
Time = 0;
TimerEnabled = false;

متغیر Time زمان بازی را ذخیره می کند که می توانید در انتهای بازی با توجه به زمان امتیازی به بازیکن بدهید.

متغیر TimerEnabled نیز برای شروع گذشت زمان استفاده می شود. هنگامی که این متغیر false می باشد، به زمان بازی افزوده نمی شود.

در ادامه نیز متغیرهایی برای ذخیره ابعاد خانه ها و فاصله ی جدول بازی از کناره های صفجه به کد اضافه می نماییم:

//Some useful variables
PanelLeftBorder = 8;
PanelRightBorder = 8;
PanelTopBorder = 32;
PanelBottomBorder = 8
BoxWidth = 16;

حال می خواهیم که اندازه ی اتاق بازی را به اندازه ی جدول بازی کوچک کنیم. توجه نمایید که تغییر در ابعاد اتاقی که در آن هستیم، به صورت آنی اعمال نمی شود. برای این کار می بایست اتاق ریستارت شود.

کد زیر را در ادامه اضافه نمایید:

//Resize room
if(room_width != Rows * BoxWidth + PanelLeftBorder + PanelRightBorder || room_height != Columns * BoxWidth + PanelTopBorder + PanelBottomBorder)
{
room_set_width(room, Rows * BoxWidth + PanelLeftBorder + PanelRightBorder);
room_set_height(room, Columns * BoxWidth + PanelTopBorder + PanelBottomBorder);
room_restart();
}

این کد بررسی می کند که اگر ابعاد اتاق به اندازه ی ابعاد ذکر شده نبود، ابعاد جدید را به آن اعمال و اتاق را ریستارت کن. توجه نمایید که در این کد شرط اندازه نبودن ابعاد اتاق با ابعاد دلخواه باعث جلوگیری از ریستارت پی در پی و افتادن در حلقه بدون پایان می شود.

پس از آن که اتاق به اندازه دلخواه در آمد می خواهیم خانه های بازی را بسازیم. کد زیر را در ادامه اضافه نمایید:

//Create boxes
var i, j;

for(i = 0; i < Rows; i += 1)
{
for(j = 0; j < Columns; j += 1)
{
Box[i, j] = instance_create(PanelLeftBorder + i * BoxWidth, PanelTopBorder + j * BoxWidth, obj_Box);
Box[i, j].Row = i;
Box[i, j].Column = j;
Box[i, j].Mined = false;
Box[i, j].Flag = 0;
Box[i, j].Clicked = false;
}
}

در اینجا با استفاده از دو حلقه for یک آرایه دو بعدی به ابعاد Rows و Columns را پیموده ایم و در هر عضو این آرایه یک آبجکت obj_Box که بعدا تعریف می کنیم ایجاد کردیم. هر آبجکت را در مکان مورد نظر در صفحه قرار داده ایم.

متغیرهای اندیس هر خانه در آرایه را در آبجکت ایجاد شده ذخیره می نماییم که بعدا بتواند به مکان خود در آرایه راحت تر دسترسی داشته باشد.

متغیرهای دیگری را نیز برای هر خانه ی ایجاد شده مقدار دهی می کنیم که بعدا از آن ها استفاده می کنیم. Mined به این معناست که این خانه حاوی مین است. Flag علامت پرچم را در خود ذخیره می کند. و Clicked مشخص می کند که خانه ی مورد نظر کلیک شده است یا خیر.

در کد بالا تمامی خانه ها را بدون مین رها کردیم. حال می خواهیم مین هایی را در خانه هایی به صورت تصادفی قرار دهیم. کد زیر را در ادامه اضافه نمایید:

//Put mines
var PMines;
PMines = Mines
while(PMines > 0)
{
i = floor(random(Rows));
j = floor(random(Columns));

if(!Box[i, j].Mined)
{
Box[i, j].Mined = true;
PMines -= 1;
}
}

در اینجا ابتدا متغیری تعریف کردیم و تعداد مین ها را در آن ذخیره کردیم.

سپس در حلقه ی while مکانی تصادفی را انتخاب نمودیم. تابع random عددی حقیقی از 0 تا عدد مورد نظر را باز می گرداند، چون عدد ممکن است اعشاری باشد با تابع floor آن را به عدد صحیح کوچکتر از خود گرد کردیم.

پس از انتخاب مکان تصادفی، بررسی می شود اگر در آن مکان (آبجکت) مین نبود. یک مین در آن قرار دهد و از تعداد مین های کاشته نشده یکی کم شود. تا زمانی که همه ی مین ها کاشته نشوند، حلقه پایان نمی یابد.

درست است! ممکن است حلقه ده برابر تعداد مین ها اجرا نشود، اما معمولا سریع انجام می شود.

پس از کاشتن مین ها، وضعیت بازی را در حالت نرمال قرار می دهیم که بازیکن بتواند بازی را آغاز کند:

//Define game state
GameState = “Normal”;

حال آبجکت obj_Box را ایجاد نمایید و سپرایتی که تصویر آن را در بالا دریافت نمودید برای آن انتخاب نمایید.

توجه کنید فریم های سپرایت را به همان ترتیبی که هستند بگذارید!

در رویداد Create آبجکت obj_Box کد زیر را قرار دهید:

image_speed = 0;
image_index = 9;//Up Box

این کد سرعت اجرای انیمیشن آبجکت را صفر می کند و فریم شماره 9 که تصویر خانه ی معمولی است را برای آن انتخاب می کند.

از رویدادهای ماوس رویداد Left Pressed را انتخاب کنید و کد زیر را در آن قرار دهید:
Clicked = true;
این کد مشخص می نماید که این آبجکت کلیک شده است.

کد زیر را در ادامه اضافه نمایید:

cont_MinePanel.TimerEnabled = true;

این کد نیز به تایمر بازی اجازه ی اجرا شدن می دهد. پس با کلیک بر روی اولین خانه، زمان بازی می تواند شروع به حرکت کند.

برای حرکت زمان به آبجکت cont_MinePanel رفته و در رویداد Step آن کد زیر را وارد کنید:

if(GameState == “Normal” && TimerEnabled && fps > 0)
Time += 1/fps;

این کد سه چیز را بررسی می کند. ابتدا این که وضعیت بازی نرمال است و بازیکن می تواند بازی کند؛ دوم این که تایمر فعال باشد؛ و سوم این که fps بازی بزرگتر از صفر باشد تا خطای تقسیم بر صفر رخ ندهد. اگر این سه پارامتر را بگذراند می تواند به زمان اضافه کند.

برای اینکه زمان بازی در هر ثانیه 1 عدد صحیح اضافه شود، در هر Step به آن 1 روی fps واحد اضافه می کنیم. fps مقدار فریم اجرا شده در هر ثانیه از بازی می باشد. این عدد برابر یا کمتر از room_speed می باشد.

به رویداد Left Pressed آبجکت obj_Box بازگردید.

پس از کلیک بر روی هر خانه بررسی می کنیم که آیا آن خانه مین بوده یا خیر. کد زیر را در انتهای کد این رویداد اضافه نمایید:

if(Mined)
{
//Lose
image_index = 14;

cont_MinePanel.GameState = “Lose”;
exit;
}

این کد بررسی می کند که اگر این خانه حاوی مین بود، تصویر مین برای آن انتخاب شود و وضعیت بازی به حالت Lose در بیاید. سپس با دستور exit از اجرای ادامه کد خودداری می کند.

حال اگر خانه حاوی مین نبود، می باید تصویر پاکسازی برای آن به نمایش در آید. در این بازی اگر خانه ای حاوی مین نباشد، پس از پاکسازی تعداد مین های اطراف آن در آن به نمایش در می آید. پس ما باید تعداد مین های خانه های اطراف را بیابیم و در آن نمایش دهیم.

در اینجا تابعی را توسط اسکریپتی به نام InRange تعریف کرده ام که بررسی می کند خانه ای با اندیس های مورد نظر وجود دارد یا خیر. این تابع برای این استفاده می شود که خانه ای با اندیس های خارج از محدوده ی تعریف شده در cont_MinePanel انتخاب نکنیم. پس اسکریپتی با نام InRange تعریف کنید و کد زیر را در آن قرار دهید:

// InRange(X, Y, Width, Height);
return (argument0 >= 0 && argument1 >= 0 && argument0 < argument2 && argument1 < argument3);

این کد اگر X و Y بزرگتر مساوی صفر و کوچکتر از طول و ارتفاع ذکر شده باشند مقدار صحیح باز می گرداند.

به رویداد Left Pressed آبجکت obj_Box بازگردید.

در انتهای کد ابتدا متغیری تعریف کنید که تعداد مین های اطراف خانه ی مورد نظر را ذخیره کند:

var NearBombCount;
NearBombCount = 0;

سپس باید برای هر 8 خانه ی اطراف خانه ی مورد نظر بررسی کنیم اگر حاوی مین بود، به مقدار متغیر NearBombCount یک واحد افزوده شود:

if(InRange(Row – 1, Column – 1, cont_MinePanel.Rows, cont_MinePanel.Columns))
if(cont_MinePanel.Box[Row – 1, Column – 1].Mined)
NearBombCount += 1;

if(InRange(Row , Column – 1, cont_MinePanel.Rows, cont_MinePanel.Columns))
if(cont_MinePanel.Box[Row, Column – 1].Mined)
NearBombCount += 1;

if(InRange(Row + 1, Column – 1, cont_MinePanel.Rows, cont_MinePanel.Columns))
if(cont_MinePanel.Box[Row + 1, Column – 1].Mined)
NearBombCount += 1;

if(InRange(Row – 1, Column, cont_MinePanel.Rows, cont_MinePanel.Columns))
if(cont_MinePanel.Box[Row – 1, Column].Mined)
NearBombCount += 1;

if(InRange(Row + 1, Column, cont_MinePanel.Rows, cont_MinePanel.Columns))
if(cont_MinePanel.Box[Row + 1, Column].Mined)
NearBombCount += 1;

if(InRange(Row – 1, Column + 1, cont_MinePanel.Rows, cont_MinePanel.Columns))
if(cont_MinePanel.Box[Row – 1, Column + 1].Mined)
NearBombCount += 1;

if(InRange(Row, Column + 1, cont_MinePanel.Rows, cont_MinePanel.Columns))
if(cont_MinePanel.Box[Row, Column + 1].Mined)
NearBombCount += 1;

if(InRange(Row + 1, Column + 1, cont_MinePanel.Rows, cont_MinePanel.Columns))
if(cont_MinePanel.Box[Row + 1, Column + 1].Mined)
NearBombCount += 1;

همان طور که می بینید ابتدا بررسی می شود که خانه ی مورد نظر در محدوده ابعاد جدول وجود دارد یا خیر. این هنگامی کاربرد دارد که بر روی خانه های حاشیه جدول کلیک کنیم.

پس از یافتن تعداد مین های اطراف خانه، فریم متناسب با آن را برای آبجکت انتخاب می کنیم:

image_index = NearBombCount;

توجه کنید که اگر اطراف خانه ای مین وجود نداشته باشد، پس با آرامش خاطر می توان بر روی آن ها کلیک کرد. برای راحتی بازیکن ما می توانیم به صورت خودکار این کار را انجام دهیم. پس باید رویداد کلیک هر 8 خانه ی اطراف را فراخوانی نماییم:

if(NearBombCount == 0)
{
if(InRange(Row – 1, Column – 1, cont_MinePanel.Rows, cont_MinePanel.Columns))
with(cont_MinePanel.Box[Row – 1, Column – 1]){event_perform(ev_mouse, ev_left_press);}
if(InRange(Row, Column – 1, cont_MinePanel.Rows, cont_MinePanel.Columns))
with(cont_MinePanel.Box[Row, Column – 1]){event_perform(ev_mouse, ev_left_press);}
if(InRange(Row + 1, Column – 1, cont_MinePanel.Rows, cont_MinePanel.Columns))
with(cont_MinePanel.Box[Row + 1, Column – 1]){event_perform(ev_mouse, ev_left_press);}
if(InRange(Row – 1, Column, cont_MinePanel.Rows, cont_MinePanel.Columns))
with(cont_MinePanel.Box[Row – 1, Column]){event_perform(ev_mouse, ev_left_press);}
if(InRange(Row + 1, Column, cont_MinePanel.Rows, cont_MinePanel.Columns))
with(cont_MinePanel.Box[Row + 1, Column]){event_perform(ev_mouse, ev_left_press);}
if(InRange(Row – 1, Column + 1, cont_MinePanel.Rows, cont_MinePanel.Columns))
with(cont_MinePanel.Box[Row – 1, Column + 1]){event_perform(ev_mouse, ev_left_press);}
if(InRange(Row, Column + 1, cont_MinePanel.Rows, cont_MinePanel.Columns))
with(cont_MinePanel.Box[Row, Column + 1]){event_perform(ev_mouse, ev_left_press);}
if(InRange(Row + 1, Column + 1, cont_MinePanel.Rows, cont_MinePanel.Columns))
with(cont_MinePanel.Box[Row + 1, Column + 1]){event_perform(ev_mouse, ev_left_press);}
}

دستور with قطعه کد را برای نمونه آبجکت مورد نظر اجرا می نماید. تابع event_perform رویداد مورد نظر را برای آبجکت اجرا می کند. پس برای هر 8 خانه اطراف خانه ی مورد نظر ما رویداد کلیک اجرا می شود. در هر کدام از آن ها نیز اگر مین های اطراف برابر با صفر باشد، رویداد کلیک 8 خانه ی اطراف اجرا می شود. به چنین حالتی، فراخوانی بازگشتی می گویند. در فراخوانی بازگشتی می بایست نقطه توقفی باشد. یک نقطه توقف ما زمانی است که تعداد مین های اطراف برابر صفر نباشد. اما اگر در ناحیه ای چندین خانه بدون مین باشند، حلقه ی بدون پایانی ایجاد می شود که دائما کلیک خانه ها اجرا می شود. برای جلوگیری از این کار، ابتدای اجرای رویداد کلیک باید بررسی شود که خانه قبلا کلیک شده است یا خیر. پس در ابتدای رویداد کلیک، کد زیر را اضافه کنید:

//If clicked, cannot click again
if(Clicked)
exit;

همچنین باید بررسی کنید که بازیکن می تواند بازی کند، برای این کار کد زیر را در ابتدای رویداد کلیک اضافه نمایید:

// If can’t play
if(cont_MinePanel.GameState != “Normal”)
exit;

خب! پس از هر کلیک باید بررسی کنیم که آیا با این کلیک بازی تمام شده است (برنده شده ایم) یا خیر. برای بررسی برنده بودن می توانید تعداد خانه های کلیک نشده را با تعداد مین ها مقایسه کنید. اگر با هم برابر بودند، یعنی تمامی خانه های کلیک نشده مین هستند. کد زیر را در انتهای رویداد کلیک اضافه نمایید:

// CheckWin();
var cOver;

cOver = 0;
for(i = 0; i < instance_number(obj_Box); i += 1)
{
if(!instance_find(obj_Box, i).Clicked)
cOver += 1;
}

if(cOver == cont_MinePanel.Mines)
{
cont_MinePanel.GameState = “Win”;
}

اتاقی ایجاد کنید و آبجکت cont_MinePanel را در آن قرار دهید.

بازی را اجرا کنید و تغییرات را ببینید.

خب! حالا می خواهیم امکان قرار دادن پرچم در صورت اطمینان و قرار دادن علامت سوال در صورت مشکوک بودن را بر روی خانه ها قرار دهیم. برای این کار ما از متغیر Flag استفاده می کنیم. در صورتی که Flag برابر صفر باشد حالت بدون علامت است؛ در صورتی که برابر 1 باشد برابر علامت پرچم می باشد و در صورتی که مقدار آن برابر 2 باشد به نشان علامت سوال است.

خب، از رویدادهای ماوس، Right Pressed را انتخاب کنید.

در آن کدهای زیر را قرار دهید:

// If can’t play
if(cont_MinePanel.GameState != “Normal”)
exit;

//If clicked, cannot click again
if(Clicked)
exit;

حال طبق توضیحاتی که در ابتدا دادیم، باید با کلیک راست، مقدار Flag تغییر کند:

Flag += 1;

همچنین این مقدار محدود به صفر، 1 و 2 است:

Flag = Flag mod 3;

سپس باید تصویر متناسب با علامت پرچم را انتخاب کنیم. چون این تصاویر را به ترتیب قرار داده ایم، کد آن به آسانی زیر است:

image_index = 9 + Flag;

همچنین باید پس از پرچم دار کردن یک خانه، از پرچم های cont_MinePanel یک واحد کم کنیم و هنگامی که یک خانه را از حالت پرچم در آوردیم، به پرچم های cont_MinePanel یک واحد اضافه شود. پس هنگامی که مقدار Flag برابر 1 شد، از پرچم های cont_MinePanel یک واحد کم و هنگامی که برابر 2 شد به پرچم های cont_MinePanel یک واحد اضافه می شود:

if(Flag == 1)
cont_MinePanel.Flags -= 1;
if(Flag == 2)
cont_MinePanel.Flags += 1;

کد این رویداد همین جا به پایان می رسد.

حال به رویداد Left Pressed بازگردید. کد زیر را در ابتدای آن اضافه کنید که اگر خانه ای دارای پرچم بود کلیک نشود:

//If flaged, cannot click
if(Flag == 1)
exit;

بازی تا به اینجای کار کامل شده است. اما بهتر است هنگامی که روی یک مین کلیک می کنیم، علاوه بر باختن، مکان همه ی مین ها دیده شود. و همچنین بفهمیم که کدام مین ها را درست پرچم گذاری کرده ایم. برای این کار کد زیر را به جای کد بررسی باختن قرار دهید:

if(Mined)
{
//Lose
image_index = 14;
for(i = 0; i < instance_number(obj_Box); i += 1)
{
var cObj;
cObj = instance_find(obj_Box, i);
if(cObj.Clicked)
continue;

if(cObj.Mined)
if(cObj.Flag == 1)
cObj.image_index = 15;
else
cObj.image_index = 13;
}
cont_MinePanel.GameState = “Lose”;
exit;
}

همان طور که میبینید یک حلقه for اضافه شده است. این حلقه برای تک تک خانه ها بررسی می کند که اگر حاوی بمب بود، اگر پرچم دار بود تصویر بمب خنثی شده و اگر پرچم دار نبود تصویر بمب را نمایش دهد. همچنین برای بررسی سریع تر خانه هایی که کلیک شده اند را رد می کنیم. این کار با دستور continue انجام می شود. خط فرمان با رسیدن به این دستور به ابتدای دور بعدی از حلقه می رود.

بازی را اجرا کنید و بازی کنید.

می بینید که پس از باختن یا بردن، دیگر خانه ها از کار می افتد. پس باید بتوان با دکمه ای بازی را ریستارت کرد. برای این کار من دکمه ی F2 را توسط آبجکت cont_MinePanel برای ریستارت کردن بازی انتخاب کرده ام. در رویداد فشردن این دکمه در آبجکت cont_MinePanel کد زیر را قرار دهید:

game_restart();
همچنین بهتر است تعداد مین ها، زمان و پیغام مناسب هنگام بردن یا باختن نمایش داده شود. برای این کار کد زیر را در رویداد Draw این آبجکت قرار دهید:

draw_set_color(c_white);
draw_set_valign(fa_top);
draw_set_halign(fa_left);
draw_text(8, 8, round(Time));
draw_set_halign(fa_right);
draw_text(room_width – 8, 8, Flags);

draw_set_valign(fa_middle);
draw_set_halign(fa_center);
switch(GameState)
{
case “Normal”:
break;
case “Lose”:
draw_text(room_width / 2, room_height / 2, “You Lose!#Press [F2] To Restart”);
break;
case “Win”:
draw_text(room_width / 2, room_height / 2, “You Win!#Press [F2] To Restart”);
break;
}

البته دقت نمایید که کدهای آبجکت cont_MinePanel قبل از خانه های جدول اجرا می شود؛ پس پیغام های برد و باخت در زیر جدول پنهان می شود. برای جلوگیری از این کار باید Depth خانه های جدول بیشتر از cont_MinePanel باشد تا آن ها زودتر رسم شوند و متن های cont_MinePanel دیرتر رسم شود. شما می توانید Depth آبجکت obj_Box را عددی بیشتر از cont_MinePanel قرار دهید تا زودتر از آن رسم شود. برای این کار از پنجره ی آبجکت مورد نظر در سمت چپ زیر گزینه های Visible و Solid این گزینه را می یابید. مقدار آن را از 0 به 1 تغییر دهید.

 

ساخت این بازی به پایان رسید. کارهای بیشتری می توان انجام داد که آن ها را به شما واگذار می کنم:

قابلیت انتخاب اندازه ی جدول و تعداد مین ها توسط بازیکن.
دادن امتیاز پس از برنده شدن بازیکن بر حسب زمان.
افزودن افکت های جالب هنگام انفجار بمب یا برنده شدن.
افزودن صدا به بازی

و…

 

minesweeper

آموزش ساخت بازی Minesweeper در گیم میکر

کامنت ها

لطفا اگر سوالی نامرتبط با این مطلب دارید، از تب «پرسیدن سوال» استفاده کنید

پاسخ دهید

نشانی ایمیل شما منتشر نخواهد شد.

لطفا اگر سوالی خارج از موضوع این مطلب دارید آن را در فروم مطرح کنید.

<