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 Eclipse
In order to get started, your computer will need a copy of the latest version of Java, as well as an IDE. You should have at least Java 1.7, and preferably Java 1.8. If you use OS X, note that Apple stopped shipping Java as part of OS X with Mavericks; you will have to install it separately.
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 Eclipse. It is cross-platform and allows you and your team mates to work on whatever platform you wish. With that said, we think the text editor in Eclipse is pretty horrible too.
You will also need the Gradle build-system to handle the project files in this course. While Gradle is compatible with Eclipse, it is not built into Eclipse. You must download a Gradle plug-in to use LibGDX in Eclipse. We recommend the Nodeclipse Gradle plug-ins that can be downloaded for free from Eclipse Marketplace, as shown below.
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.
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 have used this API for years, 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.
When you select the Generate button, this app will create a Gradle project, not an Eclipse project. The next step is to load that project into Eclipse. From Eclipse, choose Import from the File menu. You will get a choice of options to import. You should select Gradle Project from this menu, as shown below.
Once you select Gradle Project, it will ask you for the folder with the project. This is the folder that you created with the set-up app. In this example, using the preferences from our start-up app, our project is in the folder shown below.
The last thing we need to do is to press the button Build Model. This will generate the project and all of its subprojects. Remember to check all of the subprojects before pressing the Finish button. Once you have done that, you are ready to start work on your project.
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, Marmalade, 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. OS X 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 three project folders: one with no suffix, one with the -core suffix and one with the -desktop suffix. The one no suffix is used to synchronize the latter two; you should not ever need to touch it. The -core project is where almost all of your source code should go.
The -desktop is project is for code that is unique for desktop platforms (Mac and PC). In this course you almost never need to add classes to this course. The only time that you might need to add a class to this project is if you need to access the LJWL 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 project, it is an extremely important part of development. It is how you run your program! You cannot run the code in the shared -core project. 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, simply select the class DesktopLauncher in Eclipse and hit the Run button. The first time you run it, Eclipse may ask you how you would like to run it. Run it as a Java application.
While this is the first year that we have used LibGDX for the intro class, Cornell students have been using the engine for four years now. Originally we adopted it as for our Android games (to replace the horribly designed AndEngine, which everyone hated). However, over time we have come to see it as a much more robust engine with many applications. In fact, the TAs have been asking me to move this course to LibGDX for quite some time now.
One of the reasons we finally made the switch is because we finally feel that there is enough documentation for the engine. 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.
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 have to make a change to your Eclipse settings. 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.
There are some basic profiling tools built into Eclipse. This link is to a tutorial on how to use them. However, we have heard that there are some issues with this profiler and GUI applications, particularly those that use a lot of OpenGL (like this one). If you have success with this profiler, let us know.
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.