Поиск
Меню
Категории раздела
Рекомендуем
|
Создание простой игры — часть 1Перед погружением в API libgdx, давайте создадим очень простую игру, которая будет затрагивать все модули. Будет представлено несколько концепций, не вдаваясь в подробные детали. Будет рассмотрено:
Настройка проектаСледуйте инструкциям по настройке, запуску и отладке проекта. Будут использованы следующие имена:
ИграИдея игры очень простая:
Активы (Assets)Для того чтобы игра выглядела хорошо, нужно несколько изображений и звуковых эффектов. Для графики будет использовано разрешение 800×480 пикселей (ландшафтная ориентация на Android). Игра запуститься на устройстве с другим разрешением экрана, то все просто масштабируется по границам экрана. Изображения капля и ведра должны занимать небольшую часть экрана по вертикали, поэтому пусть они имеют размер 64×64 пикселей. Активы взяты из следующих источников:
Для того, чтобы активы были доступны в игре, их нужно поместить в папку Настройка Starter классовВыполнив необходимые требования можно настроить Starter классы. Начнем с Desktop проекта. Откройте класс 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».Мы также хотим экономить заряд батареи и отключить акселерометр и компас. Мы делаем это в файле 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. Как мы уже говорили ранее, мы просто масштабируем разрешение 800×480 до размеров экрана устройства. Наконец, мы хотим убедиться, что HTML5 проект тоже использует область рисования размером 800×480 пикселей. Для этого нужно изменить файл 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(); } } Примечание: нам не нужно указывать какие версии OpenGL использовать для этой платформы, так как она поддерживает только OpenGL 2.0. Теперь все Starter классы правильно настроены, поэтому можно переходить к реализации игры. КодМы разделим наш код на несколько частей. Для простоты мы будем держать все в файле Загрузка активовНашей первой задачей будет загрузить активы и сохранить ссылки на них. Активы обычно загружаются в методе 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(); ... еще не все ... } // Остальная часть опущена для ясности Для каждого актива есть поля в классе В следующем шаге мы загружаем звуковой эффект и музыку для фона. Libgdx различает звуковые эффекты, которые хранятся в памяти, и музыку, которая воспроизводиться как поток из места ее расположения. Музыка обычно слишком объемная, чтобы полностью хранить ее в памяти, отсюда вытекают различия. Как правило, вы должны использовать экземпляр класса Загрузка экземпляров В конце метода Камера и SpriteBatchДалее мы создадим камеру и Мы добавим два новых свойства в классе и назовем их camera и batch OrthographicCamera camera; SpriteBatch batch; В методе create() мы сначала создадим камеру, следующем образом:
camera = new OrthographicCamera(); camera.setToOrtho(false, 800, 480); Это позволяет убедиться в том, что камера всегда показывает область мира игры, которая размером 800×480 единиц. Думайте об этом как о виртуальном окне в наш мир. В настоящее время мы интерпретировали единицы как пиксели, для облегчения жизни. Камера является очень мощным механизмом и позволяет делать очень много разных вещей, которые мы не будем рассматривать в этой базовой статье. Для получения дополнительной информации смотрите руководство разработчика libgdx.
batch = new SpriteBatch(); Мы почти завершили создание всех компонентов, необходимых для запуска нашей простой игры. Добавляем ведроПока еще отсутствуют такие сущности, как ведро и капли. Давайте подумаем о том, что нам нужно для их представить их в коде.
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, разве она не должна рассчитываться как Примечание: Можно изменить эти установки, чтобы Y-ось уходила вниз и начальные координаты были в верхнем левом углу экрана. OpenGL и камера настолько гибки, что можно иметь угол обзора какой вы захотите, в 2D и в 3D. Визуализация ведраВремя нарисовать ведро. Первое, что мы хотим сделать, это очистить экран темно-синим цветом. Просто измените метод @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 нарисует все что будет находиться в заданных координатах.Далее мы просим В этом OpenGL помогает класс Делаем ведро подвижным (прикосновение/мышь)Время, чтобы позволить пользователю управлять ведром. Ранее мы сказали, что мы будем позволять пользователю перетаскивать ведро. Давайте сделаем задачу немного легче. Если пользователь прикасается к экрану (или нажимает кнопку мыши), то ведро центрируется по горизонтали, относительно точки прикосновения. Добавление следующего кода в конец метода 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() , который спрашивает модуль ввода: есть ли на данный момент прикосновение к экрану (или нажатие кнопки мыши)? Далее идет преобразование координат прикосновения/мыши в систему координат камеры. Это необходимо, поскольку система координат прикосновения/мыши может отличаться от используемой нами системы для представления объектов в мире.
Примечание: очень и очень плохо, когда постоянно создается экземпляр нового объекта, в нашем случает экземпляр Примечание: Делаем ведро подвижным (клавиатура)На 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Мы также должны убедиться в том, что ведро остается в пределах экрана: if(bucket.x < 0) bucket.x = 0; if(bucket.x > 800 - 64) bucket.x = 800 - 64; Добавляем каплюДля хранения капель используется список экземпляров Array raindrops;
Класс Так же необходимо отслеживать последние появление капли, так что добавим еще одно поле. long lastDropTime;
Мы будет хранить время в наносекундах, поэтому мы используем Для облегчения создания капли мы напишем метод, называемый 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(); } Метод должен быть довольно очевидным. Класс В методе raindrops = new Array(); spawnRaindrop(); Затем добавляем несколько строк в метод render() , который будет проверять сколько времени прошло с тех пор, как была создана новая капля и если необходимо создать еще одну новую каплю.
if(TimeUtils.nanoTime() - lastDropTime > 1000000000) spawnRaindrop(); Также нужно сделать так, чтобы капли двигались. Давайте пойдем легким путем, и пусть они двигаются с постоянной скоростью 200 пикселей в секунду. Если капля находится ниже нижнего края экрана, мы удаляем ее из массива. Iterator iter = raindrops.iterator(); while(iter.hasNext()) { Rectangle raindrop = iter.next(); raindrop.y -= 200 * Gdx.graphics.getDeltaTime(); if(raindrop.y + 64 < 0) iter.remove(); } Капли нужно отобразить на экране. Мы добавим это в код визуализации, который выглядит сейчас так: 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(); } Метод ОчисткаПользователь может закрыть приложение в любой момент. Для этого просто примера ничего особенного делать не нужно. Тем не менее, хорошим тоном считается небольшая помощь операционной системе в виде наведения порядка в созданном нами бардаке. Любые классы libgdx классы, которые реализуют интерфейс @Override public void dispose() { dropImage.dispose(); bucketImage.dispose(); dropSound.dispose(); rainMusic.dispose(); batch.dispose(); } После освобождения ресурса вы не должны больше обращаться к нему. Ресурсы реализующие Обработка паузы и возобновленияAndroid имеет особенность приостанавливать и возобновлять приложения всякий раз, когда пользователю звонят или при нажатии кнопки home. Libgdx для таких случаев делает много вещей автоматически, например перезагружает изображения, которые могут быть потеряны (потеря OpenGL контекста), останавливает и возобновляет потоковую музыку и так далее. В нашей игре нет реальной необходимости в обработки паузы и возобновления. Как только пользователь заходит обратно в приложение, игра продолжается с того момента где она и была. Обычно можно реализовать экран паузы и просить пользователя прикоснуться к экрану, чтобы продолжить игру. Это останется в качестве упражнения для читателя. Смотрите методы |