Абстрагування У ГРІ MRNOM – РОЗРОБКА ІГОР ДЛЯ ОС ANDROID

&nbsp

Ми нарешті закінчили реалізацію найбільш нудних компонентів для користувача інтерфейсу і написали частина коду для різної рутинної обробки Тепер створимо абстракції для Містера Нома і всіх обєктів в ньому Наш містер Ном перестане залежати від дозволу екрану і зможе жити в своєму невеликому маленькому світі зі своєю невеликою системою координат

&nbsp

Абстрагування світу містера Нома: модель, вид, контролер

Якщо ви програмуєте вже давно, то, можливо, вже чули про патерну проектування Це свого роду стратегії, доречні при створенні коду для даного сценарію Деякі з них тільки теоретичні, інші застосовуються на практиці Для створення гри ми можемо запозичити деякі ідеї з паттерна проектування Модель – вид – контролер (Model-View-Controller, MVC) Він досить часто використовується, наприклад в базах даних, щоб розділити модель даних від рівня представлення та рівня управління даними Ми не будемо строго дотримуватися цього патерну проектування, а приспособим його до наших потреб у спрощеній формі

Що ж це означає для містера Нома Насамперед нам необхідно відокремити представлення нашого світу від растрових зображень, звуків, фреймбу-феров і подій вводу Замість цього змоделюємо світ містера Нома з кількома простими класами в обєктно-оріентрованном стилі У нас буде клас для плям в ігровому світі і клас для самого містера Нома Персонаж складається з голови і хвоста, які ми також представимо в окремому класі Щоб звязати всі разом, використовуємо універсальний клас, що представляє весь світ містера Нома, що включає плями і самого містера Нома Все це буде моделлю у складі MVC

Вид (view) в MVC буде кодом, що відповідає за відображення світу містера Нома У нас буде клас або метод, який отримує клас світу, читає його поточний стан і візуалізує його на екрані Сам процес візуалізації не стосується класів моделі Проте це найбільш важливий урок, який ми можемо витягти з MVC Класи моделі не залежать ні від чого, однак класи та методи виду залежать від класів моделі

У MVC нам також необхідний контролер Він вказує класам моделі змінити їх стан, грунтуючись на таких факторах, як користувальницький введення чого-небудь або хід часу Класи моделі надають методи контролеру (наприклад, інструкції: Поверніть містера Нома направо), які контролер може пізніше використовувати для того, щоб модифікувати стан моделі У нас в класах моделі немає ніякого коду, який мав би прямий доступ до таких компонентів, як сенсорний екран або акселерометр Таким чином, наші класи моделі не мають ніяких зовнішніх залежностей

Можливо, це звучить досить складно і ви дивуєтеся, чому ми діємо саме так Проте подібний підхід має безліч переваг Ми можемо реалізувати всю логіку гри без урахування властивостей графіки, аудіо або пристроїв введення Ми можемо модифікувати візуалізацію світу гри, і нам не доведеться при цьому змінювати самі класи моделі Ми можемо навіть перенести світ з 2D в 3D, а також легко підтримувати нові пристрої введення, використовуючи контролер Контролер всього лише перетворює події введення в виклики методів, що відносяться до класів моделі Хочете повертати містера Нома за допомогою акселерометра Ні проблем – прочитайте значення акселерометра в контролері і перетворіть їх у виклик методу Повернути містера Нома наліво або Повернути містера Нома направо в моделі Містер Ном Хочете додати підтримку пристрою Zeemote Виконайте ті ж самі дії, що і у випадку з акселерометром При використанні контролерів особливо зручно те, що ми не змінимо жодного рядка коду Містера Нома для того, щоб все це вийшло

Почнемо опис світу містера Нома Щоб це зробити, трохи відхилитися від суворого паттерна MVC і використовуємо наші графічні ресурси для ілюстрації основних ідей Це також допоможе нам пізніше реалізувати вид – другий компонент розглянутої моделі (абстрактний світ містера Нома буде виражений в пікселах)

На рис 65 показаний екран гри з накладеним на нього у вигляді клітинок світом містера Нома

Рис 65 Світ містера Нома, накладений на ігровий екран

