Predicted Identities
Predicted identities are regular Unity components that PurrDiction simulates deterministically. The system snapshots and reconciles their STATE against server authority, while your code focuses on clean, singleโplayerโstyle logic.
Variants
PredictedIdentity<STATE>- For stateful systems without direct player input (AI, timers, pooled objects).
- Implement:
GetInitialState,GetUnityState,SetUnityState,Simulate,LateSimulate(optional),UpdateView(optional).
PredictedIdentity<INPUT, STATE>- Adds an
INPUTpipe for local/remote control. Implement:GetFinalInput,UpdateInput(optional),SanitizeInput,Simulate(INPUT, ref STATE, float),ModifyExtrapolatedInput(optional).
- Adds an
StatelessPredictedIdentity- For pure event/logic systems that donโt carry custom state. Override
Simulate(float delta).
- For pure event/logic systems that donโt carry custom state. Override
Both INPUT and STATE must be structs that implement the appropriate prediction interfaces (IPredictedData, IPredictedData<T>).
Lifecycle Hooks
LateAwake()โ Called once after fresh spawn initialization. Viewโonly setup is appropriate here.SimulationStart()โ First tick only, before simulation begins. Good for caching or oneโtime transitions.Simulate(...)โ Deterministic simulation each tick. Use onlySTATE/INPUTand deterministic data.LateSimulate(...)โ Optional late pass afterSimulateeach tick (e.g., composing derived values).Destroyed()โ Called when despawning/cleanup occurs (pool or destroy).ResetState()โ Clears ownership/IDs and resets interpolation and internal caches for pooling.
Unity bridging:
GetUnityState(ref STATE state)โ Read Unity components intoSTATE.SetUnityState(STATE state)โ ApplySTATEback to Unity components after rollback.
View & interpolation:
UpdateView(STATE viewState, STATE? verified)โ Render using the current interpolatedviewState.verifiedholds the last server snapshot, when present.ResetInterpolation()โ Clear internal smoothing/error.
Ownership and Control
ownerโ OptionalPlayerIDwho owns this identity.isOwnerโ True ifowner == PredictionManager.localPlayeron this client.isControllerโ True for the owner on clients; on server, also true for bots/AI cases.OnViewOwnerChanged(oldOwner, newOwner)โ Viewโonly callback when ownership changes; do not mutate simulation here.
Use these flags for visuals (camera, highlights, UI). Keep simulation deterministic and independent of local presentation.
Example Skeleton (Stateful with Input)
public struct MyInput : IPredictedData {
public Vector2? input; public void Dispose() {}
}
public struct MyState : IPredictedData<MyState> {
public Vector3 pos; public Quaternion rot; public void Dispose() {}
}
public class MyPredicted : PredictedIdentity<MyInput, MyState>
{
protected override MyState GetInitialState() => new MyState {
pos = transform.position, rot = transform.rotation
};
protected override void GetUnityState(ref MyState s)
{ s.pos = transform.position; s.rot = transform.rotation; }
protected override void SetUnityState(MyState s)
{ transform.SetPositionAndRotation(s.pos, s.rot); }
protected override void GetFinalInput(ref MyInput i)
{ i.input = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")); }
protected override void SanitizeInput(ref MyInput i)
{
if (!i.input.HasValue) return;
var v = Vector2.ClampMagnitude(new Vector2(i.input.Value.x, i.input.Value.y), 1f);
i.input = v;
}
protected override void Simulate(MyInput i, ref MyState s, float dt)
{
if (!i.input.HasValue) return;
transform.position += new Vector3(i.input.Value.x, 0, i.input.Value.y) * dt * 5f;
}
protected override void UpdateView(MyState view, MyState? verified)
{ transform.SetPositionAndRotation(view.pos, view.rot); }
}