[ 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

    android {     ...      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 ресурсах и хватит. Может, если продукт интересный, он сам найдёт свою аудиторию? (мечты…). Хотел бы почитать в комментариях, как лучше (и желательно недорого) продвигать свой продукт. А то большинство статей на эту тему либо заказные, либо попросту устарели.

Итог

Игру я выложил совсем недавно, поэтому рано ещё говорить о чём бы то ни было. Однако, я многому научился за время разработки игры и полученный опыт можно будет применить на будущих проектах.

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

ссылка на оригинал статьи http://habrahabr.ru/post/265815/

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *