Як зробити арканоид - докладний урок

Увага! Даний сайт не оновлюється. Нова версія: shatalov.su

Продовжуємо розгляд арканоїда. Перед читанням другої частини обов'язково необхідно ознайомитися з першою.

Друга версія була написана десь за двадцять годин - повні вихідні. Сам ігровий процес був повністю скопійований з версії 0.1, яку ми обговорювали в минулому уроці.

У програмі використовується музичний супровід. Для виведення звуку я використовував XAudio2. На жаль, це занадто велика тема, щоб навіть коротко торкнутися її в сьогоднішньому випуску. Про звуці ми будемо говорити набагато пізніше.

Код я вам не дам. Ви повинні написати його самі. Картинки для інтерфейсу знаходяться в папці з грою.

Ігровий інтерфейс

Інтерфейс 0.2 версії грунтується на тому, що був в програмі Клітини v0.3. Але при цьому є невеликі поліпшення. Давайте поглянемо на метод малювання елемента інтерфейсу:

Якщо на зображенні є білий колір, то він взагалі не копіюється. Це дозволяє створювати елементи інтерфейсу будь-якої форми.

У програмі 41 елемент інтерфейсу. Більшу частину написання програми ви витратите якраз на заповнення інтерфейсу:

У більшості елементів батьківськими є або EDITOR або MENU. Деякі елементи стоять особняком - END_GAME. Коли ви почнете будувати інтерфейс, вам стане зрозуміло, який елемент куди помістити.

Елементи зберігаються в масиві покажчиків (я взяв з запасом 50):

Сама таблиця заповнюється в функції InitGraphics. Функція нічим крім кількості елементів не відрізняється від тієї, що була в клітинах.

Після завантаження елементів інтерфейсу відбувається завантаження графіки для м'яча і блоків (для платформи використовується суцільний чорний колір). Ось як виглядає завантаження картинки одного з блоків:

std :: ifstream is4 ( "images / game / metal.bmp", std :: ios :: binary); is4.seekg (54,0); for (int i = 19; i> = 0; --i) is4.read (reinterpret_cast (mdTiles [2] [i * 50]), 50 * 4); is4.close ();

Картинка зчитується через підрядник (розмір блоку - 20 * 50).

Інтерфейс обробляється також як і в клітинах - через функції CheckInt і IntProc. В даному випадку IntProc дуже довга функція.

Тут користувач натискає кнопку, яка збільшить кількість рядків (more - більше). Ми відразу можемо зробити активної кнопку, зменшує кількість рядків. Другий рядок поки чіпати не будемо - тут змінюється розмір рівня. Після цього елемент SAVE_LEVEL можна зробити активним - рівень можна зберігати, тільки коли в ньому були зроблені хоч якісь зміни. В кінці викликається функція CheckInterface. Подивимося на її шматок:

Зверніть увагу, як вважається права межа останнього блоку: c-> ox + c-> columns * (c-> blockWidth + c-> xgap) -c-> xgap. Подібних обчислень в цій функції багато. Такі обчислення потрібні, щоб при збільшенні відступу, кількості блоків, відстані між блоками, нічого не вилізло за екран. Приємного програмування!

Найважливіше - два рядки перед викликом CheckInterface. Перед тим як зміниться поточний рівень, він перезавантажується (reload). Кожен раз, коли користувач натискає "Зберегти рівень", цей рівень записується в файл. При перезавантаженні все зміни, зроблені після останнього збереження, губляться. У ReloadLevel відбувається завантаження рівня з відповідного йому файлу.

Висновок блоків на екран

Ігрова логіка

Під час гри кінцевий автомат може перебувати в одному з трьох станів: NEW_GAME_STATE, START_LEVEL_STATE, RUN_STATE. Всього в програмі шість станів:

Коли користувач натискає "Нова гра", кінцевий автомат переходить в NEW_GAME_STATE:

Тут встановлюється поточний рівень, окуляри, текст, який відображається вгорі, прапор поразки. Гра переходить в стан START_LEVEL_STATE. Код для стану START_LEVEL_STATE набагато складніше, ніж в попередній версії:

Тут можна виділити три частини. У першій відбувається установка платформи і м'яча. Друга і третя частини набагато цікавіше.

У другій частині коду обробляється значення змінної currentLevel рівне 10. Це значення змінна отримує тільки в одному випадку - в кінці гри, коли були пройдені рівні від нуля до 9 (ці рівні можуть бути порожніми). Якщо lvl не дорівнює 10 (в грі були порожні рівні), то в текстовий рядок, яка виводиться у верхній частині екрану, заноситься однозначне число. Якщо lvl дорівнює 10 (були зіграні всі десять рівнів), то в рядок заноситься двозначне число - десять.

Після цього робиться видимим фінальне меню.

Переходимо до коду, наступного за перевіркою на фінальний рівень. Нагадую, що ми знаходимося в стані початку нового рівня (START_LEVEL_STATE). Тут потрібно перевірити прапорець смерті. Якщо в даний стан гравець потрапив з активним прапорцем, то потрібно продовжувати поточний рівень (гравець пропустив м'яч - мінус одне життя). Якщо прапорець смерті не активний (це означає, що гравець пройшов попередній рівень), то ми перезавантажуємо рівень.

В останній частині перевіряється прапор notHollow (hollow - порожній). Тут перевіряється, чи є в рівні хоча б один блок. Якщо в рівні блоків немає, то переходимо (currentLevel) на наступний рівень (lvl не змінюється).

І в кінці переходимо до стану RUN_STATE. Залишився один прапорець - levelLoaded. Під час гри він повинен дорівнювати нулю. Цей прапорець встановлюється при завантаженні гри з диска. Як він працює, трохи нижче.

Останнє стан - RUN_STATE. Код тут дуже схожий на той, який був використаний в версії 0.1. Я позначу лише відмінності:

Під час гри можна вийти в меню. Коли користувач набрав хоч трохи очок, кнопка "Продовжити" стає активною. "Нова гра" переходить в стан GAME_INIT_STATE, а "Продовжити" - в RUN_STATE. Коли користувач виходить в меню, він може натиснути на кнопку "Завантажити". У цей час встановлюється прапорець levelLoaded. Коли користувач натискає "Продовжити", то цей прапорець каже, що треба проинициализировать currentLevel рівень заново. Заодно платформа і м'яч перемістяться в початкове положення.

Наступний цікавий нам шматок коду розташований в перевірці вильоту м'яча за платформу:

Тут встановлюється прапорець смерті. Далі зменшується кількість життів. Тепер потрібно перевірити, чи не скінчилися чи життя? Якщо скінчилися, то показуємо фінальне вікно. Якщо не скінчилися, то переходимо до START_LEVEL_STATE. Нагадую, що при встановленому deadFlag рівень не перезавантажується (це дуже важливо - користувачеві не потрібно починати спочатку).

Тепер потрібно перевірити закінчення рівня. Якщо на полі залишилися тільки порожні і тверді (які не можна розбити) блоки, то рівень скінчився.

При закінченні рівня потрібно зробити кілька речей:

Спочатку збільшується currentLevel. Потім змінюється стан кінцевого автомата на початок рівня. Збільшується lvl. Тут же вдруге перевіряється однозначність / двозначність числа lvl - дійшов користувач до десятого рівня.

Після цього блоку викликається ProcessBall. Функція залишилася практично без змін. Єдине, в даній версії з'явилися тверді блоки, тому потрібна перевірка на зіткнення з ними.

За меню хотілося б зробити кілька зауважень:

Нарешті ми підійшли до частини програми, яку мені б хотілося виділити особливо.

У грі доступно десять рівнів. Всі рівні лежать в папці levels. Файл levels в цій папці - це збережений рівень. Спочатку я хотів, щоб була можливість давати імена файлам, але тоді виникає ряд питань, роз'яснення яких вимагало б випуску два. Тому я використовував задані імена.

Давайте подивимося на клас рівнів:

Тут змінюється ім'я (останній символ в імені - число). І для кожного імені викликається конструктор. Під час завантаження файлу потрібно врахувати два випадки: у файлі може перебувати рівень, і файл може бути порожнім. У першому випадку ми витягуємо всі змінні з файлу. Порядок змінних можете визначити самі. Головне: останнім повинен йти масив map. У другому випадку ми створюємо рівень на льоту (я створюю поле блоків розміром 5 * 5 з порожніми блоками). Ось код конструктора:

У конструкторі можна побачити, як визначається існування файлу.

У деструкції потрібно обов'язково звільнити пам'ять від map.

Метод LoadLevel призначений для завантаження рівня. У використанні трохи відрізняється від ReloadLevel. LoadLevel використовується для завантаження гри, яку користувач зберіг раніше. У метод передається ім'я файлу (завжди levels).

Метод ResizeLevel призначений для зміни розміру рівня (викликається при зміні map). В даному випадку створюється тимчасовий масив, в який зберігається map. map видаляється. Потім виділяється пам'ять для map нового розміру і в map копіюється вміст тимчасового.

Тут є один важливий момент. map - одновимірний масив. Виникає питання: як видалити правий стовпець? Я рекомендую написати цей метод самостійно. Надзвичайно важливо розуміти, як це працює. Про всяк випадок приведу повний код. Аргументами передається нове кількість рядків і стовпців:

Правильне додавання і видалення стовпців показано в перевірках c columns.

У програмі є ще дві функції (не метод): SaveLevel і LoadLevel. У цих функціях викликаються відповідні методи поточного рівня. Крім цього в файл save.sav зберігається (або завантажується з нього) інформація про поточну грі: кількість життів, поточний рівень (і lvl, і currentLevel), кількість очок.

За програмою все.

Зіткнення двох прямокутників

Пора додати в наші програми новий тест - зіткнення двох прямокутників. Це зробить наші програми трохи більш реалістичними. Зіткнення двох прямокутників набагато складніший випадок, ніж зіткнення точки і прямокутника. Способів здійснити таку перевірку досить багато. Я покажу найпростіший.

Спочатку давайте розберемося, як зіткнення двох прямокутників виглядає на одній осі - перетин двох відрізків:

Координати цих відрізків виглядають так: 1 (3,6), 2 (5,8). Минулий випуск розсилки показав, що іноді для спрощення рівнянь потрібно використовувати не самі координати, а мінімальні і максимальні значення. Мінімуми і максимуми цих відрізків будуть виглядати так:

Тест на зіткнення двох відрізків дуже простий: максимум першого відрізка повинен бути більше мінімуму другого і максимум другого - більше мінімуму першого:

Зверніть увагу, що цей тест працює і ось в такому випадку (завдяки тому, що ми використовуємо min і max):

Тепер можна перенести цей тест в двомірне простір. Досить додати таку ж перевірку для другої осі:

Залишилося тільки написати найпростішу функцію визначення мінімуму і максимуму з двох чисел.

поліпшення інтерфейсу

Сьогодні ми додали непогану можливість до нашого інтерфейсу, не витративши на це майже ніяких зусиль. Давайте подумаємо, як ще можна поліпшити інтерфейс, кардинально його не зраджуючи.

Зараз елемент вважається натискує, якщо був пройдений тест:

Тобто якщо курсор був натиснутий над прямокутником, який позначає даний елемент. Ми можемо дуже легко додати в наш інтерфейс елементи будь-якої форми. Для цього досить після вищенаведеної перевірки зробити ще одну - на який піксель потрапив курсор: якщо на прозорий (білий), то елемент не натиснуто. Це може виглядати так:

Тоді весь тест буде виглядати так:

Тепер можна створювати елементи будь-якої форми. Досить тільки неактивні зони позначити білим кольором.

висновок

Програма написана більше двох місяців тому. Єдине чим я незадоволений - клас для рівнів можна було б спростити - у мене багато повторюваного коду. Сподіваюся, у вас вийде це зробити.

вправи

Арканоід

Як зробити арканоид - докладний урок

Камера
Як зробити арканоид - докладний урок

клітини
Як зробити арканоид - докладний урок

спрайт
Як зробити арканоид - докладний урок

Схожі статті