Зверніть увагу, що світ містера Нома складається з 10 х 13 клітин Ми адресуємо клітини в координатної системі, починаючи з верхнього лівого кута в точці (0, 0) і пересуваючись до правого нижнього кута до (9 12) Будь частина містера Нома повинна бути в одній з цих клітин, а також мати цілочисельні значення координат х і у всередині ігрового світу Це правило діє і для плям у світі Кожна частина містера Нома займає рівно одну клітку – 1 одиницю Запамятайте, що тип одиниці неважливий – це придуманий нами світ, який не залежить ні від системи СІ, ні від пікселів

Містер Ном не може подорожувати за межами цього невеликого світу Якщо він дійде до краю, він просто зявиться з іншого боку, а за ним і всі його частини (До речі, на Землі у нас з вами все точно так ж, просто йдіть в якусь сторону досить довго, і ви повернетеся в місце, звідки пішли) Містер Ном може також пересуватися тільки клітина за клітиною-Всі його частини матимуть цілочисельні координати Наприклад, він ніколи не зможе зайняти дві з половиною клітини

ПРИМІТКА

Як було зазначено вище, тут ми працюємо з нестрогим патерном MVC Якщо ви хочете докладніше вивчити класичний варіант даного патерну, можете почитати у Е Гамма, Р Хелм Прийоми обєктно-орієнтованого проектування Патерни проектування – СПб: Питер, 2012 У цій е патерн проектування MVC іменується Observer (Спостерігач)

&nbsp

Клас Stain

Найпростіший обєкт у світі містера Нома – це пляма Воно просто знаходиться в клітці світу, чекаючи, поки його зїдять Коли ми проектували містера Нома, ми створили три різних на вигляд плями Тип плями не має ніякого значення в світі містера Нома, однак ми все одно включимо цей показник в наш клас Stain У лістингу 68 показаний клас Stain

Лістинг 68 Stainjava package combadlogiсandroidgamesmrnom:

Клас Stain визначає три загальнодоступні статичні константи, які кодують тип плями Кожен обєкт Stain має три члени, х-і у-координати в світі містера Нома і тип, визначений однією з констант Щоб спростити код, ми обійдемося без методів-установників і методів-одержувачів, хоча зазвичай працюємо з ними Ми закінчуємо клас невеликим конструктором, який дозволяє нам легко інстанціювати примірник Stain

Єдиний момент, на який варто звернути увагу, – відсутність будь-якого звязку з графікою, звуком і іншими класами Клас Stain відокремлений і кодує атрибути плями в світі містера Нома

Класи Snake і SnakePart

Містер Ном – це двигающаяся ланцюжок, що складається із зєднаних частин, що рухаються один за одним, коли ми вибираємо одну частину і перетягуємо її Кожна частина займає одну клітку в світі містера Нома, як і пляма У нашій моделі немає відмінності між головою і хвостом, так що ми можемо використовувати один клас для представлення обох цих частин містера Нома

У лістингу 69 показаний клас SnakePart, який використовується для того, щоб описати обидві частини містера Нома

Лістинг 69 SnakePartjava package combadlogiсandroidgamesmrnom

Цей клас практично нічим не відрізняється від класу Stain, ми просто прибрали член type Перший по-справжньому цікавий клас в нашій моделі світу містера Нома – Snake Подивимося, що він повинен вміти робити Отже, тут ми будемо: зберігати хвостову і головний частини дізнаватися, в яку сторону містер Ном рухається в даний момент додавати новий шматок хвоста, коли містер Ном зїдає пляма пересуватися на одну клітку в обраному напрямку

Виконати перший і другий пункти досить просто Ми просто використовуємо список екземплярів класу SnakePart Перша частина в ньому – голова, а решта – хвіст Містер Ном може рухатися вгору, вниз, вліво і вправо Ми можемо запрограмувати це за допомогою декількох констант і зберегти поточний напрямок містера Нома в члені класу Snake

Виконати третій пункт також нескладно Ми просто додаємо ще один екземпляр класу SnakePart в список частин, які у нас вже є Питання полягає в тому, в якому місці слід додавати цю частину Можливо, ви будете здивовані, але нова частина отримує таку ж позицію, що і остання частина в списку Причина цього прояснюється, коли ми дивимося на те, як реалізується останній елемент у попередньому списку – руху містера Нома

На рис 66 показаний містер Ном в його початковому вигляді Він складається з трьох частин: голови в точці (5, 6) і двох частин хвоста в (5, 7) і (5 8)

Рис 66 Містер Ном в початковому вигляді

Частини в списку збудовані по порядку, починаючи з голови і закінчуючи останньою частиною хвоста Коли містер Ном пересувається на одну клітину, всі його частини, що знаходяться за головою, слідують за нею Проте частини містера Нома не завжди становлять пряму лінію, як на рис 66, тому простого пересування всіх частин в напрямку, в якому рухається містер Ном, недостатньо Нам належить зробити дещо складніше

Необхідно почати з останньої частини у списку, незважаючи на те, що це здається нелогічним Ми пересуваємо її туди, де перебувала частина перед нею, і повторюємо це для всіх інших частин в списку за винятком голови, оскільки перед нею немає ніякої частини У випадку з головою ми дивимося, в якому напрямку містер Ном рухається в поточний момент, і змінюємо позицію голови відповідно з цим Малюнок 67 демонструє це з трохи більш складною конфігурацією містера Нома

Рис 67 Містер Ном рухається, і хвіст переміщається за ним

Ця стратегія пересування добре поєднується зі стратегією поїдання Коли ми додаємо нову частину містера Нома, то до того, як він зробить рух, нова частина буде стояти там же, де стоїть частину перед ній Зверніть також увагу, що це дозволить нам легко реалізувати переміщення містера Нома на іншу сторону світу, коли він перетинає край екрана Ми просто правильно встановлюємо позицію голови, інше робиться автоматично

Тепер, коли у нас є вся ця інформація, ми можемо реалізувати клас Snake, що представляє собою містера Нома (лістинг 610)

Лістинг 610 SnakeJava містер Ном в коді package combadlogiсandroidgamesmrnom

Ми починаємо з визначення декількох констант, що задають напрямок руху містера Нома Запамятайте, що він може повернути тільки праворуч або ліворуч, так що дуже важливо, як ми визначимо значення константи Пізніше це дозволить нам з легкістю повертати на 90 °, просто збільшуючи або зменшуючи поточне значення направлення на одиницю

Далі визначаємо список parts, який містить всі частини містера Нома Перший елемент у цьому списку – голова, інші елементи – частини хвоста Другий член класу Snake містить напрям, в якому в даний момент рухається містер Ном

У конструкторі ми задаємо, що містер Ном складається з голови і двох частин хвоста, розташованих в центрі світу, так, як це було показано на рис 66 Ми також ставимо напрямок як SnakeUP, щоб містер Ном переміщався на одну клітину вперед, коли йому скомандують рухатися в наступний раз

Методи turnLeft і turnRight просто змінюють елемент напрямки в класі Snake Щоб повернути ліворуч, ми збільшуємо його на одиницю, а щоб повернути праворуч, зменшуємо на одиницю Нам також необхідно переконатися, що ми правильно виконаємо поворот, якщо значення напрямки перевищить діапазон констант, який ми визначили раніше

Далі розглянемо метод eat Він додає новий елемент SnakePart до кінця списку, ця нова частина буде мати таку ж позицію, як і поточна кінцева частина Наступного разу, коли містер Ном переміститься на одну клітку, ці перекривають один одного частини розєднаються, як ми і обговорювали це раніше

Наступний метод advance реалізує логіку, показану на рис 67 Спочатку ми пересуваємо кожну частину на позицію йде перед нею частини, починаючи з останньої Ми виключаємо голову з цієї операції Далі переміщаємо голову відповідно з поточним напрямком пересування містера Нома Нарешті перевіряємо, чи не вийшов містер Ном за межі світу Якщо вийшов, перекидаємо його на протилежну частину екрана

Останній метод checkBitten – невеликий допоміжний код, який перевіряє, не вкусив би містер Ном свій хвіст Він перевіряє, щоб частини хвоста не перебували в тій же позиції, що і голова Якщо ж це відбувається, містер Ном вмирає і гра закінчується

