In the first part of this article, I covered planetoid creation and navigation. Let’s press right on into how we navigate BETWEEN the planets!
Jetpackin’
I actually approached this project as first and foremost a jetpack “simulator” (though that’s a bit of a grand term). Even before the planetoids and multiple gravity sources, the first thing I implemented was my jetpack control method.
The basic idea is that the pack operates via thrust vectoring, with one or more moving nozzles providing lateral as well as vertical thrust. If the player leaves the directional controls alone, the nozzle(s) point downwards giving vertical lift. If they point the stick forwards, they rotate backwards to drive the user in the direction they expect. Of course, you lose lift at the same time, so it becomes a balance to find enough forward speed without dropping yourself out of the sky.
The system I went with is physics-based; the player object has a non-kinematic rigidbody, and the user’s inputs are turned into forces. The first step of that is to turn them into a nozzle orientation, which is nice and simple:
// The maximum amount in degrees the nozzle can rotate on each axis
const float kMaxThrustNozzleRotation = 80.0f;
// Nozzle rotation around the X axis is driven by the left stick Y axis
// (forward/back)
float xRot = controllerAxisLeftY * kMaxThrustNozzleRotation;
// Nozzle rotation around the Z axis is driven by the left stick X axis
// (left/right)
float zRot = controllerAxisLeftX * -kMaxThrustNozzleRotation;
// Build the rotation quaternion
Quaternion rot = Quaternion.Euler(xRot, 0.0f, zRot);
// Our thrust direction becomes the up vector rotated by the
// quaternion, transformed to the player object local space
Vector3 thrustDirection = transform.TransformDirection(rot * Vector3.up);
Then all we need to do is scale thrustDirection by our input value (I use the trigger so we can apply variable amounts of thrust), and apply the thrust as a force with rigidbody.AddForce(jetpackThrust)
in the FixedUpdate
function.
Orbits
The planets I described in the first article were static. That’s fine, but planets and moons usually orbit others, and it would be cool to have that working.
Luckily it’s simple to do. I just added a rigid body to my planetoids, set all drag to zero, set them up with a system that applies an initial force (for movement) and torque (for spin), and put them around another gravitational body. As long as you get the initial force close to correct, they’ll fall into a nice orbit.
Ah, but what will happen if something bumps into them? Well, even if their rigid body has an extremely high mass, if the other object’s is set to kinematic it will disturb them. The planetoid will probably deorbit, with no doubt tragic consequences for all involved.
The workaround for this is to create another object with which all collisions will take place. It will shadow the planet without actually being parented (as parenting it would impart the results of any collision back to its parent’s rigid body).
Take any collider off the original planet, give one to the shadow object instead, then every update set its transform position and rotation to be the same as its owner. The timing for this operation is important; I tried LateUpdate
(collider will lag by a frame) and FixedUpdate
(collider won’t update smoothly) first, but Update
is the magic bullet which will give you the correct position and orientation.
The effect of this is that any object will now collide with your fake shell and not the planet itself, which will remain blissfully unaffected by the otherwise catastrophic results of innocent kinematic players landing on its surface.
But hang on – why is the player using a kinematic rigid body? Didn’t we just discuss a physics-based jetpack system?
Landing on Planetoids
Those of you paying attention will note that in the last article I cautioned against the use of a force-based movement system for our character when on the ground, and yet here I am using one for my jetpack. Before I get into why I’m doing that, let’s see why using forces for character movement is a bad idea when attaching to planets in the first place.
When landing on an orbiting planetoid, how do we keep our character attached to it as it moves through space? Well, the normal thing to do is to parent our character to the object it’s just landed on. Parenting means that our character’s transformation matrix will be transformed relative to the parent’s matrix and thus all of our movement becomes local to that parent object. In effect, we’re “stuck” to it.
The problem is this: parenting non-kinematic rigid bodies together is a no-no. In fact, you shouldn’t parent non-kinematic rigid bodies to any moving object. It might seem to work most of the time, but you’ll get odd effects when you least expect them; the parent’s transform updates don’t play nice with force-based movement.
So what do we do? We actually have a few options. We could use potentially use a joint, such as a FixedJoint
, which is a method of attaching rigid bodies together. However this has some differences to the standard parent-child relationship, and wouldn’t work for our needs here. Another approach would be to go to a completely kinematic solution – replacing the force-based jetpack system with a method of updating the object transform manually, which would involve keeping track of my own accumulated thrust vectors, collisions and so on. However frankly this seemed like a lot of work, and I already had a movement system that felt great which I didn’t want to wreck. So I decided to switch my character’s rigid body from kinematic to non-kinematic depending on whether it was on the ground.
A kinematic rigid body is one which doesn’t respond to forces. If you set one up by checking the Is Kinematic
box in the inspector, it’s expected that you control it manually by updating its transform. So what I do is when the player is jetpacking through space, his rigid body is a standard non-kinematic one which responds to gravity and jetpack thrust. As soon as I detect a ground landing though, the rigid body is switched to kinematic and my code changes to use the movement system described in the previous article. If the player applies thrust, or if gravity from another body begins to pull him in a new direction, we switch back to non-kinematic again to allow the forces to take control.
This works great and only has one real drawback – the bouncy, slightly untethered feeling of light gravity vanishes when walking on a low-gravity planetoid. That’s because gravity isn’t actually applied when the player character is on a surface, and we rely purely on the ground-snapping from the movement code to keep us attached. That may sound bad (and it’s definitely not ideal), but it’s not actually that big of a problem for my use case. If it becomes more important, I’ll look for a solution.
One other point about parenting objects – never parent an object to another with non-uniform scale. If your objects are rotated, this will introduce shear into the child object and you’ll get very odd results, usually manifesting as a sudden and gigantic increase in scale. Avoid at all costs!
Alright! So now we can navigate between orbiting planetoids. What’s next, I wonder?
A probably very queasy astronaut goes for a ride in a busy solar system.