Physics Engines

For this lab, you will get your hands dirty with Box2d, a popular 2D physics engine. While the API is a little strange, it is one of the most robust, efficient physics engines you can use for your 2D game. Before Unity and Godot became popular, it was the physics engine used by all 2D indie games. And the lessons that you can learn from it apply to almost any other physics engine out there.

Scuba

Once again, this lab is not due during this lab period (it is due well after February Break), but it is to your advantage to work through (and/or learn) as much as you can now, while assistance is immediately available. If you are experiencing any problems, contact one of the programming TAs.

Table of Contents


Game Rules

Download: PhysicsSoln.jar

This “game” is actually three different minigames showing off the different ways you can use physics in a game. Once you complete one minigame, it immediately takes you to the next one. In addition, you can press N (for Next) and P (for Previous) to navigate through the minigames at will. Pressing R will reset the current game.

We have also provided you with a precompiled solution. Do not decompile the solution. We know how easy it is to decompile Java (and have caught many people with Moss in the introductory courses). Doing so is a violation of the Academic Integrity policy of the course. Do not be that person.

When you run this solution, you will see the following three different minigames.

Rocket Lander

The first mini game is a rocket ship in a room with crates and an exit. The game is over when the ship reaches the exit. Currently, the rocket ship has no thrusters. It is up to you to apply forces to the ship so that you can guide it to the exit.

Platformer

The second minigame is a platformer world where you need to guide Traci Nathans-Kelly to another exit. Traci can run (left and right arrows), jump (up arrow) and shoot (space bar). She needs to somehow get over the initial gap, jump up on the platforms to the right, and get back over the gap to the exit.

There used to be rope bridge over the gap, but it is falling apart. You need to repair it. There is also a spinning platform near the exit that Traci can spin (by shooting it) to give you just enough room to jump to the exit. You need to add a joint to keep the platform from falling.

Rag Doll

The last minigame is a fishtank with a ragdoll of Professor White. Currently, he is in pieces after an unfortunate shark-related snorkeling incident. You need to reassemble this ragdoll so that it is all in one piece. The result is a sandbox game where you can drag the ragdoll, but there is no way to win.


Project Files

Download: Physics.zip

The code for this lab is some of the most complicated that you have seen so far. There are 20 classes and multiple packages. In fact, each minigame is grouped in its own package. The top-level classes provide the outer game engine, but most of the work is done by the classes in the individual folders.

In addition, this lab is the most advanced usage of the gdiac.jar so far. It uses a lot of features from that library. Indeed, many of the tools in gdiac.jar were specifically designed with this lab in mind. We expect that there will be a lot about this lab that you do not understand. We will be lecturing about this lab for many, many weeks, using it as a case study.

We have put a lot of work into this lab because it is our experience that everyone uses this lab as a template for their gameplay prototype (and some even build off this for their technical prototype). This absolutely acceptable. Just remember that, when you borrow code for a project, you must cite its origin in your comments.

Code Organization

There are way too many classes for us to give a dependency diagram this time. And honestly, the many of the more complicated classes are actually in gdiac.jar, for which you do not have the source. You only have the documentation for these classes. But some of this is for a good reason. We have created all these classes to make this lab as straight-forward as possible for you. A large part of this activity is more about understanding the code structure than it is trying to write code of your own.

With that said, a lot of this code should look familiar. There is a GDXRoot defining the entry point, and an input controller. We have several scenes including a loading scene and a scene for each minigame. But we are missing s ome things as well. You will notice that there is no CollisionController, which was present in the previous assignment. That is because box2d handles that detail, so it embedded into each scene.

In addition, the model classes this time are really weird. So here are the classes to pay attention to.

Obstacle

As we will learn later, box2d physics objects have two components: the Body which defines its position and orientation, and the Fixture which defines its area for determining collisions. You have to combine the two of these together to define a physics object (though there are cases where the Fixture is missing). This can be daunting. So that is where Obstacle comes in. It is a class that combined Body and Fixture together and does all the hard work for you. Think of it as box2d on training wheels.

You should read the documentation for this class. You should also be familiar with the four subclasses.

Each of them corresponds to a different shape. Even though PolygonObstacle can technically do any shape, there are important optimization reasons for the others. CapsuleObstacle is particularly helpful for platformers because the smooth, rounded bottom means the character will not get caught on any cracks.

ObstacleSprite

This is the bizarre class. Up until now you have been drawing textures. Textures are defined by image files, which are rectangular. While they can be scaled, rotated, and translated, they are still just rectangles. We want to draw things that potentially match the shape of the physics object. So instead, we will use a SpriteMesh. This is a shape with an image attached to it. We will talk about how these work later in this course.

An ObstacleSprite is therefore just a SpriteMesh attached to an Obstacle. It uses the Obstacle to place the mesh on the screen. So this means that you don’t actually have to do anything special to draw it. You just define the mesh and call the method draw. However, to work properly, you have to set the Obstacle physics units. As we discuss in the instructions below, there is a mismatch between the coordinates that box2d uses and the coordinates you use to draw to the screen.

You will note that this is the base class for almost all of our model classes. This is powerful class that is good for storing data about an object and displaying it to the screen. We typically do not want to subclass box2d objects because there is no additional information we could add to box2d. Anything we want should be done relative to this class. Look at the classes Rocket and Traci for some interesting ObstacleSprite subclasses.

The hard part of ObstacleSprite is knowing how to use meshes. We will talk about this later in class. And there are some examples about what to do in the code for the various models. But for the most part, we can typically get away with the initialization constructor That constructor automatically creates a mesh with the same outline as the physics object, so long as you set the physics units correctly.

ObstacleGroup

The class ObstacleSprite makes sense for a single object. But as part of this lab, we will often group together multiple physics objects using something called joints. An obstacle group is a container with a set of obstacle sprites all connected to each other via joints. We do’t technically need this class. But we find it makes it a lot easier to understand this assignment. The primary method you need to focus on in this class is createJoints.

PhysicsScene

This class exists because of the minigames. Instead of copying and pasting the same code between each minigame, we have factored out much of the code that they have in common. Because gameplay is unique to each minigame, that means this class essentially functions like CollisionController from the previous labs. It also handles player input as that is fairly uniform too.

The primary field in PhysicsScene is world. This is a box2d World object that stores all of the active physics objects. When you add a new physics object, or create new joints, you have to do it with this object. When an object is destroyed, you must manually delete it from this World or it will hang around, continuing to collide with other objects. Indeed, this is what our “garbage collection” code does: remove deleted objects from the World object.


Box2d Basics

The most difficult part of this lab is going to be reading the manual. The LibGDX API for box2d is complete but not fully documented. Your instructor had to look at the source code in the GitHub for LibGDX a few times to get this lab right. You should look at both the LibGDX API and the box2d manual.

The manual for box2d is very good. However, the problem here is that it is written for C++, which is the native language for this library. As a result, you may find yourself going back and forth between the two links above quite a bit. Here are a few guidelines for to help you understand the manual.

  • In C++, all classes are prefixed b2; these are removed in LibGDX.
  • Pointer references (either * or & attached to a variable) should be ignored.
  • All instances of -> should be replaced by a period (.).
  • A :: (e.g. b2World::CreateBody) represents class::method.

Most of these difference are because Java does not have pointer (it has references, which are not the same). The biggest issue will be getting the class names right. For example, b2Body in C++ is Body in Java.

If you see anything else that you do not understand, please ask a TA or post your question on Ed Discussions.

Physics Units

There is one thing that Obstacle has that is not built into box2d: the physics unit. Physics engines like box2d use real world measurements. One unit in box2d corresponds to 1 meter. If you took a 32x32 pixel image and gave those measurements to box2d, you are saying that you tiny image is the size of building. box2d is not designed to handle larger numbers of gigantic objects, so this will break the engine very quickly.

On the other hand if you try to draw a box2d object on the screen using box2d measurements it will be too tiny to see. So all box2d objects must be scaled up for you to see them. That is the purpose of the physics unit. This is the number of pixels that correspond to one box2d unit (or 1 meter). In this lab, this is computed from your window size, but if you use the default window measurements it is 32 pixels per box2d unit.

The interesting part is that this value is set in Obstacle. You would think that because it is related to on-screen drawing that it belongs in ObstacleSprite. However Obstacle has its own draw method. This is for debugging purposes. At any time, you can get wireframes representing the physics objects by pressing D on your computer. This is really important when the sprite mesh and the physics shape do not quite match.

For an example of this, run the platformer minigame and press D. You will see that Traci is a complicated image, but her physics body is a simple capsule. This is often happens with physics bodies associated with the character or NPCs. In addition, there are some cases where the physics object is invisible (it has no mesh), but we still want to be able to see the physics object in debug mode. You will encounter this in the lab instructions.

Box2d Tutorials

This is an extremely popular physics engine. If you get stuck there are lots of resource online to help you. Here are just a few that we have found.

The key thing to keep in mind is the classes that end in Def (e.g. BodyDef, FixtureDef and JointDef. You do not make a new Body or Joint with new. You must allocate it through the World object. The World object uses these Def objects to initialize the object when it allocates them. Essentially, box2d has an internal malloc that it prefers instead of the one for your programming language. For example, to create a new physics body, you need to do the following at a minimum:

    BodyDef def = new BodyDef();
    Body body = world.createBody(def);

For the most part, the class Obstacle takes care of all of this for you. But there is one place in the code where we had to do this directly. If you look at the method createSensor in the Traci class, we are creating a sensor fixture to prevent double (or even triple) jumping. We have these lines

    FixtureDef sensorDef = new FixtureDef();
    sensorDef.density = data.getFloat("density",0);
    sensorDef.isSensor = true;
    ...
    sensorDef.shape = sensorShape;
    ...
    Fixture sensorFixture = body.createFixture( sensorDef );

We set the attributes in the def to define the fixture and then passed it to another method to create the actual Fixture object.


Lab Instructions

This lab is broken into five parts. It is a combination of writing code and answering questions. The coding is often the simplest part of the assignment. The questions are more important, as they demonstrate that you have a basic understanding of physics and know how to use the Box2d engine. And if you forgotten your physics, this lab acts as a bit of a refresher.

Basic Physics Questions

Most of things should be familiar from your high school physics class (e.g. you do not need Calculus). For others, you may need to do some online hunting. Chris Hecker’s Rigid Body Dynamics articles are a particularly useful resource. If cannot figure out a question, please ask a TA.

You should answer each of the following questions in 20 words or less:

  1. What is Newton’s second law of motion?
    Do not just write F = ma; explain the intuition behind it.
  2. What is momentum? Impulse?
  3. What is a perfectly elastic collision?
    Give a real-life example of a (nearly) perfectly elastic collision.
  4. What is a perfectly inelastic collision?
    Give a real-life example of a (nearly) perfectly inelastic collision.
  5. What is the coefficient of restitution?
    What range of values can it take?
  6. What is angular velocity? Angular acceleration?
  7. What is the moment of inertia? Angular momentum?
  8. What is torque?
  9. What is the relationship between torque, moment of inertia, and angular acceleration?

You should write your answers in a text file called readme.txt, which you will submit as part of this assignment.

box2d Questions

The next set of questions require that you read both the box2d Documentation and the LibGDX API Remember to refer to our translation guide when trying to read the box2d API (which is in C++).

For each of these questions. we try to make it clear which of the two manuals we are refering to. You should answer each question in a few sentences:

  • In box2d, what is the difference between a Shape and a Body?
  • Between a Shape and a Body, which do you go to to change a physical property?
    To apply a force?
  • When would you want a Body to contain multiple Shapes?
  • In box2d, what is a World? What are some of its important properties?
  • What is the advantage of sleeping an object with a Body?
    When would you want to do this?
  • In box2d, what is the difference between a static body and a dynamic body?
    How do you specify which type a body is?
  • In box2d, what is a Bullet and when do you want an object to be one?
  • In LibGDX, what can you do with a ContactListener inside of a World?
    How does this help with sensors?
  • In LibGDX, what are the steps that you must take to add a Shape to a Body?

You should add your answers to the file readme.txt

Forces

After you have answered the physics questions, you are ready to start work on the minigames. The first minigame involves controlling a rocket ship in a land of crates. For this part os the assignment you will need to modify two different files: Rocket.java and RocketScene.java, Each serves a different role. Look at the comments in the source code to see what is going on. We talk about each of these classes (or at least their subclasses) in our code organization overview

All of the code that you write should go inside of the regions marked INSERT CODE HERE. You should never need to write code outside of these regions.

1. Move the Rocket

The rocket needs to be navigated to the exit but there is currently no way to move it! You need to add code in two places. In RocketScene you need to convert the player input into a force (remember to use the getThrust() method), and put that force inside the RocketModel. Once you assign the force, you should call the method applyForce to move the rocket.

The method applyForce in Rocket is itself unfinished. We perform a translation on the force for you (to include the angle of the ship). However, we do not apply the force to the Box2d body. You must do this. If you are unsure of this step, look at the code in PlatformScene and Traci. It is very close to what we are asking for.

2. Stabilize the Rocket

Now that the rocket is moving, you’ll notice it is hard to control because it will rotate upon crashing into things. Add code to prevent the rocket from rotating in Rocket’s constructor. This should take no more than one line of code. Do not remove any code that came with the lab.

Ropes and Joints

In the second minigame, Traci needs to walk on foot to the exit. This is a classic platformer where you can make Traci move around with the left and right arrow keys, jump with the up key, and shoot bullets with space. However, the bridge is out and the rotating platform is not connected to anything.

For this part of the lab, you will need to consult the box2D Documentation to learn about joints. Remember to consult our translation hints if you need help understanding the C++ API.

3. Fix the Rope Bridge

Currently the planks of the bridge are not connected to each other and just fall into the pit. We have attached the bridge for you at the end points, but we have not attached the planks in between. Implement the joints in RopeBridge so that the bridge is crossable.

You might want to look into Revolute Joints in the box2d API to get an idea as to how to connect the bridge together. The bridge should behave similarly to the solution (i.e. it should not be completely rigid).

4. Fix the Spinning Platform

When the level start, you will notice that a large pole falls from the sky. This pole is provided by the class Spinner. While it is a ObstacleGroup like the bridge, right now it only has a single Obstacle. This should give you a hint about what is wrong.

You need to do two things in this class: (1) add a pivot for it to spin around, and (2) a joint that connects the spinner to the pivot. Your solution should allow the spinner to stay fixed in space and rotates. To get an idea of how this should operate, consider looking at the solution executable. For an idea on how to implement this, go look RopeBridge again. Note that the code is split over two different places.

The pin should be a WheelObstacle. Most of your work will be defining this obstacle. Because of how our engine polymorphically handles objects, we must wrap it in an ObstacleSprite as well. But we do not want the pin visible. We only want to see it in debug mode. To do that use the default constructor for ObstacleSprite and add the Obstacle via the setter. This will create an empty mesh but still show the pin as a circle in debug mode.

Pay attention to the properties of Obstacle when defining your pin. In addition to writing the code for the solution, you should explain in readme.txt why you it is not enough to set body type to BodyType.StaticBody (e.g. why is the joint necessary as well), like we do with the other platforms. If you are unsure of the answer, try this your solution out without a joint and see what happens.

Ragdoll

In a random twist, Professor White has become completely discombobulated in a shark-related snorkeling incident! To put him back together, you should do the last two activities.

5. Assemble the Ragdoll

As with the RopeBridge, you should use joints to create a ragdoll out of his body parts. You will notice a diagram in the comments of Ragdoll detailing which parts should be connected by joints. You will add these joints to the same method that you did for the rope bridge.

As referenced in the documentation for the Ragdoll.java, the ragdoll has the following structure:

o = joint
                  ___
                 |   |
                 |_ _|
  ______ ______ ___o___ ______ ______
 |______o______o       o______o______|
               |       |
               |       |
               |_______|
               | o | o |
               |   |   |
               |___|___|
               | o | o |
               |   |   |
               |   |   |
               |___|___|

When computing the location of these joints, you take the centers and separate their centers by an offset vector (e.g. if the vector is zero, the centers are glued on top of each other). If you look at the JSON file ragdoll/constants.json you will see that the torso has an absolute position, but all of the other body parts have offsets from each other. Look at the code creating the bodies in makePart to see how access these values. For example, to compute the offsets for the left forearm, you use

String name = BODY_PARTS[partToAsset( PART_LEFT_FOREARM )];
x = data.get( name ).get("offset").getFloat( 0 );
y = data.get( name ).get("offset").getFloat( 1 );

You are querying the JSON for that body part, which gives a float array of two elements. The result of partToAsset is the index for the correct JSON key in BODY_PARTS. Since this the forearm, and not the torso, you want the child "offset". You then access the first for the x-coordinate of the offset and the second for the y-coordinate of the offset.

The reason for this approach is because different body parts may share the same textures (and hence the same offsets). Remember that the torso has an absolute position in box2d coordinates. All other body parts are positioned relative to another body part. In particular, the offset for each part is interpreted as follows:

  • PART_HEAD: The offset is from the torso center to the face center
  • PART_LEFT_ARM: The offset is from the torso center to the left arm center
  • PART_RIGHT_ARM: The offset is from the torso center to the right arm center
  • PART_LEFT_FOREARM: The offset is from the left arm center to the forearm center
  • PART_RIGHT_FOREARM: The offset is from the right arm center to the forearm center
  • PART_LEFT_THIGH: The offset is from the torso center to the left thigh center
  • PART_RIGHT_THIGH: The offset is from the torso center to the right thigh center
  • PART_LEFT_SHIN: The offset is from the left thigh center to the shin center
  • PART_RIGHT_SHIN: The offset is from the right thigh center to the shin center

Remember that joint coordinates are relative to the center of body, and are not part of the world coordinates. If you do not understand this issue, please ask a programming TA.

6. Extra Credit

This last part of the assignment is completely optional, and will earn extra credit (but no more than 5 points). The box2d engine provides a lot of very cool features, such as gravity controllers, polygon decomposition, dynamic shape destruction, etc. All of these and more can be seen in (and copied from) the LibGDX testbed. For extra credit, incorporate something you think is cool into the fishtank and tell us about it in the file readme.txt.


Submission

Due: Sun, Feb 23 at 11:59 PM

Theoretically, you should have only modified the following classes:

  • Rocket.java
  • RocketScene.java
  • RopeBridge.java
  • Spinner.java
  • Ragdoll.java

However, you may have modified much more in order to complete the extra credit. Whatever the case, you should create a ZIP file with three components:

  • The file readme.txt, containing the written questions for the lav
  • Any source (.java) file that you have modified.
  • An executable Jar of your solution.

Submit this zip file as lab4code.zip to CMS