Создание системы сцен для игрового движка

от автора


Предисловие

В настоящее время я работаю над собственным игровым движком. С использованием минимального количества сторонних библиотек, после реализации игрового цикла (game loop), отрисовки кадра, функции «update», загрузки текстур и пр., основная «начинка» движка была готова. Пришло время реализации еще одной важной составляющей — сцены (scene).

Введение

В статье я предполагаю, что движок уже оснащен игровым циклом с «callback»-функциями. Весь код будет написан на Java, но может быть легко перенесен на любой другой язык, поддерживающий garbage collection. Что-ж, приступим.

Что уже есть

Как упоминалось ранее, мы уже располагаем игровым циклом. Пусть он выглядит примерно так:

void awake() {     RenderUtil.init(); // настройка параметров OpenGL     run(); }  void run() {     // game loop     // ...     // прочитать input, обновить frame rate и т. п.     //     if (Window.isCloseRequested()) {   // если игру закрыли         stop();         return;     }     update();     render(); } 

Реализацию методов render() и stop() я приведу чуть позже.

Определяем сцену

Прежде чем начинать писать класс для сцены, необходимо решить, что она будет собой представлять. В моем случае — это объект, включающий в себя множество игровых объектов. Оторвитесь на секунду от чтения и посмотрите вокруг себя: все, что вы видите(почти), мы назовем игровыми объектами, а где находитесь — сценой.

Что я подразумеваю под игровым объектом? Это объект, реализующий «callback»-функции игрового цикла. Для тех, кто знаком с Unity3D: аналогия с объектом, класс которого реализует MonoBehaviour.

Пусть этот самый игровой объект представляется интерфейсом(или абстрактным классом — в зависимости от нужного функционала), который мы назовем GameListener (опять же, в данной статье я подразумеваю, что этот класс уже так или иначе реализован).
Примитивная реализация интерфейса может выглядить следующим образом:

public interface GameListener {     void start() ;     // вызывается, когда игра началась     void update();  // вызывается каждый фрейм     void draw();     // аналогично update()     void destroy(); // вызывается, когда объект "отключается" } 

Количество функций зависит от нужной степени контроля, например, у Unity3D их достаточно много.

Реализуем класс Scene

После определения архитектуры и строения нашей сцены, можно наконец-таки приступить к её реализации.

public abstract class Scene implements GameListener {      ArrayList<GameListener> gameListeners = new ArrayList<>();      public abstract void initializeScene();      public final void AddToScene(GameListener gameListener) {         gameListeners.add(gameListener);     }      public final void onInitializeScene() {         if (gameListeners.isEmpty())             initializeScene();     }      @Override     public final void start() {         for (GameListener gameListener : gameListeners)             gameListener.start();     }          @Override     public final void update() {         for (GameListener gameListener : gameListeners)             gameListener.update();     }      @Override     public final void draw() {         for (GameListener gameListener : gameListeners)             gameListener.draw();     }          public final void onDestroy() {         for (GameListener gameListener : gameListeners)             gameListener.destroy();          gameListeners.clear();     }          @Override     public final void destroy() {} 

Комментарии я распишу по пунктам:

  • Наших игровых объектов сцена хранит в коллекции ArrayList
  • Метод initializeScene() — абстрактный. В нём мы будем добавлять игровых объектов в сцену, используя метод AddToScene() в нашем конкретном классе-сцене;
  • Метод onDestroy() мы будем вызывать после смены/перезапуска сцены или закрытия игры. В нём мы очищаем сцену от игровых объектов, garbage collector позаботится об остальном (можно намекнуть JVM провести очистку, вызвав System.gc());

Стоит заметить, что все методы (кроме initializeScene() естественно) помечены ключевым словом final, таким образом, в классе Scene пользователь движка может только добавить своих игровых объектов (такое ограничение пока вполне меня устраивает).

Преобразования в игровом цикле

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

Scene runningScene;  void awake() {     RenderUtil.init();     runningScene = SceneManager.getScene(0);     run(); }  void run() {     if (Window.isCloseRequested()) {          stop();         return;     }     runningScene.update();     runningScene.render(); }  void stop() { runningScene.onDestroy(); }  void render() {  runningScene.draw(); } 

Все наши созданные сцены мы можем добавить в массив, содержащийся, к примеру, в каком-нибудь классе, под названием SceneManager. Тогда он будет выступать в качестве контроллера нашей системой сцен, представляя методы getScene(), setScene() и т. п.

На данном этапе реализация системы очень сильно напоминает паттерн «Состояние». Так оно и есть.

Смена сцен

Для смены сцен мы можем определить аналогичный экземпляр класса Scene в SceneManager:

private static Scene currentScene;

Далее напишем setter setCurrentScene(Scene):

public static void setCurrentScene(Scene scene) { currentScene = scene; }

Тогда в игровом цикле мы сравниваем runningScene с currentScene и, если они не совпадают, меняем сцену:

void run() {     if (Window.isCloseRequested()) {          stop();         return;     }     runningScene.update();     if (runningScene != SceneManager.getCurrentScene()) {        runningScene.onDestroy();        runningScene = SceneManager.getCurrentScene();     }     runningScene.render(); } 

Важно не забыть вызвать метод onDestroy() текущей сцены для удаления её игровых объектов.

Реализация additive loading

В той же Unity3D есть возможность «аддитивной» загрузки сцен. При таком методе объекты «старой» сцены не удаляются (в нашем случае — не вызывается метод onDestroy()), а новая сцена загружается «поверх» старой.

Этого можно достичь, например, создав контейнер, хранящий список загруженных аддитивно сцен. Тогда наряду с вызовом

runningScene.update();

нужно будет сказать что-то типа

for (Scene additive : additives) additive.update();

и так далее.

Вызвать onDestroy() придется в случае перезагрузки/смены основной сцены(runningScene) или закрытия игры.

Архитектура, процедура добавления игровых объектов и самой сцены остаются такими же.

ссылка на оригинал статьи http://habrahabr.ru/post/272161/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *