The principles of a bridge-building game

Variants of bridge-building games have been around for a long time. The classical Bridge Builder is a 2D physics game where the player is required connect beams to create a bridge strong enough for a train (or some other moving object) to pass.

This recipe will describe most of the core functionalities needed to create such a game, including making the objects stay 2D and not wander off on the z axis.

We'll have some basic controls for the game:

  • Left-click will select a previously built node in the bridge
  • Right-click will add a new node or connect two previously built ones
  • The Space bar will turn on the physics

The following figure shows a bridge:

The principles of a bridge-building game

Getting ready

Before we begin with more physics-related functions, we should set up the basic application.

First of all, we create a new class that extends SimpleApplication.

Later on, we're going to use the following two lists:

private List<Geometry> segments;
private List<Point2PointJoint> joints;

We also need some strings as input mappings: LEFT_CLICK, RIGHT_CLICK, and TOGGLE_PHYSICS.

We add a RigidBodyControl field called selectedSegment that will contain the last selected segment in the game.

Since we're strictly making a 2D game, we should change the camera to be orthographic. This can be done by performing the following steps:

  1. Disable flyCam.
  2. Find out the aspect ratio by dividing the cam width by its height and storing it.
  3. Set cam.parallelProjection to true.
  4. Then, change frustrum of the camera to suit and orthographic view as follows:
    cam.setFrustum(1, 1000, -100 * aspect, 100 * aspect, 100, -100);
  5. We move it some way along the z axis and rotate it back towards the center, as follows:
    cam.setLocation(new Vector3f(0, 0, 20));
    cam.setRotation(new Quaternion().fromAngles(new float[]{0,-FastMath.PI,0}));

Now, we can initialize bulletAppState as we usually do. Turn on the debug mode, and most importantly, set speed to 0. We don't want any physics on while we build the bridge.

The world needs a gap to be bridged. So, for this, we'll use RigidBodyControl to represent two cliffs, one on either side, as follows:

  1. Create one RigidBodyControl instance for each side and give it BoxCollisionShape with a size of Vector3f(75f, 50f, 5f) and 0 mass.
  2. Place one of them at Vector3f(-100f, -50f, 0) and the other one at Vector3f(100f, -50f, 0).
  3. Then, add them to physicsSpace.

How to do it...

We're going to start by creating two methods that will help us add new bridge segments to the game:

  1. We define a method called createSegment that takes a Vector3f parameter called location as the input.
  2. The first thing we do is set the z value of location to 0. This is because we're making a 2D game.
  3. Then, we create a new RigidBodyControl instance called newSegment. We add SphereCollisionShape to it and then add newSegment to physicsSpace. It's important that it has some mass. This can be implemented as follows:
    RigidBodyControl newSegment = new RigidBodyControl(new SphereCollisionShape(1f), 5);
    bulletAppState.getPhysicsSpace().add(newSegment);
  4. Now, we create a Geometry instance based on a Sphere shape with the same radius as RigidBodyControl. We will use this as a target for mouse clicks.
  5. The Geometry object needs modelBound for which we'll use BoundingSphere. The radius may be bigger than RigidBodyControl.
  6. The RigidBodyControl object is added to Geometry as a control and we use the setPhysicsLocation method to move it to the to the supplied location, as follows:
    geometry.addControl(newSegment);
    newSegment.setPhysicsLocation(location);
  7. The Geometry object is then added to the segments list we defined earlier and then it is attached to rootNode.
  8. If selectedSegment is not null, we will call a method we will define next:
    createJoint(selectedJoint, newSegment);
  9. Lastly, in the createJoint method, we set selectedSegment to be newSegment.
  10. Now, we can define the createJoint method. It takes two RigidBodyControl parameters as the input, as shown in the following code:
    createJoint(RigidBodyControl body1, RigidBodyControl body2)
  11. First, we find out the location that should be the pivot point of body2. This is the same as physicsLocation of body2 subtracted from physicsLocation of body1, as follows:
    Vector3f pivotPointB = body1.getPhysicsLocation().subtract(body2.getPhysicsLocation());
  12. Then, we define Point2PointJoint by joining the two segments. The vectors supplied mean that body2 will pivot in a way that is relative to body1; we do this using the following code:
    Point2PointJoint joint = new Point2PointJoint(body1, body2, Vector3f.ZERO, pivotPointB);
  13. We then add the newly created joint to the joints list and to physicsSpace.

We're now getting to the controls of the application and need another method to help us. The method will check whether a mouse click has hit any segment and return it. To do this, perform the following steps:

  1. We define a new method called checkSelection, which returns RigidBodyControl.
  2. Inside this method, we create a new Ray instance, which will have the current mouse cursor's location as the origin; the following code tells you how to do this:
    Ray ray = new Ray();
    ray.setOrigin(cam.getWorldCoordinates(inputManager.getCursorPosition(), 0f));
  3. Since the view is orthographic, we let the direction be Vector3f(0, 0, -1f).
  4. Now, we define a new CollisionResults instance to store any segments that Ray collides with.
  5. The next thing we do is parse through the segment's list and check whether the ray hits any of them.
  6. If it does, we're done, and then return RigidBodyControl of segment to the calling method.

We defined a couple of input mappings earlier. Now, we can all implement the functionality for them in the onAction method by performing the following steps:

  1. If the left mouse button is clicked, we should call checkSelection. If the returned value is not null, we should set selectedSegment to that value, as follows:
    if (name.equals(LEFT_CLICK) && !isPressed) {
      RigidBodyControl newSelection = checkSelection();
      if (newSelection != null) {
        selectedSegment = newSelection;
      }
    }
  2. If the right mouse button is clicked, we should also call checkSelection. If the returned value is not null and it's not selectedSegment, we call createJoint with selectedSegment and the value of checkSelection to create a link between selectedSegment and the segment returned from the method, as shown in the following code snippet:
    else if (name.equals(RIGHT_CLICK) && !isPressed) {
      RigidBodyControl hitSegment = checkSelection();
      if (hitSegment != null && hitSegment != selectedSegment) {
        createJoint(selectedSegment, hitSegment);
      }
  3. Otherwise, if we didn't hit anything, we call createSegment with the position of the mouse cursor to create a new segment at that location as follows:
    createSegment(cam.getWorldCoordinates(inputManager.getCursorPosition(), 10f));
  4. If the Space bar has been pressed, all we need to do is set the speed of bulletAppState to 1 to start the physics.

We're almost done with our simulation now, but we need to do a few more things. This last section will handle the update method and what happens when the physics is running and the bridge is being tested:

  1. In the update method, we parse through all the items in the segment list and set the z value of linearVelocity to 0, as follows:
    Vector3f velocity = segment.getControl(RigidBodyControl.class).getLinearVelocity();
    velocity.setZ(0);
    segment.getControl(RigidBodyControl.class).setLinearVelocity(velocity);
  2. After this, we parse through all the items in the joint's list. For each, we should check whether the joint's appliedImpulse value is higher than a value, let's say 10. If it is, the joint should be removed from the list as well as from physicsSpace, as follows:
    Point2PointJoint p = joints.get(i);
      if (p.getAppliedImpulse() > maxImpulse) {
        bulletAppState.getPhysicsSpace().remove(p);
        joints.remove(p);
    
      }

How it works...

The createSegment method creates a new bridge segment that is sphere shaped, both in physicsSpace and the visible world. This is the part that has a mass and can be selected by clicking on it, since Ray only collides with spatials.

The createJoint method creates the visible connection between the newly created segment, and the currently selected one. It does this using Point2PointJoint. This is different from, for example, HingeJoint, since it's not fixed in space, when several Point2Pointjoints are connected and you have something that resembles a bridge.

The mouse selection is covered more in depth in other chapters, but it works by shooting Ray from the mouse's position on the screen, inwards into the game world. Once Ray hits Geometry (which has BoundingSphere that is slightly larger than the visible mesh for increased selectability), the corresponding RigidBodyControl will be selected.

There's no challenge in a bridge-building game if the segments don't have a maximum force they can handle before they break. This is what we take care of in the update method where we check appliedImpulse on each segment. If it goes above a certain threshold, it can be considered to be overloaded and removed, often with disastrous results. We also set linearVelocity along the z axis on each segment to 0 since it's a 2D game and we don't want anything to move to the depth layer.

We start the game with the physics simulation off by setting the speed of bulletAppState to 0. Without doing so, building the game will get tricky pretty fast as everything will fall down. Pressing the Space bar will start the physics, and let the player know whether their engineering skills are up to par.

There's more…

There are a couple of things missing from the recipe to make it a full-blown bridge builder. First of all, there is usually a limit to the length the segments can have. There might also be a grid structure along which they have to be placed.

It's also quite easy since the bridge currently only has to support its own weight. In a full game, the difficulty is usually increased by adding a heavier object that needs to pass the bridge to complete the level.

Add some monetary constraints to this or a varied terrain and you have a challenging game.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.159.178