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:
The following figure shows a bridge:
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:
flyCam
.cam
width by its height and storing it.cam.parallelProjection
to true
.frustrum
of the camera to suit and orthographic view as follows:cam.setFrustum(1, 1000, -100 * aspect, 100 * aspect, 100, -100);
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:
RigidBodyControl
instance for each side and give it BoxCollisionShape
with a size of Vector3f(75f, 50f, 5f)
and 0
mass.Vector3f(-100f, -50f, 0)
and the other one at Vector3f(100f, -50f, 0)
.physicsSpace
.We're going to start by creating two methods that will help us add new bridge segments to the game:
createSegment
that takes a Vector3f
parameter called location
as the input.z
value of location
to 0
. This is because we're making a 2D game.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);
Geometry
instance based on a Sphere
shape with the same radius as RigidBodyControl
. We will use this as a target for mouse clicks.Geometry
object needs modelBound
for which we'll use BoundingSphere
. The radius may be bigger than RigidBodyControl
.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);
Geometry
object is then added to the segments list we defined earlier and then it is attached to rootNode
.selectedSegment
is not null, we will call a method we will define next:createJoint(selectedJoint, newSegment);
createJoint
method, we set selectedSegment
to be newSegment
.createJoint
method. It takes two RigidBodyControl
parameters as the input, as shown in the following code:createJoint(RigidBodyControl body1, RigidBodyControl body2)
body2
. This is the same as physicsLocation
of body2
subtracted from physicsLocation
of body1
, as follows:Vector3f pivotPointB = body1.getPhysicsLocation().subtract(body2.getPhysicsLocation());
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);
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:
checkSelection
, which returns RigidBodyControl
.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));
Vector3f(0, 0, -1f)
.CollisionResults
instance to store any segments that Ray
collides with.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:
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; } }
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); }
createSegment
with the position of the mouse cursor to create a new segment at that location as follows:createSegment(cam.getWorldCoordinates(inputManager.getCursorPosition(), 10f));
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:
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);
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); }
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 Point2PointJoin
t. 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 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.
18.226.104.27