Клас World

Останній клас нашої моделі називається World Він повинен виконувати наступні завдання: відстеження містера Нома (у вигляді екземпляра класу Snake), а також плям, розкиданих по світу У світі завжди повинно бути не менше однієї плями надання методу, який оновить містера Нома за хронологічним принципом (наприклад, він повинен пересуватися на одну клітку кожні 0,5 секунди) Цей метод також перевіряє, зїв би містер Ном пляма або вкусив себе відстеження рахунки, що фактично є підрахунком кількості плям, зїдених на даний момент, помножених на 10 збільшення швидкості містера Нома після кожних 10 плям, які він зїв Це трохи ускладнить гру відстеження, чи живий містер Ном досі Це знадобиться нам пізніше для закінчення гри створення нового плями після того, як містер Ном зїв поточний (Невелика, але важлива і дивно складне завдання)

У цьому списку є тільки два пункти, які ми досі не обговорили: оновлення світу за хронологічним принципом і розміщення нового плями

Переміщення містера Нома за хронологічним принципом

Ми говорили про хронологічному принципі переміщення Це фактично означає, що ми задаємо швидкість всіх обєктів нашої гри, вимірюємо час, який минув з моменту останнього оновлення (дельта часу), і пересуваємо обєкти, множачи їх швидкість на дельту часу У прикладі для того, щоб цього досягти, ми використовували значення з плаваючою точкою Частини містера Нома мають цілочисельні значення позицій, тому нам належить зясувати, як пересувати обєкти в цьому випадку

Для початку визначимо швидкість містера Нома У світі містера Нома існує час, вимірюваний в секундах Спочатку містер Ном повинен просуватися зі швидкістю одна клітина в 0,5 секунди Все, що нам потрібно, – Стежити за тим, скільки часу пройшло з того моменту, як ми посунули містера Нома востаннє Якщо минулий час перевищує 0,5 секунди, ми викликаємо метод Snakeadvance і скидаємо лічильник часу Звідки ми отримуємо ці дельти часу Памятайте метод Screen update О Він отримує дельту часу для кожного кадру Ми просто передаємо цю інформацію методу update нашого класу World, який веде підрахунки Щоб зробити гру трохи складніше, ми зменшуємо поріг на 0,05 секунди кожен раз, коли містер Ном зїдає 10 плям Звичайно, ми повинні стежити за тим, щоб поріг не досяг 0, інакше містер Ном буде пересуватися зі швидкістю світла Ейнштейну це б не сподобалося

Розміщення плям

Друга проблема, яку нам належить вирішити, – як розмістити нове пляма, коли містер Ном зїв поточне Воно повинне зявитися в випадкової клітці світу Так чому б нам не інстанціювати нове пляма у випадковій позиції На жаль, все не так просто

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

Однак знову це не дуже правильно Якщо ми кожного разу будемо починати пошук з однією і тією ж позиції, пляма не буде розташовуватися випадково Замість цього ми щоразу будемо починати пошук у випадковій позиції світу, сканувати всі клітини до кінця світу, а потім починати спочатку, якщо в цій області вільних клітин не виявилося

Як нам перевірити, чи вільна клітина Найпростішим рішенням буде перевірити всі клітини, взяти х-і у-координати кожної і порівняти всі частини містера Нома з цими координатами У нас є 10 х 13 = 130 клітин, і містер Ном може займати до 55 клітин Це буде 130х55 = 7150 перевірок Звичайно, більшість пристроїв зможе впоратися з такою кількістю операцій, але ми можемо зробити набагато краще

Створимо двомірний масив булевих значень, кожен елемент масиву буде представляти одну клітку світу Коли нам потрібно помістити нове пляма, ми спочатку проходимо через всі частини містера Нома і відзначаємо в масиві зайняті елементи як true Коли ми потім просто вибираємо випадкову позицію, з якої починаємо сканувати, знаходимо вільну клітину, в яку можемо помістити нове пляма У разі, коли містер Ном складається з 55 частин, це займе 130 + 55 = 185 перевірок Ось так набагато краще

Визначення закінчення гри

