Расширение простой игры

В этом уроке мы будет расширять простую libGDX игру с названием "Drop", сделанную в предыдущем базовом уроке. Мы добавим экран меню и пару возможностей, делающих игру более полнофункциональной.

Давайте начнем со знакомства с более продвинутыми libGDX классами в игре.

Интерфейс Screen

Экраны имеют основополагающее значение для любой игры с несколькими компонентами. Экраны содержат много методов, которые вы использовали в ApplicationListener объектах, и включают в себя пару новых методов show и hide, которые вызываются при получении и потери фокуса соответственно.

Класс Game

Класс Game в libGDX является абстрактным и предоставляет для использования реализацию ApplicationListener, наряду со вспомогательными методами для обработки визуализации экрана.

Вместе объекты Screen и Game в libGDX используются для создания простой и мощной структуры для игр.

Мы начнем с создания Game объекта, который будет входной точкой нашей libGDX игры.

Давайте посмотрим и пройдемся по коду:

package com.badlogic.drop;

import com.badlogic.gdx.Game;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;


public class Drop extends Game {

    SpriteBatch batch;
    BitmapFont font;

    public void create() {
        batch = new SpriteBatch();
        // libGDX по умолчанию использует Arial шрифт.
        font = new BitmapFont();
        this.setScreen(new MainMenuScreen(this));
    }

    public void render() {
        super.render(); // важно!
    }

    public void dispose() {
        batch.dispose();
        font.dispose();
    }

}

Приложение начинается с создания экземпляра SpriteBatch и BitmapFont. Создание множества экземпляров, которые можно использовать совместно является плохой практикой. Объект SpriteBatch используется для отображения объектов на экране, таких как текстуры; BitmapFont используется для отображения текста на экране. Мы коснемся этого подробнее в Screen классах.

Затем мы устанавливаем Screen экран игры как MainMenuScreen, единственный параметр экземпляр Drop класса.

Общей ошибкой является забывчивость вызова super.render(). Без этого вызова экран, который вы установили в Create() методе не будет отображен!

Наконец напоминание об освобождении объектов.

Главное меню

Рассмотрим MainMenuScreen класс.

package com.badlogic.drop;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.OrthographicCamera;

public class MainMenuScreen implements Screen {

    final Drop game;

    OrthographicCamera camera;

    public MainMenuScreen(final Drop gam) {
        game = gam;

        camera = new OrthographicCamera();
        camera.setToOrtho(false, 800, 480);
    }

    // остальное опущено для краткости...

}
ф

В этом фрагменте кода, мы делаем конструктор для MainMenuScreen класса, который реализует интерфейс Screen. Интерфейс Screen не предоставляет какой-либо create() метод, поэтому вместо этого используется конструктор. Единственным необходимым параметром конструктора для игры является экземпляр Drop, так что если необходимо, то можно вызывать его методы и поля.

Последний метод в MainMenuScreen классе: render(float)

public class MainMenuScreen implements Screen {

    //public MainMenuScreen(final Drop gam)....

    @Override
    public void render(float delta) {
        Gdx.gl.glClearColor(0, 0, 0.2f, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

        camera.update();
        game.batch.setProjectionMatrix(camera.combined);

        game.batch.begin();
        game.font.draw(game.batch, "Welcome to Drop!!! ", 100, 150);
        game.font.draw(game.batch, "Tap anywhere to begin!", 100, 100);
        game.batch.end();

        if (Gdx.input.isTouched()) {
            game.setScreen(new GameScreen(game));
            dispose();
        }
    }

    // остальное опущено для краткости...

}

Этот код довольно прост, за исключением того, что нам нужно вызывать SpriteBatch и BitmapFont экземпляры из game поля, вместо создания собственных. Метод game.font.draw(SpriteBatch, String, float,float) отбражает текст на экране. libGDX поставляется с предварительно установленным шрифтом, Arial, так что можно использовать конструктор по умолчанию и все равно получить шрифт.

Замет проверяется, было ли прикосновение к экрану, если это так, то мы устанавливаем GameScreen экземпляр и освобождаем ресурсы MainMenuScreen экземпляра. Остальные методы, которые необходимы для реализации в MainMenuScreen остаются пустыми.

Экран игры

Теперь, когда главное меню закончено, время сделать игру. Мы будем использовать большую часть кода из предыдущего урока простой libGDX игры, чтобы избежать избыточности и предположения того, что это новая игра, так как это все таже Drop игра.

package com.badlogic.drop;

import java.util.Iterator;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.Screen;
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.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 GameScreen implements Screen {
    final Drop game;

    Texture dropImage;
    Texture bucketImage;
    Sound dropSound;
    Music rainMusic;
    OrthographicCamera camera;
    Rectangle bucket;
    Array<Rectangle> raindrops;
    long lastDropTime;
    int dropsGathered;

    public GameScreen(final Drop gam) {
        this.game = gam;

        // загрузка изображений для капли и ведра, 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);

        // создает камеру
        camera = new OrthographicCamera();
        camera.setToOrtho(false, 800, 480);

        // создается 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(float delta) {
        // очищаем экран темно-синим цветом.
        // Аргументы для glClearColor красный, зеленый
        // синий и альфа компонент в диапазоне [0,1]
        // цвета используемого для очистки экрана.
        Gdx.gl.glClearColor(0, 0, 0.2f, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

        // сообщает камере, что нужно обновить матрицы.
        camera.update();

        // сообщаем SpriteBatch о системе координат
        // визуализации указанных для камеры.
        game.batch.setProjectionMatrix(camera.combined);

        // начитаем новую серию, рисуем ведро и
        // все капли
        game.batch.begin();
        game.font.draw(game.batch, "Drops Collected: " + dropsGathered, 0, 480);
        game.batch.draw(bucketImage, bucket.x, bucket.y);
        for (Rectangle raindrop : raindrops) {
            game.batch.draw(dropImage, raindrop.x, raindrop.y);
        }
        game.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)) {
                dropsGathered++;
                dropSound.play();
                iter.remove();
            }
        }
    }

    @Override
    public void resize(int width, int height) {
    }

    @Override
    public void show() {
        // воспроизведение фоновой музыки
        // когда отображается экрана
        rainMusic.play();
    }

    @Override
    public void hide() {
    }

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }

    @Override
    public void dispose() {
        dropImage.dispose();
        bucketImage.dispose();
        dropSound.dispose();
        rainMusic.dispose();
    }

}

Это код состоит почти на 95% из оригинальной реализации, только теперь мы используем конструктор вместо Create() метода ApplicatonListener и передаем объект Drop как в MainMenuScreen классе. Мы также воспроизводим музыку, как только экран установлен в GameScreen.

Мы также добавили строку в верхнем левом углу игры, которая отслеживает количество собранных капель дождя.

Теперь у вас есть полностью законченная игра. Вот и все, что нужно знать о Screen интерфейсе и абстрактном Game классе, и все это создает многогранную игру с несколькими состояниями.

Пожалуйста, посетите Github для получения полного исходного кода.

Будущее

Теперь, когда у вас есть понимание о нескольких экранах, настало время ими воспользоваться. Изучите Scene2d, Scene2D.ui и Skin, чтобы сделать более красивым ваше главное меню.

Если вы также уже прочитали шаги из предыдущего урока Drop, то вы должны быть готовы сделать вашу собственную игру. Лучшей практикой будет сделать это, так что идите и создавайте собственную игру.