[ libGDX ] Досвід розробки гри з використанням Box2D

Здрастуй, Хабр! Ух! Давно я не писав тут. Отже, почну мабуть з невеликої передісторії і заодно наведу скріншот вийшла гри.

TOTAL100
Скріншот ігрового процесу

Примітка: стаття для новачків, проте, якщо ви гуру і побачили помилку у судженнях/коді автора, то прохання писати про це в ЛС або в коментарях. А я їх додам до статті.


Передісторія

Весь останній рік я працював веб-програмістом на мові Python. Не скажу, що мені це не подобалося, проте душа бажала творчості/незалежності/змін/успіху (підкресліть своє, інді розробники ігор). І був час писати щось своє вечорами, але до чогось хоч трохи значущого це так і не доростало (через банальної втоми і бажання провести час окремо від комп'ютера).
Але раптом, на горизонті замаячила можливість. Я і ще пара друзів вирішили зробити свій проект. Ідея була розписана, робилися презентації, малювалися графіки, середовище розробки була запущена, але не вистачало тільки одного — інвестора. Грошей треба було не дуже багато (за звичайними мірками), але ми так і не змогли знайти зацікавленого людини «при грошах». Що ж, подумав я, раз так, тоді пора б звільнити свою уяву і пустити його в розробку якої-небудь маленької, але гордої ігри. Про це і подальша стаття.

Ідея

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

image
Скріншот головного меню гри

Термін

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

Реалізація

Для реалізації своїх ідей я вибрав свій улюблений фреймворк libGDX, який добре інтегрований з фізичним движком box2D. Взагалі, libGDX прекрасний. У нього дійсно хороша документація, яка виручала мене в 80% випадків, а в 20% допомагав stackoverflow. Також, він зараз прекрасно інтегрується з Android Studio (раніше не міг), що знову ж додає йому плюс. Отримані додатки виходять дуже легкими (на відміну від того ж Unity3D) і досить стабільними (якщо руки не криві, як у мене :) ).

Проблеми

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

  1. Фізичний світ. Чесно, я не знаю, як я міг пропустити цей момент в документації (він описаний в коді), але довго не міг зрозуміти, чому мої фізичні тіла такі повільні. Проблема була в тому, що я вказав розміри камери рівні розміру екрану:
    // Я писав так
    box2DCamera = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
    
    // А треба було наприклад так. 14 -- це число, яке мене влаштувало. scrWidth і scrHeight -- це розміри екрана. Така формула дозволяє запускати цей код на будь-якому екрані і все розтягнеться пропорційно
    box2dCamera = new OrthographicCamera(14 * (scrWidth / scrHeight), 14);
    

  2. Спрайт/Зображення. Якщо працюєш з фізичним движком, то розміри зображень і їх позиції потрібно задавати виходячи з декількох параметрів:
    sprite = new Sprite("your_sprite");
    sprite.setBounds(body.getPosition().x / 2f, body.getPosition().y / 2f, radius, radius); // у моєму прикладі я тіло було круглим, тому тут два рази повторюється radius.
    sprite.setOrigin(sprite.getWidth() / 2f, sprite.getHeight() / 2f);
    

  3. Обертання об'єктів і спрайтів. Якщо ви хочете обертати фізичне тіло, то просто передавши значення кута спрайту ви нічого доброго не отримаєте. Необхідно перевести значення з радианов в градуси:
    sprite.setRotation(MathUtils.radiansToDegrees * body.getAngle());
    

  4. Ще були моменти з dispose() об'єктів текстур і іншої графіки. Справа в тому, що (і це описано в документації, але я її погано читав) якщо ви користуєтеся AssetManager, то ні в якому разі не робіть dispose() окремої текстури, отриманої з менеджера, так як він її видалить і згодом ви побачите чорна пляма замість об'єкта графіки.
  5. Ще було багато матів з видаленням об'єкта з фізичного світу. Рішення гуглится, але я все-таки наведу приклад тут:
    Iterator<GameObject> i = gameObjects.iterator();
    while (i.hasNext()) {
    GameObject object = i.next();
    if (!world.isLocked()) { // ВАЖЛИВО! Якщо не зробити цю перевірку, то отримаєте помилку. Так ми перевіряємо, чи можна видалити об'єкт або він якось використовується в світі (колізії тощо)
    object.getBody().setActive(false);
    if (mouseJoint == null) { // перевірка на дотик об'єкта. mouseJoint -- це захоплене тіло при події TouchDown
    world.destroyBody(object.getBody());
    i.remove();
    }
    }
    }
    

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

Трохи порад

  1. Обфускація і минификация. До цього, я ніколи не обфусцировал код, а тут вирішив спробувати. І знаєте, сподобалося! До обфускации гра важила 4,8 мб, а після її вага зменшився до 3,7 мб. Робіть висновки самі. Тим більше, робиться це просто, адже зараз ProGuard поставляється за замовчуванням з Android SDK і включається парою рядків коду:

    Файл android/build.gradle
    чоловічий {
    ...
    
    buildTypes {
    release {
    minifyEnabled true
    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
    }
    }
    }
    

    Файл android/project.properties
    // Розкоментуйте цю сходинку
    proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
    

  2. Не використовуйте фільтр Nearest (це я про текстури). За замовчуванням, TexturePacker застосовує фільтр Nearest, Nearest. Краще замінити його на Linear, Linear або MipMap, MipMap. Так все буде плавніше, навіть якщо з розмірами картинок не вгадаєте.
  3. Ще про текстури. Не потрібно робити їх максимально великими, а потім зменшувати за допомогою setSize() або setScale(). На виході може вийде змазане зображення.


Графіка і звук

Основний шрифт я взяв Google Fonts, інші елементи або малював сам в Inkscape, або брав безкоштовні іконки і трохи їх правил.
Що ж стосується звуків, щось я взяв з freesound, що зробив сам в LMMS. Вийшло цілком стерпно. До речі, в наступному оновленні хочу додати фонову музику, яку намірився робити в LMMS. Програма сподобалася. Сильно нагадала Fruity Loops Studio.

TOTAL100
Скріншот екрану рівнів

Монетизація

Я поки що вирішив викласти одну безкоштовну версію з «межстраничными» оголошеннями Admob. Потім, якщо гра сподобається публіці, введу можливість для відключення реклами, а також додаткові рівні за «коін».

Просування

Це саме нудне і не найцікавіше заняття з усіх, тому я вирішив, що опублікую на 5-10 ресурсах і вистачить. Може, якщо продукт цікавий, він сам знайде свою аудиторію? (мрії...). Хотів би почитати в коментарях, як краще (і бажано недорого) просувати свій продукт. А то більшість статей на цю тему або замовні, або просто застаріли.

Підсумок

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

Через тиждень-два оновлю статтю і докладу графіки скачувань/прибутку від реклами. Спасибі за прочитання!

Джерело: Хабрахабр

0 коментарів

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