Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon

Cross-platform Development - Build Once, Deploy Anywhere

Save for later
  • 19 min read
  • 01 Oct 2013

article-image

(For more resources related to this topic, see here.)


The demo application – how the projects work together


Take a look at the following diagram to understand and familiarize yourself with the configuration pattern that all of your Libgdx applications will have in common:

cross-platform-development-build-once-deploy-anywhere-img-0


What you see here is a compact view of four projects. The demo project to the very left contains the shared code that is referenced (that is, added to the build path) by all the other platform-specific projects. The main class of the demo application is MyDemo.java. However, looking at it from a more technical view, the main class where an application gets started by the operating system, which will be referred to as Starter Classes from now on. Notice that Libgdx uses the term "Starter Class" to distinguish between these two types of main classes in order to avoid confusion. We will cover everything related to the topic of Starter Classes in a moment.

While taking a closer look at all these directories in the preceding screenshot, you may have spotted that there are two assets folders: one in the demo-desktop project and another one in demo-android. This brings us to the question, where should you put all the application's assets? The demo-android project plays a special role in this case. In the preceding screenshot, you see a subfolder called data, which contains an image named libgdx.png, and it also appears in the demo-desktop project in the same place.

Just remember to always put all of your assets into the assets folder under the demo-android project. The reason behind this is that the Android build process requires direct access to the application's assets folder. During its build process, a Java source file, R.java, will automatically be generated under the gen folder. It contains special information for Android about the available assets. It would be the usual way to access assets through Java code if you were explicitly writing an Android application. However, in Libgdx, you will want to stay platform-independent as much as possible and access any resource such as assets only through methods provided by Libgdx.


You may wonder how the other platform-specific projects will be able to access the very same assets without having to maintain several copies per project. Needless to say that this would require you to keep all copies manually synchronized each time the assets change.

Luckily, this problem has already been taken care of by the generator as follows:

The demo-desktop project uses a linked resource, a feature by Eclipse, to add existing files or folders to other places in a workspace. You can check this out by right-clicking on the demo-desktop project then navigating to Properties | Resource | Linked Resources and clicking on the Linked Resources tab.

The demo-html project requires another approach since Google Web Toolkit ( GWT ) has a different build process compared to the other projects. There is a special file GwtDefinition.gwt.xml that allows you to set the asset path by setting the configuration property gdx.assetpath, to the assets folder of the Android project. Notice that it is good practice to use relative paths such as ../demo-android/assets so that the reference does not get broken in case the workspace is moved from its original location. Take this advice as a precaution to protect you and maybe your fellow developers too from wasting precious time on something that can be easily avoided by using the right setup right from the beginning.

The following is the code listing for GwtDefinition.gwt.xml from demo-html :

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit trunk//EN" "http://google-web-toolkit.googlecode.com/svn/trunk/ distro-source/core/src/gwt-module.dtd"> <module> <inherits name='com.badlogic.gdx.backends.gdx_backends_gwt' /> <inherits name='MyDemo' /> <entry-point class='com.packtpub.libgdx.demo.client.GwtLauncher' /> <set-configuration-property name="gdx.assetpath" value="../demo-android/assets" /> </module>



Backends


Libgdx makes use of several other libraries to interface the specifics of each platform in order to provide cross-platform support for your applications. Generally, a backend is what enables Libgdx to access the corresponding platform functionalities when one of the abstracted (platform-independent) Libgdx methods is called. For example, drawing an image to the upper-left corner of the screen, playing a sound file at a volume of 80 percent, or reading and writing from/to a file.

Libgdx currently provides the following three backends:

  • LWJGL (Lightweight Java Game Library)
  • Android
  • JavaScript/WebGL


As already mentioned in Introduction to Libgdx and Project Setup , there will also be an iOS backend in the near future.

LWJGL (Lightweight Java Game Library)


LWJGL ( Lightweight Java Game Library ) is an open source Java library originally started by Caspian Rychlik-Prince to ease game development in terms of accessing the hardware resources on desktop systems. In Libgdx, it is used for the desktop backend to support all the major desktop operating systems, such as Windows, Linux, and Mac OS X.

cross-platform-development-build-once-deploy-anywhere-img-1


For more details, check out the official LWJGL website at http://www.lwjgl.org/.

Android


Google frequently releases and updates their official Android SDK. This represents the foundation for Libgdx to support Android in the form of a backend.

cross-platform-development-build-once-deploy-anywhere-img-2


There is an API Guide available which explains everything the Android SDK has to offer for Android developers. You can find it at http://developer.android.com/guide/components/index.html.

