Let's now move on from theory to practice and get down to the actual implementation details. We will begin with implementing the first basic version of CanyonBunnyMain
, WorldController
, and WorldRenderer
. Additionally, we will use a utility class to store constant values in a new class called Constants
. It is true that this class does not appear in the class diagram, as it is just there for our convenience to avoid scattering or, even worse, duplicating certain constants all over the source code files. Also, as the stored values in Constants
are meant to be used in virtually any other class, it would only clutter up the class diagram by drawing one additional line for each class to Constants
.
Here is the listing of the code for Constants
:
package com.packtpub.libgdx.canyonbunny.util; public class Constants { // Visible game world is 5 meters wide public static final float VIEWPORT_WIDTH = 5.0f; // Visible game world is 5 meters tall public static final float VIEWPORT_HEIGHT = 5.0f; }
First, we need to define the visible world size that can be seen at once when it is not moving around in the game world. In this case, we have chosen a visible world size of five meters in terms of its width and height.
Next, we will create the other three mentioned classes, but will only add the so-called method stubs (empty methods). This way, we can focus on the layout first and gradually implement the code and other new features later, when needed. Hopefully, this approach will give you the best insight into the whole development process from start to finish.
The following listing shows the first implementation of CanyonBunnyMain
:
package com.packtpub.libgdx.canyonbunny; import com.badlogic.gdx.ApplicationListener; import com.packtpub.libgdx.canyonbunny.game.WorldController; import com.packtpub.libgdx.canyonbunny.game.WorldRenderer; public class CanyonBunnyMain implements ApplicationListener { private static final String TAG = CanyonBunnyMain.class.getName(); private WorldController worldController; private WorldRenderer worldRenderer; @Override public void create () { } @Override public void render () { } @Override public void resize (int width, int height) { } @Override public void pause () { } @Override public void resume () { } @Override public void dispose () { } }
This class implements ApplicationListener
to become one of LibGDX's starter classes.
A reference each to WorldController
and WorldRenderer
enables this class to update and control the game's flow and also to render the game's current state to the screen.
There is a TAG
variable that holds a unique label derived from the class's name. It will be used for any logging purposes. LibGDX's built-in logging facility requires you to pass in a so-called tag name for every message to be logged. So, to stay consistent in our code, we will simply add a tag variable to each class.
The following listing shows the first implementation of WorldController
:
package com.packtpub.libgdx.canyonbunny.game; public class WorldController { private static final String TAG = WorldController.class.getName(); public WorldController () { } private void init () { } public void update (float deltaTime) { } }
This class has an internal init()
method that initializes it. Naturally, all the initialization code could also be put into the constructor. However, it appears to be very helpful in many ways when an initialization code is available in a separate method. Whenever we need to reset an object in the game, we do not always want or have to completely rebuild it, thereby saving a lot of performance. Also, this approach can greatly reduce the interruptions by the Garbage Collector (GC). Instead, we try to actively reuse existing objects, which is always a recommended design goal to maximize performance and minimize memory usage. This is especially true for smartphones such as Android with limited resources.
The update()
method will contain the game logic and will be called several hundred times per second. It requires a delta time so that it can apply updates to the game world according to the fraction of time that has passed since the last rendered frame.
The following listing shows the first implementation of WorldRenderer
:
package com.packtpub.libgdx.canyonbunny.game; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.utils.Disposable; import com.packtpub.libgdx.canyonbunny.util.Constants; public class WorldRenderer implements Disposable { private OrthographicCamera camera; private SpriteBatch batch; private WorldController worldController; public WorldRenderer (WorldController worldController) { } private void init () { } public void render () { } public void resize (int width, int height) { } @Override public void dispose () { } }
This class also has an internal init()
method for its initialization. Furthermore, it contains a render()
method that will contain the logic to define in which order the game objects are drawn over others. Whenever the screen size is changed, including the event at the start of the program, resize()
will spring into action and initiate the required steps to accommodate the new situation.
The rendering is accomplished using an orthographic camera that is suitable for two-dimensional projections. Fortunately, LibGDX comes with a ready-to-use OrthographicCamera
class to simplify our 2D rendering tasks. The SpriteBatch
class is the actual workhorse that draws all our objects with respect to the camera's current settings (for example, position, zoom, and so on) to the screen. As SpriteBatch
implements LibGDX's Disposable
interface, it is advisable to always call its dispose()
method to free the allocated memory when it is no longer needed. We will do this in WorldRenderer
by also implementing the Disposable
interface. This allows us to easily cascade the disposal process when dispose()
in CanyonBunnyMain
is called by LibGDX. In this case, we will simply call the WorldRenderer
class' dispose()
method, which in turn will call the SpriteBatch
class' dispose()
method.
Notice that this class requires a reference to an instance of WorldController
in its constructor so that it will be accessible later on to render all the game world objects that are managed by the controller.