Залишилося ще одна річ, про яку нам необхідно подбати: що робити, якщо всі клітини зайняті містером номом У цьому випадку гра буде закінчена, оскільки містер Ном стане всім світом Беручи до увагу, що ми додаємо 10 очок кожного разу, коли містер Ном зїдає пляма, максимально можливий рекорд 10 х (13 – 3) х 10 = 1000 очок (памятаєте, містер Ном починає з трьох частин)

Реалізація класу World

Нам з вами треба реалізувати ще чимало речей, так що почнемо У лістингу 611 показаний код класу World

Лістинг 611 Worldjava package combadlogiсandroidgamesmrnom

Як завжди, починаємо з визначення пари констант В даному випадку це ширина і висота світу в клітинах, значення, на яке ми збільшуємо рахунок кожен раз, коли містер Ном зїдає пляма, початковий тимчасової інтервал, що використовується для пересування містера Нома (tick), і значення, на яке ми зменшуємо tick кожен раз, коли містер Ном зїдає 10 плям, щоб трохи прискорити персонажа

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

Визначаємо ще чотири закритих члена пакету: двомірний масив, який будемо використовувати для того, щоб розмістити нове пляма, екземпляр класу Random, за допомогою якого будемо отримувати випадкові числа, щоб розмістити пляму і згенерувати його тип, змінна лічильника часу tickTime, до якої будемо додавати дельту, і поточна тривалість tick, визначальна, наскільки часто ми просуваємо містера Нома

У конструкторі створюємо екземпляр класу Snake, який буде містити початкову конфігурацію, показану на рис 66 Ми також розмістимо перший пляма у випадковому місці за допомогою методу piaceStai

Метод piaceStaiп реалізує стратегію розміщення, описану вище Ми починаємо з очищення осередків масиву Далі встановлюємо всі комірки, зайняті частинами містера Нома в true Після скануємо масив у пошуках вільного осередку, починаючи з випадкового місця Як тільки ми знайшли вільну комірку, створюємо пляма випадкового типу Зверніть увагу, що якщо всі клітини зайняті містером номом, цикл ніколи не закінчиться Ми виключимо таку ситуацію в наступному методі

Метод update відповідає за оновлення класу World і всіх обєктів в ньому залежно від значення дельти часу, який ми йому передаємо Цей метод буде викликатися для кожного кадру ігрового екрана, щоб світ гри постійно оновлювався Спочатку перевіряємо, закінчена Чи гра Якщо вона закінчена, то, природно, нам не потрібно нічого оновлювати Далі додаємо дельту часу до нашого лічильнику Цикл while буде задіяти стільки tick, скільки накопичилося (наприклад, коли tickTime дорівнює 1,2, а один tick дорівнює 0,5 секунди, необхідно оновити світ двічі, залишивши 0,2 секунди в лічильнику) Цей прийом називається моделюванням з фіксованим кроком часу (fixed-time-step simulation)

У кожній ітерації ми спочатку забираємо інтервал tick від лічильника Далі наказуємо містеру Ному рухатися Ми перевіряємо, не вкусив він себе Якщо вкусив, встановлюємо прапор Гра закінчена (gameOver) Потім перевіряємо, чи не знаходиться голова містера Нома на тій же клітині, що і пляма Якщо знаходиться, збільшуємо кількість набраних очок і вказуємо містеру Ному вирости на одну частину Далі перевіряємо, чи не чи складається містер Ном з такої ж кількості частин, що і його світ Якщо складається, гра закінчена У всіх інших випадках розміщуємо нове пляма за допомогою методу pi aceSta in Останнє, що ми робимо, – Перевіряємо, зїв би містер Ном ще 10 плям Якщо зїв і наш поріг вище нуля, зменшуємо поріг на 0,05 секунди Наш tick стане коротшим, що змусить містера Нома рухатися швидше

Отже, ми закінчили написання безлічі класів моделі Тепер реалізуємо ігровий екран

Клас GameScreen

Нам залишилося реалізувати ще всього один екран Розглянемо, що цей екран повинен робити

