If you are programmer, then you will find this page to be a useful collection of
resources. While you all know Java, there are a lot of new APIs to learn. For
cross-platform purposes, you should limit your use of the basic Java API to
java.util. Almost everything else will be provided in the other
Table of Contents
In order to get started, your computer will need a copy of the latest version of Java, as well as an IDE. We recommend that you use IntelliJ, which provides both. For stability and compatibilty reasons, we are using Java 11 as the default. You should only go higher than this if you absolutely need the features (it will make bundling your application much more complicated).
You are free to use any Java development environment that you want. However, the only IDE that we will officially support in this course is IntelliJ (the Community Edition). The game engine LibGDX is especially designed for IntelliJ, and it will be the easiest IDE to use.
More importantly, we no longer support Eclipse. The projects in this course rely on the Gradle build-system. Eclipse has made many poor choices regarding Gradle plugins, making it very difficult to load LibGDX projects. We abandoned Eclipse several years ago and it made us very happy. And recently 2110 has started to see the light here.
If you want to try Visual Studio, you are welcome to do so. However, you are completely on your own, and you should tell us if you have had any success. In particular, we have no idea how to get the Gradle plug-in for Visual Studio working. Java support in XCode is largely a fail, so we recommend you do not try this at all.
LibGDX is a Java game engine that is heavily inspired by Microsoft’s XNA. In fact, it adopted all the good things in XNA and does a lot of things better than that engine. Of course, it is in Java. However, we have found that the performance hit is not noticeable for the types of games we develop in 3152. In fact, the Android developers in 4152 used this engine for many years (before we switched to C++), on a platform with less memory and slower CPUs. For the types of lessons we want to learn in this course, it is currently the best option.
You do not install LibGDX like you would install Java or Eclipse. You simply create a LibGDX project. This is a project that has all of the LibGDX libraries in the same folder as your source code. This is necessary because when you want to distribute your game, you must distribute the LibGDX libraries as well.
Downloading all of the LibGDX libraries sounds really annoying, especially if you have to do it each time you make a new project. Fortunately, LibGDX has a set-up app to automate this entire process for you. This is a Java App that asks you several questions about your game, as shown below. For this class, we recommend that you always unselect the Android and iOS options. In addition, you should always select the Freetype, Controllers, and Box2d extensions.
When you select the Generate button, this app will create a Gradle project, not an IntelliJ project. The next step is to load that project into IntelliJ. When you start up IntelliJ, you should see the screen below.
Select Open. Navigate to the project folder that you want. However, do not stop
at the project folder. Instead, open up the file
build.gradle. IntelliJ will
ask whether you wish to open this as a Project or a File. Choose Open as Project.
The first time you open up the project, IntelliJ will pause to initialize the Gradle system. You are ready to go once you get a pop-up identifying three modules: core, desktop, and gradle.
Exploring a LibGDX Project
One of the main advantages of LibGDX is that it is cross-platform. This is a major feature of all Indie game engines these days, like Unity or Godot, or even SDL. If you are going to work with professional game development tools, you should learn how cross-platform development works.
You may have thought that you have written cross-platform software before. Isn’t that the whole point of Java’s compile-once, run everywhere? However, true cross-platform development is much more complicated. MacOS laptops do not have a touch screen and iPhones do not have a physical keypad or mouse, so it is impossible to write one piece of software that runs on both. In cross-platform development you have to break up your software into shared code, which is the same for all platforms, and platform-specific code like user input handlers.
You can see that in the project organization of LibGDX. Even if you are only creating
a desktop version of your game, you will see two modules in the IDE: one called
suffix and the other called
core module is where almost all
of your source code should go.
desktop module is for code that is unique for desktop platforms (Mac and PC).
In this course you almost never need to add classes to this module. The only time that
you might need to add a class to this project is if you need to access the
LWJGL APIs, which are not a standard part of LibGDX.
We will explore this project more in the game labs.
Running a LibGDX Project
Even though you are probably never going to add files to the
desktop module, it is
an extremely important part of development. It is how you run your program! You
cannot run the code in the shared
core module. None of those classes have a
main method must be targeted to a specific platform. The lone class
DesktopLauncher is the platform-specific main class.
To run your LibGDX project, you need will need to create a launch configuration. See that drop-down menu to the left of the play button in the top right corner? Select it and choose Edit Configurations.
Choose the plus symbol in the top left corner to create a configuration. You will be given a list of configuration types. Choose Application. When you do this, you will see input fields like the one shown below.
The very first thing you need to do is to select Use classpath of module. Everything
else will give you an error until you do this. Choose desktop.main. Next you can set
the main class to
DesktopLauncher (use the full name, including the package).
However, in the picture above, you may notice that there is a box above the class name
-XstartOnFirstThread. This is an important addition because of the move
to LWJGL3 (required for all Silicon Macs). Graphics always have to be computed on the
main thread, and this will not happen without this setting (so your game will crash).
But this box is missing in the default configuration. To get this box, you need to click
modify options, and choose Add VM Options.
Finally, you need to set the Working directory. This is how IntelliJ finds your art
assets. For all modern LibGDX projects, the value is
you are done give your configuration a name. We usually pick the name of the project,
as shown above.
Once the launch configuration is set, you can run the application by pressing the play button.
Creating a Stand-alone JAR File
Throughout the course, we will ask you to make a stand-alone JAR file for us, either of a lab or of your project. This makes it easy for us to test and run your applications without having to build everything from your project. Like the Launch Configuration, this is not set-up by default and is something that you will have to add to your project.
Because of a problem with how Maven generates manifest files, the first thing that you need
to do is create a new directory. Right click on the module
core and choose
New > Directory. Name this directory
Now you are ready to create a JAR Configuration. Choose File > Project Structure… In the window that pops up, select Project Settings > Artifacts. Click the plus button to the right and select JAR > From modules with dependencies… The window that you see will look like the one below.
For the main class, press the
... button and select the
It will also fill in the directory for the manifest file. Change the
src at the end
resources, as shown above.
You will now see a page listing all of the files that will be added to the JAR file.
You need to add one more thing: the art assets. To do this, select the plus button and
choose Directory Contents, as shown below. Add the
assets folder and
click OK. This completes the JAR configuration.
From this point on, whenever you want to make a stand-alone JAR, choose Build > Build Artifacts… This will give you a pop-up menu to build the JAR. The JAR will be created and placed placed in the directory specified by Output directory.
One of the main reasons we adopted LibGDX is because of the quality of the documentation. While we will be helping you with lectures and game labs, you will be on your own for a lot of this course. Therefore, it is important that you have good documentation to fall back on
In case you do not have it book-marked already, you should be able to read the Java API.
We have linked the API for Java 11, as that is the version preferred by game development.
With that said, you should limit your use of this API to
java.util. Just about anything else – particularly file I/O and drawing code
– will not be cross platform. You should rely on LibGDX for those features.
In addition, you should avoid most of the data structures (LinkedList, HashMap, TreeSet)
java.util. These data structures are not memory efficient for game programming.
Use the data structures provided by LibGDX instead.
Most of the documentation for LibGDX is provided in their GitHub wiki. This will give you an overview of the basic structure of a game, as well as descriptions of the most important classes. If you cannot understand a class simply from reading the JavaDocs, you should go here.
90% of your code will be written with this API. We will talk about this API in class, and it will be a major part of the game labs. With that said, there are a lot of things in this API that we will never talk about. You are still free to use them.
While LWJGL may look like a competitor to LibGDX, it is not. It is very low-level and does not do much more than give you access to the hardware. LibGDX is built on top of LWJGL. In particular, any desktop release of your game will use LWJGL to manage the computer hardware. Releases on Android, iOS, or web release do not use LWJGL, as they have their own hardware interface.
There are some features of LWJGL (such as window placement) that are not available
in LibGDX, so you may wish to use them directly. However, the LWJGL libraries
are only available in the
-desktop project, not
LWJGL is one of the few reasons why you might want to add a class to the
Because LibGDX tries to be cross platform, not everything is built into the engine. For example, fonts are important for showing text on screen, but Freetype – the primary system for drawing fonts on screen – is not compatible with HTML5. This extension is used to provide font support.
The GDX AI optional module is also not included as part of the LibGDX official API. This is a very cumbersome API that we will talk about throughout the course. It provides support for pathfinding and other features, but is not always better than writing your own algorithms. To determine whether or not this API is for you, you should read the official documentation.
The Box2D physics engine is used in just about every 2D game imaginable. Originally written in C++ by a Blizzard employee, it has been ported to every single programming language used by a game engine. In particular, it is available in Java as part of LibGDX.
However, while everyone ports the code for Box2D, almost no one ports the documentation. There are still many features that are only explained in the C++ documentation. Therefore, we have included this documentation here, even though we are programming in Java. If you are really having trouble with this documentation, look at the iForce Tutorials.
Many of the features of LibGDX are clear from the documentation. But sometimes you need a little boost in the form of a hands-on tutorial. Fortunately, there are quite a few out there to choose from.
With that said, you should always read game design tutorials critically. There is a lot of “help” available on the web that consists of very poor software engineering decisions. You will see things in tutorials that we will tell you never to do. This is particularly true with scene graphs, stages, and other frameworks meant to “simplify” game development. These frameworks tightly couple your classes and make gameplay refactoring extremely difficult. We will talk about these in great detail when we discuss architecture design.
This is the official collection of LibGDX tutorials. It is a little spartan, but it is a good place to get started. In particular, it is the most modern of tutorials as LibGDX went through some major upgrades in 2020.
This is a much, much older series from the early days of LibGDX (when we first adopted the engine). With that said, is a wealth of examples showing you how to use the various APIs. As always, keep in mind that the writer sometimes makes software engineering choices that we do not support, particularly with the Scene2D API. But it is good resource, nonetheless.
The Box2d documentation is not super-friendly. This is one of the harder APIs to wrap your head around (and why we will spend class time on it). But if you need to learn how to do anything that we do not teach you, the iforce2d tutorials are the way to go.
Understand however, that all of these tutorials are in C++, which is the original language for Box2d. It is up to you to convert these tutorials to the Java equivalents in LibGDX.
Performance is very important in game programming, and you will often find yourself optimizing your code. However, there is always a balance between performance and readability. The following tips should help make easy to maintain code while avoiding some of the more basic performance pitfalls.
Unlike C++, Java does not have stack-based objects. Every time you want to create a new object, you must allocate it in the heap with the new keyword. Furthermore, since it is in the heap, you must depend on the Java garbage collector to clean it up. This can cause serious performance problems when your main game loop is executing 60 times a second. Therefore, we recommend that you obey the following rule of thumb:
Do not use the new operator unless you are in a constructor, or a method that is only called by other constructors.
You might think that this is easy. That is, until you start to look at the
com.badlogic.gdx.math package. That library contains classes like
that make linear algebra a lot simpler. These are objects, and so they must be
allocated on the heap. More importantly, if you have two vector objects and add
them together, you get a third vector object. The number of objects and heap
allocations starts to add up quickly.
There are two solutions to this problem.
Sometimes you know exactly how many objects you need. For example, in the collision
detection class for the first lab, we know that we need exactly three
objects each time we call
checkForCollision: one for the normal, one for the
velocity, and one to store scaled versions of either of these.
When that happens, you can store the objects as fields. The objects themselves are
allocated by the constructor. But the contents of the objects are reinitialized whenever
necessary. This requires that the appropriate classes have
set method that
allows you reinitialize the object contents. If you look at the API for package
com.badlogic.gdx.math, you will notice that most of the classes are designed
Memory pools are a generalization of the notion of a cache object. A memory pool is a collection of preallocated objects of the same type. Whenever you need a new object, you take it from the collection and reinitialize the contents. When you are done with the object, you release it to the pool for someone else to use the object. A memory pool is much like managing your own heap, except that (because how Java objects work) all of the contents of the heap must have the same type.
Memory pools are useful if your object garbage collection is well-behaved enough that
it is better to do it yourself than leave it up to Java. The class
in the first lab is an example of Memory Pool. Because objects are always released in
the order that they are created, this makes allocation and deallocation very simple.
Asserts and Logging
Games are complex pieces of software. You are going to create many bugs that are not immediately apparent. And even when you can see the bug in your playthrough, it is going to be very difficult to find the source of your bug in your code. It is in your best interest to code defensively. You should add code that detects problems and raises errors as soon as they happen.
This sounds a lot like exceptions, and exceptions are one way to handle errors. But exceptions are very heavy-weight. Because of the high performance requirements of games you are going to want to be able to turn error detection on and off very quickly. There are two powerful tools for doing this.
The simplest tool, and one that many of you are familiar with, is to use asserts. An assert should be used whenever you make an assumption about your game state to verify that it is indeed the case. For example, suppose you assume that the player’s health is greater than 0. Then you should add the line
assert health >= 0 : "player health is negative";
Other good things to assert are
target != self and
level != null. Note that the
assert has a text message which it displays if the assert is not actually true.
Java ignores asserts by defaults. In other words, it treats assert statements as comments
and will not execute them. This is great for performance, but bad for error checking.
If you want to enable asserts, you will need to add
-enableassertions to the
VM options category in your
This way you can turn asserts on and off to find bugs when you need to, but not
hurt performance when you need it.
Asserts have several limitations, however. The are a property of the JVM and so cannot be turned on and off by the game itself. In addition, they are all or nothing. You either enable all the asserts, or none of them. If you want a more fine tuned approach to defensive programming, you should use the GDX Logging framework. This is a way to create an error log that can be turned on or off, and has different levels of granularity than can be controlled.
GDX Logging is not just for error detection. Sometimes is just for creating print
statements to display the current state of the game. While you could do this with
System.out, it is always better to use the logging framework. First of all,
logging is cross-platform while
System.out is not (what does
go to on an Android device?). Second of all, with your main loop running at 60 times
a second, you are going to get a lot of print statements when you run a game; using
tags is an excellent way of organizing your print statements so that they are easier
Floating Point Numbers
Round-off error is the bane of all game designers. It will happen; accept it. In games, object position is in floating point numbers, but is rounded to the nearest pixel. This will cause your sprites or polygons to be off by a pixel, and not quite line up the way you want it to. If it matters that things line up properly, you should get used to “snapping” your objects. That is, check if something is very near where it should be, and then nudging it to exactly where it should be. For example, if your game uses a grid, you might want to snap your character to the center of the grid square as soon as he/she/it is close enough.
Another feature that you will find with floating point numbers is that you may be tempted
to compare two of them with the
== comparison. This is fine and valid if you
know they can be exactly the same, but in many cases all you really want know is whether
the numbers are within a certain range of tolerance within each other. In order words, you
often want to test.
if (Math.abs(floatA - floatB) < 0.001f)
if (floatA == floatB)
If you are relying on super-high precision for your game to work (e.g. you absolutely have
==), you are doing something wrong anyway; high precision is not going
to fix your problem for you.
Yes, performance is important. But the most important rule in programming is this:
Premature optimization is the root of all evil
This means that if you focus on heavily optimizing your code from the very beginning, you will likely make your code unreadable and unmaintable. Most importantly, you will find it very difficult to modify heavily optimized code, and iterative development is an incredibly important part of game developement.
Certainly you should avoid things that are clearly unperformant. If it is the choice between an O(n2) algorithm and a O(n log n) algorithm, you should always chose the latter. But do not worry about shaving off a constant factor until you know that the program is working.
If your program is running slowly, the best thing to do is to use a profiler. A profiler runs through your code and identifies what parts of your code are taking up the most amount of time. That way you know to optimize those parts and no other. Many times when people run a profiler they discover the part that they were optimizing really had no effect on the performance at all.
Fortunately, you have some options.
If you cannot get the other profilers to work, there are some classes built into LibGDX that allow you get some very coarse profiling information. It is not a lot, but it is better than nothing.
If you need help with any of these profilers, please consult with one of the programming TAs.
Recording and Replaying a Game
This is an issue that we will talk about in lecture, but it is important enough that it is worth mentioning up front. Debugging games can be infuriatingly difficult. That is because some bugs only crop up when you play the game in some special way, and you cannot always reproduce it.
For that reason, game designers often add features to their code to make it easier to reproduce those hard to find bugs. The way that they do it is to make sure that you can replay a game exactly like it was played the last time so that you can look at the same error over and over again.
To be able to do this, essentially have to record the game like a movie and play it back. But you do not want an actual movie (e.g. just the game state). You want to see how the game state was computed. What you really want to record is all of the features of your game that are nondeterminstic. That, is the parts of your program that, when give a game state, will not produce the same result every time.
There are generally two nondeterministic features in a game: user input and random numbers. Random numbers are an easy problem to solve. Random number generators are actually pseudo-random. You can force them to produce the same results by giving them the same seed. Generally you seed on the computer clock, giving you a different game each time. But if you include the option to start the game with a specific seed value, that eliminates this one source of nondeterminism.
This leaves user input as the primary source of nondeterminism. The way to solve this problem is via logging. Record the user input to file. You need to record exactly what the user input was, and what frame it occurred in. Your game should then include the ability to read this file and “play it”. That is, use the input from the file rather than from the controller. If you fixed the seed for your random number generator, this replay should look exactly like the game that you played when it was recorded.