Java Game Development Resources
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.lang and java.util. Almost everything else will be provided in the other APIs.
Since we have moved to a new engine, we are always on the look-out for additional APIs. If you find any additional resources that you feel should be included on this page, please contact the course staff.
Java and IntelliJ
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. Because of licensing issues, most game development is done in Java 1.8. You should only go higher than this if you absolutely need the features (it will make bundling your application much more complicated). And whatever the case you should not use anything more recent than Java 11.
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 and it made us very happy. If only 2110 would learn from us.
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.
In case you do not have it book-marked already, you should be able to read the Java API. With that said, you should limit your use of this API to java.lang and 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) in java.util. These data structures are not memory efficient for game programming. Use the data structures provided by LibGDX instead.
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 Gadle 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.
Anatomy of 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 Cocos2D-X. 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 core suffix and the other called desktop. The core module is where almost all of your source code should go.
The 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. Your 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 ones 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. Now you can set the main class to DesktopLauncher (use the full name, including the package). You also need to set the Working directory. This is how IntelliJ finds your art assets. For all LibGDX projects, the value is core/assets. Finally, give you configuration a name when you are done. 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 resourses.
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 DesktopLauncher class. It will also fill in the directory for the manifest file. Change the src at the end to 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 in the top-level classes folder.
One of the main reasons we adopted LibGDX is because of the wealth of online tutorials. 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 tutorials and documentation to fall back on.
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 video tutorial series will help you get started with LibGDX. It includes the details about Eclipse integration that we have already covered. It also has a lot more documentation about how to use Gradle to build for platforms other than the desktop.
This series from the website Games from Scratch is the most popular tutorial for creating LibGDX games. It 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.
This short tutorial shows you how to use standard TrueType fonts in LibGDX. Believe it or not, text support is always one of the trickiest things in a game engine. Drawing images is easy; your graphics card handles all that. But converting text into glyphs (the font elements that you see on the screen) and spacing them correctly is very hard, particularly if you want to support professional kerning.
Game engines punt on this problem by using BitMap fonts. This is an image file with all of the characters inside of it. That way they can go back to drawing images, and just rely on primitive spacing with no advanced kerning options. This tutorial shows you how to load a TrueType font and convert it to a BitMap font automatically.
AI has always been a tricky problem for this course. We cover it very late, almost in the alpha stage of development. In addition, most of the game engines that we have used in the past do not have a lot of support for AI, so everyone had to write their own AI classes. As a result, games that rely heavily on AI have a hard time getting started.
LibGDX has a special optional module called GDX AI that provides some AI functionality. We do not actually know a lot about this API, but you are free to use it. We will try to integrate this API into our lectures some this semester.
The Box2D physics engine is used in just about every 2D game imaginable (and, as a general rule, 2D games that do not use Box2D have horrible physics). Originally written in C++ by a Blizzard employee, it has been ported to every single 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.
One of the nice things about LibGDX is that it has a large community of users, beyond this class. If you have an questions that cannot be answered by the course staff, you might want to ask them on the official forums.
Most of the APIs that you will use in this course are part of LibGDX, or provided by the Set-up App as an optional library. In fact you will will rarely even use the standard Java libraries beyond java.lang or java.util.
If you find another third-party API that you would like to use, please talk to us first. Adding new APIs can make it difficult to distribute your game and that is an important course. But if you do find something interesting, we may add it to this page as well.
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 -core. Accessing LWJGL is one of the few reasons why you might want to add a class to the -desktop project.
LibGDX is changing all the time. Not everything supported the engine is included in the official API. In particular, you may notice that the API for TrueType fonts is missing. For newer APIs like this, you have to look at the source code in their GitHub repository and read the source comments.
The GDX AI optional module is also not included as part of the LibGDX official API. While you can learn much of what you want to know from the documentation page, you still need a class reference to see how to use everything. Once again, you must look at the source code files from the GitHub repository.
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 Vector2 and Affine2 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 Vector2 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 that way.
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 PhotonQueue 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
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 Launch Configuration. 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 System.out 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 to search.
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 you are relying on super-high precision for your game to work (e.g. you absolutely have to use '=='), 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.
As we said, we will discuss this more in lecture. Until that time, consult with a programming TA if you need help.