ТРЮКИ ПРИ РОЗРОБЦІ 20-ІГОР – РОЗРОБКА ІГОР ДЛЯ ОС ANDROID

Ми дізналися, що OpenGL ES пропонує безліч функцій для графічного 20-програмування Серед них – обертання, масштабування і автоматичне розтягнення конуса відображення до розмірів області перегляду OpenGL ES також дозволяє працювати швидше, ніж при використанні Canvas

Тепер давайте вивчимо більш складні теми, повязані з 20-програмування-ням ігор Деякі з цих концепцій ми використовували інтуїтивно, коли писали Містера Нома, як, наприклад, хронологічно-залежні поновлення станів і робота з атласами текстур Багато чого з того, що нам належить вивчити, інтуїтивно зрозуміло, і цілком імовірно, що рано чи пізно ви самі прийшли б до подібного рішення

Ми розглянемо зручні і найбільш важливі концепції 20-програмування ігор Деякі з них будуть стосуватися графіки, інші будуть присвячені тому, як ми представляємо емуляціях наш ігровий світ В основі всієї цієї роботи лежать лінійна алгебра і тригонометрія Але не бійтеся, щоб написати гру рівня Супер Маріо, математики потрібно зовсім небагато Так що приступимо до вивчення деяких концепцій двомірної лінійної алгебри та тригонометрії

ПЕРЕД СТАРТОМ

Ми збираємося створити кілька прикладів, щоб краще зрозуміти, що відбувається Ми знову використовуємо матеріали, підготовлені в попередній: в основному це будуть класи GLGame, GLGraphics, Texture і Vertices, а також інші класи фреймворка

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

Кожен з цих тестів – це знову-таки екземпляр інтерфейсу Game, а сама логіка тестів реалізована у вигляді Screen, що міститься в Game реалізації тесту Я продемонструю лише ті частини Screen, які важливі в конкретному прикладі, щоб ви краще розуміли процес Що стосується назв, ми знову використовуємо XXXTest і XXXScreen для реалізацій GLGame і Screen кожного тесту

Тепер поговоримо про вектори

СПОЧАТКУ БУВ ВЕКТОР

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

Позиція Ми вже використовували таку інтерпретацію для кодування координат обєктів відносно початку координат

Швидкість і прискорення Про ці фізичних величинах ми поговоримо в наступному розділі Хоча в повсякденному розумінні швидкість і прискорення – це скалярні величини, в 2D або 3D вони виражаються у вигляді векторів Вони кодують не тільки швидкість обєкта (наприклад, машини, що їде зі швидкістю 100 км / ч), але також напрямок, у якому рухається обєкт Зверніть увагу, що така інтерпретація не означає, що вектор знаходиться на початку системи координат Це логічно, оскільки швидкість і напрямок руху не залежать від місця розташування обєкта Уявіть собі машину, що їде на північний захід по прямій трасі зі швидкістю 100 км / ч Якщо швидкість і напрямок не будуть змінюватися, вектор швидкості також залишиться незмінним

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

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

Рис 81 Базові інтерпретації вектора

Ще одна деталь, опущена на рис 81, – інформація про те, в яких одиницях вимірюються компоненти вектора Це завжди слід перевіряти Наприклад, швидкість

Боба може вимірюватися в метрах в секунду, і він буде пересуватися зі швидкістю 2 м вліво, 3 м вгору за одну секунду Це стосується і позицій, і відстаней, які також можуть бути виражені в метрах Напрямок Боба – це особливий випадок, оскільки це безрозмірна величина Без розмірність зручна, якщо потрібно визначити загальний напрямок обєкта, тримаючи фізичні величини напрямки окремо Така ж операція застосовна і для швидкості Боба: можна зберегти напрямок його швидкості в якості вектора напряму, а швидкість зберегти як скалярний значення Для цього вектор напрямку повинен мати довжину 1, однак ми обговоримо це пізніше

Робота з векторами

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

Нічого дивного, ми це вже сто разів робили Кожен вектор має компоненти х і у в двомірному просторі Ми також можемо складати вектори:

Щоб отримати кінцевий вектор, потрібно скласти компоненти Спробуйте це зробити з векторами на рис 81 Припустимо, місце розташування Бобар = (3, 2), а його швидкість v = (-2, 3) Ми переміщаємося в нове місце р = (3 + -2 2 + 3) = (1,5) Не звертайте уваги на апостроф після р, він просто показує, що у нас є новий вектор р Звичайно, це маленька операція доцільна лише тоді, коли місце розташування і швидкість вимірюються в одних і тих же одиницях У цьому випадку допустимо, що місце розташування вимірюється в метрах (м), а швидкість – в метрах в секунду (м / с)

Природно, ми можемо також вичитати вектори:

Знову-таки ми просто комбінуємо компоненти двох векторів Зверніть увагу на те, що порядок, в якому ми віднімаємо один вектор з іншого, дуже важливий Візьміть, наприклад, рис 81 Зелений Боб (на крайньому правому малюнку зверху) знаходиться на pg = (1, 4), а червоний Боб (на крайньому правому малюнку знизу) – на рг = (6 1), де pg і рг означають місця розташування зеленого і червоного відповідно Коли ми віднімаємо вектор відстані червоного Боба від зеленого Боба, виконуються наступні обчислення:

Ось що дивно Цей вектор фактично спрямований від червоного Боба до зеленого Щоб отримати вектор напрямку від зеленого Боба до червоного, потрібно поміняти порядок вирахування:

Якщо ми хочемо знайти вектор відстані між точками, ми використовуємо таку загальну формулу:

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

Ми також можемо множити вектор на скаляр:

Ми множимо кожен компонент вектора на скаляр Це дозволяє нам змінювати довжину вектора Візьміть як приклад вектор напрямку з рис 81 Він визначений як d = (0 -1) Якщо ми помножимо його на скаляр, рівний 2, ми вдвічі збільшимо його довжину: d х s = (0 -1 х 2) = (0 -2) Природно, ми можемо таким шляхом і зменшити його довжину, використовуючи значення скаляра менше одиниці, наприклад d, помножене на s = 0,5, призводить до створення нового вектора d = (0 -0,5)

Говорячи про довжину, ми також можемо підрахувати довжину вектора (у тих одиницях, в яких вона вимірюється):

Запис а просто означає, що ми говоримо про довжину вектора Якщо ви не прогуляли уроки лінійної алгебри в школі, вам має бути відома формула довжини вектора Це теорема Піфагора, застосована до 2Е-вектору Компоненти х і у вектора утворюють дві сторони трикутника, третя сторона – це довжина вектора (рис 82)

Рис 82 Піфагору б сподобалися вектори

Довжина вектора завжди більше нуля, як і квадратний корінь Якщо застосувати це до вектора відстані між червоним і зеленим Боба, то зясуємо, як далеко вони знаходяться один від одного (якщо обидва місця розташування дані в метрах)

Зверніть увагу, що якби забирали рд – рг, отримали б такий же результат, оскільки довжина не залежить від напрямку вектора Однак звідси ми можемо винести ще один урок: коли ми множимо вектор на скаляр, довжина вектора змінюється відповідно Вектор d = (0 -1) з початковою довжиною в 1 одиницю, помножений на 2,5, дасть нам новий вектор з довжиною 2,5 одиниці

Ми вже обговорювали, що, як правило, напрямок векторів є безрозмірною величиною Ми можемо зробити цю величину і розмірної, помноживши вектор на скаляр – наприклад, щоб отримати вектор швидкості v, ми можемо помножити вектор напрямку d = (0 1) на константу швидкості s = 100 м / с: v = (0 х 100 1 х 100) = (0 100) Тому зручно, щоб довжина векторів дорівнювала 1 Вектори, чия довжина дорівнює 1, називаються одиничними (unit vectors) Ми можемо перетворити будь-який вектор в одиничний, розділивши кожен його компонент на його довжину:

Запамятайте, що d означає тільки довжину вектора d Припустимо, нам потрібен вектор напрямку, який вказує рівно на північний схід: d = (1 1) Нам може здаватися, що цей вектор вже одиничний, оскільки обидва його компонента рівні 1, правильно Ні, неправильно:

Ми можемо легко це виправити, перетворивши вектор в одиничний

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

Трохи тригонометрії

Давайте на хвилинку звернемося до тригонометрії У тригонометрії є дві засадничі функції: косинус і синус Кожна з них бере один аргумент: кут Ми звикли вимірювати кути в градусах (наприклад, 45 ° або 360 °) Однак у більшості математичних бібліотек тригонометричні функції вимірюються в радіанах Можна легко перевести градуси в радіани за допомогою наступного рівняння:

Тут використовується до, суперконстант, яка приблизно дорівнює 3,14159265 до радіан дорівнює 180 °

Що ж підраховують функції косинуса і синуса, вимірюючи кути Вони підраховують х-і у-компоненти одиничного вектора відносно початку (рис 83)

Рис 83 Косинус і синус створюють одиничний вектор з його кінцевою точкою, що лежить на одиничному колі

Маючи кут, ми можемо легко створити одиничний вектор напрямку:

Ми можемо вчинити інакше: підрахувати кут вектора відносно осі х

Функція atan2 – це штучна конструкція Вона використовує функцію арктангенса (що є зворотною функцією тангенса, ще однією фундаментальною функцією в тригонометрії), щоб створити кут від -180 ° до 180 ° (або від-pi до pi, якщо кут вимірюється в радіанах) Це все досить складно і не відноситься до теми нашої дискусії, у-і х-компоненти нашого вектора є аргументами функції Зверніть увагу, що вектор необовязково повинен бути одиничним, щоб функція atan2 працювала Крім того, зауважте, що г /-компонент, як правило, дається перше, а х-компонент – другий, однак це залежить від математичної бібліотеки, яку ми використовуємо Це один з найбільш частих джерел помилок

Розглянемо кілька прикладів Маючи вектор v = (cos (97 °) sin (97 °)), ми отримаємо результат atan2 (sin (97 °) cos (97 °)), рівний 97 ° Відмінно, це було досить просто Використовуючи вектор v = (1 -1), ми отримуємо atan2 (-l 1) = -45 ° Так що якщо-компонент вектора негативний, ми отримаємо негативний кут, рівний від 0 ° до -180 ° Ми можемо виправити це, додавши 360 ° (або 2pi), якщо результат atan2 негативний У попередньому прикладі ми тоді отримали б 315 °

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

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

Разом зі складанням, відніманням і множенням векторів на скаляр ми фактично можемо самі реалізувати всі матричні операції OpenGL Це одна з частин комплексного рішення для подальшої оптимізації роботи нашого BobTest Ми поговоримо про це в наступних розділах Тепер сконцентруємося на тому, що ми обговорили, і переведемо все це в код

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

Ми хочемо створити зручний у використанні векторний клас для 20-векторів Назвемо його Vector2 Він повинен складатися з двох елементів, для запису х-і у-компонентів вектора Додатково він повинен мати кілька зручних методів, які дозволять нам виконувати наступні операції: складати і віднімати вектори множити векторні компоненти на скаляр вимірювати довжину векторів нормалізувати вектори обчислювати кут між вектором і віссю х повертати вектор

У Java не застосовується перевантаження операторів, так що нам доведеться знайти механізм, який допоможе зробити роботу з класом Vector2 не такий громіздкою В ідеалі у нас повинно вийти щось в такому роді:

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

Лістинг 81 Vector2java: реалізація функціоналу 2D-вектора

Ми переміщаємо цей клас в пакет com badl ogic androi dgames framework math, де будемо також зберігати всі математичні класи

Починаємо з опису двох статичних констант: T0 RADI ANS і TODEGREES Щоб перевести кут, даний у радіанах, потрібно просто помножити його на TODEGREES щоб перевести в радіани кут, зазначений у градусах, ми множимо його на TO RADIANS Ви можете перевірити ще раз це, подивившись на два попередніх описаних рівності, які призначені для перекладу з градусів в радіани і назад Ця невелика виверт дозволяє обійтися без поділу, тим самим трохи прискоривши роботу

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

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

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

