The Java world is a fertile place for gaming, and there are numerous game engines available. JOGL and LWJGL are low-level frameworks that ease and abstract certain game-construction duties, whereas high-level frameworks like libGDX and Slick2D provide you with all the tools you need to easily author your game — often for multiple platforms at the same time. Both of those are fine choices, but for this series we’ll leverage libGDX. It has as robust community, excellent documentation, and can be used to target the Windows, Android, Mac and Linux platforms.
This post will not attempt to be a tutorial for installing libGDX nor a configuration guide for its many powerful features. Rather, we’ll walk through basic libGDX usage to get a high-level overview of how it works — often a helpful starting point for a newcomer to the technology. The canonical source for libGDX is its official wiki, with supplemental information found at the libGDX-users wiki.
Note that here we are now embracing one of the key features of JRuby: leveraging Java libraries. libGDX is, after all, a Java framework. But why write your game code in ugly, verbose, old-fashioned Java when you can use your favorite language to do it? The code will certainly be tidier, more readable, and a lot more enjoyable to write and maintain.
Now is an appropriate time to point you to the official Github repository for this blog series’ source code. This repository not only includes all the source code that has been (and will be) discussed in this blog series, but also includes a simplistic but runnable “lunar lander” type game to help cement the concepts with real, working code. I believe you’ll find it very helpful as a learning tool.
Our app will need a “main.rb”. This is the entry point for the overall application. We’ll do the major setup here, such as requiring the libGDX Java libraries and setting up a handy logger for game-wide use:
require 'java' require "gdx-backend-lwjgl-natives.jar" require "gdx-backend-lwjgl.jar" require "gdx-natives.jar" require "gdx.jar" java_import com.badlogic.gdx.ApplicationListener java_import com.badlogic.gdx.Gdx java_import com.badlogic.gdx.Input java_import com.badlogic.gdx.graphics.GL10 java_import com.badlogic.gdx.graphics.Texture java_import com.badlogic.gdx.graphics.OrthographicCamera java_import com.badlogic.gdx.graphics.g2d.SpriteBatch java_import com.badlogic.gdx.graphics.g2d.BitmapFont java_import com.badlogic.gdx.backends.lwjgl.LwjglApplication java_import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration require 'my_game' require 'logger' # Think of the following as the equivalent to the stuff you'd find in the # Java world's "public static void main(String argv)" method. @@logger = Logger.new(STDERR) # or ("log.txt") @@logger.level = Logger::DEBUG cfg = LwjglApplicationConfiguration.new cfg.title = "LunarLander" cfg.useGL20 = true cfg.width = 640 cfg.height = 480 LwjglApplication.new(MyGame.new, cfg)
You’ll notice that we’re creating a LwjglApplication. This is because the high-level libGDX framework actually leverages LWJGL for its backend. (libGDX can also use other backends such as JOGL, but LWJGL is fast and well-maintained, therefore a fine choice.)
You’ll also notice that main.rb is fundamentally just a place for high-level configuration. Once it has loaded the libraries it hands control to a libGDX construct called, appropriately, a Game. A Game is simply a sort of “conductor” for the overall game-play. It’s the glue that holds together the various screens of your game, and provides switching between them. It is also a good place to store game-wide variables such as a clock. Our lunar lander game has the class MyGame which derives from Game.
java_import com.badlogic.gdx.Game require 'startup_state' class MyGame < Game include ApplicationListener attr_reader :game_clock def create @game_clock = Time.utc(2000,"jan",1,20,15,1) setScreen(StartupState.new(self)) end def increment_game_clock(seconds) @game_clock += (seconds) end end
libGDX also provides a Screen interface. In libGDX a Screen corresponds to what you would expect: a particular game screen such as a high-score screen, or the playing screen, or the game-over screen. So, a Game has one or more Screens. Our lunar lander game has two: a startup screen, and a playing screen. There’s a class for each — StartupState and PlayingState — and each one implements Screen. (In Ruby, we treat the Java Screen interface as a mix-in and ‘include’ it.)
Each game screen must conform to the Screen interface. This interface includes methods such as show, hide, pause, resume, resize and render. Some of these are Android-specific and are only relevant to that platform. For today we only care about show() and render(). The show() method is called once when your screen is activated; this is a great place for one-time setup of things like fonts, background images, and the camera. (More on the camera in a bit.) This is also a fine place to configure your EntityManager and your systems. Here’s the show() method for our startup screen:
def show @bg_image = Texture.new(Gdx.files.internal(RELATIVE_ROOT + 'res/images/bg.png')) @camera = OrthographicCamera.new @camera.setToOrtho(false, 640, 480); @batch = SpriteBatch.new @font = BitmapFont.new end
Subsequently the screen enters the render() method in a repeating fashion. The render() method is called every frame; this means for a 60 FPS game, render() is being called 60 times per second. Everything that needs to happen to drive the game — animation, calculation, determining if a bullet hit, whatever — happens inside render(). Here is the render() method for our startup screen:
def render(gdx_delta) # Make sure you "layer" things in here from bottom to top... @camera.update @batch.setProjectionMatrix(@camera.combined) @batch.begin @batch.draw(@bg_image, 0, 0) @font.draw(@batch, "P to play!", 8, 250); @font.draw(@batch, "Lunar Lander (Q to exit)", 8, 20); @batch.end if Gdx.input.isKeyPressed(Input::Keys::Q) Gdx.app.exit elsif Gdx.input.isKeyPressed(Input::Keys::P) @game.setScreen PlayingState.new(@game) end end
Camera and Batch
Let’s address two additional libGDX concepts that might be unfamiliar: the SpriteBatch, and the Camera.
The SpriteBatch is simply an efficiency mechanism, but an important one. It’s an expensive operation to send graphics individually to the GPU, whereas sending all the graphics to the GPU in a batch boosts performance considerably. Once we have configured a batch we can reuse it again and again. Batched graphics is a key concept in making any sort of high-performance game with even a few graphical elements.
The Camera is present because libGDX can support both 2D and 3D games, and while having a “camera” pointed at a “scene” might not make immediate intuitive sense for a 2D game, we must still embrace it to use libGDX. Fortunately it’s quite easy: we just define a camera and a scene of certain dimensions for it to look at. For our 2D lunar lander game we’ll configure a what libGDX calls an OrthographicCamera, which simply means a camera looking down at a single plane and representing its contents orthographically. Any side-scroller game you’ve played has been this type of representation. (For a 3D game, libGDX offers a PerspectiveCamera but we won’t be using that here.)
@camera = OrthographicCamera.new @camera.setToOrtho(false, 640, 480)
We now have enough libGDX knowledge to be productive. In the next installment of this series I’ll return to Entity-Component programming, using the “lunar lander” game as a teaching tool.