Some games require handling gravity from multiple variable sources. In this recipe, we'll handle this and create a simple miniature solar system to demonstrate it using ThrusterControl
from the Building a rocket engine recipe. To (greatly) simplify the relation between the planets, they won't affect each other with their gravity, but only the ship. It will also be made in a 2D-asteroids-like fashion, although the gravity would still apply for a 3D game.
We'll add some basic controls to rotate the ship to the left and right, and you can use the thruster to make the ship move forward.
Apart from ThrusterControl
, we'll create two more small classes and an application class that joins everything together. Let's start with a class that represents the player's ship. This will consist of the following six steps:
SpaceShip
, which has a Node
field called shipNode
in it.RigidBodyControl
instance with BoxCollisionShape
. To create it in a way that it is affected by gravity, we also give it a mass of 1
that will be supplied in the constructor as follows:RigidBodyControl control = new RigidBodyControl(new BoxCollisionShape(new Vector3f(1, 1, 1)), 1); shipNode.addControl(control);
Node
instance called thruster
. We also set the name of Node
to Thruster
for the control to find it automatically, as shown in the following line of code:Node thruster = new Node("Thruster");
localTranslation
to be at one of the sides of the spaceship and attach it to shipNode
, as follows:thruster.setLocalTranslation(-1, 0, 0); shipNode.attachChild(thruster);
shipNode.rotate(0, FastMath.PI, 0);
ThrusterControl
instance to the spaceship's spatial.That's it for the SpaceShip
class. Now, we create a class for our planets, as follows:
StellarBody
, which extends AbstractControl
. The StellarBody
class has four float fields: size
, speed
, orbit
, and cycle
.size
, speed
, and orbit
) as the input, as shown in the following code:public StellarBody(float orbit, float speed, float size)
setSpatial
method and add RigidBodyControl
to the supplied spatial with SphereCollisionShape
, using size
as the radius and 0
for mass:RigidBodyControl rigidBody = new RigidBodyControl(new SphereCollisionShape(size), 0f); rigidBody.setGravity(Vector3f.ZERO); spatial.addControl(rigidBody);
controlUpdate
method, we make it move along its orbit by increasing the speed of the cycle by multiplying it by tpf
, as follows:cycle += (speed * tpf) % FastMath.TWO_PI;
sin
and cos
methods of the FastMath
class:float x = FastMath.sin(cycle); float z = FastMath.cos(cycle);
localTranslation
of the spatial to the new location as follows:spatial.setLocalTranslation(x * orbit, 0, z * orbit);
physicsLocation
of RigidBodyControl
to the same location.getGravity
, that will take the position of the ship as an input Vector3f
.worldTranslation
, to get the position of the ship relative to the StellarBody
class, as follows:Vector3f relativePosition = spatial.getWorldTranslation().subtract(position);
relativePosition.normalizeLocal(); return relativePosition.multLocal(size * 1000 / relativePosition.lengthSquared());
To test all of this, we need to add a few things to SimpleApplication
. To do this, perform the following set of steps:
AnalogListener
.ArrayList<StellarBody>
list called gravitationalBodies
.simpleInitApp
method, we should begin by initializing bulletAppState
and set up some controls for the spaceship. We add actions to rotate the spaceship to the left and right as well as fire the ship's thruster, as follows:String[] mappings = new String[]{"rotateLeft", "rotateRight", "boost"}; inputManager.addListener(this, mappings); inputManager.addMapping("boost", new KeyTrigger(KeyInput.KEY_SPACE)); inputManager.addMapping("rotateLeft", new KeyTrigger(KeyInput.KEY_LEFT)); inputManager.addMapping("rotateRight", new KeyTrigger(KeyInput.KEY_RIGHT));
cam.setLocation(new Vector3f(0, 300f, 0)); cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);
ship
of SpaceShip
and attach its geometry to rootNode
and physicsSpace
of bulletAppState
.StellarBody
instances using the following steps:Geometry
class with a Sphere
shape that will have the same radius as the size we will supply to the StellarBody
control.Geometry
class should both be attached to rootNode
and physicsSpace
of bulletAppState
.StellarBody
as a control to the Geometry
class and the gravitationalBodies
list.update
method, we have to take into account the gravity of the StellarBody
instances. Vector3f
instance called combinedGravity
.gravitationalBodies
list and apply the following line of code to apply the gravity to combinedGravity
:combinedGravity.addLocal(body.getGravity(ship.getSpatial().getWorldTranslation()));
ship.setGravity(combinedGravity);
statement.Due to the extreme difficulty in creating a stable solar system with more than three bodies, StellarBody
controls the need to have a static orbit around the center of the system. Using 0
as mass ensures that they aren't affected by gravity. The orbit field represents the orbit's distance from the center of the system, and it will rotate around the center using speed as a factor. The cycle field stores information on how far along its orbit it has come, and will reset once it reaches two PI (a full circle).
The getGravity
method returns the gravity relative to the position that is supplied, which in this case is the location of the ship. It first determines the direction and then applies the gravity based on the distance between the two.
By using the gravitationalBodies
list, we have a dynamic way to simply add up all the gravitational forces in the system to a single Vector3f
object, which we then apply to the spaceship in the update
method of the application.
52.15.80.101