Привет!
Вспомнил я тут недавно, что скоро День святого Валентина и на хабре год назад (и не только) были какие-то топики, касающиеся его. Я решил поддержать данную традицию и сделать в этом году тоже что-нибудь оригинальное и необычное, а позже написать об этом топик.
И решил я создать простенькое приложение под Android с сердечками, которые бы имели свои физические модели и взаимодействовали друг с другом. Это было легко и быстро реализовано. Далее я добавил ко всему этому зоопарку текст, звуки, частицы и некоторые другие красивости. В результате получилось даже что-то вменяемое и оригинальное, о чем я и поспешу рассказать в этой статье, а заодно и пропиарить описать замечательную кроссплатформенную библиотеку libgdx.
В конце топика выложены исходники проектов, jar исполняемый файл и некоторые использованные утилиты, чтобы лишний раз вам не гуглить. Далее — под кат.
Для реализации своей задумки я использовал следующие программы и библиотеки:
Eclipse — свободная интегрированная среда разработки модульных кроссплатформенных приложений. Собственно на чем будем все кодить и компилить. Также возможно подходят IntelliJ IDEA и NetBeans.
ADT Plugin for Eclipse — плагин для eclipse, позволяющий создавать и компилировать проекты под Android.
libgdx — кроссплатформенная (PC, Mac, Linux, Android) Java-библиотека для разработки игр и не только. Эта библиотка распространяется под лицензией Apache License 2.0. Некоторые участки кода оптимизированы с помощью JNI (например Box2d).
box2d-editor — Редактор для создания физических моделей, используемых в физическом движке box2d, который встроен в libgdx. Здесь он будет использоваться для сопоставления рисунка сердечка и его физической модели.
Hiero bitmap font generator — Программа для конвертации векторных шрифтов в растровые (поскольку только такие могут использоваться в libgdx).
Particle Editor — редактор для создания систем частиц, написанный автором libgdx. У нас он будте использоваться для создания эффекта во время уничтожения сердечка.
Paint.NET я исползовал для редактирования изображения сердечка, найденного на просторах интернета, и создания фона.
Как видим, все программы и компоненты свобонораспространяемые, а это большой плюс.
Мой выбор пал на библиотеку libgdx, потому что, во-первых, я уже имею некоторый опыт работы с ней (пишу сейчас игру под андроид), а, во-вторых, при ее использовании отпадает необходимость в тормознутом android эмуляторе, поскольку она является кроссплатформенной и позволяет тестировать приложения в нативном java окружением с последующей компиляцией под зелененького.
libgdx «Hello World»
Итак, сначала я в двух словах расскажу как создавать проекты на libgdx в eclipse. Для десктопа создается обычный Java проект (File -> New -> Java Project) и добавляются необходимые внешние libgdx библиотеки (Правый клик на проекте -> Java Build Path -> Libraries). Их четыре штуки:gdx.jar, gdx-backend-jogl.jar, gdx-backend-jogl-natives.jar, gdx-natives.jar).
Затем создается Android проект (File -> New -> Other -> Android Project), аналогично добавляются библиотеки (теперь уже gdx.jar, gdx-backend-android.jar, а также armeabi и armeabi-v7a). Затем добавляется ссылка на наш первый созданный проект (Правый клик на проекте -> Java Build Path -> Projects).
После этого в первом проекте (пусть он называется ValentinesDayHearts) нужно создать класс ValentinesDayHearts, который будет реализовывать интерфейсы ApplicationListener (для обаботки событий инициализации, рендера, финализации и других состояний), а также InputProcessor (для того, чтобы обрабатывать пользовательский ввод).
Далее создаем класс DesktopStarter с точкой входа main:
public static void main(String[] args) {new JoglApplication(new ValentinesDayHearts(), "Valentine's Day Hearts", 800, 480, false);}
И в проекте под Android создается ValentinesDayHeartsActivity:
public class ValentinesDayHeartsActivity extends AndroidApplication {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initialize(new ValentinesDayHearts(), false);
}}
Больше кода в android проекте не будет.
Единственно, нужно добавить разрешения (permissions) в конфигурацию приложения Android (вибрация и запрещение спящего режима, чтобы не сбрасывалось состояние приложения), а также установить альбомную ориентацию (чтобы мир не переворачивался):
и
android:screenOrientation="landscape"
Все, каркас готов! Теперь данное приложение будет компилиться и под PC и под Android.
Общее
Данное приложение небольшое по размеру и имеет простую структуру: в классе ValentinesDayHeats перегружаются методы create, render, dispose, touchDown.
В методе create происходит инициализация всех ресурсов (текстуры, шрифты, частицы, звуки), создание физического мира.
В методе render происходит просчет и отрисовка всех объектов мира:
@Overridepublic void render() {
updatePhysics();
updateBackground();
updateSprites();
updateParticles();
refresh();
renderBackground();
renderHearts();
renderFonts();
renderParticles();}
В методе dispose — освобождение всех ресурсов. Да, да, несмотря на то, что в java есть сборка мусора, неуправляемые ресурсы (объекты Box2d и некоторые другие) все равно надо освобождать в ручную. Правда в связи со спешкой я не особо заморачивался с этим.
Метод touchDown срабатывает на клик мышкой или прикосновение к тачскрину. Если точка соприкосновения пересекается с некоторым сердечком, то оно удаляется. В противном случае, новое сердечкой создается в месте клика.
Объект «сердце» Heart имеет следующие свойства:Body — физическая модель.
Sprite — графическая модель (спрайт).
String — отображаемый на сердце текст.
Font — шрифт, которым рисуется текст.
ParticleEffect — создающиеся при уничтожении частицы.
BreakSound — звук при уничтожении.
Далее я вкратце опишу аспекты разрабатываемого приложения.
Текстуры
Первоочередной задачей являлась задача поиска или создания графических ресурсов. Недолго думая, я нагуглил изображение сердечка и немного его подредактировал немного (добавил свечение и сделал прозрачный фон).
Для загрузки текстур в libgdx используется класс Texture, но, так одну и ту же текстуру могут использовать несколько раз, вводятся дополнительные объекты Sprite. Спрайты же отрисовываются в методе render. Параметрами отрисовки являются позиция спрайта и его угол. Эти параметры являются параметрами физической модели сердечка.
Для разнообразия я решил сделать так, чтобы сердца имели не одинаковые цвета, а цвета с различными оттенками. Для этого я использовал палитру HSL, которая позволяет манипулировать оттенком, насыщенностью и осветленностью, а не непосредственно цветами как в RGB. Формулу преобразования RGB -> HSL и HSL -> RGB можно легко нагуглить, а я же переписал методы некой C# библиотеки на Java. В моем коде все преобразования цвета находятся в методах prepareHeartsTextures, prepareHslData и generateHeartTexture.
Вот пример одного из методов:
Pixmap pixmap = new Pixmap(fileHandle);float[][][] result = new float[pixmap.getWidth()][pixmap.getHeight()][4];for (int i = 0; i < pixmap.getWidth(); i++)for (int j = 0; j > 24) & 0xFF) / 255.0f;float g = (float)((color >> 16) & 0xFF) / 255.0f;float b = (float)((color >> 8) & 0xFF) / 255.0f;float a = (float)(color & 0xFF) / 255.0f;
result[i][j] = ColorUtils.RgbToHsl(r, g, b, a);}return result;
Кстати, из-за этих генераций текстур с разными оттенками, Android приложение загружается с некоторой задержкой.
Шрифты
Так как libgdx умеет работать только с растровыми шрифтами, я использовал программу Hiero Bitmap Font Generator, которая создает изображения всех символов в формате png и файл fnt, который содержит информацию о координатах каждого символа на изображении.
Вот скрин этой программы (не свой, потому что она что-то перестала у меня работать):
После того, как необходимые файлы сгенерированы, шрифт можно использовать в нашем libgdx приложении так:
font = new BitmapFont(
Gdx.files.internal("data/Jura-Medium.fnt"),
Gdx.files.internal("data/Jura-Medium.png"), false);
font.setColor(Color.WHITE);
и потом ренедрить так:
font.draw(spriteBatch, heart.String, screenPosition.x, screenPosition.y);
При рендере я столкнулся с некоторыми тонкостями: например шрифт нельзя рендерить под углом, как это можно делать со спрайтом. Для решения этой проблемы нужно изменять проективную матрицу у SpriteBatch, а затем рендерить шрифт следующим образом:
projection.translate(rotationCenter.x, rotationCenter.y, 0);
projection.rotate(0, 0, 1, body.getAngle() / (float)Math.PI * 180);
projection.translate(-rotationCenter.x, -rotationCenter.y, 0);
spriteBatch.begin();
font.setScale(heart.Size.x * FontSizeHeartSizeCoef.x, heart.Size.y * FontSizeHeartSizeCoef.y);
font.draw(spriteBatch, heart.String, screenPosition.x, screenPosition.y);
font.setScale(1, 1);
spriteBatch.end();
Физика
Думаю, что по box2d любой желающий сможет найти туеву хучу много материалов в интернете. Так что подробно на нем останавливаться не буду, а лучше расскажу о взаимодействии libgdx и box2d и создании физической модели для сердечка.
Для сопоставления графической и физической моделей я воспользовался box2d-editor:
Я сначала не понял как там что-то делать, но посмотрев в хелп и потыкав немного по кнопочкам, разобрался.
Данная программа генерирует файл в форматах bin, xml или json. Далее эти файлы используются в приложении (загрузка происходит в методе addHeart).
При загрузке моделей я столкнулся с проблемой, что загруженные тела не вращаются. Немного погуглив, я понял, что для этого им нужно задать плотность, трение и упругость. Но данное действие не исправило проблему. Позже решение все-таки было найдено: после присовения вышеупомянутых характеристик телу, нужно вызывать функцию resetMassData:
BodyDef heartBodyDef = new BodyDef();
heartBodyDef.type = BodyType.DynamicBody;
heartBodyDef.angle = ..;
heartBodyDef.position.x = ..;
heartBodyDef.position.y = ..;
Body body = world.createBody(heartBodyDef);
fixtureAtlas.createFixtures(body, "heart.png", newWidth, newHeight);for (int j = 0; j < body.getFixtureList().size(); j++) {
Fixture fixture = body.getFixtureList().get(j);
fixture.setFilterData(filter);
fixture.setFriction(0.75f);
fixture.setDensity(1.0f);
fixture.setRestitution(0.4f);}
body.resetMassData();
Для того, чтобы сердца не улетали за экран, по бокам в нашем мирке просто создаются четыре статичных прямоугольника.
На Android телефонах хорошо бы менять гравитацию в зависимости от ориентации телефона, подумал я. Сказано, сделано:
if (Gdx.app.getType() == ApplicationType.Android) {
gravity.x = -Gdx.input.getPitch() / 90.0f;
gravity.y = Gdx.input.getRoll() / 90.0f;
gravity.mul(gravityCoef);
world.setGravity(gravity);}
Система частиц
В libgdx система частиц задается с помощью специальных файлов, которые могут быть сгенерированы в редакторе:
Как видно, этот редактор имеет достаточно много настроек: можно загружать различные текстуры, менять время жизни, форму распространения, прозрачность и многие другие.
Я же сделал частицы в виде сердечек, которые будут появляться при нажатии и уничтожении одного физического сердечка.
В приложении я работаю с частицами следующим образом:Инициализация:
ParticleEffect effect = new ParticleEffect();
effect.load(Gdx.files.internal("data/destroy.p"), Gdx.files.internal("data"));
Начало жизненного цикла (главное не забыть про start, как это сделал я и недоумевал, почему же частицы не отображаются):
effect.setPosition(.., ..);
effect.start();
Звуки
Звуки загружаются так:
sound = Gdx.audio.newSound(Gdx.files.internal());
и затем проигрываются так: sound.play(1);
Казалось бы, что может быть проще? Ан нет — здесь тоже есть свои подводные камни.
Дело в том, что у меня почему-то загружались файлы только в формате .ogg и битрейте 96 кБит/сек. Не знаю почему, но загрузка других файлов не работала.
Заключение
Я конечно понимаю, что текст получился не слишком интересным, связным и отформатированным, но это из-за того, что писал я его второпях — хотел с одной стороны успеть ко Дню святого Валентина, с другой — осветить как можно большее количество аспектов и нюансов libgdx.
Я думаю, что данные знания будут полезны многим, и они пригодятся в будущем для разработки игры с помощью libgdx.
Также не особо ругайте за дизайн и звуки — тоже торопился, поэтому возможно где-то неправильно подобрал цвета, фактуру, звук. Но я готов выслушать любую конструктивную критику и по коду тоже :)
Разрешаю всем использовать мои исходники и ресурсы, дорабатывать их напильником и дарить готовые приложения на День святого Валентина своим половинкам :)
P.S. Все слова, отображаемые на сердечках, можно менять в файле data/words.txt даже без перекомпиляции.
P.P.S. В Маркет не стал выкладывать, потому что это УГ, коим захламлен весь маркет и не успел пока еще зарегистрироваться в Google Checkout.
Исходники, исполняемые файлы и утилиты
jar исполняемый файлapk файл для AndroidИсходникиРедактор физических моделей, шрифтов и частиц