Game Dev: Разделяем игру на экраны

Примитивная игра на LibGDX
В этой короткой статье мы немного изменим примитивную одноэкранную игру из предыдущей статьи, добавив экран, который впоследствии станет главным меню нашей игрушки.
На данный момент у нас в подпроекте "core" всего один класс Drop:
public class Drop implements ApplicationListener {
   public void create() {...}

   public void render() {...}

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

   public void pause() {}

   public void resume() {}

   public void dispose() {...}
}
Он расширен засчет интерфейса ApplicationListener, т.е. представляет из себя класс, который первым делом вызывается при запуске игры (из класса-лаунчера).
Для создания многоэкранных приложений в LibGDX существует класс Game. Это наследник ApplicationListener, в него добавлены всего лишь 2 метода:
public void setScreen (Screen screen)
public Screen getScreen ()
Как вы можете догадаться, они предназначены для работы с игровыми экранами.
Что же такое Screen? По факту это тот же самый ApplicationListener, но вызывается не классом-лаунчером, а классом-игрой. Класс Game позволяет переключаться с экрана на экран, благодаря чему можно сделать меню для игры, сплэш скрин, различные внутриигровые окна типа карты, окон торговли, разговора, миниигр.
Поскольку окон, в отличие от самой игры, может быть несколько и их можно создать одновременно, то метод create() заменён обычным конструктором класса. Второе отличие Screen от ApplicationListener - метод render(float delata). Класс Game вызывает метод render с аргументом времени, прошедшим с момента последнего вызова этого же метода. Т.е. окна всегда знают, когда обновлялись в предыдущий раз. И последнее отличие - наличие двух вспомогательных методов для работы экранов.

Создаём экран с игрой

Так как у нас уже есть готовая игра, то проще будет начать с игрового экрана, а потом уже добавить меню. Для этого нам необходимо скопировать класс Drop и назвать копию GameScreen. Класс Drop мы полностью изменим и превратим в обработчик экранов, а класс GameScreen подправим так, чтобы он стал игровым экраном. Последним сейчас и займёмся.
Для этого мы меняем класс-расширитель с ApplicationListener на Screen:
public class GameScreen implements ApplicationListener
public class GameScreen implements Screen
И сразу же получаем 3 ошибки. Начнём исправлять с самого простого - добавим методу render() аргумент:
	public void render(float delta) {
Теперь изменим create() на конструктор.
	@Override
	public void GameScreen() {
Меняем на
	public GameScreen(final Drop game) {
В качестве аргумента экран получит ссылку на обработчик экранов. Чтобы её не потерять, нужно создать переменную в блоке переменных
	private Drop game;
Ну и конечно же необходимо присвоить полученное значение:
	public GameScreen(final Drop game) {
		this.game = game;

		...
	}
Для работоспособности экрана осталось лишь добавить отсутствующие методы
	@Override
	public void show() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void hide() {
		// TODO Auto-generated method stub
		
	}
Игровое окно готово, чуть позже мы его немножко оптимизируем, а сейчас займемся обработчиком окон.

Класс Drop

Первым делом меняем интерфейс ApplicationListener на класс-предок Game
public class Drop implements ApplicationListener {
public class Drop extends Game {
Поскольку теперь это не класс с игрой, а всего лишь связующее звено для экранов, то мы можем смело удалить весь игровой код и заменить этим:
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 {

    public SpriteBatch batch;
    public BitmapFont font;

    public void create() {
        batch = new SpriteBatch();
        font = new BitmapFont();
        this.setScreen(new GameScreen(this));
    }

    public void render() {
        super.render(); // Архиважно! Иначе ничего не будет рисоваться.
    }

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

}
Хорошим тоном в ООП считается реюзабельность объектов, в частности, в нашей игре мы будем использовать один и тот же батч для всех игровых экранов. Поэтому мы создаём публичные переменные
    public SpriteBatch batch;
    public BitmapFont font;
Для того, чтобы к ним могли все обращаться через вызов класса Drop из переменной game.
game.batch.draw(...);
Аналогично со шрифтом. При использовании пустого конструктора создаётся стандартный Arial шрифт.
В методе create() батч и фонт инициализируются, а также запускается акивный экран - тот экран, который игрок увидит первым делом.
Метод render() запускает отрисовку активного игрового экрана. Если вы перфекционист и вам не нравятся лишние процедурные вызовы, можете заменить вызов метода-предка на строку
if (screen != null) screen.render(Gdx.graphics.getDeltaTime());
Метод dispose(), разумеется, очищает глобальные переменные.

Зачаток главного меню

Сейчас мы не будем делать меню как таковое, привычное для нас. Оно будет без каких-либо кнопок, в нём будет лишь одна пояснительная строчка о том, что нужно тыкнуть мышью(пальцем) в любое место на экране, чтобы начать игру. Делать насстоящее меню мы будем в следующей статье.
Создайте третий класс, назовите его MainMenuScreen и расширьте интерфейсом Screen.
Создайте ему конструктор с аргументом Drop объявите соответствующую переменную:
public class MainMenuScreen implements Screen {

	private Drop game;
	
	public MainMenuScreen(final Drop game){
		this.game = game;
	}
	
	...
}
А теперь в методе render(float delta) выведем строчку текста.
public void render(float delta) {
	Gdx.gl.glClearColor(0, 0, 0.2f, 1);
	Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
	
	game.batch.begin();
	game.font.draw(game.batch, "Tap anywhere to begin!", 100, 100);
	game.batch.end();
	
	if (Gdx.input.isTouched()) {
		game.setScreen(new GameScreen(game));
		dispose();
	}

}
Сперва при помощи модуля gl очищаем экран. Затем испольуем глобальные батч и фонт для рисования строки. И в конце мы ловим клик и переключаемся на игровой экран.
Чтобы "включить" наше меню, необходимо исправить обработчик экранов. Помните, что там устанавливается сразу игровой экран? Заменяем игровой экран на экран меню.
this.setScreen(new GameScreen(this));
this.setScreen(new MainMenuScreen(this));
Теперь перед началом игры мы видим синий экран с белой надписью. Кликаем мышкой - и начинаем игру. Но это ещё не всё, нам нужно немножко оптимизировать GameScreen.

Исправляем GameScreen

В данный момент GameScreen использует свой собственный батч для прорисовки. Нам нужно убрать его.
Вернее, заменить глобальным, который хранится в обработчике экранов. Это значит, что нужно удалить переменную,
private SpriteBatch batch;
её инициализацию
// и батча
batch = new SpriteBatch();
и её очищение.
batch.dispose();
И заменить все вызовы на вызовы глобальной переменной
batch.end();
game.batch.end();
Разумеется, батч и фонт - не единственное, что стоит вынести из игрового экрана. Например, текстуры и звуки - ведь их можно использовать в других экранах. Запустить фоновую музыку в главном меню, пустить там же фоном капли и т.д.. Но это в качестве домашнего задания.
Также в качестве самостоятельной работы вы можете добавить счетчик в GameScreen, учитывать каждую пойманную каплю и выводить счёт на экран.

Просмотров: 987

Комментарии пока отсутcтвуют