Предисловие
В настоящее время я работаю над собственным игровым движком. С использованием минимального количества сторонних библиотек, после реализации игрового цикла (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/
Добавить комментарий