Foreword
I am currently working on my own game engine. Using the minimum number of third-party libraries, after the game loop was implemented (game loop), frame rendering, update function, texture downloads, etc., the main stuffing of the engine was ready. It's time to implement another important component - the scene.
Introduction
In this article, I assume that the engine is already equipped with a game cycle with “callback” functions. All code will be written in Java, but can be easily transferred to any other language that supports the
garbage collection . Well, let's get started.
What is already there
As mentioned earlier, we already have a game cycle. Let it look something like this:
void awake() { RenderUtil.init();
I will provide implementation of the render () and stop () methods a bit later.
')
We define the scene
Before you start writing a class for a scene, you must decide what it will represent. In my case, this is an object that includes many game objects. Tear off for a second from reading and look around you: everything that you see (almost) we will call game objects, and where you are - a scene.
What do I mean by game object? This is an object that implements the "callback" function of the game cycle. For those familiar with
Unity3D : an analogy with an object whose class implements MonoBehaviour.
Let this very game object be represented by an interface (or an abstract class - depending on the required functionality), which we will call
GameListener (again, in this article I mean that this class is already implemented one way or another).
A primitive implementation of the interface can look like this:
public interface GameListener { void start() ;
The number of functions depends on the desired degree of control, for example, Unity3D has
quite a lot of them .
Implement the Scene class
After determining the architecture and structure of our scene, you can finally begin to implement it.
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() {}
Comments I will write down the points:
- Our game objects are stored in the ArrayList collection.
- The initializeScene () method is abstract. In it, we will add game objects to the scene using the AddToScene () method in our particular scene class;
- We will call the onDestroy () method after changing / restarting the scene or closing the game. In it, we clear the scene of the game objects, garbage collector takes care of the rest (you can hint the JVM to clean up by calling System.gc () );
It is worth noting that all methods (except for initializeScene (), of course) are marked with the
final keyword, thus, in the Scene class, the user of the engine can only add their game objects (this restriction is fine for me so far).
Transformations in the game cycle
Now it is necessary to carry out transformations in the game cycle. All of them, in fact, intuitive.
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(); }
We can add all our created scenes to an array contained, for example, in some class called
SceneManager . Then it will act as a controller for our scene system, introducing the methods
getScene () ,
setScene (), and so on.
At this stage, the implementation of the system is very much like the
“State” pattern. The way it is.
Scene change
To change scenes, we can define a similar instance of the Scene class in the SceneManager:
private static Scene currentScene;
Next, write
setter setCurrentScene (Scene):
public static void setCurrentScene(Scene scene) { currentScene = scene; }
Then in the game loop we compare
runningScene with
currentScene and, if they do not match, change the scene:
void run() { if (Window.isCloseRequested()) { stop(); return; } runningScene.update(); if (runningScene != SceneManager.getCurrentScene()) { runningScene.onDestroy(); runningScene = SceneManager.getCurrentScene(); } runningScene.render(); }
It is important not to forget to call the
onDestroy () method of the current scene to delete its game objects.
Implement additive loading
In the same Unity3D
there is the possibility of “additive” loading of scenes. With this method, the objects of the “old” scene are not deleted (in our case, the
onDestroy () method is not called), and the new scene is loaded “on top” of the old one.
This can be achieved, for example, by creating a container that stores a list of additively loaded scenes. Then along with the challenge
runningScene.update();
will need to say something like
for (Scene additive : additives)
additive.update();
and so on.
Call
onDestroy () is necessary in case of restarting / changing the
main scene (runningScene) or closing the game.
The architecture, the procedure for adding game objects and the scene itself remain the same.