صفر تا صد ساخت بازی با سی شارپ 10#

صفر تا صد ساخت بازی با سی شارپ 10#
در ابتدا عذر خواهی می کنم بابت تاخیری که به وجود آمد. در جلسه قبلی ساخت دشمنان را تمام کردیم و حرکت آنها را برنامه نویسی کردیم. در این جلسه بحث برخورد موشک با دشمن رو برنامه نویسی می کنیم این بخش شاید کمی سخت بنظر بیاد اما نگران نباشید انقدرها هم سخت نیست.
خرید شارژ ایرانسل
خرید شارژ ایرانسل، همراه اول، رایتل | خرید بسته های اینترنت ایرانسل | etore.ir
دانلود بازی اندروید
اندروید سیتی | بررسی و دانلود آخرین های اندروید
خرید سرور مجازی
ارائه سرور مجازی SSD NVMe برای اولین بار در ایران ؛ نهایت سرعت را تجربه کنید.
خودتان را اینجا معرفی کنید

حالا که هم حرکت موشک ها تمام شده و هم حرکت دشمنان باید و باید به سراغ برنامه نویسی لحظه برخورد برویم. اما پیش از آن اجازه بدهید یک کار نا تمام را تمام کنیم. در قسمت پرتاب موشک فقط موقعیت موشک را برابر موقعیت سفینه قرار دادیم و کار تمام شد در صورتی که ما گفتیم موشک باید از وسط سفینه شلیک شود. برای اجرای این کار به ادامه مطلب توجه کنید.
 
اگر موقعیت موشک ها را مساوی موقعیت  سفینه قرار دهید موشک ها از کنار سفینه پرتاب می شوند یعنی همان اتفاقی که قبلا می افتاد مثل تصویر زیر :
 
rocket.Location = ptb_spaceShip.Location;
 
و اگر موقعیت سفینه را با نصف عرض سفینه جمع کنیم و موقعیت موشک حاصل این جمع باشد، موشک از کنار نیمساز سفیه پرتاب می شود به تصویر زیر نگاه کنید تا بهتر متوجه موضوع شوید:
 
 
 
rocket.Location = new Point(ptb_spaceShip.Location.X + ptb_spaceShip.Size/2,ptb_spaceShip.Location.Y);    
اما کار درست این است که موشک درست از وسط سفینه پرتاب شود. یعنی اگر یک نیمساز برای سفینه و یک نیم ساز برای موشک در نظر بگیرید، این دو نیمساز باید روی هم قرار گیرند. درست مثل تصویر زیر :
 
 
برای رسیدن به این هدف مجبور هستیم که کد را کمی پیچیده تر کنیم،اما برای اینکه کمی از پیچیدگی کد به کاهیم چند متغییر محلی تعریف می کنیم و موقعیت موشک و سفینه را در آنها قرار می دهیم. با این کار چند خط به کد اضافه می شود اما در زمان تنظیم موقعیت موشک کار بسیار ساده تر می شود. به قطعه کد زیر نگاه کنید می بینید که خیلی بلند و شلوغ است.
rocket.Location = new Point(ptb_spaceShip.Location.X + ((ptb_spaceShip.Size.Width /2)-(rocket.Size.Width/2)), ptb_spaceShip.Location.Y);
در اصل ما باید این کد را در برنامه وارد کنیم. اما با تقسیم این کد به بخش های کوچکتر کار را ساده می کنیم. در قطعه کدهای زیر همین کار را انجام داده ام هر بخش از عبارت بالا را جدا کرده ام و درون یک متغییر قرار داده ام.
 
int SSLX = ptb_spaceShip.Location.X;
 
این متغییر موقعیت افقی سفینه را نگهداری می کند، نام آن مخفف SpaceShipLocationX است.
 
int SSLY = ptb_spaceShip.Location.Y;
 
این متغییر موقعیت عمودی سفینه را نگهداری می کند، نام آن مخفف SpaceShipLocationYاست.
 
int SSW = ptb_spaceShip.Width;
 
این متغییر عرض سفینه را نگهداری می کند، نام آن مخفف SpaceShipWidth است.
 
int RW = rocket.Width;
 
این متغییر عرض موشک را نگهداری می کند، نام آن مخفف RocketWidth است.
 
در پایان با استفاده از این متغییرها کد بالا بصورت زیر در می آید. همانطور که مشاهده می کنید این خط خیلی کوتاه تر و قابل فهم شد.
 
rocket.Location = new Point(SSLX + ((SSW/2)-RW/2),SSLY);

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

 
 
 
مسلما روش دوم عقلانی تر است، اما پیداه سازی پیچیده تری دارد. اما برای درک بهتر موضوع بصورت مرحله به مرحله پیش می رویم. 
برای بخش اول به این شکل کار می کنیم، زمانی که موشک به محدوده افقی دشمنان رسید برنامه به بازیکن یک پیغام می دهد، در آینده این کار خیلی کمک می کند، چرا؟ چون تا زمانی که موشک به بالای صفحه نرسیده است نباید دستورات درون این شرط را اجرا کنیم که بار اضافی بر روی دوش سیستم گذاشته نشود. کد زیر را رخداد Tik درون (if (element is Button بعد از خط اول که مربوط به حرکت موشک است قرار دهید.
 

if (element.Location.Y <= (enemiesRow * sizeOfEnemies) + (enemiesRow * verticalSpace))
{
       tmr_main.Stop();
       MessageBox.Show("mooshak be mahdoodeh doshman resid");
}
در تصویر زیر محل قرار گرفتن این قطعه کد را به شما نمایش داده ام.
 
 
برنامه را اجرا کنید مشاهده می کنید که طبق انتظار با رسیدن موشک به سطر دشمنان پیغام نماش داده می شود. اما چگونه کار می کند. بعد از هر بار کم شدن ارتفاع موشک در شرط چک می شود که آیا به سطری که دشمنان قرار دارند رسیده است یا خیر؟ اگر به شرط نگاه کنید در سمت چپ علامت <= موقعیت عمودی موشک قرار دارد و در سمت راست علامت یک عملیات ریاضی، از آنجایی که می دانیم تعداد سطرهای دشمنان در متغییر enemiesRow را قرار دارد، می توانیم با ضرب این متغییر در (ارتفاع دشمنان بعلاوه فاصله عمودی) بین آنها به آخرین ارتفاع ماتریس دشمنان دسترسی پیدا کنیم. با هر بار حرکت هر موشک اینکار انجام می شود و در صورتی که موشک به ارتفاعی برسد که محدوده دشمنان باشد شرط اجرا می شود و سپس تایمر متوقف می شود و پیغام نمایش داده می شود.
*نکته : اگر جای خط MessageBox و tmr_main را جابجا کنید برنامه شما چند پیغام پشت سر هم نمایش می دهد، چون تا زمانی که شما تمام MessageBox ها را نبسته باشید متد stop تایمر اجرا نمی شود و موشک به حرکت خود ادامه می دهد و زیرا با هر Tik این کد اجرا می شود، پشت سر هم به شما پیغام نمایش داده می شود.
اما این کار درست نیست فقط رسیدن به محدوده افقی دشمنان برای ما کافی نیست ما باید به نحوی چک کنیم و ببینیم که آیا این موشک در چارچوب ماتریس قرار دارد یا خیر؟ به این منظور کد فوق را به شکل زیر تغییر می دهیم.
 

if (element.Location.Y <= (enemiesRow * sizeOfEnemies) + (enemiesRow * verticalSpace))
{
if (element.Location.X >= enemies[0,0].Location.X  &&
        element.Location.X  <= enemies[3, 3].Location.X)
          {
                            tmr_main.Stop();
                            MessageBox.Show("Moshak vard mahdoodeh matrix shod");
           }
}

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

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

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

if (element.Location.Y <= (enemiesRow * sizeOfEnemies) + (enemiesRow * verticalSpace))
{
  for (int i = 0; i < enemiesCol; i++)
  {
     for (int j = 0; j < enemiesRow; j++)
     {
 
     }
  }
}

با نوشتن این خط کد زمانی که موشک به ارتفاعی که دشمنان در آن قرار دارند برسد، یکبار کل دشمنان پیمایش می شوند. اما پیش از اینکه ادامه دهم دوست دارم یک بخش دیگر به این کد اضافه کنم چون این بخش کد کمک زیادی به سرعت اجرا برنامه و نیز عملکرد بهتر آن می کند و تا حدودی از پیچیدگی کد هم می کاهد.
می دانیم زمانی که موشک به یک دشمن برخورد می کند هم دشمن و هم موشک از بین می روند و در ادامه برنامه نیازی به آنها نداریم و نباید آنها را ببینیم و یا در برنامه با آنها کار کنیم. در نتیجه در کدهای بعدی که خواهیم نوشت، در زمان برخورد متد Dispose را برای موشک و دشمن فراخوانی می کنیم. با انجام این کار موقعیتی فراهم می شود که دشمنانی را که از بین رفتند مجددا بررسی نشوند. قطعه کد زیر را به حلقه درونی یعنی حلقه ای که شمارنده آن j است اضافه کنید.
if (!enemies[i, j].IsDisposed)
{
}

IsDisposed یک ویژگی مخصوص کنترل ها است. اگر یک کنترل از با متد Dispose از بین ببرید این ویژگی مقدار True را بر می گرداند و اگر هنوز از بین نرفته باشد مقدار False را بر می گرداند. در این شرط ابتدا چک می کنیم که آیا این دشمن قبلا از بین رفته است یا خیر؟ اگر از بین رفته باشد نیازی به بررسی آن نداریم. اما چرا از علامت ! استفاده کردم. فرض کنید که الان یک موشک به یک دشمن رسیده است و زمان برخورد است. اما این اولین باری است که یک موشک به این دشمن برخورد می کند اگر علامت ! وجود نداشته باشد جواب شرط False است زیرا تا کنون متد Dispose برای این دشمن اجرا نشده است پس دستورات درون این شرط اجرا نمی شوند. اما باید اجرا شوند پس با استفاده از علامت ! نتیجه را معکوس می کنیم تا دستورات درون شرط اجرا شوند. حالا اگر دوباره یک موشک به این دشمن برسد جواب IsDispose می شود True اما معکوس آن می شود False و دستورات درون شرط اجرا نمی شوند و برنامه بدرستی کار می کند.
حالا نوبت قسمت اصلی رسید ما تا اینجا بررسی کردیم که آیا ارتفاع موشک به ارتفاع دشمنان رسیده یا خیر؟ اما بررسی نکردیم که نقطه وسط موشک وارد بدنه یکی از دشمنان شده است یا خیر. در این قسمت باید این کار را انجام دهیم. به این منظور باید کد زیر را در بدنه شرط بالا کپی کنیم. اما شما فعلا این کار را انجام ندهید و فقط به کد نگاه کنید.

همانطور که می بینید خطوط بسیار طولانی شدند که باعث سختر شدن فهم آن می شود. اما کل کاری که در این دو شرط انجام شده است این است که در شرط اول چک می شود که آیا موشک از روی محور عمودی در ارتفاعی قرار دارد که بین بالا و پایین دشمن باشد یا خیر؟ در شرط دوم نیز چک می شود که آیا موشک از روی محور افقی بین دو نقطه شروع و پایان دشمن قرار گرفته است یا خیر؟ زمانی که یک موشک هم روی محور عمودی در محدوده یک دشمن باشد و هم روی محور افقی در محدودی یک دشمن باشد، به این معنی است که برخورد صورت گرفته است.
اما چگونه نقطه شروع و پایان یک دشمن مشخص می شود؟ ساده است نقطه شروع که همان موقعیت حال حاضر دشمن است و نقطه پایان را می توان از جمع نقطه شروع با اندازه دشمن به دست آورد. بعنوان مثال در شرط اول موقعیت عمودی دشمن با ارتفاع آن جمع شده است.
هنوز یک نکته دیگر باقی مانده است، چرا در شرط دوم موقعیت عمودی موشک با نصف عرض آن جمع شده است سپس بررسی کرده ایم که آیا بین شروع و پایان دشمن قرار دارد یا خیر؟ خب ما گفتیم که می خواهیم برخورد زمانی صورت بگیرد که وسط موشک وارد بدنه دشمن می شود، با اضافه کردن این قسمت ما موقعیت افقی موشک را با نصف عرض آن جمع می کنیم که در نتیجه موقعیت نقطه میانی بدست می آید.
همانطور که دیدید این کد پیچیده نبود اما چون کمی بلند بود، فهمیدن آن سخت شده بود برای راحتر شدن کار ما مثل قسمت پرتاب موشک آن را به بخش های کوچکتر تقسیم می کنیم و سپس از آن بخشهای کوچکتر استفاده می کنیم. در این بخش سعی کردم تا جایی که ممکن است نام متغییرها را واضح انتخاب کنم و همچنین سعی کردم تا تعداد آنها را زیاد نکنم که ساده تر شود.
ما برای انجام این کار چه چیز هایی نیاز داریم:
مختصات عمودی گوشه بالای موشک
مختصات افقی نقطه وسط موشک
مختصات عمودی و افقی نقطه شروع دشمن
مختصات عمودی و افقی نقطه پایان دشمنحالا که می دانیم چه چیزهایی لازم داریم آنها را بصورت زیر تعریف می کنیم. هر بخش را با شماره مشخص کرده ام.
int elementLY = element.Location.Y; 
int elementMiddlePoint = element.Location.X + (element.Size.Width / 2); 
int startEnemieY = enemies[i, j].Location.Y;
startEnemyX = enemies[i, j].Location.X; 
int endEnemyY = startEnemieY + enemies[i, j].Size.Height;
endEnemyX = startEnemyX + enemies[i, j].Size.Width;
بدنه شرط قبلی را به صورت زیر تغییر دهید:

مواد لازم آماده است و باید شرط ها را بسازیم. شرطهای زیر را در برنامه تان درست زیر همین متغییرهایی که تعریف کردید بنویسید.
if (elementLY >= startEnemieY && elementLY <= endEnemyY)
{
       if (elementMiddlePoint >= startEnemyX && elementMiddlePoint <= endEnemyX)
       {
       }
}
تمام کارهایی که برای لحظه برخورد لازم بود را انجام دادیم حالا نوبت آن رسیده تا کدهای مربوط به این لحظه را بنویسیم.
در لحظه برخورد موشک و دشمن از بین می روند و باید از لیست کنترل های زمینه بازی کم شوند. پس دو خط زیر را در بدنه شرط می نویسیم.
ptb_backGround.Controls.Remove(element);
ptb_backGround.Controls.Remove(enemies[i, j]);
همانطور که قبلا هم گفتم متد Remove بر خلاف متد Add عمل می کند زمانی که بخواهیم یک کنترل را از لیست کنترل های یک نگه دارنده یا کنترل دیگر حذف کنیم به روش فوق عمل می کنیم و نام آن کنترل را به تابع Remove می دهیم.
حالا دیگه نه موشک و نه دشمن جز کنترل های پس زمینه نیستند و در سری بعدی که تابع Tik اجرا می شود آنها بررسی نمی شوند. اما هنوز منابع سیستم را در اختیار دارند پس باید، این قسمت را نیز انجام دهیم.
 
element.Dispose();
enemies[i, j].Dispose();
متد Dispose را به خوبی می شناسید با استفاده از آن منابع سیستم را آزاد می کنیم و کار تمام است. فعلا دیگر با این قسمت کاری نداریم تا زمانی که به قسمت امتیاز دهی بازی برسیم.
امیدوارم که این قسمت به خوبی متوجه شده باشید، مطمعا هستم که اگر با دقت آن را بخوانید،متوجه خواهید شد که خیلی هم ساده است.
سورس بازی را از اینجا دانلود کنید.
 
 

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

نظرات و سوالات کاربران

ارسال پاسخ قاسم
قاسم
شنبه ۰۸ اسفند ۱۳۹۴ ۲۳:۰۸
سلام و تشکر
یه سوال.
چرا شرط قرارگرفتن مختصه X موشک در بین ابتدا و انتهای ماتریس دشمن ها را قبل از اینکه IsDisposed بررسی بشه لحاظ نکردین؟
اینطوری برای همه موشکهایی که فقط Y شان شرط محور عمودی را رعایت کند باید همه محاسبات بررسی شود و با تک تک دشمن ها برخورد یا عدم برخوردش چک بشه. در حالیکه اگر شرط مختصه X رو هم قبل IsDisposed بگذاریم ، این بررسی فقط برای موشک هایی انجام میشه که واقعا درون ماتریس افتادن و نیازی نیست الکی کار اضافی برای موشکهای به درد نخور انجام بشه.
نظرتون چیه؟