Understanding ReplicateState

Being familiar with what each state means will help you fine-tune your gameplay on spectated objects.

If you need a refresher on what each state means see our API or simply navigate to the ReplicateState enum in your code editor.

Each state is well commented in the source code as well the API, so rather than cover in detail what each state means, examples of how you might apply certain states will be shown.

There are several extensions to check states. For example, state.IsFuture() will return true if the state is ReplayedFuture or CurrentFuture. See ReplicateStateExtensions for all built-in extensions.

Invalid

An invalid ReplicateState should never occur. This would imply internally Fish-Networking failed to properly set the state.

Created

When a state is created it is known to be true. This means a created state has absolutely no chance to contain incorrect information as it's what was received from the client, or run locally for the owner.

Predicted

These states are marked obsolete because they are currently not used, though we may implement them in the future. The documentation elaborates on what these might indicate when they are implemented.

Current

A state leading with 'Current' simply indicates the input is being run outside of a reconcile. For example, calling your replicate from OnTick would hold the state as Current.

Future

Future states are when the client has not received inputs for those ticks from the server yet. Remember that part of client-side prediction is the client always moving in real-time locally, but like any networking library, they still must wait for data to arrive from the server before it can be known(Created). Given the client is moving in real-time, they are always ahead a number of ticks more than the last data they received from the server. Latency will determine how much further the client is ahead, as more latency means the client will receive known states later. This is where you may predict server states locally; our Predicting States covers this more thoroughly.

Replayed

States leading with the 'Replayed' prefix indicate that the data is being performed within a reconcile. The server never replays states as it has no need to reconcile, but for clients states will be replayed on owned and spectated objects.

A replayed states does not mean that data is known(Created), it simply indicates the states are run during a reconcile.

Replayed states will always be ReplayedCreated on objects the client owns. This is because the owning client has real-time information on the states, since they are the ones creating them. On non-owned objects clients will see a number of states as ReplayedFuture; this was elaborated on as why just above.

An common problem developers run into is where data in their replicate methods flip when the state is Created vs Future, and the developers logic does not accommodate the differences. Here is example of a spectated object might have 'Sprint' held in their ReplayedCreated state, but show as not held in a ReplayedFuture state. Since future states indicate data not yet known, said data will be default. When the client runs logic on default data unexpected behavior may happen.

Below is an example of code that might cause a problem.

[Replicate]
private void MovePlayer(ReplicateData data, ReplicateState state = ReplicateState.Invalid, Channel channel = Channel.Unreliable)
{
    //Move twice as fast if sprinting.
    float moveMultiplier = (data.Sprint) ? 2f : 1f;
    //This value is irrelevant other than showing we are moving in a direction.
    Vector3 moveDirection = Vector3.right;

    transform.position += moveDirection * moveMultiplier;
}

We know that a future state will have default data, in result Sprint will be false for future states. If Sprint were true previously in a Created state then the player would move faster during the created state, then slower during the future state, even though the player could very well could still be sprinting. At the very least this would cause a positional de-synchronization, which might be covered up by the smoothing. But what if the code was slightly more complex like this ...

[Replicate]
private void MovePlayer(ReplicateData data, ReplicateState state = ReplicateState.Invalid, Channel channel = Channel.Unreliable)
{
    //Move twice as fast if sprinting.
    float moveMultiplier = (data.Sprint) ? 2f : 1f;
    //This value is irrelevant other than showing we are moving in a direction.
    Vector3 moveDirection = Vector3.right;

    transform.position += moveDirection * moveMultiplier;
    ShowVFX(data.Sprint);
}

Now not only the position is being updated with false for Sprint, but the VFX are. Our built-in graphical smoothing only handles interpolating the graphical object, so your visual effects would regularly flicker between sprinting and not.

The resolution for such a case is straight forward enough. You can either predict the state, a reminder that this is discussed in the next guide, or simply do nothing if the state is future. Which you choose is entirely relevant to your needs.

Let's see what it might look like if you do not want to predict states.

[Replicate]
private void MovePlayer(ReplicateData data, ReplicateState state = ReplicateState.Invalid, Channel channel = Channel.Unreliable)
{
    if (state.IsFuture())
        return;
    //Move twice as fast if sprinting.
    float moveMultiplier = (data.Sprint) ? 2f : 1f;
    //This value is irrelevant other than showing we are moving in a direction.
    Vector3 moveDirection = Vector3.right;

    transform.position += moveDirection * moveMultiplier;
    ShowVFX(data.Sprint);
}

Rather than perform logic at all, we simply exit the method if the state is in the future. This will eliminate the chance of running logic based on potentially incorrect inputs.

Doing something along these lines does have disadvantages. When you are not predicting into the future the object will remain behind based on your clients latency. This would potentially in result cause the client to react differently depending on what they see. To re-iterate, what you may want to do is entirely relevant to your needs.

Below is are examples of how you might want to keep a rigidbody in the past, either by canceling it's forces or not applying additional forces.

'In the past' is a simple way of us saying, not current with the server and/or not predicted.

This example shows simply exiting the method if in the future. While doing so the rigidbody will continue to move using it's current velocities as well be impacted by anything which might affect it. In a way, this is a simple approach to offering some basic future prediction on a rigidbody.

if (state.IsFuture())
    return;

The next example is canceling forces entirely on the rigidbody if the state is in the future. This keeps the rigidbody in the past for the client by not allowing it to move beyond what we know to be true.

if (state.IsFuture())
{
    //This assumes you are using PredictionRigidbody, which you should be.
    _myPredictionRigidbody.Velocity(Vector3.Zero);
    _myPredictionRigidbody.AngularVelocity(Vector3.Zero);
    return;
]

Understandably how to handle future states might be confusing at first. As you develop for your game you'll have a better idea of what feels best for the player, determining how to implement future states.

Last updated