WebGL


WebGL support is one of the latest additions to the Libgdx framework. This backend uses the GWT to translate Java code into JavaScript and SoundManager2 ( SM2 ), among others, to add a combined support for HTML5, WebGL, and audio playback. Note that this backend requires a WebGL-capable web browser to run the application.

cross-platform-development-build-once-deploy-anywhere-img-3


You might want to check out the official website of GWT: https://developers.google.com/web-toolkit/.

You might want to check out the official website of SM2: http://www.schillmania.com/projects/soundmanager2/.

You might want to check out the official website of WebGL: http://www.khronos.org/webgl/.

There is also a list of unresolved issues you might want to check out at https://github.com/libgdx/libgdx/blob/master/backends/gdx-backends-gwt/issues.txt.

Modules


Libgdx provides six core modules that allow you to access the various parts of the system your application will run on. What makes these modules so great for you as a developer is that they provide you with a single Application Programming Interface ( API ) to achieve the same effect on more than just one platform. This is extremely powerful because you can now focus on your own application and you do not have to bother with the specialties that each platform inevitably brings, including the nasty little bugs that may require tricky workarounds. This is all going to be transparently handled in a straightforward API which is categorized into logic modules and is globally available anywhere in your code, since every module is accessible as a static field in the Gdx class.

Naturally, Libgdx does always allow you to create multiple code paths for per-platform decisions. For example, you could conditionally increase the level of detail in a game when run on the desktop platform, since desktops usually have a lot more computing power than mobile devices.

The application module


The application module can be accessed through Gdx.app. It gives you access to the logging facility, a method to shutdown gracefully, persist data, query the Android API version, query the platform type, and query the memory usage.

Logging


Libgdx employs its own logging facility. You can choose a log level to filter what should be printed to the platform's console. The default log level is LOG_INFO. You can use a settings file and/or change the log level dynamically at runtime using the following code line:

Gdx.app.setLogLevel(Application.LOG_DEBUG);




The available log levels are:

  • LOG_NONE: This prints no logs. The logging is completely disabled.
  • LOG_ERROR: This prints error logs only.
  • LOG_INFO: This prints error and info logs.
  • LOG_DEBUG: This prints error, info, and debug logs.


To write an info, debug, or an error log to the console, use the following listings:

Gdx.app.log("MyDemoTag", "This is an info log."); Gdx.app.debug("MyDemoTag", "This is a debug log."); Gdx.app.error("MyDemoTag", "This is an error log.");



Shutting down gracefully


You can tell Libgdx to shutdown the running application. The framework will then stop the execution in the correct order as soon as possible and completely de-allocate any memory that is still in use, freeing both Java and the native heap. Use the following listing to initiate a graceful shutdown of your application:

Gdx.app.exit();



You should always do a graceful shutdown when you want to terminate your application. Otherwise, you will risk creating memory leaks, which is a really bad thing. On mobile devices, memory leaks will probably have the biggest negative impact due to their limited resources.


Persisting data


If you want to persist your data, you should use the Preferences class. It is merely a dictionary or a hash map data type which stores multiple key-value pairs in a file. Libgdx will create a new preferences file on the fly if it does not exist yet. You can have several preference files using unique names in order to split up data into categories. To get access to a preference file, you need to request a Preferences instance by its filename as follows:

Preferences prefs = Gdx.app.getPreferences("settings.prefs");




To write a (new) value, you have to choose a key under which the value should be stored. If this key already exists in a preferences file, it will be overwritten. Do not forget to call flush() afterwards to persist the data, or else all the changes will be lost.

prefs.putInteger("sound_volume", 100); // volume @ 100% prefs.flush();



Persisting data needs a lot more time than just modifying values in memory (without flushing). Therefore, it is always better to modify as many values as possible before a final flush() method is executed.


To read back a certain value from a preferences file, you need to know the corresponding key. If this key does not exist, it will be set to the default value. You can optionally pass your own default value as the second argument (for example, in the following listing, 50 is for the default sound volume):

int soundVolume = prefs.getInteger("sound_volume", 50);



Querying the Android API Level


On Android, you can query the Android API Level, which allows you to handle things differently for certain versions of the Android OS. Use the following listing to find out the version:

Gdx.app.getVersion();



Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at €18.99/month. Cancel anytime

On platforms other than Android, the version returned is always 0.


Querying the platform type


You may want to write a platform-specific code where it is necessary to know the current platform type. The following example shows how it can be done:

switch (Gdx.app.getType()) { case Desktop: // Code for Desktop application break; case Android: // Code for Android application break; case WebGL: // Code for WebGL application break; default: // Unhandled (new?) platform application break; }



Querying memory usage


You can query the system to find out its current memory footprint of your application. This may help you find excessive memory allocations that could lead to application crashes. The following functions return the amount of memory (in bytes) that is in use by the corresponding heap:

long memUsageJavaHeap = Gdx.app.getJavaHeap(); long memUsageNativeHeap = Gdx.app.getNativeHeap();



Graphics module


The graphics module can be accessed either through Gdx.getGraphics() or by using the shortcut variable Gdx.graphics.

Querying delta time


Query Libgdx for the time span between the current and the last frame in seconds by calling Gdx.graphics.getDeltaTime().

Querying display size


Query the device's display size returned in pixels by calling Gdx.graphics.getWidth() and Gdx.graphics.getHeight().

Querying the FPS (frames per second) counter


Query a built-in frame counter provided by Libgdx to find the average number of frames per second by calling Gdx.graphics.getFramesPerSecond().

Audio module


The audio module can be accessed either through Gdx.getAudio() or by using the shortcut variable Gdx.audio.

Sound playback


To load sounds for playback, call Gdx.audio.newSound().

The supported file formats are WAV, MP3, and OGG.

There is an upper limit of 1 MB for decoded audio data. Consider the sounds to be short effects like bullets or explosions so that the size limitation is not really an issue.

Music streaming


To stream music for playback, call Gdx.audio.newMusic().

The supported file formats are WAV, MP3, and OGG.

Input module


The input module can be accessed either through Gdx.getInput() or by using the shortcut variable Gdx.input.

In order to receive and handle input properly, you should always implement the InputProcessor interface and set it as the global handler for input in Libgdx by calling Gdx.input.setInputProcessor().

Reading the keyboard/touch/mouse input


Query the system for the last x or y coordinate in the screen coordinates where the screen origin is at the top-left corner by calling either Gdx.input.getX() or Gdx.input.getY().

  • To find out if the screen is touched either by a finger or by mouse, call Gdx.input.isTouched()
  • To find out if the mouse button is pressed, call Gdx.input.isButtonPressed()
  • To find out if the keyboard is pressed, call Gdx.input.isKeyPressed()

Reading the accelerometer


Query the accelerometer for its value on the x axis by calling Gdx.input.getAccelerometerX(). Replace the X in the method's name with Y or Z to query the other two axes. Be aware that there will be no accelerometer present on a desktop, so Libgdx always returns 0.

Starting and canceling vibrator


On Android, you can let the device vibrate by calling Gdx.input.vibrate().

A running vibration can be cancelled by calling Gdx.input.cancelVibrate().

Catching Android soft keys


You might want to catch Android's soft keys to add an extra handling code for them. If you want to catch the back button, call Gdx.input.setCatchBackKey(true). If you want to catch the menu button, call Gdx.input.setCatchMenuKey(true).

On a desktop where you have a mouse pointer, you can tell Libgdx to catch it so that you get a permanent mouse input without having the mouse ever leave the application window. To catch the mouse cursor, call Gdx.input.setCursorCatched(true).

The files module


The files module can be accessed either through Gdx.getFiles() or by using the shortcut variable Gdx.files.

Getting an internal file handle


You can get a file handle for an internal file by calling Gdx.files.internal().

An internal file is relative to the assets folder on the Android and WebGL platforms. On a desktop, it is relative to the root folder of the application.

Getting an external file handle


You can get a file handle for an external file by calling Gdx.files.external().

An external file is relative to the SD card on the Android platform. On a desktop, it is relative to the user's home folder. Note that this is not available for WebGL applications.

The network module


The network module can be accessed either through Gdx.getNet() or by using the shortcut variable Gdx.net.

HTTP GET and HTTP POST


You can make HTTP GET and POST requests by calling either Gdx.net.httpGet() or Gdx.net.httpPost().

Client/server sockets


You can create client/server sockets by calling either Gdx.net.newClientSocket() or Gdx.net.newServerSocket().

Opening a URI in a web browser


To open a Uniform Resource Identifier ( URI ) in the default web browser, call Gdx.net.openURI(URI).

Libgdx's Application Life-Cycle and Interface


The Application Life-Cycle in Libgdx is a well-defined set of distinct system states. The list of these states is pretty short: create, resize, render, pause, resume, and dispose.

Libgdx defines an ApplicationListener interface that contains six methods, one for each system state. The following code listing is a copy that is directly taken from Libgdx's sources. For the sake of readability, all comments have been stripped.

public interface ApplicationListener { public void create (); public void resize (int width, int height); public void render (); public void pause (); public void resume (); public void dispose (); }




All you need to do is implement these methods in your main class of the shared game code project. Libgdx will then call each of these methods at the right time.

The following diagram visualizes the Libgdx's Application Life-Cycle:

cross-platform-development-build-once-deploy-anywhere-img-4


Note that a full and dotted line basically has the same meaning in the preceding figure. They both connect two consecutive states and have a direction of flow indicated by a little arrowhead on one end of the line. A dotted line additionally denotes a system event.

When an application starts, it will always begin with create(). This is where the initialization of the application should happen, such as loading assets into memory and creating an initial state of the game world. Subsequently, the next state that follows is resize(). This is the first opportunity for an application to adjust itself to the available display size (width and height) given in pixels.

Next, Libgdx will handle system events. If no event has occurred in the meanwhile, it is assumed that the application is (still) running. The next state would be render(). This is where a game application will mainly do two things:

  • Update the game world model
  • Draw the scene on the screen using the updated game world model


Afterwards, a decision is made upon which the platform type is detected by Libgdx. On a desktop or in a web browser, the displaying application window can be resized virtually at any time. Libgdx compares the last and current sizes on every cycle so that resize() is only called if the display size has changed. This makes sure that the running application is able to accommodate a changed display size.

Now the cycle starts over by handling (new) system events once again.

Another system event that can occur during runtime is the exit event. When it occurs, Libgdx will first change to the pause() state, which is a very good place to save any data that would be lost otherwise after the application has terminated. Subsequently, Libgdx changes to the dispose() state where an application should do its final clean-up to free all the resources that it is still using.

This is also almost true for Android, except that pause() is an intermediate state that is not directly followed by a dispose() state at first. Be aware that this event may occur anytime during application runtime while the user has pressed the Home button or if there is an incoming phone call in the meanwhile. In fact, as long as the Android operating system does not need the occupied memory of the paused application, its state will not be changed to dispose(). Moreover, it is possible that a paused application might receive a resume system event, which in this case would change its state to resume(), and it would eventually arrive at the system event handler again.

Starter Classes


A Starter Class defines the entry point (starting point) of a Libgdx application. They are specifically written for a certain platform. Usually, these kinds of classes are very simple and mostly consist of not more than a few lines of code to set certain parameters that apply to the corresponding platform. Think of them as a kind of boot-up sequence for each platform. Once booting has finished, the Libgdx framework hands over control from the Starter Class (for example, the demo-desktop project) to your shared application code (for example, the demo project) by calling the different methods from the ApplicationListener interface that the MyDemo class implements. Remember that the MyDemo class is where the shared application code begins.

We will now take a look at each of the Starter Classes that were generated during the project setup.

Running the demo application on a desktop


The Starter Class for the desktop application is called Main.java.

The following listing is Main.java from demo-desktop :

package com.packtpub.libgdx.demo; import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; public class Main { public static void main(String[] args) { LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration(); cfg.title = "demo"; cfg.useGL20 = false; cfg.width = 480; cfg.height = 320; new LwjglApplication(new MyDemo(), cfg); } }




In the preceding code listing, you see the Main class, a plain Java class without the need to implement an interface or inherit from another class. Instead, a new instance of the LwjglApplication class is created. This class provides a couple of overloaded constructors to choose from. Here, we pass a new instance of the MyDemo class as the first argument to the constructor. Optionally, an instance of the LwjglApplicationConfiguration class can be passed as the second argument. The configuration class allows you to set every parameter that is configurable for a Libgdx desktop application. In this case, the window title is set to demo and the window's width and height is set to 480 by 320 pixels.

This is all you need to write and configure a Starter Class for a desktop.

Let us try to run the application now. To do this, right-click on the demo-desktop project in Project Explorer in Eclipse and then navigate to Run As | Java Application. Eclipse may ask you to select the Main class when you do this for the first time. Simply select the Main class and also check that the correct package name ( com.packtpub.libgdx.demo ) is displayed next to it.

cross-platform-development-build-once-deploy-anywhere-img-5


The desktop application should now be up and running on your computer. If you are working on Windows, you should see a window that looks as follows:

cross-platform-development-build-once-deploy-anywhere-img-6


Summary


In this article, we learned about Libgdx and how all the projects of an application work together. We covered Libgdx's backends, modules, and Starter Classes. Additionally, we covered what the Application Life Cycle and corresponding interface are, and how they are meant to work.

Resources for Article:





Further resources on this subject: