Приложение в честь Дня святого Валентина на libgdx

    Периодически появляются топики, посвященные дню Валентина. В этом году я тоже решился включиться в эту тему и сделать что-нибудь оригинальное и необычное. Было решено создать простенькое приложение под Android с сердечками, которые бы имели свои физические модели и взаимодействовали друг с другом. Позже я добавил текст, звуки, частицы и некоторые другие красивости. В результате получилось даже что-то вменяемое и оригинальное! В данной статье описывается процесс создания вместе описанием возможностей и подводных камней библиотеки libgdx.


    .


    Содержание



    Программы и инструменты


    Для реализации задумки использовались следующие программы и библиотеки:


    1. IntelliJ IDEA — свободная среда разработки модульных и кроссплатформенных приложений. В качестве альтернативы подходит Android Studio, Eclipse.
    2. libgdx — кроссплатформенная (PC, Mac, Linux, Android) Java-библиотека для разработки игр и не других графических приложений. Эта библиотека распространяется под лицензией Apache License 2.0. Некоторые участки кода оптимизированы с помощью JNI (например Box2d).
    3. box2d-editor — Редактор для создания физических моделей, используемых в физическом движке box2d, который встроен в libgdx. Здесь он будет использоваться для сопоставления рисунка сердечка и его физической модели.
    4. Hiero bitmap font generator — Программа для конвертации векторных шрифтов в растровые (поскольку в libgdx поддерживаются только растровые).
    5. Particle Editor — редактор для создания систем частиц от автора libgdx. Используется для частиц в эффекте "взрыва" при уничтожении сердечка.
    6. 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. Это работает даже без перекомпиляции.


    Исходники и исполняемые файлы


    Поделиться публикацией

    Похожие публикации

    Комментарии 22

      0
      Забавный эффект получается если в Windows развернуть на весь экран :)
        0
        Ну я на весь экран вообще не тестировал)
        Главная цель windows приложения — проверить работоспособность, чтобы потом запустить на Android.
          0
          Windows — кириллица — ни 1251 ни utf-8 не работает, печаль…
            0
            Насколько я понял, там дело не в винде и кодировки, а в том, что кириллица не поддерживается шрифтом, поэтому текст не отображается.

            Поманипулируете с hiero — возможно удастся сгенерировать и использовать шрифт с поддержкой кириллицы.
        +1
        Нужно ограничить возможное создание сердец. Если создать сердец больше чем физически помещается в окно, то они все начинаю дрожать и программа занимает большую часть процессорного времени. У меня получилось загрузить одно ядро процессора(Intel Core Duo E6750 2.66GHz) на 70%.

        А вообще прикольная штука. :)
          +1
          Круто! Весьма интересно, я полгода назад к дню рождения сделал нечто похожее с использованием Javascript, Canvas и Box2d. Большая часть кода взята отсюда.
            0
            Спасибо :)
              0
              Извиняюсь за нубский вопрос, а можно русскими буквами писать?
                0
                Да, можно. Уже не помню подробностей, но бывали проблемы с кодировкой. Но я их решил и финальная версия была с русскими фразами.
                  0
                  Я правильно понимаю, финальная версия конфиденциальная, посмотреть нельзя?
                  У меня получаются знаки вопроса в ромбах.
                    0
                    Ну оригинальная — да. Но я обновил на дроп-боксе версию (ссылка выше), добавил русское сообщение. У меня меня в Линуксе работает во всех браузерах. Вот на всякий случай еще и архив
                0
                в хроме правая кнопка вызывает таки меню, в ФФ — нет. Можете пофиксить?
                  0
                  Сомневаюсь. Я вообще не уверен, в чем там проблема и как ее фиксить. В оригинальном решении та же ситуация была… Так еще в моём случае всё осложняется отсутствием времени и мотивации копаться, потому что эта штучка уже показана и больше мне не пригодится.
              • НЛО прилетело и опубликовало эту надпись здесь
                  +1
                  Странно. Не только у вас такая проблема.
                  Буду разбираться почему так происходит.
                  +5
                  image
                    +1
                    Android телефон сказал, что не в силах пропарсить *.apk, а в целом большое спасибо за статью.
                    Пойду попробую пересобрать.
                      0
                      Пересобрал. Приложение не понимает разрешения. Сердца в пространстве экрана, но надписи и эффект разрушения не масштабируются, улетая за пределы экрана.

                      Автор, пожалуйста помогите поправить. Я полагаю ошибка где-то здесь?

                      stringSize = new Vector2(
                        bounds.width * Size.x * ValentinesDayHearts.FontSizeHeartSizeCoef.x,
                        bounds.height * Size.y * ValentinesDayHearts.FontSizeHeartSizeCoef.y);
                      
                        0
                        Все. Прошу прощения. Разобрался. С libgdx не работал раньше просто, а надо бы. Спасибо.
                        0
                        Залил новую версию с исправленным багом при запуске на x86 системах.
                        Также пофиксил разрешения (это касается Android).
                          0
                          ubuntu, gcj-4.4-jdk
                          java.lang.NullPointerException посреди кучи других exceptions — но так и не понятно чего ей надо — это родовая болезнь java?
                            0
                            Честно сказать не знаю: то ли это мои кривые руки, то ли разработчиков libgdx, то ли java. Наиболее вероятнее последнее, т.к. на Win x86 и Win x64 работает.

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                          Самое читаемое