Перед погружением в API libGDX, давайте создадим очень простую игру, которая будет затрагивать все модули. Будет представлено несколько концепций, не вдаваясь в подробные детали.
Будет рассмотрено:
- Простейший доступ к файлам
- Очистка экрана
- Отрисовка изображений
- Использование камеры
- Основы обработки ввода
- Воспроизведение звуковых эффектов
Настройка проекта
Следуйте инструкциям по настройке, запуску и отладке проекта. Будут использованы следующие имена:
- Имя приложения: drop
- Имя пакета: com.badlogic.drop
- Game класс: Drop
После импорта в Eclipse вы должны иметь 4 проекта: drop, drop-android, drop-desktop и drop-html5.
Игра
Идея игры очень простая:
- Нужно ведром ловить дождевые капли.
- Ведро расположено в нижней части экрана.
- Капли случайным образом появляются вверху экрана каждую секунду и устремляются вниз.
- Игрок с помощью мыши, клавиш клавиатуры или нажатия на сенсорный экран может горизонтально передвигать ведро.
- Игра не имеет условий окончания.
Assets
Для того чтобы игра выглядела хорошо, нужно несколько изображений и звуковых эффектов. Для графики будет использовано разрешение 800x480 пикселей (ландшафтная ориентация на Android). Если игра запуститься на устройстве с другим разрешением экрана, то все просто смасштабируется по границам экрана.
Игры высокого профиля
Для игр высокого профиля, возможно, придется иметь различные assets ресурсы для экранов с разным разрешением. Это довольно большая тема и здесь она не будет рассматриваться.
Изображения капли и ведра должны занимать небольшую часть экрана по вертикали, поэтому пусть они имеют размер 64x64 пикселей.
Assets взяты из следующих источников:
- Звук падающей воды от jungle.
- Звук дождя от acclivity.
- Изображение капли от mvdv.
- Изображение ведра от mvdv.
Для того чтобы assets были доступны в игре их нужно поместить в assets
директорию Android проекта. Имена файлов следующие: drop.wav, rain.mp3,
droplet.png и bucket.png. Поместите их в drop-android/assets/
директорию. Desktop и HTML5 проекты должны ссылаться на эту директорию, поэтому
можно хранить только одни assets.
Настройка Starter классов
Выполнив необходимые требования можно настроить Starter классы. Начнем с Desktop проекта. Откройте Main.java
класс в drop-desktop/
директории. Мы хотим, чтобы размер окна был 800x480 пикселей и заголовок был "Drop". Код должен выглядеть следующим образом:
package com.badlogic.drop; import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; public class Main { public static void main(String[] args) { LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration(); cfg.title = "Drop"; cfg.width = 800; cfg.height = 480; new LwjglApplication(new Drop(), cfg); } }
Перейти в Android проект, так как мы хотим, чтобы приложение запускалось в ландшафтном режиме. Для этого нам нужно изменить AndroidManifest.xml файл в корневой директории Android проекта, который выглядит следующим образом:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.badlogic.drop" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="5" android:targetSdkVersion="15" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:name=".MainActivity" android:label="@string/app_name" android:screenOrientation="landscape" android:configChanges="keyboard|keyboardHidden|orientation"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
Программа установи уже заполнила для нас правильные значения, атрибут android:screenOrientation
выставлен в "landscape". Если бы мы хотели,
чтобы игра запускалась в портретном режиме, то нужно этот атрибут установить в "portrait".
Мы также хотим сохранить заряд батареи и отключить акселерометр и компас. Мы делаем это в MainActivity.java
файле Android проекта, который
выглядит примерно так:
package com.badlogic.drop; import android.os.Bundle; import com.badlogic.gdx.backends.android.AndroidApplication; import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration; public class MainActivity extends AndroidApplication { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration(); cfg.useAccelerometer = false; cfg.useCompass = false; initialize(new Drop(), cfg); } }
Мы не может определить разрешение экрана Activity, так как оно устанавливается операционной системой Android. Как мы определили ранее, мы просто масштабируем разрешение 800x480 до размеров экрана устройства.
Наконец, мы хотим убедиться, что HTML5 проект тоже использует область рисования размером 800x480 пикселей. Для этого нужно изменить
GwtLauncher.java
файл в HTML5 проекте.
package com.badlogic.drop.client; import com.badlogic.drop.Drop; public class GwtLauncher extends GwtApplication { @Override public GwtApplicationConfiguration getConfig () { GwtApplicationConfiguration cfg = new GwtApplicationConfiguration(800, 480); return cfg; } @Override public ApplicationListener getApplicationListener () { return new Drop(); } }
HTML5 и версия OpenGL
Нам не нужно указывать какие версии OpenGL использовать для этой платформы, так как она поддерживает только OpenGL 2.0.
Теперь все Starter классы правильно настроены, поэтому можно переходить к реализации игры.
Код
Мы хотим разделить наш код на несколько частей. Для этого мы просто будет держать все в Drop.java
файле основного проекта.
Загрузка Assets
Нашей первой задачей будет загрузить assets и сохранить ссылки на них. Assets обычно загружаются в ApplicationListener.create()
методе:
public class Drop implements ApplicationListener { Texture dropImage; Texture bucketImage; Sound dropSound; Music rainMusic; @Override public void create() { // Загрузка изображений капли и ведра, каждое размером 64x64 пикселей dropImage = new Texture(Gdx.files.internal("droplet.png")); bucketImage = new Texture(Gdx.files.internal("bucket.png")); // Загрузка звукового эффекта падающей капли и фоновой "музыки" дождя dropSound = Gdx.audio.newSound(Gdx.files.internal("drop.wav")); rainMusic = Gdx.audio.newMusic(Gdx.files.internal("rain.mp3")); // Сразу же воспроизводиться музыка для фона rainMusic.setLooping(true); rainMusic.play(); ... еще не все ... } // Остальная часть опущена для ясности
Для каждого asset есть поля в Drop
классе, чтобы мы могли позже ссылаться на них. Первые две строки в create
методе загружают
изображения капли и ведра. Texture
представляет загруженное изображения, которое храниться в видео памяти. Texture
загружается
передачей в конструктор FileHandle
соответствующего asset файла. Такие FileHandle
экземпляры можно получить с помощью одного из
методов Gdx.files
. Существуют различные типы файлов, мы использует внутренний (internal) тип файла для ссылки на asset. Внутренние файлы
располагаются в assets
директории Android проекта. Desktop и HTML5 проекты ссылаются на ту же директорию через связь в Eclipse.
В следующем шаге мы загружает звуковой эффект и музыку для фона. libGDX различает звуковые эффекты, которые хранятся в памяти, и музыку, которая
воспроизводиться как поток из места ее расположения. Музыка обычно слишком большая, чтобы полностью хранить ее в памяти, отсюда вытекают различия. Как
правило, вы должны использовать экземпляр Sound
класса, если продолжительность меньше чем 10 секунд, и экземпляр Music
класса для
более долгих аудио частей.
Загрузка Sound
и Music
экземпляров осуществляется через методы Gdx.app.newSound()
и Gdx.app.newMusic()
.
Оба
метода принимают FileHandle
, так же как и Texture
конструктор.
В конце create()
метода мы говорим Music
экземпляру, чтобы музыка повторялась, и сразу же запускалось ее воспроизведение. Если вы
запустите это приложение, то увидите розовый фон и услышите звук дождя.
Камера и SpriteBatch
Далее мы хотим создать камеру и SpriteBatch
. Мы использует такое же разрешение, чтобы убедиться в том, что может делать визуализацию с
разрешением 800x480 независимо от фактического разрешения экрана. SpriteBatch
это специальный класс, который используется для рисования 2D
изображений, таких как текстуры, которые мы загрузили.
Мы добавим два новых поля в класс и назовем их camera и batch
OrthographicCamera camera; SpriteBatch batch;
В create()
методе мы сначала создаем камеру следующем образом:
camera = new OrthographicCamera(); camera.setToOrtho(false, 800, 480);
Метод setToOrtho()
позволяет убедиться в том, что камера всегда показывает область мира игры, которая размером 800x480 единиц. Думайте об этом
как о виртуальном окне в наш
мир. В настоящее время мы интерпретировали единицы как пиксели для облегчения жизни. Камера является очень мощным механизмом и позволяет делать очень много
разных вещей, которые мы не будет рассматривать в этой базовой статье. Смотрите остальное руководство libGDX разработчика для больше информации.
Замет необходимо в том же create()
методе создать SpriteBatch
:
batch = new SpriteBatch();
Мы почти завершили создание всех вещей необходимых для запуска этой простой игры.
Добавляем ведро
Пока еще отсутствует представление ведра и капли. Давайте подумаем о том, что нам нужно, чтобы представить их в коде.
- Ведро и капля имеют x и y координаты в 800x480 мире.
- Ведро и капля имеют ширину и высоту, выраженные в единицах нашего мира.
- Ведро и капля имеют графическое представление, у нас оно уже есть в форме
Texture
экземпляров.
Для описания ведра и капли необходимо сохранить их позицию и размер. libGDX предоставляет класс Rectangle
, который можно использовать для этой
цели. Давайте начнем с создания Rectangle
, который будет представлять ведро. Добавим новое поле:
Rectangle bucket;
В create()
методе создается Rectangle
и указываются начальные значения. Мы хотим, чтобы ведро было на 20 пикселей выше нижней
границы экрана и центрировалось по горизонтали.
bucket = new Rectangle(); bucket.x = 800 / 2 - 64 / 2; bucket.y = 20; bucket.width = 64; bucket.height = 64;
Мы центрируем ведро по горизонтали и размещаем на 20 пикселей выше нижней границы экрана. Возникает вопрос - почему bucket.y координата установлена в 20, разве она не должна рассчитываться как 480-20? По умолчанию вся визуализация в libGDX (и в OpenGL) осуществляет вверх по y-оси. Координаты x/y bucket определяют нижний левый угол ведра, нулевые координаты находятся в нижнем левом углу. Ширина и высота прямоугольника устанавливается в 64x64.
Начальные координаты
Можно изменить эти установки, чтобы y-ось уходила вниз и начальные координаты были в верхнем левом углу экрана. OpenGL и камера настолько гибки, что можно иметь угол обзора какой вы захотите, в 2D и в 3D.
Визуализация ведра
Время нарисовать ведро. Первое, что мы хотим сделать, это очистить экран темно-синим цветом. Просто измените render()
метод, чтобы он выглядел
следующим образом:
@Override public void render() { Gdx.gl.glClearColor(0, 0, 0.2f, 1); Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); ... еще не все ... }
Эти две строки кода единственное, что вам нужно знать о OpenGL, если вы используете классы высокого уровня, такие как Texture
и SpriteBatch
.
Первый вызов установит цвет очистки в синий цвет. Аргументами являются красный, зеленый, синий и альфа-компонент цвета, каждый в диапазоне [0, 1]. Следующий
вызов говорит OpenGL очистить экран.
Затем мы должны сказать камере, чтобы убедиться в том, что она обновляется. Камеры используют математическую сущность, называемую матрицу, которая отвечает за создание системы координат для визуализации. Эти матрицы нужно пересчитывать каждый раз, когда мы меняем свойства камеры, как и ее положение. Мы не делаем этого в нашем простом примере, но как правило, хорошей практикой является обновление камеры один раз за кадр:
camera.update();
Теперь можно нарисовать ведро:
batch.setProjectionMatrix(camera.combined); batch.begin(); batch.draw(bucketImage, bucket.x, bucket.y); batch.end();
В первой строке сообщается SpriteBatch
использовать систему координат камеры. Как отмечалось ранее, это делается с помощью, так называемой
матрицы, если быть более конкретным, то матрицы проекции. Поле camera.combined
является такой матрицей. SpriteBatch
нарисует все,
что будет находиться в заданных координатах.
Следующим мы говорим SpriteBatch
начать новую batch серию. Зачем нам это и что такое серия? OpenGL не любит когда ему говорят только об одном
изображении. OpenGL хочет знать обо всех изображениях, чтобы нарисовать за один раз как можно больше.
OpenGL в этом помогает класс SpriteBatch
. Он будет записывать все команды рисования между SpriteBatch.begin()
и SpriteBatch.end()
.
Как только мы вызываем SpriteBatch.end()
метод, он предоставит все запросы рисования, повышая скорость визуализации. Все это может выглядеть
непонятным в начале, но это то, что дает разницу между рисованием 500 спрайтов при 60 кадрах в секунду и рисованием 100 спрайтов при 20 кадрах в секунду.
Делаем ведро подвижным (прикосновение/мышь)
Время, чтобы позволить пользователю управлять ведром. Ранее мы сказали, что мы будем позволять пользователю перетаскивать ведро. Давайте сделаем задачу
немного легче. Если пользователь прикасается к экрану (или нажимает кнопку мыши), то ведро центрируется по горизонтали относительно точки прикосновения.
Добавление следующего кода вниз render()
метода будет делать это:
if(Gdx.input.isTouched()) { Vector3 touchPos = new Vector3(); touchPos.set(Gdx.input.getX(), Gdx.input.getY(), 0); camera.unproject(touchPos); bucket.x = touchPos.x - 64 / 2; }
Сначала вызывается Gdx.input.isTouched()
метод, который спрашивает модуль ввода о том, есть ли на данный момент прикосновение к экрану (или
нажатие кнопки мыши). Далее идет преобразование координат прикосновения/мыши в систему координат камеры. Это необходимо, поскольку система координат
прикосновения/мыши может отличаться от используемой нами системы для представления объектов в мире.
Gdx.input.getX()
и Gdx.input.getY()
возвращают текущую позицию прикосновения/мыши (libGDX поддерживает multi-touch). Для
преобразования этих координат в систему координат нашей камеры, мы должны вызвать camera.unproject()
метод, в который передается трехмерный
вектор Vector3
. Мы создаем вектор, устанавливаем текущие координаты прикосновения/мыши и вызываем соответствующий метод. Теперь вектор содержит
координаты прикосновения/мыши в такой же системе координат, как и ведро. Далее изменяется позиция ведра так, чтобы центр находился в координатах
прикосновения/мыши.
Постоянное создание экземпляра нового объекта
Очень и очень плохо, когда постоянно создается экземпляр нового объекта, в нашем случае это экземпляр Vector3
класса. Причиной
этого является сборщик мусора, который должен часто освобождать память этих недолго живущих объектов. На Desktop это не так уж и важно, но
сборщик мусора на Android может вызвать паузу на несколько миллисекунд, что приведет к затормаживанию. Чтобы решить эту проблему, в данном случае можно
сделать
touchPos
полем в Drop
классе, вместо постоянного создания экземпляра.
touchPos
является трехмерным вектором. Вы можете удивиться, почему это так, если мы работаем только в 2D. OrthographicCamera
на самом деле 3D камера, которая также принимает во внимание Z-координату.
Делаем ведро подвижным (клавиатура)
На Desktop и в браузере можно также обрабатывать ввод с клавиатуры. Давайте заставим ведро двигаться по нажатию на клавиши влево и вправо.
Мы хотим, чтобы ведро передвигалось без ускорения, на двести пикселей в секунду влево или вправо. Для реализации такого зависящего от времени движения, мы должны знать время, прошедшее между последним и текущем кадром визуализация. Вот как можно сделать это:
if(Gdx.input.isKeyPressed(Keys.LEFT)) bucket.x -= 200 * Gdx.graphics.getDeltaTime(); if(Gdx.input.isKeyPressed(Keys.RIGHT)) bucket.x += 200 * Gdx.graphics.getDeltaTime();
Метод Gdx.input.isKeyPressed()
сообщает о нажатии определенной клавиши. Перечисление Keys
содержит все коды клавиш, которые
поддерживает libGDX. Метод Gdx.graphics.getDeltaTime()
возвращает время, прошедшее между последним и текущим кадром в секундах. Все что нам
нужно сделать – изменить x-координаты ведра путем добавления/удаления 200 единиц умноженных на дельту времени в секундах.
Мы также должны убедиться в том, что ведро остается в пределах экрана:
if(bucket.x < 0) bucket.x = 0; if(bucket.x > 800 - 64) bucket.x = 800 - 64;
Добавляем каплю
Для хранения капель используется список Rectangle
экземпляров, каждый из которых хранит позицию и размер капли. Добавим список в качестве поля:
Array<Rectangle> raindrops;
Класс Array
– специальный класс для использования вместо стандартных Java коллекций, таких как ArrayList
. Проблема с ними в том,
что они производят мусор различными способами. Класс Array
пытается минимизировать мусор в максимально возможной степени. libGDX предлагает
другие сборщики мусора для коллекций хэш-таблиц и множеств.
Так же необходимо отслеживать последнее появление капли, так что добавим еще одно поле.
long lastDropTime;
Мы будет хранить время в наносекундах, поэтому мы используем long
.
Для облегчения создания капли мы напишем метод, называемый spawnRaindrop()
, который создает новый Rectangle
, устанавливает его в
случайной позиции в верхней части экрана и добавляет его в raindrops
массив.
private void spawnRaindrop() { Rectangle raindrop = new Rectangle(); raindrop.x = MathUtils.random(0, 800-64); raindrop.y = 480; raindrop.width = 64; raindrop.height = 64; raindrops.add(raindrop); lastDropTime = TimeUtils.nanoTime(); }
Метод должен быть довольно очевидным. Класс MathUtils
– libGDX класс, предлагающий статические методы, связанные с математикой. В этом случае
возвращается случайное число между нулем и 800 - 64. Класс TimeUtils
– другой libGDX класс, который предоставляет очень простые статические
методы, связанные со временем. В этом случае мы записываем текущее время в наносекундах, основываясь на том, следует ли порождать новую каплю или нет.
В методе create()
сейчас создается экземпляр массива капель и порождается первая капля.
raindrops = new Array<Rectangle>(); spawnRaindrop();
Затем добавляем несколько строк в render()
метод, который будет проверять, сколько времени прошло, с тех пор как была создана новая капля и
если
необходимо, создавать еще одну новую каплю.
if(TimeUtils.nanoTime() - lastDropTime > 1000000000) spawnRaindrop();
Также нужно сделать так, чтобы капли двигались. Давайте пойдем легким путем, и пусть они двигаются с постоянной скоростью 200 пикселей в секунду. Если капля находится ниже нижнего края экрана, мы удаляем ее из массива.
Iterator<Rectangle> iter = raindrops.iterator(); while(iter.hasNext()) { Rectangle raindrop = iter.next(); raindrop.y -= 200 * Gdx.graphics.getDeltaTime(); if(raindrop.y + 64 < 0) iter.remove(); }
Капли нужно отобразить на экране. Мы добавим отображение в SpriteBatch
код визуализации, который выглядит сейчас так:
batch.begin(); batch.draw(bucketImage, bucket.x, bucket.y); for(Rectangle raindrop: raindrops) { batch.draw(dropImage, raindrop.x, raindrop.y); } batch.end();
Одна последняя корректировка: если капля попала в ведро, то нужно воспроизвести соответствующий звук и удалить каплю из массива. Просто добавьте следующие строки в цикл обновления капли:
if(raindrop.overlaps(bucket)) { dropSound.play(); iter.remove(); }
Метод Rectangle.overlaps()
проверяет, если прямоугольник пересекается с другим прямоугольником. В нашем случае воспроизводится звук, и капля
удаляется из массива.
Очистка
Пользователь может закрыть приложение в любой момент. Для этого простого примера ничего особенного делать не нужно. Тем не менее, хорошей идей является немного помочь операционной системе и навести порядок в созданном нами беспорядке.
Любые libGDX классы, которые реализуют Disposable
интерфейс и имеют dispose()
метод, должны быть освобождены вручную, если они
больше не используются. В нашем примере это относится к текстурам, звукам, музыки и SpriteBatch
. Будучи хорошими гражданами, мы реализуем
ApplicationListener.dispose()
метод следующим образом:
@Override public void dispose() { dropImage.dispose(); bucketImage.dispose(); dropSound.dispose(); rainMusic.dispose(); batch.dispose(); }
После освобождения ресурса вы не должны больше обращаться к нему.
Ресурсы, реализующие Disposable
интерфейс, обычно являются нативными ресурсами и не обрабатываются сборщиком мусора Java. Это причина того, что
мы должны
вручную освобождать их. libGDX предоставляет различные способы помощи в управлении asset ресурсами. Читайте остальные части руководства.
Обработка паузы и возобновления
Android имеет особенность приостанавливать и возобновлять приложения всякий раз, когда пользователю звонят или при нажатии кнопки home. libGDX для таких случаев делает много вещей автоматически, например, перезагружает изображения, которые могут быть потеряны (потеря OpenGL контекста), останавливает и возобновляет потоковую музыку и так далее.
В нашей игре нет реальной необходимости в обработки паузы и возобновления. Как только пользователь заходит обратно в приложение, игра продолжается с того
момента где она и была. Обычно можно реализовать экран паузы и просить пользователя прикоснуться к экрану, чтобы продолжить игру. Это останется в качестве
упражнения для читателя. Смотрите ApplicationListener.pause()
и ApplicationListener.resume()
методы.
Полный исходный код игры
Вот крошечный исходный код нашей простой игры:
package com.badlogic.drop; import java.util.Iterator; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input.Keys; import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.audio.Sound; import com.badlogic.gdx.graphics.GL10; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.TimeUtils; public class Drop implements ApplicationListener { Texture dropImage; Texture bucketImage; Sound dropSound; Music rainMusic; SpriteBatch batch; OrthographicCamera camera; Rectangle bucket; Array<Rectangle> raindrops; long lastDropTime; @Override public void create() { // загрузка изображений для капли и ведра, 64x64 пикселей каждый dropImage = new Texture(Gdx.files.internal("droplet.png")); bucketImage = new Texture(Gdx.files.internal("bucket.png")); // загрузка звукового эффекта падающей капли и фоновой "музыки" дождя dropSound = Gdx.audio.newSound(Gdx.files.internal("drop.wav")); rainMusic = Gdx.audio.newMusic(Gdx.files.internal("rain.mp3")); // сразу же воспроизводиться музыка для фона rainMusic.setLooping(true); rainMusic.play(); // создается камера и SpriteBatch camera = new OrthographicCamera(); camera.setToOrtho(false, 800, 480); batch = new SpriteBatch(); // создается Rectangle для представления ведра bucket = new Rectangle(); // центрируем ведро по горизонтали bucket.x = 800 / 2 - 64 / 2; // размещаем на 20 пикселей выше нижней границы экрана. bucket.y = 20; bucket.width = 64; bucket.height = 64; // создает массив капель и возрождает первую raindrops = new Array<Rectangle>(); spawnRaindrop(); } private void spawnRaindrop() { Rectangle raindrop = new Rectangle(); raindrop.x = MathUtils.random(0, 800-64); raindrop.y = 480; raindrop.width = 64; raindrop.height = 64; raindrops.add(raindrop); lastDropTime = TimeUtils.nanoTime(); } @Override public void render() { // очищаем экран темно-синим цветом. // Аргументы для glClearColor красный, зеленый // синий и альфа компонент в диапазоне [0,1] // цвета используемого для очистки экрана. Gdx.gl.glClearColor(0, 0, 0.2f, 1); Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); // сообщает камере, что нужно обновить матрицы camera.update(); // сообщаем SpriteBatch о системе координат // визуализации указанной для камеры. batch.setProjectionMatrix(camera.combined); // начинаем новую серию, рисуем ведро и // все капли batch.begin(); batch.draw(bucketImage, bucket.x, bucket.y); for(Rectangle raindrop: raindrops) { batch.draw(dropImage, raindrop.x, raindrop.y); } batch.end(); // обработка пользовательского ввода if(Gdx.input.isTouched()) { Vector3 touchPos = new Vector3(); touchPos.set(Gdx.input.getX(), Gdx.input.getY(), 0); camera.unproject(touchPos); bucket.x = touchPos.x - 64 / 2; } if(Gdx.input.isKeyPressed(Keys.LEFT)) bucket.x -= 200 * Gdx.graphics.getDeltaTime(); if(Gdx.input.isKeyPressed(Keys.RIGHT)) bucket.x += 200 * Gdx.graphics.getDeltaTime(); // убедитесь что ведро остается в пределах экрана if(bucket.x < 0) bucket.x = 0; if(bucket.x > 800 - 64) bucket.x = 800 - 64; // проверка, нужно ли создавать новую каплю if(TimeUtils.nanoTime() - lastDropTime > 1000000000) spawnRaindrop(); // движение капли, удаляем все капли выходящие за границы экрана // или те, что попали в ведро. Воспроизведение звукового эффекта // при попадании. Iterator<Rectangle> iter = raindrops.iterator(); while(iter.hasNext()) { Rectangle raindrop = iter.next(); raindrop.y -= 200 * Gdx.graphics.getDeltaTime(); if(raindrop.y + 64 < 0) iter.remove(); if(raindrop.overlaps(bucket)) { dropSound.play(); iter.remove(); } } } @Override public void dispose() { // высвобождение всех нативных ресурсов dropImage.dispose(); bucketImage.dispose(); dropSound.dispose(); rainMusic.dispose(); batch.dispose(); } @Override public void resize(int width, int height) { } @Override public void pause() { } @Override public void resume() { } }
Куда двигаться дальше
Это был очень простой пример того, как использовать libGDX для создания мини игры. Некоторые вещи могут быть улучшены, такие как класс управления памяти,
для повторного использования экземпляра Rectangle
, который высвобождает сборщик мусора каждый раз, когда мы удаляем каплю. OpenGL не любит
когда ему дают много различных изображений. В нашем случае это нормально, так как у нас было только два изображения. Обычно все изображения помещаются в
один Texture
, так же известный как TextureAtlas
. Screen
и Game
экземпляры тоже могут быть использованы
для повышения взаимодействия; узнайте больше в следующей части руководства по libGDX основанной на этой статье.
Рекомендует прочитать остальные части руководства разработчика и посмотреть демонстрационные примеры и тесты в Git репозитории.