Скриншот игрового процесса
Примечание: статья для новичков, однако, если вы гуру и увидели ошибку в суждениях/коде автора, то просьба писать об этом в ЛС или в комментариях. А я их добавлю в статью.
Предыстория
Весь последний год я работал веб-программистом на языке Python. Не скажу, что мне это не нравилось, однако душа желала творчества/независимости/перемен/успеха (подчеркните своё, инди разработчики игр). И было время писать что-то своё по вечерам, но до чего-то хоть немного значимого это так и не дорастало (ввиду банальной усталости и желания провести время отдельно от компьютера).
Но вдруг, на горизонте замаячила возможность. Я и ещё пара друзей решили сделать свой проект. Идея была расписана, делались презентации, рисовались графики, среда разработки была запущена, но не хватало только одного — инвестора. Денег надо было не очень много (по обычным меркам), но мы так и не смогли найти заинтересованного человека «при деньгах». Что ж, подумал я, раз так, тогда пора бы освободить своё воображение и пустить его в разработку какой-нибудь маленькой
Идея
Я намеренно старался не брать чью-то идею или просто копировать уже готовое. Изначальная идея была в том, чтобы просто бросать шарики разного цвета на стенки того же цвета и вести счёт. Но сделав первый вариант игры я понял, что она скучная и ни разу не затягивает. Тогда-то мне пришла мысль сделать цифры с разными знаками (плюс, минус), вместо шариков и добавить «ненавистный нуль» (с), обнуляющий все очки игрока.
Потом я понял, что игрока нужно как-то ограничивать и добавил таймер. Затем, добавил уровни (хотя, изначально хотел сделать просто таблицу рекордов). Уровни должны были как-то усложняться, поэтому я добавил препятствия в виде стенок и платформ. Уже ближе к концу разработки я подумал, что нулю нужно дать особые права и сделал его неуязвимым для стенок и платформ, что очень злило моих «домашних тестеров» и крайне забавляло меня.
Скриншот главного меню игры
Сроки
Поскольку, у меня в любой момент могло «кончиться» свободное время, я решил поставить себе цель — написать игру за неделю. Цель эту я достиг. Игра, безусловно, ещё сыровата, но всё же уже в маркете и меня это радует.
Реализация
Для реализации своих идей я выбрал свой любимый фреймворк libGDX, который хорошо интегрирован с физическим движком box2D. Вообще, libGDX прекрасен. У него действительно хорошая документация, которая выручала меня в 80% случаев, а в 20% помогал stackoverflow. Также, он сейчас прекрасно интегрируется с Android Studio (раньше не мог), что опять же добавляет ему плюс. Получившиеся приложения получаются весьма и весьма лёгкими (в отличие о того же Unity3D) и довольно стабильными (если руки не кривые, как у меня :) ).
Проблемы
Проблемы — это конечно громкое слово, однако напишу здесь о нескольких возникших проблемках, которые отняли у меня кучу нервов и времени.
- Физический мир. Честно, я не знаю, как я мог пропустить этот момент в документации (он описан в коде), но долго не мог понять, почему мои физические тела такие медленные. Проблема была в том, что я указал размеры камеры равные размеру экрана:
// Я писал так box2DCamera = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); // А надо было например так. 14 -- это число, которое меня устроило. scrWidth и scrHeight -- это размеры экрана. Такая формула позволяет запускать данный код на любом экране и всё растянется пропорционально box2dCamera = new OrthographicCamera(14 * (scrWidth / scrHeight), 14);
- Спрайты/Изображения. Если работаешь с физическим движком, то размеры изображений и их позиции нужно задавать исходя из нескольких параметров:
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);
- Вращение объектов и спрайтов. Если вы хотите вращать физическое тело, то просто передав значение угла спрайту вы ничего хорошего не получите. Необходимо перевести значение из радианов в градусы:
sprite.setRotation(MathUtils.radiansToDegrees * body.getAngle());
- Ещё были моменты с dispose() у объектов текстур и прочей графики. Дело в том, что (и это описано в документации, но я её плохо читал) если вы пользуетесь AssetManager, то ни в коем случае не делайте dispose() отдельной текстуры, полученной из менеджера, так как он её удалит и впоследствии вы увидите чёрное пятно вместо объекта графики.
- Ещё было много матов с удалением объекта из физического мира. Решение гуглится, но я всё таки приведу пример здесь:
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(); } } }
Вроде бы, это все проблемы, с которыми я столкнулся при разработке. Пишите в комментариях о ваших проблемах, может чем-нибудь помогу.
Немного советов
- Обфускация и минификация. До этого, я никогда не обфусцировал код, а тут решил попробовать. И знаете, понравилось! До обфускации игра весила 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
- Не используйте фильтр Nearest (это я про текстуры). По умолчанию, TexturePacker применяет фильтр Nearest, Nearest. Лучше заменить его на Linear, Linear или MipMap, MipMap. Так всё будет плавнее, даже если с размерами картинок не угадаете.
- Ещё про текстуры. Не нужно делать их максимально большими, а потом уменьшать с помощью setSize() или setScale(). На выходе может получится смазанное изображение.
Графика и звук
Основной шрифт я взял в Google Fonts, остальные элементы либо рисовал сам в Inkscape, либо брал бесплатные иконки и немного их правил.
Что же касается звуков, то что-то я взял с freesound, что-то сделал сам в LMMS. Получилось вполне сносно. Кстати, в следующем обновлении хочу добавить фоновую музыку, которую как раз намерился делать в LMMS. Программа понравилась. Сильно напомнила Fruity Loops Studio.
Скриншот экрана уровней
Монетизация
Я пока решил выложить одну бесплатную версию с «межстраничными» объявлениями Admob. Потом, если игра понравится публике, введу возможность для отключения рекламы, а также дополнительные уровни за «коины».
Продвижение
Это самое скучное и не интересное занятие из всех, поэтому я решил, что опубликую на 5-10 ресурсах и хватит. Может, если продукт интересный, он сам найдёт свою аудиторию? (мечты...). Хотел бы почитать в комментариях, как лучше (и желательно недорого) продвигать свой продукт. А то большинство статей на эту тему либо заказные, либо попросту устарели.
Итог
Игру я выложил совсем недавно, поэтому рано ещё говорить о чём бы то ни было. Однако, я многому научился за время разработки игры и полученный опыт можно будет применить на будущих проектах.
Через неделю-две обновлю статью и приложу графики скачиваний/прибыли от рекламы. Спасибо за прочтение!
Комментарии (11)
sim-dev
01.09.2015 08:23Оставил в PlayMarket отзыв с замечаниями, более подробно напишу здесь.
Я столкнулся с тем, что «поймать» летящую цифру пальцем очень сложно. Возможно, на планшете это легко (наверняка там все крупнее), но на смартфоне зацепить пальцем быстролетящую цифру удается с 4-й или 5-й попытки, чаще все мимо.
Ну и, пожалуй, уровни должны отличаться не только всякими преградами, но и скоростью полета цифр. Поначалу для привыкания пусть летят не очень быстро…newca9h
01.09.2015 08:51Спасибо за отзыв! На счёт «поймать» цифру — у моих знакомых сначала это тоже получалось не с первой попытки, но потом они приловчились :) Вообще, мне кажется, что это какая-то особенность box2D, так как пример захвата физического объекта я брал из документации. Может кто-нибудь подскажет, как это лучше сделать? Сейчас это реализовано через queryAABB(). На счёт уровней. Это всё реализуется в будущих обновлениях, главное, что вас игра хоть немного, но «зацепила». Ещё раз благодарю за отзыв и критику!
Coverbacker
03.09.2015 13:36+1Знакомые могут быть бета-тестерами только на очень ранних этапах. Вводи системы сбора данных об игре, читай отзывы и старайся понять реальных игроков. Тем более, в твоем случае у знакомых ТОЖЕ не получалось в начале поймать цифру, но они продолжали потому, что ты их попросил. Игрок продолжать не будет, он уйдет, потому что твоя игра не последняя в мире — есть огромный выбор проектов. На одном только LibGDX игр более 2 000, насколькоя знаю.
Mishok2000
01.09.2015 18:10Пожалуйста, повторите ссылку на игру в конце страницы, а то многие статью просто пролистывают в поиске самой игры.
Afganec
07.09.2015 21:55+1Хочу сказать вам, что вы герой на все 100% только потому, что довели дело до конца. Не все и не всегда доходят до финишной. Тем более, что справились с задачей за неделю! Успехов вам в дальнейших начинаниях.
PapaBubaDiop
Хороший шрифт, дружит ли он с русским языком?
newca9h
Забыл написать название шрифта ( Luckiest Guy ). На счёт русского языка — увы, но нет, не дружит.