Методи add і sub бувають двох різновидів: в одному випадку вони працюють з двома аргументами у вигляді чисел з плаваючими точками, в іншому – використовують ще один екземпляр класу Vector2 Всі чотири методу повертають посилання на цей вектор, щоб ми могли створити ланцюг операцій

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

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

Math, пропонованого Java SE Це спеціальний клас Android API, який працює, він трохи швидше, ніж його аналог Math

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

Метод angl е обчислює кут між вектором і віссю х, використовуючи метод atan2, що ми і обговорили вище Нам необхідно застосувати метод Math Atan2, оскільки у класу FastMath немає цього методу Повернений кут дається в радіанах, так що ми переводимо його в градуси, множачи його на TODEGREES Якщо кут менше нуля, додаємо до нього 360 °, щоб він мав значення від 0 ° до 360 °

Метод rotate просто повертає вектор навколо початку координат на даний кут Оскільки методи FloatMathcos і FloatMathsinO вимагають вказівки кута в радіанах, переводимо спочатку градуси в радіани Потім використовуємо раніше описані рівності, щоб підрахувати нові компоненти вектора хну, і нарешті повертаємо вектор для додавання в ланцюг

У результаті у нас є два методи, які підраховують відстані між цим та іншим вектором

Ось наш чудовий клас Vector2, який ми будемо застосовувати для подання місця розташування, швидкості, відстані та напрямки в подальшому коді Щоб краще зрозуміти цей клас, використовуємо його на простому прикладі

Простий приклад використання

Ось мій варіант простого тесту

Ми створимо свого роду гармату, представлену трикутником, який має фіксоване місце розташування в світі Центр трикутника буде знаходитися в координатах (2,4 0,5)

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

Конус відображення показує зону нашого світу між (0, 0) і (4,8 3,2) Ми не працюємо з пиксельними координатами, але замість цього описуємо свою систему координат, де одна одиниця дорівнює одному метру Ми також будемо працювати в альбомній орієнтації

Існує кілька нюансів, які заслуговують особливої ​​уваги Ми вже знаємо, як описати трикутник в просторі моделі: для цього можна використовувати екземпляр класу Vertices За замовчуванням наша гармата повинна вказувати направо під кутом 0 ° На рис 84 показана гармата-трикутник в просторі моделі

Коли ми визуализируем цей трикутник, ми просто використовуємо gl Transl atef, щоб пересунути його на його місце у світі, а саме на (2,4 0,5)

Ми також хочемо повернути гармату таким чином, щоб її кінець вказував у бік точки, де відбулося торкання Для цього нам необхідно зясувати, де в нашому світі відбулося остання подія торкання Методи GLGame GetInput getTouchX і getTouchY повернуть точку дотику в екранних координатах з початком у верхньому лівому кутку Крім того, нагадую, що екземпляр класу Input НЕ перетворює координати подій в фіксовану систему координат, як це було в Містері Номі Замість цього ми отримуємо координати (479 319), коли торкаємося нижнього правого кута екрана (ландшафтна орієнтація) на Her, і (799 479) на Nexus One Нам потрібно перевести ці координати торкання в координати нашого світу Ми вже робили це в обробнику торкань в Містері Номі і заснованому на Canvas фреймворці гри Єдина різниця полягає в тому, що наша система координат трохи менше, а вісь у направлена ​​вгору Ось псевдокод, що показує, як ми можемо досягти переведення в загальному випадку, що представляє собою практично те ж саме:

Рис 84 Гармата-трикутник в просторі моделі

Ми нормалізуємо координати дотику до діапазону (0 1), ділячи їх на дозвіл екрана У випадку з у-координатою віднімаємо нормалізовану у-координату події торкання від 1, щоб змінити по модулю напрямок по осі у Залишилося масштабувати координати х-і у-координат відносно висоти і ширини конуса відображення – в нашому випадку це 4,8 і 3,2 З координат world X і world Y можна створити величину Vector2, де буде збережена позиція точки дотику в системі координат ігрового світу

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

