We’re back with the 4th entry in our dev logs on the new GameObject layer for Netcode. You can check what we’ve been up to with our first entry here.
Prediction
GhostBehaviour Prediction
Netcode for Entities (N4E) has had prediction for a while, but you had to write ECS systems in order to benefit from it. With the new GameObject layer, we’re exposing a PredictedUpdate method in GhostBehaviours to allow writing prediction code in an object-oriented way.
public partial class MyCharacter : GhostBehaviour
{
GhostField<int> m_Health;
GhostComponentRef<MyInput> m_Input; // Design TBD on inputs, we have this working for now, but can do better.
// This gets called once server side at a fixed tick rate and multiple times per frame on a client, from the latest received
// snapshot packet's tick to the tip of your predicted tick.
public override void PredictedUpdate()
{
this.m_Health.Value = 123; // This is rolled back automatically
Move(this.m_Input.Value.MoveDirection); // Inputs are set automatically in the background to the appropriate tick
// transforms are automatically rolled back
Instantiate(MyMissilePrefab); // Predicted spawn works the same as instantiating a new GhostObject from the prediction loop. We have checks in place to make sure you don't instantiate from a client outside of prediction (since N4E is server authoritative).
}
}
Tick Accuracy
Any GhostField you access from this method should be safe to use within the prediction context. It’ll be rolled back and progress to the appropriate tick. This is where you would consume your inputs too (which are also tick accurate). The same applies to transforms. As we’re adding compatibility, more and more unity objects will become tick accurate.
Prediction will behave similarly to the regular Update() and FixedUpdate(), but updating at a different rate (from 0 to n times per frame). For example, if your GhostBehaviour is disabled, PredictedUpdate won’t run. If you specify script execution order, PredictedUpdate will respect it.
Note that this applies to all ghosts, not just player ghosts. Netcode does full world prediction, making interactions between your predicted players and other predicted environment objects tick accurate.
For background, you can see our doc on prediction here for more details on prediction as a concept and how it behaves with entities.
Predicted Spawn
Calling GameObject.Instantiate inside PredictedUpdate will allow you to predictively spawn objects you own. This will allow for reactive and synchronized gameplay like ball/grenade/missile throwing. If you mispredicted this spawn for any reason (e.g. your player was stunned server side and your client did not know about it yet), Netcode will automatically destroy mispredicted spawns for you.
Owner Prediction and Interpolation
Setting a ghost prefab as OwnerPredicted will dynamically switch instances’ mode to Predicted if on the owner client and Interpolated if on other clients. Interpolated ghosts are server driven and their state will be buffered before being made available to gameplay code. Note that all ghost fields are buffered when in interpolation mode, not just transforms. Which means any logic depending on order of arrival of certain field updates will be tick accurate for a given ghost in both predicted or interpolated mode. In other words, if a ghost is on tick x, all its GhostFields, transforms, rigidbody, etc will be on tick x, whether interpolated or predicted. Any other non-GhostFields will remain untouched.
Manual Prediction Switching
Prediction has a runtime cost. You execute gameplay logic for all predicted ghosts, multiple times per frame. In a world with 10,000 ghosts, this would take too many resources.
Netcode allows each client to dynamically change the ghost’s mode between interpolated (i.e. server driven) and predicted for each ghost. You can have a prediction bubble around your player, for example, where anything the player interacts with is predicted (to stay on the same timeline) and all other ghosts remain interpolated.
On the GameObject side, this looks like this:
// MyGhostBehaviour
this.Ghost.ConvertToPredicted(transitionTime: 0.1f); // converting between timelines means a jump in time. To hide this, transitionTime allows lerping this.
// This means that for 0.1f second, the ghost's position will be smoothed. This is similar to Rigidbody's interpolation. The GameObject's position will be the lerped value outside of PredictedUpdate(), then reset back to the predicted value while your code is in the PredictedUpdate method.
// To get the non-smoothed tick accurate value at all times, you can use
this.Ghost.Position
this.Ghost.Rotation
this.Ghost.GhostTransform
A good rule of thumb is to keep a maximum of a few dozen predicted ghosts and every other ghosts stay interpolated.
See the entities doc for more details.
Next Steps
Predicted everything:
We’re currently working with the physics team on predicted physics primitives. We’re first looking at Rigidbody. CharacterController and Colliders would be next. Just like we have a PredictedUpdate method, we’d also have a FixedPredictedUpdate method for gameplay code that needs to interact with predicted physics. Just like with N4E, the package takes over the ticking of the physics simulation.
I don’t have much more to share here yet and will post more on this later.
NetworkTime:
N4E has the NetworkTime singleton to get tick information. We have created a Netcode.Time API that can be accessed from PredictedUpdate, but we’re ideating on other options. More on this later.
Tooling:
We’re currently working on tracing and analyzer tooling to help detect and debug mispredictions. More on this later.
OnCorrected:
@Yoraiz0r suggested adding some way to detect if a value was corrected in our last dev log.
Niki is working on some changes that would make this kind of detection easier, she’ll post about this later.