Як описано у проекті містера Нома, програма може знаходитися всього в чотирьох станах: очікування, поки користувач підтвердить, що він готовий режим роботи очікування в режимі паузи і очікування, поки користувач натисне кнопку в стані Гра закінчена Опишемо ці стани докладніше: в стані готовності ми просто просимо користувача доторкнутися до екрана, щоб запустити гру в працюючому стані оновлюємо світ, визуализируем його, а також наказуємо містеру Ному повернути ліворуч або праворуч, коли користувач натискає одну з кнопок внизу екрану в стані паузи ми просто показуємо дві команди: повернутися до гри і вийти з неї в стані Гра закінчена повідомляємо користувачеві, що гра закінчена і надаємо йому кнопку, щоб він міг повернутися в головне меню

Для кожного стану у нас є різні методи для реалізації оновлення та поточного стану (update і present), оскільки кожний стан відповідає за різні речі і показує різні користувальницькі інтерфейси

Коли гра закінчена, необхідно переконатися, що ми зберегли результат, якщо він є рекордним

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

Малюнок 68 демонструє чотири різних стану

Рис 68 Ігровий екран в чотирьох станах: готовність, робота, пауза, гра закінчена

Зверніть увагу, що ми також визуализируем набрані очки в нижній частині екрана разом з лінією, яка відокремлюємо світ містера Нома від кнопок внизу Набрані очки візуалізуються так само, як і на екрані HighscoreScreen Крім того, ми розміщуємо їх по центру, грунтуючись на ширині смужки набраних очок

Останній відсутній фрагмент інформації стосується того, як візуалізувати світ містера Нома, грунтуючись на його моделі Загалом-то, це вельми просто Подивіться на рис 61 та 65 знову Кожна клітина має розмір рівно 32 х 32 пікселя Зображення плями також мають розмір 32 х 32 пікселя, як і частини містера Нома Розмір голови містера Нома у всіх напрямках становить 42 х 42 пікселя, так що вона не поміщається повністю на одній клітці

Проте це не проблема Все, що нам треба, щоб візуалізувати світ містера Нома, – взяти кожну пляму і частина містера Нома і помножити координати світу на 32, щоб потрапити в центр обєкта в пікселах на екрані Наприклад, центр плями з координатами (3, 2) в світі буде знаходитися на 96 х 64 екрана У такій ситуації залишається тільки вибрати відповідний обєкт і відобразити його, центруючи по координатах Розглянемо код У лістингу 612 наведено клас GameScreen

Лістинг 612 GameScreenjava package combadlogiсandroidgamesmrnom

Починаємо з визначення переліку під назвою GameState, який задає чотири стани (готовий, робота, пауза, гра закінчена) Далі визначаємо член, який містить поточний стан екрану, і член, який включає в себе екземпляр класу World, а також ще два члени, що містять поточні набрані очки у вигляді цілого числа і рядки Причина, по якій нам необхідні останні два члени, полягає в тому, що ми не хочемо постійно створювати нові рядки із змінної World score кожен раз, коли отрісовиваємих рахунок Замість цього ми кешіруем рядок і створюємо нову, тільки коли рахунок змінюється Таким чином, у збирача сміття не виникає проблем

Конструктор даного класу просто викликає конструктор свого батьківського класу і створює новий екземпляр класу World Ігровий екран буде в стані готовності після того, як конструктор поверне управління викликає стороні

Далі слід метод update екрана Він вибирає TouchEvent і KeyEvent з модуля введення і передає їх для оновлення одного з чотирьох відповідних методів, які ми реалізуємо для кожного стану в залежно від поточного стану:

Наступний метод називається updateReady Він буде викликатися, коли екран знаходиться в стані готовності Він перевіряє, чи був натиснутий екран Якщо натискання було, він змінює стан на робочий

