GameUtility.cs

Mixing Fake Physics with "Real" Physics

Physics-like behavior in Airspace Defender.

Physics-driven Action

In gamedev, the word "Physics" often refers to the black box api which comes with every modern game engine. You assign a physics component to an actor, and now it has a center of mass, and a velocity, and so on.

One of my earliest games was an attempt to make a physics-driven combat simulator, and I learned a tough lesson -- gameplay actions can't happen entirely in the black box. The designer and user need them to be at least moderately predictable. Part of the issue is the complexity of real physics -- it's hard to make a robot walk like a person, so it would be similarly hard to make a ragdoll do it. But moreover, the black box API isn't real physics. It's as "real" as possible in the game world, since it provides a single lockstep simulation for every element in the scene, but it is also an approximation, with a fixed timestep, optimized for performance, with unstable results for multi-body interactions.

An unstable physics simulation.

Nonetheless, there are many cases in games where physics-like behaviour would look or feel good. In order to make these elements controllable from a game design or user standpoint, I have increasingly turned to "fake physics".

Fake Physics

In "Fake Physics", I often use real (though simple) physics equations. However, by calculating frame updates myself (instead of letting the black box do it), I have total control over the inputs and results.

public class FakeFallingExample
{
    private float _timer;
    
    void FixedUpdate()
    {
        // We can set position as a function of a timer
        // This is marginally more accurate than a falling Rigidbody
        // And we can easily evaluate future positions
        _timer += Time.fixedDeltaTime;
        transform.position = Vector3.down * .5 * 9.8 * _timer * _timer;
    }
}

At the end of the essay I will reveal my one weird trick to make the Fake Physics work well with the "Real" Physics. But, first I want to go through a few Fake Physics case studies, to clarify the types of situations in which this is useful:

A parabolic projectile travels to a target

Aiming the Mortar in Airspace Defender.

Let's start with a simple example. The Mortar weapon in Airspace Defender always fires at an arc to a player-selected destination. This is a parabolic arc, where we know the start position, end position, and the force of gravity. Thus, we can pick the right initial velocity by solving the following system of equations: x = (v + v_0) * t / 2, and v^2 = v_0^2 + 2 * a * x. After that, we set the initial velocity and let the physics system go! Nothing "fake" about it, if we take that approach.

However, I also wanted to draw the arc before firing, so the player could aim, which means integrating the mortar projectile formula to assess every point in the displayed path. And now, given that I'd made a clear promise to the player about the path of the projectile, it became somewhat noticeable that the parabolic trajectory accumulates error every physics frame... So, I opted to re-use the integrated path from the arc, and set the projectile position over time directly. I.e., at a given time t, I set x via x = v_0 * t + .5 * a * t^2 (like in the FakeFallingExample above). Although it was possible to let the physics engine control the mortar's projectile, I chose to control its position directly for greater precision.

A helicopter bobs as it travels to a precise firing point

Helicopter motion in Airspace Defender.

I added a helicopter to Airspace Defender because I needed a low-flying enemy to threaten the player's headquarters if they lost too many buildings. So the helicopter needs to launch missiles at a precise elevation, but it also needs to look good flying in -- it shouldn't just go directly to its target like a wire puppet.

For the looking good part, a helicopter is self-correcting -- when it over-rotates, it gets more lift on the "lower" side. I found I could model this as a sine curve rolling back and forth. In fact, I added sine curves to bob, pitch, and sway from side to side too. By "add" here, I mean maintain offsets to the base orientation and position, and update them each frame.

But with such complex-looking movement, how can the helicopter start and end in specific positions? Well, because we're using sine curves to describe position, we know the offset at any future point in time. Thus, the chopper's "initial position" and "final position" can have their own offsets to guarantee results.

A virtual camera reacts to player input

Space ship controller from my contract work on Drift: Space Survival (used with permission).

For this space ship, the initial prototype was controlled by the black box! The player applied acceleration, and the ship experienced drag (in space!), while the camera caught up by a percentage of the distance each frame. You could really feel the acceleration, since the camera fell behind at first! But, it was hard to fly, and the camera behavior was framerate dependent...

I also wanted turning to feel just as impactful in terms of camera response. How to interpolate in a physics-y way between so many possible states of acceleration or rotation? My solution was a critically damped 3-dimensional spring (using an equation from The Orange Duck). Every frame the spring pulls the camera to where it should be according to the current player input.

Why not just interpolate? Well, a spring guarantees no velocity discontinuity. Thus, not only will the camera accelerate (and decelerate thanks to the critical damping) pleasingly, the behavior will be subtly different if you change directions suddenly. I suspect most players only notice subconsciously, but it adds "game feel".

What makes good Fake Physics?

I think the general secret to Fake Physics is that the behaviors we are constructing are simpler than the full simulation, and thus we can use the integral form. If you have access to an analytic integral for the motion you are describing, then you can precisely control start and end points, without sacrificing the player's sense of accelerating physical "stuff".

However, there are clearly cases where an analytic integral is impossible: for instance, Inverse Kinematics, which clearly reflects the Fake Physics goal of "a predictable destination with natural motion", generally relies on iterative solvers.

The Best of Both Worlds

Ok, so this is going to seem dumb (given the previous paragraphs about the advantages of the integral form), but one of the weaknesses of Fake Physics is that, if you run it in the physics loop (which you surely want to do if you have any physics-driven gameplay), then you lose access to the engine's built-in physics interpolation, and get choppy-looking motion. This was especially noticeable for me in VR for Airspace Defender, since VR has strict performance requirements and high framerates.

So... the solution is to calculate the next frame position, subtract the current position, and set the velocity according to the delta. A.k.a. rigidbody.velocity = (newPos - transform.position) / Time.fixedDeltaTime. What can I say, it works! You still get total control (so long as the object only has triggers), and you get motion with the same interpolation as the "Real" Physics engine.

Post-Script (Angular Velocity)

Can you do the interpolation trick above with angular velocity? Well... yes, but you probably don't want to. Angular velocity is in a version of scaled-angle-axis form, which is neither quaternion nor euler, so you're unlikely to be calculating in the right form as a matter of course.

But, if you're curious, here are my helper functions to convert from euler delta to angular velocity:

public static Quaternion Abs(this Quaternion q)
{
    return q.w < 0 ? new Quaternion(-q.x, -q.y, -q.z, -q.w) : q;
}

public static Vector3 ToScaledAngleAxis(this Quaternion q)
{
    q.ToAngleAxis(out var angle, out var axis);
    return angle * axis;
}

public static Vector3 EulerDeltaToScaledAngleAxis(Quaternion current, Vector3 delta)
{
    var intendedResult = Quaternion.Euler(current.eulerAngles + delta); // Desired resulting orientation
    var localDelta = Quaternion.Inverse(current) * intendedResult; // Local rotation that achieves result
    var angleAxis = localDelta.Abs().ToScaledAngleAxis(); // Local angular velocity (i.e. local transformation in angle-axis formulation)
    return current * angleAxis;
}

Note that the final angular velocity requires a unit conversion:

rigidbody.angularVelocity = MathG.EulerDeltaToScaledAngleAxis(transform.rotation, eulerDelta) 
  * Mathf.Deg2Rad * .5f / Time.fixedDeltaTime;

GameUtility.cs is the personal blog of Guillaume Bailey. For more rendering and gameplay explainers, check out my favorite blogs on the Home page. To get in touch with questions or feedback, send me an email.

< Previous (Bug-Free UI In Unity)