Як набити купу шишок і випустити гру

Все почалося в далекому 2013 році. Тоді іграшок на Android було хоч і багато, але всяко менше ніж зараз. І випустивши свою гру на тоді ще Android Market, можна було отримати яку-ніяку, а грошики. А оскільки мені подобається робити ігри, сумнівів не було — випускаємо гру. Ми — це маленька команда з програміста, художника і тестувальника\генератора ідей. Забігаючи наперед — граблів і грабель ми зібрали предостатньо. В основному буде огляд технічних моментів, тому стаття буде корисна всім, хто якось пов'язаний з розробкою ігор. Не повторюйте наших помилок.





ІДЕЯ ГРИ
З існуючих на той момент ігор нам сподобалась гра, де катається кульку по лабіринту, ухиляючись від дірок, і намагаючись дістатися до фінальної лузи. На той момент таких ігор вже було кілька штук, але по частині графіки вони були не дуже. Тому ми вирішили зробити свою, з упором на барвистість. Хочеться відзначити художника, що малював він дуже і дуже класно. Завдяки йому гра вийшла такою як є — красивою. Ім'я грі дали просте — Labirinth.

В якості движка було вирішено використовувати libGDX. Я добре знав його на той момент, і він мені подобався, вже були завершені проекти на ньому. Я був впевнений, що ми з ним дійдемо до переможного кінця. Як виявилося — дійшли.

ОСОБЛИВОСТІ ГЕЙМПЛЕЯ
В принципі, геймплей вийшов стандартним для ігор такого типу — катаємо кулька з допомогою акселерометра. Додаткова фішка — у нас є телепорти, а стіни можуть бути не просто нудними прямокутниками. Але про це далі.

З особливостей хочеться відзначити керування. Ми довго підбирали налаштування акселерометра для комфортної гри, але наш тестер постійно наголошував, що щось не так. Як приклад він наводив схожу гру з Android Market. В кінці-кінців, я скачав цю гру, декомпилировал (ще здивувався, що вона була не обфусцирована), і подивився момент з керуванням. Я виявив, що код ідентичний моєму, відрізняються лише коефіцієнти. Зазначу, що декомпилированная гра була теж написана на libGDX — тобто, в той час на ньому вже писали масові ігри.


У нас можна налаштувати управління

ТЕХНІЧНА ЧАСТИНА
Як я вже згадав, гра написана на libGDX. У грі використовується безліч ресурсів — графіка, звуки, карти, шрифти. Пройдемося по кожному моменту.

Графіка. В грі є кілька сотень спрайтів. З метою продуктивності, всі спрайт були упаковані в текстурні атласи. Окремий атлас для екрана меню, окремий — для вибору рівнів і т. д. Стандартна техніка, яка економить і відеопам'ять, і витягає продуктивність. Атласи пакувались програмою Texture Packer (доступна для скачування на сайті libGDX). Якби я робив це зараз — я б використовував вбудований клас TexturePacker. Упаковка атласу виглядає приблизно так — TexturePacker.pack(«folder-with-sprite», «output-folder», «atlas-name»). Перевага такого підходу — не потрібно перемикатися з IDE на іншу програму, щоб перепакувати атласи.


Ось так виглядає зменшений атлас з різними кульками. Це мала частина, у нас кілька таких атласів.

Формат атласів — 32 біта, png. Деякі картинки, що без прозорості, упаковані в jpeg атласи. Якби я робив зараз — атласи без прозорості я пакував у ETC1. У libGDX є рідна підтримка цього формату, а для Android це економія відеопам'яті.

Для керування графікою (завантаження\вивантаження атласів, отримання потрібної картинки) був написаний свій самопальний клас Images. Я знав про вбудований AssetManager, але на той момент мені потрібно було більш зручне рішення. Наприклад, якщо я хочу отримати якусь фонову картинку, я хочу, щоб попередній запитаний фон був автоматично вивантажений. Моє рішення дозволяло робити такі речі.

Відходячи трохи в сторону — коли я пишу гру, я часто роблю свої менеджери ресурсів. Часто на базі стандартного AssetManager. Мета — більш простий доступ до ресурсів. На більш пізніх стадіях розробки читаність коду відіграє дуже важливу роль. А добре написаний код, як книга. Простіше зрозуміти рядок images.createButton(«green-button»), ніж assetManager.get(TextureAtlas.class, «data/atlases/buttons»).findRegion(«green-button»). З мінусів такого підходу — коли повертаєшся до гри через тривалий час, потрібно згадати особливості цих менеджерів. З плюсів — коли згадаєш, далі правити що-то вже просто.

Звуки. Всі звуки в грі у форматі .ogg. Чому не mp3? На деяких моделях телефонів на той момент були проблеми з відтворенням декількох звуків поспіль у форматі .mp3. Я припускаю, що це були мої криві руки, але з .ogg проблем не виникло. Благо libGDX підтримує обидва формати, заміна .mp3 .ogg була нескладною.

З метою оптимізації звуки були пожаты в мінімальний бітрейт, формат — моно. Розробка велася на linux, для конвертації використовував Sound Converter. На мій погляд, Sound Converter — це зразок відмінною утиліти, якою я користуюся до сих пір. Одне маленьке віконце з налаштуваннями для вихідного формату, і кнопка «Convert». Відмінний відповідь монструозным конвертера.

Шрифти. В грі використовуються два типи шрифтів.

Перший тип — це .ttf шрифти і бібліотека gdx-freetype. Вона дозволяє під час виконання генерувати .ttf шрифти растрові зображення (bitmap fonts). Перевага бібліотеки — можна задати потрібний розмір шрифту прямо під час запуску програми. Це гарантує, що шрифт завжди буде саме того розміру, що потрібно. З недоліків — оскільки шрифт генерується «на льоту» в текстуру, це означає, що це буде їсти оперативну пам'ять. І чим більше розмір шрифту, тим більше її потрібно, цієї самої пам'яті. Ще один неочевидний момент — оскільки шрифт — це окрема текстура, то при відображенні будь написи буде додатковий draw call (за винятком моменту, коли ми малюємо кілька написів поспіль). Виходячи з цього, область застосування .ttf була обмежена лише екраном «About».

Другий тип шрифтів — це distance field шрифти. Ідея проста — за допомогою програми Ієрогліф (доступна для завантаження на офіційному сайті libGDX) генерується спеціальна текстура шрифту. В текстурі для кожної літери виділена своя область. Але ця область не малюється «як є». Замість цього застосовується спеціальний шейдер, який особливим чином малює цей регіон (звичайно, це теж ламає батчинг). Перевага — з відносно маленькою текстури можна перетворювати шрифт дуже великим розміром без втрати якості. Недоліки — шрифт потрібно заздалегідь спланувати, і для шрифту недоступні ефекти (тінь, обведення, тощо). Також, як я згадав, це ламає батчинг. Можна лише під час відтворення встановлювати колір шрифту. Для наших потреб цього вистачало, тому в грі повсюдно використовується саме Distance Field шрифти.


Ось так виглядає Distance Field шрифт. Фон повинен бути прозорим, я лише для наочності зробив його чорним.

Карти. Однією з «фішок» хорошої гри є кількість контенту. Можна генерувати його автоматично, а можна підготувати набори рівнів. Ми пішли другим шляхом — у нас більше сотні рівнів. Рівні розбиті на коробки — в коробці 15 рівнів. Зрозуміло, що з такою кількістю рівнів виникає питання їх зручного редагування та зберігання. Потрібен якийсь редактор карт. Справа ускладнюється тим, що у нас є два типи карт — прості і «оригінальні» (під час розробки ми називали їх саме так).

Прості карти — це стандартні для цього типу ігор лабіринти з прямокутними перешкодами. Для редагування таких карт я написав редактор — простенька Java-програма з використанням Swing як графічної бібліотеки. Вручну можна розставити перешкоди, дірки, і т. д. Перешкоди можна обертати, змінювати розмір як завгодно — в цьому плані редактор вийшов досить зручним. Тестер скаржився, що незручно, що не можна скасовувати дії — я його зрозумів, і написав скасування\повторення дій, вивчивши патерн Команда.


Редактор карт в дії


Проста банальна карта — нічого незвичайного

Фішка — щоб швидше тестувати карти, в редакторі була можливість перемикання в гру. Одразу (в цьому ж вікні) запускалася гра, де можна було перевірити карту. У ролі акселерометра виступала миша. Можу сказати, що написання редактора — це та фраза, «краще день втратити, потім за п'ять хвилин долетіти».

Оригінальні карти. Так ми називали вручну намальовані художником картинки, де окремі елементи зображення — це перешкоди. Нижче — приклад такої карти.


Футбольне поле — вже цікавіше

Зрозуміло, що стандартний редактор карт не підходить в цьому випадку. Я подумав, і вирішив проблему наступним чином — беремо редактор tiled. У ньому створюємо два шари — один фоновий, там буде картинка оригінального рівня. Другий шар — це шар розмітки. На ньому примітивами (колами, квадратами і полігонами) розмічається карта. Наприклад, дірка — це коло з ім'ям «hole» (tiled дозволяє кожному об'єкту призначити своє ім'я). На виході ми отримуємо xml-файл у форматі tiled. Але нам цей формат не підходить. Тому я написав додаткову утиліту, яка бере згенерований tiled xml, і конвертує в мій формат рівня. На цьому ж етапі з'ясувалося, що box2d не підтримує полігони, які складаються більше ніж з 4-х вершин — простіше кажучи, чотирикутники (принаймні порт box2d для libGDX).


Розмічаємо оригінальну карту

Весь цей «Франкенштейн» хоч і виглядав незграбно, але своє завдання виконав на відмінно. Майже половина ігрових рівнів «оригінальні», і завдяки tiled ми обійшлися без написання ще одного редактора. Висновок — іноді не потрібно писати своє щось складне і велике, достатньо озирнутися по сторонах, і взяти готове рішення з мінімальним допиливанием «під себе».

АНІМАЦІЯ КУЛЬКИ
Коли кулька рухається, він повинен якось анимироваться, створювати ілюзію руху. Деякі ігри цього жанру не заморочуються з цим, і використовують просту статичну картинку. Ми вирішили заморочити.

У нас є кілька «скінів» кульки — скляний, вогненний (лава), футбольний і т. д. На той момент я був не в курсі можливостей 3D в libGDX, і використовувати 3D-модельку не міг. Тому вирішили робити покадрової анімації. Повний оборот кульки у нас займав близько 50 кадрів. В залежності від швидкості руху я змінював частоту зміни кадрів, створюючи ілюзію більш швидкого або повільного обертання. Ну і звичайно міняв поворот цих кадрів. Не можу сказати, що вийшло ідеально, 3d модель однозначно дала б більш красиву картинку, але що є, то є.

ФІЗИКА
У грі використовується движок box2d (точніше, його порт під libGDX). З плюсів — він швидкий, і можливостей у нього багато. З мінусів — потрібно його знати :) Кулька — це динамічне тіло, перешкоди — статичні тіла. Коли завантажується рівень, створюється фізична модель світу, і, власне, починається гра. При паузі симуляція зупиняється. В принципі, це все, що можна сказати про фізику — особливих проблем з нею не виникало.

ДОДАТКОВІ ФІШКИ
Екран меню у нас — непростий. Він теж виконаний, як частина гри. По ньому катається кулька, слухаючись акселерометра. А коли кулька вдаряється про кнопки, він відскакує. А якщо торкнутися кулькою певних кнопок в певному порядку, відкриється портал до секретний рівень :)


Кулька катається і відскакує від кнопок — як в пинболе

Є багато досягнень — за проходження рівнів, за певну кількість виграшів, за певний час в грі, і т. д. Ми не використовували стандартні Android Google Play досягнення, а зробили свою систему досягнень.


Мала частина досягнень

МОМЕНТИ, ЯКІ Я Б РОБИВ ІНАКШЕ
Зрозуміло, що не буває ідеальних рішень, і з досвідом приходить розуміння, що та або інша частина зроблена неоптимально. Я скажу, що я б переробив, роби гру я зараз.

Перший момент — це вибір коробок. Якщо ви відкриєте гру, і подивіться на коробки, ви побачите, що на кожній коробці є точки. В залежності від складності, цих точок більше або менше. Так от — для розстановки цих точок я написав клас, який завантажує json-файл, і з цього конфіг-файл розставляє крапки. Мотивація, чому я так робив — заощадити місце в текстурних атласах. Тобто, ми зберігаємо загальну картинку-фон коробки, одну точку, і порядок розташування цих точок для конкретної коробки. Чому це погано — складність модифікації. Якщо б кожна коробка була окремою картинкою, досить підправити картинку — а тепер треба лізти в конфіг-файли і розбиратися що і як. Краще б зберігати це просто картинками.


Кожна коробка — це не просто картинка. Це підкладка + текстовий файл-конфіг з розташуванням точок.

Другий момент — я дуже заморочился з оптимізацією (приклад вище — тому підтвердження). Всі екрани (меню, вибір коробок, ігровий і т. д.) я створив в одному примірнику, і просто переключається між ними. Це позбавило від створення цих екранів щоразу, коли ми переходимо в інший екран, але призвело до проблеми очищення. Наприклад, ігровий екран зберігає безліч параметрів для конкретного рівня (наприклад, окуляри, час тощо). Коли ми завантажуємо новий рівень, потрібно спочатку скинути стан ігрового екрана. Але у міру додавання нових фішок легко було забути щось очистити. Це «сьєло» пристойний шматок часу, налагодження таких проблем. Коли я роблю гру зараз, я роблю інакше. Якщо потрібно змінити екран — я зберігаю потрібні для цього екрану параметри в якомусь класі налаштувань, потім створюю новий екран. Той новий екран створюється, і читає потрібні дані з класу налаштувань. При цьому витрачається більше ресурсів, але на тлі сучасних потужних телефонів цей час непомітно.

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

ВИСНОВОК
Ідеальних ігор не буває, як і не буває ідеальною розробки. Через деякий час, ми довели гру до кінця, і вона побачила світ в Google Play. Що сказати — незважаючи на всі шишки і граблі, займатися розробкою ігор цікаво. Всяких моментів виникає багато, деякі моменти не можна вирішити, і доводиться шукати обхідні шляхи. Але рішення цих проблем піднімає ваш рівень, як розробника. Мені було цікаво писати цю гру, і я вірю, що в неї буде цікаво грати.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.