Best Practices
Use Structs for INPUT and STATE
Define
INPUTandSTATEas structs in C#, not classes.Structs are value types, which ensures predictable behavior during prediction and reconciliation.
Example:
public struct MyInput : IPredictedData { public float horizontal; public float vertical; public bool jump; } public struct MyState : IPredictedData<MyState> { public Vector3 position; public Quaternion rotation; public bool isJumping; }
Why?
- Structs are copied by value, making them ideal for storing snapshot data that needs to be reconciled.
- Avoids unintended side effects that can occur with reference types (classes).
2. Initialize State with GetInitialState
- Use the
protected override STATE GetInitialState()method to define the default values for yourSTATEstruct. - This method is called when the entity is first created, ensuring that it starts with a valid initial state.
Example:
protected override MyState GetInitialState()
{
return new MyState
{
position = Vector3.zero,
rotation = Quaternion.identity,
isJumping = false
};
}
Why?
- Ensures that your entity starts with a consistent and predictable state.
- Avoids undefined behavior caused by uninitialized state variables.
3. Treat STATE as the Source of Truth
- Any data that affects the simulation should be part of the
STATEstruct. - Use
STATEto store:- Entity position, rotation, and velocity.
- Flags or variables that control behavior (e.g.,
isJumping,isShooting).
- Avoid modifying Unity components directly (e.g.,
Transform.position) without synchronizing them with theSTATE.
Why?
- The
STATEstruct is reconciled by the CSP system, ensuring consistency between the client and server. - Directly modifying Unity components can lead to desynchronization and unpredictable behavior.
4. Use GetUnityState and SetUnityState for External Components
- If your
STATEaffects Unity components (e.g.,Transform,Rigidbody), use these overrides to synchronize them:protected override void GetUnityState(ref STATE state):- Updates the
STATEstruct with data from Unity components (e.g., reading theTransform.position).
- Updates the
protected override void SetUnityState(STATE state):- Applies the
STATEto Unity components (e.g., setting theTransform.position).
- Applies the
Example:
protected override void GetUnityState(ref MyState state)
{
state.position = transform.position;
state.rotation = transform.rotation;
}
protected override void SetUnityState(MyState state)
{
transform.position = state.position;
transform.rotation = state.rotation;
}
Why?
- These methods ensure that Unity components are properly synchronized with the
STATE, maintaining consistency during prediction and reconciliation.
5. Make SerializeField Constants Only
- Use
SerializeFieldonly for constant values that do not change during simulation (e.g., speed, prefab references). - Avoid using
SerializeFieldfor variables that are part of the simulation logic (e.g., position, velocity).
Why?
SerializeFieldvariables are not reconciled by the CSP system, so changing them during simulation can lead to desynchronization.
6. Keep Simulation Logic Deterministic
- Ensure that all simulation logic (e.g., movement, physics) is deterministic and based on the
STATEorINPUT.
Why?
- Deterministic logic ensures that the client and server produce the same results, even when running at different times or frame rates.
7. Implement IDuplicate<T> on Complex Structs
- PurrDiction copies states frequently for history and reconciliation.
- If your
STATEcontains nested complex structs, implementIDuplicate<T>on those types so the packer can clone them without serialize/deserialize overhead. - Also implement
IEquatable<T>to speed up delta/equality checks.
8. Prefer Inspector Labels in Docs
- Use the Inspectorโs friendly labels in guidance (e.g., Interpolation Settings, Float Accuracy).
- Code still references exact identifiers; search by label or identifier as needed.
Summary of Best Practices
| Practice | Why It Matters |
|---|---|
Use structs for INPUT and STATE |
Ensures predictable behavior and avoids side effects of reference types. |
Use GetInitialState for defaults |
Provides a consistent starting state for entities. |
Treat STATE as the source of truth |
Prevents desynchronization and maintains consistency between client and server. |
Use GetUnityState and SetUnityState |
Properly synchronizes Unity components with the STATE. |
Make SerializeField constants only |
Avoids desynchronization caused by unreconciled changes. |