Every time I set up a new project I learn something about how Gradle works and the default project setup for Libgdx. I’ve decided to write this down so the next time I start a project I don’t have to relearn all of this from scratch. I’m using Intellij IDE, and from what I read things are different in Eclipse. If using Intellij it is important to note that a Gradle run configurations targeting a sub-project run task can be used in lieu of an application run configuration. The advantage of doing this is that asset location and jvm arguments are all handled in Gradle files making project set up easier for multiple users.
Most start-using-Gradle sites say you should start by installing Gradle. Good news is Libgdx uses Gradle Wrapper, which provides scripts for both Linux and Windows allowing Gradle to be run without installing. Yay!
The next thing most start-using-Gradle tutorials will say is ‘create a hello world task’ and this is where we’re going to diverge a bit, since files and tasks were created automatically during by the libgdx project setup program. So I’m going to discuss the various Gradle files the Libgdx project setup program created.
The Files
gradle.properties
Not totally sure what this does, but it appears to be arguments for when Gradle gets launched by the wrapper.
settings.gradle
This is a short file indicating the structure of the project. It indicates how the various sub-projects are organized. The basic setup just lists ‘core’ + all of the target platforms.
build.gradle
There are a number of build.gradle files; one in the main folder and one in each of the platform folders. Having multiple build.gradle files the standard format for Gradle projects with subprojects.
The build.gradle file in the main folder contains dependencies for both external libraries and the internal subprojects. This is where the platform subprojects are related to the core subproject and the libgdx and other library dependencies are managed.
The build.gradle files in the subproject folder contain build tasks associated with each of the projects. For example the desktop subproject has a run task that can be used to launch the desktop application.
Now let’s look at modifying things.
If we want to add a runtime argument to indicate the location of a log4j2 configuration file we would add the line
If a client-server project is desired where the platform and core projects become sub projects of the client project this can be done by prepending ‘client:’ to the existing project names in the settings.gradle file and the main build.gradle file. This assumes that both the platform and core sub-projects are contained in the client folder as shown below.
I’ve spent most of the week working on making destructible boxes for the platformer. The actual mechanics of this are fairly simple, and psedocode for the process is below.
if condition is met then
destroy box
The conditional statement can be checked during the regular update cycle (otherwise known as polling) or whenever a different condition is met (otherwise known as a listener).
For the destructible boxes the condition would be met if the impulse on the box would exceed a threshold value. I decided to use the listener method of checking the destruction condition; since, I already had a Box2D contact listener for the projectiles.
And now the fun starts so let’s add some equations. first impulse courtesy of Wikipedia.
According to our good friends at Wikipedia impulse is the sum of the forces applied over a time period (t1 to t2) which can be expressed as a change in momentum and as mass times change in velocity. hyper physics
It is not surprising then that the initial implementation measured the change in velocity between when the Box2D contact listener called beginContact() and endContact() on the destructible box. (there are legitimate issues with this implementation I know about them. If you’re having trouble imagining them think of someone pushing a sled gradually up to speed and then the pusher stops abruptly.) This was done because velocity is relatively easy to visualize and calculate. for example if the box were to start falling and impact the ground the some time later. The impact velocity could be calculated using either the fall time or the distance. Assuming the velocity prior to impact >0 and immediately after impact is <=0. This should make it easy to tune the destruction velocity threshold depending on how many stories an object is expected to fall and remain intact.
After doing some mathematical manipulation and assuming initial velocity is zero. we get v² = 2ax. And we’re already to pick out some velocity thresholds. So let’s move into the real err platformer world; where I took the liberty of having destructible box impact velocities logged for the record as well as start heights.
Experiment
acceleration
distance
expected velocity
actual velocity
Experiment 1
12 m/s/s
7.4 m
9.4 m/s
2.4 m/s
Experiment 2
12 m/s/s
7.3 m
9.4 m/s
1.99 m/s
Experiment 3
12 m/s/s
20.5 m
15.7 m/s
3.4 m/s
Something weird is definitely going on here. I’m currently assuming this is an artifact of how I was getting the velocity from Box2D. But I didn’t really dig into it too far since I realized there were other problems with this method, and switched to getting a ‘PostSolve’ impulse reading from Box2D which is closer but still different from the values I’m calculating. I suspect that I’m getting readings at the wrong time or that Box2D is taking additional things into consideration that were left out of the simple motion equations. (e.g. friction).
My conclusion for the week is, since a 10 cm difference in height resulted in a massive shift in impact velocity. This is annoying because it means that objects will require manual tuning of impact forces.
This week I created a bug/feature list to better keep track of issues and what I wanted to do. And the list says that this week missile rate of fire was limited, explosion rendering was fixed, the player’s physics body was updated, jumping was converted jumpjets rather than legs, the player can now be controlled using an x-box controller, and work was started on a HUD. That sounds like progress to me! w00t!
I’m beginning to remember why I didn’t want to do a status update post every week when we started this website.
It’s that what gets accomplished in one week tends to be easily summarized in a few words. For example this week I got projectiles working completely and explosions working partially (unless being able to walk on compressed air waves is a feature in which case explosions are done), and while someone who does game development would think that’s a reasonably productive week when combined with bug crushing. Those of you who are not developers are likely thinking, ‘that doesn’t sound like much. Why didn’t he do more? And where are the screenshots.’
Well the screenshots are coming … eventually. I could post some box2D debug renders, but that’s just a bunch of rectangles outlined with different colors. Ahh the days when someone around here did artwork.
Speaking of Box2D I’ve got more things to say about it. Some good some bad.
The Good
I used Box2D to implement the explosion. I think it is fantastic and reasonably straight forward now that I’ve done it once.
The Bad
Things get stuck at the seams of butted objects; not always but often enough to be annoying. This is a known issue and is supposedly being worked on. Currently suggested is don’t place objects contiguously. This means that loading a world tile by tile is a no go.
On first implementations of things I frequently pass bad data or make inappropriate calls to the Box2D world. This is a normal part of the development process however since I’m developing on java and Box2D runs in native libraries I get lovely unhelpful error messages like.
Process finished with exit code 255 (Box2D, java, libgdx)
Which I got when I tried to add a new object while the world was stepping.
And
EXCEPTION_ACCESS_VIOLATION (0xc0000005), AL lib: (EE) alc_cleanup: 1 device not closed – (Box2D, java, libgdx)
[Update] which was caused by attempting to address(dispose of) a body that had been disposed of.
I also had an equally unhelpful error when I disposed of a shape before instantiating a fixture based on that shape. I’m too lazy to reproduce this error so no console capture for that one.
Since these crashes happen in native code that means that lost objects aren’t cleaned by the java garbage collector, and it’s entirely possible they sit around causing a memory leak. yay. Oh wait that other thing, booo.
In our second installment of Coder’s Corner we’ll discuss SpatialHashes. For those of you thinking, “This got intense fast… last month was IDE setup – shouldn’t this be some sort of libgdx ‘hello world’?” Tough. There are many, many tutorials for people (and robots) just starting out in coding and game development. There is less discussion on more intermediate and advanced topics. So that’s where Rho_Bot and I have decided to focus Coder’s Corner. To answer the other half of the initial question, we chose SpatialHashes specifically because I actually made one for Stoned in Space.
Which brings us to the question most of you may be asking: What is a SpatialHash? (Just to be clear we’re going to discuss the data structure used for accelerating collision calculations not the one for compacting loose spatial data.)
According to some eggheads: “Spatial hashing is a process by which a 3D or 2D domain space is projected into a 1D hash table.”
While this is probably true if you delve far enough into how the data is actually stored (It’s exactly how I backed my SpatialHash), I think it misses describing the concept in favor of describing the implementation.
At the most general level a SpatialHash is data structure where an object can be looked up based on its location in space. As a side effect, objects that are physically close together get grouped. Or to describe it in more computer science-y terms, a SpatialHash is a special case of a HashTable where an object can be referenced by its location. That means you can almost say SpatialHash = HashTable<multi-dimensional-point, object>. However, a salient difference between a HashTable and a SpatialHash is that HashTables almost always have unique key-value associations. For a SpatialHash this is not the case. Depending on your situation you might want a relation that links one key to many values or one value to many keys. If the SpatialHash is going to be used for collision detection, having a single key to a single value relationship defeats the point of grouping an object with things it is likely to collide with.
And collision detection was the reason that I created a SpatialHash for Stoned In Space. I originally anticipated there would be a lot more objects and, therefore, many, many potential collisions to check for.
Having a large number of objects means that doing brute force collision detection would take up substantial computational time every frame and make the game unpalatably slow. So we needed a way to make the collision checks faster. I can hear the coders and cyborgs in the audience saying, “You want to partition space. Just use a QuadTree for that.” Maybe… But before we settle on a solution for the problem we should make sure we’ve accurately defined the problem scope. So, let’s round out our initial assumptions:
00. lots of objects (as previously mentioned)
01. uniform object distribution
10. uniform object size
11. Fixed world space (for example we’d always be working with 4 parsecs²; not sometimes 4 and sometimes 9 parsecs²).
Based on these assumptions it turns out that a quad tree would split down into the same number of cells as a SpatialHash. Which eliminates one of the major advantages of the QuadTree – fewer cells to check for collisions. And since the whole point of partitioning the collision space is to reduce the time spent doing collision checks, why not save additional time by sizing the cells in advance instead of splitting them every time collisions are checked?
Adding objects to a SpatialHash can be implemented two general ways. In what we’ll call “Method A”, each object can be consolidated to a point (finally we find an application for that whole center of mass concept from physics). In “Method B” we add an object to all cells intersected by its axis-aligned-bounding-box (aabb).
Method A has fast put and remove methods since it only has to deal with one cell. But since the information on the object’s size isn’t contained in the SpatialHash, all cells that might contain part of the object must scanned when looking for collisions. Assuming the object is placed in the cell that contains its center; the scan radius in cells should be “check radius = object radius / (cell size) + 1”. This method works well for uniformly sized objects with relatively low density. If all objects are approximately the same size it would be easy to size the cells to keep the number of cells scanned per object to nine. This also corresponds to the minimum safe scan number in this style.
Method B has slower put and remove methods since the aabb has to be found and then converted to hash space before the object can be placed in SpatialHash cells, but it results in faster collision detection since you’re only checking cells the object is in (minimum of one). This method is useful if objects vary dramatically in size.
Benchmarking the two styles is left as an exercise for the reader. Mainly because which performs best will depend on the particular application.
How did I implement a SpatialHash? Glad you asked. I used Method B and put together a pretty straightforward 2D SpatialHash (here’s the code). In addition to the SpatialHash object I also used a simple data structure called a HashRectangle. This structure could be replaced with class variables if desired. However I think it adds clarity to the code.
The meat of any hash is its hashing function. So I’m going to constrain my discussion to that section of code and the SpatialHash’s fields. Also I’m going to start referring to “world space” and “hash space”. In the first drawing world space is the area contained by the large rectangle and is addressable by (float, float); hash space is the subdivision of the world represented by the grid and is addressable by (int, int) or just (int).
Let’s start with fields
List<List<T>> cells;
The inner list is a list of objects in a cell, and the outer list is a list of the cells (you can probably tell from my imaginative name) in space. For reasons that have been lost in the mists of time, I chose a single list to contain all the cells in space rather than some sort of 2D array. Likely this was done for a combination of readability and to reduce memory overhead. The latter of which grows rapidly when adding extra dimensions to data structures in Java. That and you you also only need one loop to iterate over all of space when looking for collisions.
int numRows;
int numColumns;
The numRows and numColumns variables represent the extents of hash space. I’m going to discuss them together. Since I opted for a single list to represent all of the cells, we need to know the dimensions of the area of interest in hash space. The astute among you are already pointing out that I really only need one of these and cells to define the space (numRows = cells.size()/numColumns), and you’re right – one of these variables is a pre-calculated helper variable which is why I wanted to discuss them together.
float cellSize;
This is the size of a cell in world dimensions. In this case I assumed that cells would be square, but rectangular cells could be used if a specific situation called for it. This field is critical since it allows conversion between world space and hash space.
Continuing my trend of imaginative names, the hash method is called ‘hashFunction’. At first this method might appear misnamed since the actual hash conversion is done by the ‘hashFromWorldRect’ method. Which, if you haven’t read the code or guessed, converts the ‘worldRect’ to hash space by dividing the vertices of the worldRect by cellSize and truncating to form an integer. However, the second half of the method validates and constrains the raw hashRectangle returned by the ‘hashFromWorldRect’ method, and I consider validating and constraining to be part of the overall hashFunction.
At this point those of you who are still conscious and have been able to follow everything so far are probably thinking, ”ACMU, you used a dynamically resizable data structure (a list) to back the SpatialHash’s data; so you could resize this on the fly, but earlier you mentioned having a fixed world size as being part of the choice to use a SpatialHash over a QuadTree.” This is true. SpatialHashes can be resized on the fly but… one – if not the biggest – advantage of the SpatialHash is that it is pre-partitioned. If your world space is resized then it needs to be repartitioned, and partitioning early and often is where QuadTrees excel. If your world has an occasional resize a SpatialHash might still be more beneficial than a QuadTree, but that’s something you’ll have to benchmark in your particular application.
And that’s everything I know about SpatialHashes. I hope you found this mildly condescending and slightly informative.
If you’d like to read some additional views on SpatialHashing, take a look at these resources:
Apparently stuxnet managed to hybridize in a way such that it’s taken Josh out for the foreseeable future. Either that or he succumbed to snowcrash. In any event, that means that Rho-Bot and I will be conquering our weekly blog for the foreseeable future. And starting our new and likely recurring segment: Coder’s Corner.
Earlier this week I was setting up our new project (no, we’re not going to squeal on what it is yet), and I wanted to use the Log4j libraries. My primary motivation for doing so is that I like data and, if I’m not the one doing the testing, I usually get incomplete descriptions of bugs back. Since libgdx’s built in logging only outputs to the console we need some way to create actual log files ergo Log4j. I’ve used these libraries in our earlier projects with no problems. But last week I started using Intellij as my IDE. This time, however, after I dropped the libraries and my config file into the same location as the last project and I got this error:
I know what you’re computing ‘ACMU you forgot to add ?*.xml to the resource patterns in your compiler settings. As it happens, 0) I did not forget to do that, and 1) as I found out the next day it’s not strictly necessary to do that for Log4j anyway.
With that most obvious source of my difficulties out of the way I clicked over to the Log4j manual and it says
“If a JSON file cannot be located the XML ConfigurationFactory will try to locate log4j2.xml on the classpath.”
So you’d think that if my project structure looks like this:
the library would find a configuration file acceptable and use it. No dice (Did you processor that pun?). So I spent a few hours on stackexchange making no progress. Then, through some fluke, I read the Log4j configuration page again and realized that a line up at the top says,
“Log4j will inspect the “log4j.configurationFile” system property and, if set, will attempt to load the configuration using the ConfigurationFactory that matches the file extension.”
Alright so we’ll just got to Run->Edit Configurations, set our system property like this:
Drop the config file into your working directory and voila, Log4j is configured.
The next morning when I got to work it occurred to me that a work project was using Log4j too. I had just started using Intellij at work too, and I didn’t remember having any configuration issues, so I did a little digging. I went looking for the project’s log4j2.xml file and found it sitting in the /src/ directory and I hadn’t even marked *.xml as a resource file type.
Which leads to the question, “What are the differences between these two projects?”. Well, at home I set up a libgdx project using gradle for dependency management; at work I just write code with no dependency management – because enterprise applications are boring. So, an alternate (and probably better) solution than the one I found is to include the Log4j configuration file as a resource in the gradle.build file. And no I’m not going to tell you how to do that. In the words of many a professor and textbook: it’s left as an exercise for the reader.
You must be logged in to post a comment.