Book Image

Mastering LibGDX Game Development

By : Patrick Hoey
Book Image

Mastering LibGDX Game Development

By: Patrick Hoey

Overview of this book

Table of Contents (18 chapters)
Mastering LibGDX Game Development
Credits
About the Author
Acknowledgments
About the Reviewers
www.PacktPub.com
Preface
Index

Understanding the application lifecycle of LibGDX


With a better understanding of the core modules included with LibGDX, we can now look at the application lifecycle of a typical game written with LibGDX for the desktop illustrated by the following figure (Figure 6):

Figure 6

This diagram represents the cycle of your game loop, where one loop through the logic represents a frame. The following steps outline the various paths through the game loop in this figure:

  1. The LwjglApplication class will bootstrap itself with your starter class instance and the configuration that was passed into its constructor. The LwjglApplication constructor will instantiate the various subsystem modules used in LibGDX, populating the Gdx (com.badlogic.gdx) environment class with static instances of the Application, Graphics, Audio, Input, Files, and Net modules described previously in Figure 4. The LwjglApplication constructor will then spawn a thread, which we will call the main loop, that will run until the game exits. This main loop thread represents the lifecycle of your game and the instantiation is referred to by the Game Start note for the initial node in the activity diagram. Now, we can move onto step 2.

  2. The next node in the diagram after the initial start is an action node designated as create(). The create() method is an interface method that you must implement when creating your game in order to hook into the lifecycle of LibGDX. During the start of the game loop thread, create() will be called only once. This is going to be where the initialization code for your game should be located, including preloading assets, instantiating game screens, and initializing the game subsystems. Now, we can move onto step 3.

  3. This step represents our first decision node in the loop that is checked at the beginning of every cycle through the game loop, or once per frame. The test is whether the game is still running or not and allows us to exit if the game state has changed to not running. As a side note, in order to guarantee that you properly exit your game, which allows the execution of cleanup code to happen in the correct order, it is recommended that you follow the LibGDX convention of exiting your game by using the following statement when quitting:

    Gdx.app.exit();

    This call into the static instance of the application object will set the running state of the game loop to false and subsequently move to the next step allowing the graceful exit of your game. So, if the game is running, then we can move to step 7. If the game is not running, then we can move to step 4.

  4. The action node designated by pause() in the activity diagram is one of the interface methods that must be implemented when you create your game. This method guarantees that proper handling of the game will be done, such as saving the current state of the game. After this finishes, we transition to the next step.

  5. The action node designated by dispose() in the activity diagram is an interface method that guarantees proper cleanup of game resources still in use when the game is in the process of being destroyed and is the proper location to free those resources. For clarification, since this is a Java environment, all objects that implement the Disposable interface (com.badlogic.gdx.utils) should call their appropriate dispose() method here. For example, a dispose() method call for an unmanaged Texture object, under the covers, will delete the texture from the static OpenGL instance and invalidate its handle. Now, we move onto step 6.

  6. After dispose() has finished its work, the draw surface is destroyed along with any audio hardware handles still in use and then runs System.exit(). This step is referred to in the previous figure by the Game Stop note for the final node in the activity diagram.

  7. The next step in the diagram brings us to a decision node where the game loop figures out whether we are transitioning from a previous change in a draw surface state. When the game loop starts, the previous state of the draw surface is set to active as an initial placeholder and then the current draw surface is checked to see whether we are active. The active state for the draw surface, for the desktop, indicates whether the draw surface is in focus (not behind another window) or maximized. If it is determined that we have transitioned to a new state for the draw surface, then we move to the next decision node on step 8. If there is no transition to a new state, then we can continue onto step 11.

  8. After it was determined that some change in the state has occurred since the last frame (the last cycle of the loop), the next step in the diagram of the game loop will determine whether we have transitioned from a previous state of focused to the current state of not focused, or vice versa. If the draw surface was focused in the last frame but now it is not, then we are minimized, so we will transition to the action node designated by pause() in step 9. If the draw surface was not focused in the last frame, but now is focused, then we are maximized, so we will transition to the action node designated by resume() in step 10.

  9. During a cycle in the game loop, if the drawing surface state has changed from being maximized or in focus to being minimized or not focused, then pause() will be called by the game loop. After this completes, we move onto step 11.

  10. During a cycle in the game loop, if the drawing surface state has changed from being minimized or not in focus to being maximized or in focus, then resume() will be called by the game loop. The action node designated by resume() in the activity diagram is another interface method that must be implemented by your game. This method will guarantee proper handling from a previously paused state, such as reloading saved state information from disk. On Android-based devices, this would also be where you would reload unmanaged textures, but this is not a concern for the desktop. After this completes, we move onto step 11.

  11. The next node in the diagram represents a decision regarding whether or not the dimensions of the current draw surface have changed since the last frame update. If the dimensions have changed, then we move onto step 12. If the dimensions have not changed, then we move onto step 13.

  12. The size of the draw surface has changed since the last frame update, and thus this will trigger a resize event. The action node designated by resize() in the activity diagram is an interface method that must be implemented by your game. This method's parameter list represents the new dimensions, width and height in pixels, of the game screen. These dimensions are typically passed through to the screen or draw surface to be updated on the next render call. Once this step completes, we move onto step 13.

  13. This action node deals with getting state information from the input devices. This includes updating the deltaTime interval for the current frame, updating the mouse cursor location and mouse button press events, and updating the keyboard key press events. After this step completes, we move onto step 14.

  14. This action node deals with processing the state information from the input devices. This primarily includes passing the mouse cursor location, mouse button presses, and keyboard key presses up to the input event handler class, InputProcessor. Your game will usually instantiate this class and set it in the environment class, Gdx, so that you can access the input events every frame. When this step completes, we can move onto step 15.

  15. This action node deals with updating the audio resources for the current frame. For the desktop, this is primarily used to update the music since music will be playing across frames. When this step completes, we move onto step 16.

  16. This decision node deals specifically with a test for whether we should render the draw surface or not. If we need to render (such as a game object's position has changed this frame, or the mouse cursor has moved), then we move onto step 17. Otherwise, if we do not need to render, then we have completed one cycle of this game loop and we will start back at step 3 for the next frame.

  17. The action node designated by render() in the activity diagram is an interface method that must be implemented by your game. This method will be the most important one in the lifecycle of your game. Commands to render your game scenes to the screen will live here, along with processing UI components, physics calculation updates, AI routines, and game object updates. Once this step completes and we have finished updating the current frame, we will go back to step 3 to start a new one.

In summary, in the preceding figure, the actions designated as create(), resize(), render(), pause(), resume(), and dispose()are all methods in the ApplicationListener interface, as shown in the following figure (Figure 7):

Figure 7

This figure is a class diagram that describes the method names and their corresponding signatures of the ApplicationListener interface that your game must implement. These methods represent hooks that the main game loop, in LibGDX, will call into based on certain system events triggered during the lifetime of your game.

A quick recap of the responsibilities that these interface methods represent is as follows:

  • create(): This is called at the start only once during the lifetime of the game. Typically, this is the proper place for initialization code (in preparation for the start of your game) to live.

  • resize(int width, int height ): This is called every time the size of the game screen is changed. The parameters both represent the new width and height (in pixels) of the game screen.

  • render(): This method is called every time the game screen needs to be rendered, such as a resize event or changes in the game screen. The commands to render your game scenes to the screen should live here.

  • pause(): On the desktop, this method is called when the game screen is minimized, a key is pressed by the user, or when the game is exiting. Typically, this is a good location for saving the game state.

  • resume(): On the desktop, this method is called when the game screen is maximized, from a previous state of being minimized.

  • dispose(): This method is called when the game is being destroyed. This would be the appropriate place to clean up and free all resources still in use.

Historically, LibGDX started out as a small library for Android-based devices, which explains why LibGDX is event-driven in nature, since it is modeled after Android's lifecycle. There is no explicit main loop for our game, but for the purposes of defining the main loop, the render() method that you implement from the ApplicationListener interface will, for all intents and purposes, be the main body of your game.

Now that we have an overview of the packages and lifecycle of LibGDX, we can now generate a starter project that we will develop throughout the remainder of this book.