Client-side Prediction in Unity

If you're making a multiplayer game in Unity and your networking model includes a fully authoritative server, you might have found movement to be a bit of a stumbling block. Client-side prediction is how lag tends to be hidden, Glenn Fiedler has an awesome series of articles, this one explains client-side prediction nicely.

To summarise - clients send their input (e.g. key presses) to the server which computes their updated position in the game world, and sends it back to them. To hide the lag, the client runs the movement code locally, and records its input and position each frame. When an update is received from the server, it looks through all the recorded positions and compares it with the data received from the server. If the two are out of sync by too large a margin, it retrospectively corrects the client by moving them to the correct position, and then runs through all the stored inputs, replaying the users actions up to the present time. This "rewind and replay" system is fine under certain circumstances, but a big problem in Unity is physics.

For about two years I've been developing a Unity multiplayer FPS on and off. Naturally, players can run about, jump up and down, collide with objects, and so on. I have no way of triggering the Rigidbody component on the player to simulate a frame, so I can't rewind and replay can I? Well I can, if I roll my own basic Rigidbody class:

public class NetRigidbody : MonoBehaviour
{
    private Vector3 velocity;

    public void Simulate( float dt )
    {
        // s = ut + .5at^2
        Vector3 movementDelta = ( this.velocity * dt ) + 0.5f * ( Physics.gravity * dt * dt );
        this.transform.position = this.transform.position + movementDelta;
        this.velocity += Physics.gravity * dt;
    }

    public void FixedUpdate()
    {
        this.Simulate( Time.fixedDeltaTime );
    }
}

Big problem here though - collisions! Currently a GameObject with one of these attached would just clip through everything, it doesn't process any collisions, and wont receive OnCollisionEnter() messages without a standard Rigidbody attached. One way we could approach this, is make gravity zero and attach a standard Rigidbody to process collisions for us. However, this wouldn't work for rewind and replay as we still have no ability to tell Unity when to simulate the physics.

The Physics class in Unity provides some useful static functions which would allow us to process collisions ourselves, for example RayCast, SphereCast, and CapsuleCast. I'd rather not do this myself, but there's a nice shortcut in the form of CharacterController. This class moves a GameObject by calling the Move( Vector3 motion ) method, and automatically does some collisions processing (i.e. stops the CharacterController's capsule from intersecting with other colliders). It also directly calls OnControllerColliderHit() when a collision occurs - that is to say, it calls OnControllerColliderHit() before Move() exits. So the updated NetRigidbody implementation is:

public class NetRigidbody : MonoBehaviour
{
    private CharacterController _characterController;
    public CharacterController characterController
    {
        get
        {
            if( this._characterController == null )
            {
                this._characterController = this.GetComponent<CharacterController>();
            }

            return this._characterController;
        }
    }

    public Vector3 velocity;

    public void Simulate( float dt )
    {
        if( this.characterController == null )
        {
            return;
        }

        Vector3 movementDelta = ( this.velocity * dt ) + 0.5f * ( Physics.gravity * dt * dt );
        this.characterController.Move( movementDelta );

        this.velocity += Physics.gravity * dt;
    }

    public void OnControllerColliderHit( ControllerColliderHit hit )
    {
        // Negate some of the velocity based on the normal of the collision by projecting velocity onto normal
        this.velocity += Vector3.Dot( this.velocity, hit.normal ) * hit.normal;
    }

    public virtual void FixedUpdate()
    {
        this.Simulate( Time.fixedDeltaTime );
    }
}

With this we can now effectively achieve rewind and replay in Unity, full client-side prediction! Huzzah! So what are the downsides? For one, using a CharacterController like this means that a GameObject which needs to rewind and replay (i.e. player controlled entities) will only use a single CapsuleCollider for movement collisions. This is fine for something like counter strike, but what if you want to put vehicles in like Battlefield? I fear that this approach is like building a structure on jelly, it might stay up, but it might all come crashing down.

What alternatives do we have?
1) Instead of using CharacterController, use Physics static functions to properly process collisions for all colliders attached to the GameObject and its children.

2) Just don't rewind and replay. When the server disagrees with the client to a significant degree, stop sending input to the server, and stop predicting movement. Wait for the server to completely catch up and process all of the input we've sent, and once that's happened we resume control of the player. This will result in very noticeable snaps. This should be fine for a game that will only be played on LANs.

3) Take a popular open source physics library such as Bullet, and either port it to .NET (or use an existing port), or Unity Pro users could compile it down to a native .DLL. You'd still need to build a bridge between Unity and that physics library. Keep in mind that Bullet (like many physics libraries) does not simulate physics objects individually, the entire system is stepped forward as a whole. So if you want to rewind and replay, you need to either create a temporary physics world containing just the things you're rewinding and replaying, or you need to rewind and replay absolutely everything.

What did I choose to do? Well, I realised that I just wanted to implement client-side prediction like this because I found it an interesting programming excercise after reading Glenn Fiedlers excellent series of articles. I'd advise others making multiplayer games in Unity to consider semi-authoritative network models. In the case of the game I'm trying to make, it's cooperative, so cheating isn't really a big issue - I could essentially just use the server as a way to connect players, and provide some validation.

But what happens if (like me) you worry that one day you might want to add another game mode where players compete directly with eachother? My answer - I downloaded UDK.