Периодически появляются топики, посвященные дню Валентина. В этом году я тоже решился включиться в эту тему и сделать что-нибудь оригинальное и необычное. Было решено создать простенькое приложение под Android с сердечками, которые бы имели свои физические модели и взаимодействовали друг с другом. Позже я добавил текст, звуки, частицы и некоторые другие красивости. В результате получилось даже что-то вменяемое и оригинальное! В данной статье описывается процесс создания вместе описанием возможностей и подводных камней библиотеки libgdx.
Содержание
- Программы и инструменты
- Hello World
- Общее
- Текстуры
- Шрифты
- Физика
- Система частиц
- Звуки
- Заключение
- Исходники и исполняемые файлы
Программы и инструменты
Для реализации задумки использовались следующие программы и библиотеки:
- IntelliJ IDEA — свободная среда разработки модульных и кроссплатформенных приложений. В качестве альтернативы подходит Android Studio, Eclipse.
- 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 окружением с последующей компиляцией под "зелененького".
Hello World
Сначала я в двух словах расскажу как создавать проекты libgdx. С помощью gdx-setup.jar
генерируется шаблон проекта (на основе Grandle), в котором указываются нужные целевые платформы. В настоящий момент поддерживаются Desktop
, Android
, Ios
, Html
. Правда последние два попробовать не удалось, т.к. не обладаю Ios операционной системой, а с Html возникли сложности, решить которые пока что не удалось.

Также сразу же можно выбрать используемые расширения. В нашем случае — это библиотека физики Box2d.
Впрочем, все это подробно расписано в вики: Creating a libgdx project.
После генерации создается три папки:
- core
- desktop
- android
В двух последних помещаются лаунчеры под соответствующие платформы DesktopLauncher
и AndroidLauncher
, которые выглядят так:
public class DesktopLauncher {
public static void main (String[] arg) {
LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
config.width = 800;
config.height = 480;
new LwjglApplication(new ValentinesDayHeartsApp(), config);
}
}
public class AndroidLauncher extends AndroidApplication {
@Override
protected void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
initialize(new ValentinesDayHeartsApp(), config);
}
}
Специфичного под Android кода больше не будет, что является большим достоинством выбранной библиотеки. Единственно, нужно разрешить вибрацию и запрет спящего режима (чтобы мир не сбрасывался ) в конфигурации AndroidManifest.xml
. А также установить альбомную ориентацию, чтобы мир не переворачивался:
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
и
android:screenOrientation="landscape"
В core хранится общий код. Главным классом является ValentinesDayHeartsApp
, который реализует интерфейсы ApplicationListener
для обработки событий инициализации, рендера, финализации и других состояний и InputProcessor
для того чтобы можно было обрабатывать пользовательский ввод.
Все, каркас готов! Теперь данное приложение будет работать и под PC и под Android.
Общее
Проект имеет простую структуру: в классе ValentinesDayHeatsApp
перегружаются методы create
, render
, dispose
, touchDown
. В методе create
происходит инициализация всех ресурсов (текстуры, шрифты, частицы, звуки), создание физического мира. В методе render
происходит просчет и отрисовка всех объектов мира:
@Override
public 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 можно легко найти, а я портировал методы из статьи Manipulating colors in .NET на 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 < pixmap.getHeight(); j++) {
int color = pixmap.getPixel(i, j);
float r = (float)((color >> 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 (версия 5), которая создает изображения всех символов в формате 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
, а затем рендерить шрифт следующим образом:
Matrix4 projection = spriteBatch.getProjectionMatrix();
projection.setToOrtho2D(0, 0, WorldWidth, WorldHeight);
projection.translate(tmpVector1.x, tmpVector1.y, 0);
projection.rotate(0, 0, 1, body.getAngle() / (float)Math.PI * 180);
projection.translate(-tmpVector1.x, -tmpVector1.y, 0);
Vector2 stringSize = heart.getStringSize();
tmpVector1.add(heart.Size.x / PhysWorldWidth * WorldWidth * CenterDisplacement.x
- stringSize.x * 0.5f,
heart.Size.y / PhysWorldHeight * WorldHeight * CenterDisplacement.y
+ stringSize.y);
spriteBatch.begin();
BitmapFont.BitmapFontData fontData = font.getData();
fontData.setScale(heart.Size.x * FontSizeHeartSizeCoef.x, heart.Size.y * FontSizeHeartSizeCoef.y);
font.draw(spriteBatch, heart.String, tmpVector1.x, tmpVector1.y);
fontData.setScale(1, 1);
spriteBatch.end();
Физика
В качестве физического движка использовался box2d.
Для сопоставления графической и физической моделей сердца я использовал box2d-editor:

С помощью данной программы я создал полигон сердечка, который автоматически разбился на выпуклые многоугольники. Физическая модель по сути представляет собой набор координат этих многоугольников в формате json.
Далее этот файл используются в приложении (загрузка происходит в методе addHeart
). libgdx умеет загружать файлы только в бинарном формате. К счастью, был найден класс BodyEditorLoader.java, с помощью которого можно загрузить модель и из JSON, т.е. текстового представления.
Не забываем задать плотность, трение и упругость телу:
FixtureDef fdef = new FixtureDef();
fdef.density = 0.75f;
fdef.friction = 1.0f;
fdef.restitution = 0.4f;
bodyLoader.attachFixture(body, "Heart", fdef, newWidth);
body.resetMassData();
Все, теперь сердца обладают еще и физической оболочкой!
Для того, чтобы сердца не улетали за экран, по бокам в нашем мирке просто создаются четыре статичных прямоугольника. На мобильных устройствах целесообразно менять гравитацию в зависимости от ориентации:
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("path/to/file"));
и затем проигрываются так:
sound.play(1);
Казалось бы, что может быть проще? Однако здесь также есть свои подводные камни. Дело в том, что почему-то загружались файлы только в формате .ogg и битрейте 96 кБит/сек.
Заключение
Надеюсь описанные в статье знания будут полезны многим, и они пригодятся в для разработки игр с использованием libgdx. Исходники и ресурсы разрешается использовать всем. Дарите приложения на День святого Валентина своим половинкам :)
Стоит отметить, что все слова, отображаемые на сердечках, можно менять в файле data/words.txt
. Это работает даже без перекомпиляции.
Исходники и исполняемые файлы
- Исходники: https://github.com/KvanTTT/ValentinesDayHearts
- Кроссплатформенный jar исполняемый файл: ValentinesDayHearts-1.1.jar
- Apk файл для Android: ValentinesDayHearts-1.1.apk