Метод updateRunning перевіряє, чи натиснута клавіша паузи у верхньому лівому куті екрану Потім цей метод перевіряє, чи була натиснута яка-небудь з кнопок контролера внизу екрана Зверніть увагу, що ми не перевіряємо тут події відпускання (touch-up) Якщо яка-небудь кнопка була натиснута, ми повідомляємо екземпляру класу Snake в класі World повернути ліворуч або праворуч Все правильно, метод updateRunning містить код контролера нашої схеми MVC Після того як всі події торкання перевірені, ми наказуємо світу оновитися, передаючи йому дельту часу Якщо клас Worl d сигналізує, що гра закінчена, ми переходимо до відповідного стану, а також відтворюємо звук bittenogg Далі перевіряємо, чи відрізняється колишню кількість очок, яке ми помістили в кеш, від результату, який зберігає World Якщо відрізняється, нам стають відомі дві речі: містер Ном зїв пляма і рядок результатів повинна бути змінена У цьому випадку ми програємо звук eat ogg Ось, власне, і все, що стосується оновлення поточного стану

Метод updatePaused знову-таки просто перевіряє, чи була натиснута одна з команд меню, і переходить до відповідного стану

Метод updateGameOver також просто перевіряє, чи натиснута кнопка в центрі екрану Якщо натиснута, виконується перехід назад в головне меню

Далі йдуть методи відображення Метод present спочатку малює фонове зображення, оскільки воно необхідне нам у всіх чотирьох станах Далі він викликає відповідний отрісовочний метод для стану, в якому ми знаходимося Нарешті, він візуалізує світ містера Нома і малює кількість набраних очок в нижній центральній частині екрана

Метод drawWorl d малює світ приблизно за тим же принципом, що був розглянутий вище Він починає з вибору Pixmap для візуалізації плями, потім малює пляму і центрує його по горизонталі в потрібному місці екрану Далі визуализируем всі частини хвоста містера Нома, що вельми просто Потім вибираємо, який Pixmap для голови слід використовувати, грунтуючись на напрямку, в якому рухається містер Ном Малюємо Pi xmap залежно від положення голови, переведеного в екранні координати Розміщуємо його по центру, як і інші обєкти Ось код view в MVC

У методах drawReadUK, drawRunningUK, drawPausedUK іdrawGameOverUK немає нічого нового Вони виконують все ту ж візуалізацію користувача інтерфейсу, засновану на координатах, які показані на рис 68 Метод drawText аналогічний методу, використаному в HighscoreScreen, так що ми не будемо його обговорювати

Є і ще один найважливіший метод – pause, який викликається, коли активність ставиться на паузу або ігровий екран замінюється на який-небудь інший Це саме відповідне місце для збереження налаштувань Спочатку ми встановлюємо стан нашої гри як paused (Зупинено) Якщо метод paused викликається в звязку з призупиненням роботи активності, користувачеві обовязково буде запропоновано відновити гру з того місця, на якому гра була перервана Це досить непогано, тому що можна відразу приступити до гри з того моменту, на якому він закінчив Далі перевіряємо, чи не знаходиться гра в стані Гра закінчена Якщо це так – додаємо результат користувача в таблицю рекордів (або не додаємо, це залежить від значення) і зберігаємо всі налаштування в зовнішньому сховищі

Ось і все Ми написали повноцінну гру на Android з нуля Ми можемо пишатися собою, оскільки впоралися з усіма аспектами, які дозволять нам створити практично будь-яку бажану гру Тепер нам залишилися тільки всякі дрібниці

Підводячи підсумок

Ми повністю реалізували гру, засновану на нашому фреймворці Ви дізналися, чому слід відокремлювати модель від виду і контролера, а також вивчили, чому не треба описувати світ гри на рівні пікселів Ми можемо взяти створений код і частково виконати візуалізацію за допомогою OpenGL ES, зробивши містера Нома тривимірним Ми можемо також зробити поточну візуалізацію цікавіше, збагативши містера Нома анімацією, кольором, нової ігрової механікою і т д Ми лише трохи доторкнулися до доступним можливостям

Перед тим як ви продовжите читати у, я пропоную вам взяти код гри і трохи поекспериментувати з ним Додайте небудь нові режими гри, бонуси і ворогів – все, що ви зможете придумати

Ми покращимо наші навички графічного програмування, щоб зробити ігри трохи більш привабливими, а також виконаємо перші кроки в тривимірний простір

Джерело: Mario Zechner / Маріо Цехнер, «Програмування ігор під Android», пров Єгор Сидорович, Євген зазноби, Видавництво «Пітер»

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*