Doors are useful in games. Visually, it is more appealing to not have holes in the walls but doors for the players to pass through. Doors can be used to obscure the view and hide what's behind them for a surprise later. In extension, they can also be used to dynamically hide geometries and increase the performance. There is also a gameplay aspect where doors are used to open new areas to the player and give a sense of progression.
In this recipe, we will create a door that can be opened by pushing it, using a HingeJoint
class.
This door consists of the following three elements:
Simply following the steps in this recipe won't give us anything testable. Since the camera has no physics, the door will just sit there and we will have no way to push it. If you have made any of the recipes that use the BetterCharacterControl
class, many of them in Chapter 2, Cameras and Game Controls, we will already have a suitable test bed for the door. If not, jMonkeyEngine's TestBetterCharacter
example can also be used.
This recipe consists of two sections. The first will deal with the actual creation of the door and the functionality to open it. This will be made in the following six steps:
RigidBodyControl
object called attachment
with a small BoxCollisionShape
. The CollisionShape
should normally be placed inside the wall where the player can't run into it. It should have a mass of 0, to prevent it from being affected by gravity.physicsSpace
instance, as shown in the following code snippet:attachment.setPhysicsLocation(new Vector3f(-5f, 1.52f, 0f)); bulletAppState.getPhysicsSpace().add(attachment);
Geometry
class called doorGeometry
with a Box
shape with dimensions that are suitable for a door, as follows:Geometry doorGeometry = new Geometry("Door", new Box(0.6f, 1.5f, 0.1f));
RigidBodyControl
instance with the same dimensions, that is, 1
in mass
; add it as a control to the doorGeometry
class first and then add it to physicsSpace
of bulletAppState
. The following code snippet shows you how to do this:RigidBodyControl doorPhysicsBody = new RigidBodyControl(new BoxCollisionShape(new Vector3f(.6f, 1.5f, .1f)), 1); bulletAppState.getPhysicsSpace().add(doorPhysicsBody); doorGeometry.addControl(doorPhysicsBody);
HingeJoint
. Create a new HingeJoint
instance called joint
, as follows:new HingeJoint(attachment, doorPhysicsBody, new Vector3f(0f, 0f, 0f), new Vector3f(-1f, 0f, 0f), Vector3f.UNIT_Y, Vector3f.UNIT_Y);
physicsSpace
as follows:joint.setLimit(-FastMath.HALF_PI - 0.1f, FastMath.HALF_PI + 0.1f); bulletAppState.getPhysicsSpace().add(joint);
Now, we have a door that can be opened by walking into it. It is primitive but effective. Normally, you want doors in games to close after a while. However, here, once it is opened, it remains opened. In order to implement an automatic closing mechanism, perform the following steps:
DoorCloseControl
extending AbstractControl
.HingeJoint
field called joint
along with a setter for it and a float variable called timeOpen
.controlUpdate
method, we get hingeAngle
from HingeJoint
and store it in a float variable called angle
, as follows:float angle = joint.getHingeAngle();
timeOpen
using tpf
. Otherwise, timeOpen
should be reset to 0
, as shown in the following code snippet:if(angle > 0.1f || angle < -0.1f) timeOpen += tpf; else timeOpen = 0f;
timeOpen
is more than 5
, we begin by checking whether the door is still open. If it is, we define a speed to be the inverse of the angle and enable the door's motor to make it move in the opposite direction of its angle, as follows:if(timeOpen > 5) { float speed = angle > 0 ? -0.9f : 0.9f; joint.enableMotor(true, speed, 0.1f); spatial.getControl(RigidBodyControl.class).activate(); }
timeOpen
is less than 5
, we should set the speed of the motor to 0
:joint.enableMotor(true, 0, 1);
DoorCloseControl
instance in the main class, attach it to the doorGeometry
class, and give it the same joint we used previously in the recipe, as follows:DoorCloseControl doorControl = new DoorCloseControl(); doorControl.setHingeJoint(joint); doorGeometry.addControl(doorControl);
The attachment RigidBodyControl
has no mass and will thus not be affected by external forces such as gravity. This means it will stick to its place in the world. The door, however, has mass and would fall to the ground if the attachment didn't keep it up with it.
The HingeJoint
class connects the two and defines how they should move in relation to each other. Using Vector3f.UNIT_Y
means the rotation will be around the y axis. We set the limit of the joint to be a little more than half PI in each direction. This means it will open almost 100 degrees to either side, allowing the player to step through.
When we try this out, there may be some flickering as the camera passes through the door. To get around this, there are some tweaks that can be applied. We can change the collision shape of the player. Making the collision shape bigger will result in the player hitting the wall before the camera gets close enough to clip through. This has to be done considering other constraints in the physics world.
You can consider changing the near clip distance of the camera. Decreasing it will allow things to get closer to the camera before they are clipped through. This might have implications on the camera's projection.
One thing that will not work is making the door thicker, since the triangles on the side closest to the player are the ones that are clipped through. Making the door thicker will move them even closer to the player.
In DoorCloseControl
, we consider the door to be open if hingeAngle
deviates a bit more from 0. We don't use 0 because we can't control the exact rotation of the joint. Instead we use a rotational force to move it. This is what we do with joint.enableMotor
. Once the door is open for more than five seconds, we tell it to move in the opposite direction. When it's close to 0, we set the desired movement speed to 0. Simply turning off the motor, in this case, will cause the door to keep moving until it is stopped by an external force.
Once we enable the motor, we also need to call activate()
on RigidBodyControl
or it will not move.
18.116.49.247