Необхідно створити вектор відстані між центром гармати (2,4 0,5) і точкою дотику (памятаєте, що нам треба відняти координати центру гармати з координат точки дотику, а не навпаки) Коли ми отримаємо цей вектор відстані, можемо обчислити кут за допомогою методу Vector2 angl е Цей кут потім можна використаний для повороту нашої моделі за допомогою gl Rotatef

Запрограмуємо це У лістингу 82 показаний відповідний фрагмент нашого класу CannonScreen, що входить до класу CannonTest

Рис 85 Гармата в стані, що задається за замовчуванням, вказує направо (кут = 0 °), точка дотику і кут, на який потрібно повернути гармату Прямокутник – це область світу, яка буде показана в конусі відображення на екрані: діапазон від (0, 0) до (4,8 3,2)

Лістинг 82 Уривок з CannonTestjava дотик до екрану поверне гармату

Починаємо з двох констант, які описують ширину і висоту конуса відображення, як це обговорювалося вище Потім використовуємо екземпляр класу GLGraphi cs, а також екземпляр класу Verti ces Ми також зберігаємо положення гармати в Vector2, а її кут висловлює в числах з плаваючою точкою Нарешті, застосовуємо ще один Vector2, який потрібен для того, щоб обчислити кут між початком вектора і точкою дотику по осі х

Навіщо зберігати екземпляри класу Vector2 як елементи класу Ми могли б створювати їх екземпляри кожного разу, коли вони нам потрібні, однак так виходить занадто багато сміття Як правило, потрібно намагатися створювати екземпляри Vector2 тільки по одному разу і якомога частіше використовувати їх знову

У конструкторі вибираємо екземпляр класу GLGraphics і створюємо трикутник відповідно до рис 84

Далі слід метод update Ми просто перебираємо в циклі всі TouchEvent і підраховуємо кут гармати Це робиться в кілька кроків Спочатку перетворимо координати подій торкання в точки координатної системи світу, як обговорювали це вище Зберігаємо координати подій торкання в координатної системі світу в елементі touchPoi nt Потім віднімаємо положення гармати з вектора touch Poi nt, в результаті отримуємо вектор, показаний на рис 85 Потім обчислюємо кут між цим вектором і віссю х Ось і все

Метод presentC зайнятий все тієї ж нудною роботою, що й раніше Ми задаємо область перегляду, очищаємо екран, встановлюємо матрицю ортогональної проекції, використовуючи ширину і висоту конуса відображення, і повідомляємо OpenGL ES, що всі наступні операції матриці будуть застосовуватися до модельно-видової матриці Ми також завантажуємо одиничну матрицю в модельно-видову матрицю, щоб очистити її Потім множимо (одиничну) модельно-видову матрицю на матрицю трансляції, яка перенесе вершини нашого трикутника з простору моделі в простір світу Також викликаємо gl Rotatef з кутом, який ми підрахували в методі updateO, щоб повернути наш трикутник в просторі моделі, перед тим як перемістити його в простір світу Не забувайте, що перетворення застосовуються в зворотному порядку – останнє вказане зміна буде виконано перший Нарешті, привязуємо вершини трикутника, визуализируем його і одвязуємо

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

Рис 86 Трикутник-гармата, що реагує на подію торкання у верхньому лівому кутку

Зверніть увагу: неважливо, визуализируем ми на місці гармати трикутник або прямокутну текстуру, асоційовану з зображенням гармати, – для OpenGL ES це не грає ролі Ми також знову використовуємо матричні операції в методі present

Справа в тому, що таким чином набагато простіше стежити за станами OpenGL ES Досить часто ми будемо використовувати кілька конусів відображення в одному виклику present (наприклад, розрахунок світу в метрах для візуалізації нашого світу і ще один розрахунок світу в пікселах для візуалізації елементів інтерфейсу користувача) Це не так сильно впливає на швидкодію програми, як фактори, так що цілком можна користуватися цими методами постійно Просто запамятайте, що ми можемо при необхідності оптимізувати цей функціонал

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

Джерело: 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>

*

*