Prediction with GameObjects coming to Netcode for Entities!

Hey folks, we’ll be posting regular tech dev log entries while we’re working on landing a few big Netcode features. We’re hoping every ~4 weeks. We’ve been working on these for a while in a separate branch and it’s time to get your feedback and bring them to our main branch one by one.

So yes! Unity is bringing client prediction (previously available only in Netcode for Entities) to GameObject-based projects!

Note: The GameObject layer discussed in this post is still in development and specifics such as names and API implementations may change before the feature is delivered.

Some context

You might have heard about this in our last Unite 2025 presentation. We’re pretty excited to finally be able to tell you what that means and talk about what we’ve been working on.

A bit of context: currently, if you want client prediction in your Unity game, you need to use DOTS and ECS with Netcode for Entities. If you’re used to working with GameObjects, then this is a non-trivial shift in how you create games. Netcode for Entities has a spread of cool features like prediction, relevancy, importance scaling, delta compression, quantization, server rewind (lag compensation), etc. that require using ECS and have been unavailable for use with GameObjects.

A new GameObject layer

To bring those cool Netcode for Entities features to GameObject users, we are creating a new GameObject layer on top of Netcode for Entities, using entities as the foundation with the GameObject layer as a higher-level abstraction. This is a design existing Netcode for Entities users might find familiar, and we’ve had many users successfully doing this on their own already. For example, Survival Kids (our latest internal Unity production) did this: they wrote their own GameObject layer, abstracting Netcode for Entities features, and had their gameplay programmers write GameObject code.

We chose to extend Netcode for Entities in this way so that we could work from a proven, stable foundation. Netcode for Entities now has a few years under its belt of real world productions using it.

This is also placing us in line with “ECS for all” that was talked about at Unite.

The end goal of this new flow is for you to benefit from all the Netcode for Entities features we already have without having to write ECS code.

For those used to Netcode for GameObjects, you will be able to interact with this new layer through similar (but not the same) MonoBehaviours and have similar primitives like NetworkVariables, NetworkBehaviours, and NetworkObjects. But those new primitives will be backed by entities internally, with Netcode for Entities’ features like prediction also available for use with GameObject-based flows.

Implementation details

For those interested in the implementation detail: you would have a client GameObject, mapped to a client Netcode for Entities ghost entity that will sync with a server entity, which is in turn mapped to a server GameObject.

Client GameObject ←→ Client entity ←network→ Server entity ←→ Server GameObject

The GameObject side APIs are basically passthroughs to ECS components.
When using a GameObject-based flow, the underlying entities won’t be visible to you and you won’t need to configure them, similar to how GameObject Physics (backed by PhysX) has its own PhysX world. The way you interact with those is through the Rigidbody instance attached to your GameObject. As a user, you don’t have to know PhysX’s implementation details, you only need to know about Unity’s Rigidbody type. Netcode will have a similar “GhostObject” class, abstracting an entity attached to your GameObject. That entity would live in its own “Netcode World”, with its own object-oriented abstractions.

Preview

Here’s a preview of what the end experience would look like.

Please keep in mind those are just previews, the names will most likely change, but expect something in the general area of the following:

// User gameplay logic is done in GhostBehaviours, similar to NGO's NetworkBehaviour
public partial class MyCharacter : GhostBehaviour
{
    public GhostField<int> MyHealth; // Writes in the background to an ECS component. ECS components are invisible.

    public void Awake()
    {
        if (IsServer)
            Netcode.Relevancy.SetRelevantFor(this.Ghost, Netcode.Connections[0]); // static APIs are all accessible from the same Netcode static API for discoverability.

        MyHealthUpdatedRPC(); // RPC works similarly to NGO
    }

    public override void PredictedUpdate(float networkDeltaTime)
    {
        if (input.Value.Jump.IsSet)
            m_GravityVelocity.Value = JumpVelocityConfig;
        m_GravityVelocity.Value -= 9.8f * networkDeltaTime;
        transform.position += (Vector3.up * m_GravityVelocity.Value + horizontalMovement) * networkDeltaTime; // transforms are automatically rolledback and replicated

        MyHealth.Value = 100; // value rolled back automatically for prediction

        if (input.Value.Shoot.IsSet && Netcode.NetworkTime.IsFirstTimePredicting)
            GameObject.Instantiate(someRocketPrefab); // Predicted spawn.
        // GameObject.Instantiate() "just works" for a server side spawn as well
    }

    [Rpc]
    public void MyHealthUpdatedRPC()
    {
        Debug.Log("health!!");
    }
}

We’ve left out a few things on purpose, it’d add too much noise to an already big post, but don’t hesitate to ask about those! Things like input gathering, accessing component values directly, server rewind, prediction switching, and a whole bunch of other implementations we have in store will have their dedicated blog post later.

What’s next

These Netcode features will be delivered in upcoming Netcode for Entities releases over the upcoming quarters. Keep your eyes on our changelog and on upcoming Discussions posts.

Next post, we’ll talk about the first feature that landed already in the Netcode for Entities package (version 1.9.0) to support this: single world host. With this, you’re able to create a World that functions as both client and server.

In another post, we’ll talk about the GameObject to Entity mapping we’ve landed hidden behind a define (version 1.11.0).

We have GhostFields up for review and prediction and input gathering are next.

We want your thoughts! One of the main reasons we’re doing this series of posts is to chat about all of this with you guys. We’re pretty excited about this on the netcode team, this has been brewing for a long time and so if you have any questions, comments or suggestions, please let us know!

Next Entries

17 Likes

Can I create IComponentData struct itself instead of one float field and sync those?

Yes! It’s funny you’re asking this, we had a spicy conversation just last week about exactly that.
Current direction is to offer a GhostComponentRef<MyComponent> in addition to GhostField and see in the next few months if the entities team offers something nicer.
So you’d have

public struct MyShieldComp : IComponentData
{
     [GhostField(Quantization = 10)] public int Shield;
}
// ...
GhostField<int> health;
GhostComponentRef<MyShieldComp> shield;

And then a system could do

public void OnUpdate(ref SystemState state)
{
    foreach (var myShieldComp in SystemAPI.Query<MyShieldComp>())
    {
        //...
    }
}

For those reading this not familiar with ECS, this would be optional, just to clarify. GhostField on its own would be enough. GhostComponentRef would be for hybrid ECS + GameObject flows where some of your game logic lives in GameObject code and other in ECS systems.

3 Likes

I don’t have much to add, but I’m happy to finally see this GameObject layer come to fruition. I look forward to your future netcode dev log entries.

Staff mentioned a deterministic library too which is separate from this one. I guess the future posts won’t be about that?

Also, I found it strange that this official post didn’t appear in my notifications. Did you add the official tag after making the post?

1 Like

The invention of the bicycle begins… Abstraction over abstraction, which is abstraction over abstraction. How will it all work? And how are things going with productivity? How does ECS behave as a proxy between GameObjects???

Staff mentioned a deterministic library too which is separate from this one. I guess the future posts won’t be about that?

Indeed. This devlog will focus on current Netcode solutions.

Also, I found it strange that this official post didn’t appear in my notifications. Did you add the official tag after making the post?

Mmm good question, I used an internal “staging” area before posting this post, maybe that messed with things. Will ask around, thanks for raising this.

2 Likes

Hey @ep1s0de Could you please be more specific with your questions or feedback? Are there any particular points in the article that are not clear?

I am a bit confused why netcode for entities is getting a Game object wrapper.
Does this mean this system is going to replace netcode for game objects?
And if not, why isn’t this just a new netcode for game objects feature?

If the entities side works with game objects and has more features than the game objects version, that sounds like there is no reason to use the game objects version

I think it is because after EscForAll in unity 6.6 GameObjects themselves will be based on Entities instead of low level C++ things

this means that NetCode for Entities will work with GameObjects because every game object will be entity but not in full power because they dont allow to write part of NetCode on GameObject side

With this change they Do :slight_smile:

2 Likes

Yep. Netcode side, we still have to offer ways to interact with some of our Netcode specific features.

GameObjects and entities don’t know what a prediction update is for example.
We wanted to offer something close to the existing monobehaviour updates. You have Update, FixedUpdate. You’d now also have PredictedUpdate.

Same if you want to interact with our relevancy singleton. Instead of writing

cachedQuery = EntityManager.CreateEntityQuery(typeof(GhostRelevancy))
cachedQuery.GetSingleton<GhostRelevancy>().GhostRelevancySet.Add(...);

You’d be able to write

Netcode.Relevancy.Add(...)

For sure in the future, if/when Entities provide more APIs to access singletons we’ll adapt to those.

From a package implementation perspective, it’s a very thin layer on top of Netcode for Entities. Most of the complex internal logic remains in systems and components.

Right now the plan is to have both Netcode for GameObject and Netcode for Entities exist at the same time. Since we’re going with an iterative release approach, where we add GameObject wrappers one at a time, the first few iterations will have holes in its API surface. Those are all behind an experimental define. If you feel adventurous and want to try it, you could still use the usual ECS APIs for the features with holes, but if you want to use it completely with GameObject flows I’d wait a bit.

I have a TODO to add a table with the status of the new GameObject layer, the list of things we need to wrap vs what’s currently released. I’ll post here when I have it.

In 1.11.0 the only thing you’d be able to see if you enable that define is GameObject transform syncing. It’s pretty barebone. Stay tuned for upcoming posts and releases.

3 Likes

The evolution of netcode entities is exciting.
Recently, I saw a roadmap for deterministic burst compilation, burst and job system operations in web browsers, and started working on netcode entities, something I’ve been curious about for a while.

May I ask if there’s a future where netcode entities don’t send snapshots every time, or send very few, thanks to deterministic burst compilation?

I’m very pleased with the fact that dot’s overwhelming performance makes prediction and rollback much less of a burden than other systems like fishnet, which are based on monobehaviors.

If this is combined with deterministic burst compilation, I anticipate an amazing future.

1 Like

Hey @Array_ARR glad to hear it, we’re excited as well :smiley:

May I ask if there’s a future where netcode entities don’t send snapshots every time, or send very few, thanks to deterministic burst compilation?

It’s stuff we have on our mind internally for sure, but unfortunately, nothing to announce publicly from our side.

4 Likes

This is great news, will this slowly make netcode for gameobjects deprecated since, when this is ever completed, it’ll be better than ngo?

Additionally, am I correct to assume that using this will not require any sort of Unity’s backend services e.g Multiplayer services? (I’m currently on NGO and have not touched Netcode for Entities yet so not sure about this) I’m using NGO with a 3rd party non-Unity backend service.

Will this also support prediction of server objects on every client, similar to how games like Rocket League have each clients predict the server objects such the ball’s movement?

When can we expect this feature to be production ready or will a more concrete ETA be available in the months ahead?, since I’m assuming it’s in the starting phase currently.

Netcode for GameObjects plans is above my pay grade, I’m really focusing on the new GameObject layer, so can’t add much more than I said above unfortunately.
Our goal is to make this new layer as useful as possible of course.

Additionally, am I correct to assume that using this will not require any sort of Unity’s backend services e.g Multiplayer services? (I’m currently on NGO and have not touched Netcode for Entities yet so not sure about this) I’m using NGO with a 3rd party non-Unity backend service.

That’s correct. Netcode for Entities is already service agnostic and I’m not aware of any plans changing that.

Will this also support prediction of server objects on every client, similar to how games like Rocket League have each clients predict the server objects such the ball’s movement?

Yes! Netcode for Entities does full world prediction. Any “Ghost” (NetworkObjects in Netcode for GameObject land) that are marked as Predicted are then rolled back and part of the prediction loop replay. You can do “Owner Predicted” (where the owner player predicts and the other clients interpolates it) or “Predicted” (where everyone predicts). It’s kind of the fun thing with ECS, that kind of control over your update loop and data is quite nice to rollback and replay everything for prediction.

When can we expect this feature to be production ready or will a more concrete ETA be available in the months ahead?, since I’m assuming it’s in the starting phase currently.

You should see changes landing relatively fast, but I don’t want to make any promises. Those have already been worked on on a separate branch, we’re mostly backporting and adapting at this point. There’s a few places that need some additional designs, but we’ve done a lot of the leg work already. You’ll see more news as we post more about this.

I’m noticing a lot of stuff here that are quite entirely abstracted from appearing to look like any standard netcode, and while it looks exciting on the surface, I’m somewhat concerned…

I’m seeing “// transforms are automatically rolledback and replicated”… how are we able to handle to see / handle those adjustments?
Is there something watching our transforms for us?
How do we tell it a teleportation happens vs a regular movement? etc.

Similarly, “GameObject.Instantiate(someRocketPrefab); // Predicted spawn”…
What’s going on here for the predicted spawn?
How does it know to report who its owner is etc?
How would we be able to control pooling / prevent effects from doubling up?

And, absent from this - I assume it means ECS Unity Physics are at play when this is in use? What are the limitations with that vs the standard physics queries?

And, not quite directly related, but still relevant: How is your testing workflow for this looking currently? do you use two instances of Unity? Do you use Multiplayer Play Mode? Does it work with Multiplayer Play Mode? When I tested the Competitive sample a while back, there were problems.

An abstraction that looks nice on the surface is never something to complain about - but I’d like to request that proper debugging improvement efforts are submitted alongside it!

1 Like

Yeah didn’t want to bloat the original post with a whole API specification :smiley: . Happy to clarify here.

I’m seeing “// transforms are automatically rolledback and replicated”… how are we able to handle to see / handle those adjustments?
Is there something watching our transforms for us?

mmm not sure I understand what you mean, do you mean some OnValueChanged callback? Or do you mean how are transforms tracked by Netcode?

If the former, the current plan (we’ll need to iterate on that one) is to offer a GhostBehaviour wide OnValueChanged and let you deterministically write your logic in the order you expect.

If the later, Entities has transform components that Netcode is serializing/deserializing. Right now we’re doing a simple copy back/forth between the GameObject transform and the entity transform, but that’ll most likely change in a few months with some upcoming engine changes. Don’t want to announce anything for other teams, so will stop there.

How do we tell it a teleportation happens vs a regular movement? etc.

Netcode for Entities has editor/compile time settings allowing to specify teleport distance vs interpolation. We’d start with this. But personally I’d prefer having ways to manually specify “this teleports” at runtime. I had issues in the past where I had an animation clip time looping and Netcode was trying to smooth the loop back. So value teleportation at runtime will need some nicer flows both entities and GO side.

Similarly, “GameObject.Instantiate(someRocketPrefab); // Predicted spawn”…
What’s going on here for the predicted spawn?
How does it know to report who its owner is etc?

Yes! The code for this looks like this

GhostObject rocket = GameObject.Instantiate(someRocketPrefab);
rocket.OwnerId = this.Ghost.OwnerId;

Since this executes both client and server side, the owner Id is set authoritatively server side and predicted client side. If there’s a misprediction and the rocket wasn’t supposed to spawn client side, netcode automatically despawns it (with a few ticks of slack before and after to forgive slightly wrong spawns)

How would we be able to control pooling

That one would be very similar to Netcode for GameObject’s pooling. You would have a second way to instantiate if you know your ghost is pooled.

void SomeInitializationLogic()
{
  Netcode.SetSpawnHandler(myPrefab, new MySpawnHandler()); // MySpawnHandler implements an interface similar to Netcode for GameObject's INetworkPrefabInstanceHandler
}

void PredictedUpdate()
{
  Netcode.CustomSpawn(myPrefab); // checks if you have registered a spawn handler
}

This way, both your own server/predicted spawns and the ones coming from the network (on other clients) would know how to deal with your case. On a client receiving a spawn from the server, Netcode would see “this prefab has custom logic, I’ll just let it give me the GameObject and plug the right ghost info”.

prevent effects from doubling up?

You would use Netcode.NetworkTime.IsFirstTimeFullyPredictingTick. (In the background this wraps Netcode for Entities’ NetworkTime singleton

I assume it means ECS Unity Physics are at play when this is in use? What are the limitations with that vs the standard physics queries?

We’re actually looking at both options. We already have work done with GameObject (PhysX) with a bit more to come. ECS Physics side, we’re looking at what we can do. We’ll post more on this when we have wrapped up some final details.

And, not quite directly related, but still relevant: How is your testing workflow for this looking currently?

So we have built a nifty test tool Netcode for Entities side that we’re reusing for GameObject flows as well. It allows us full control over worlds and ticking. Before I continue though, just to manage expectations, it’s all internal and we don’t have plans right now to make it public.

It’s called NetcodeTestWorld I can show you one of our simple spawn tests

[Test(Description = "Validates assumptions regarding spawn/despawn GameObject timings")]
public async Task TestSimpleSpawnTimings([Values] bool isPredicted)
{
    await using var testWorld = new NetCodeTestWorld();
    await testWorld.SetupGameObjectTest();
    await testWorld.ConnectAsync(enableGhostReplication: true); // this does a lot of the boilerplate of connecting, ticking, enabling replication
    var prefab = GhostAdapterUtils.CreatePredictionCallbackHelperPrefab("BasicData", autoRegister: false);
    prefab.Ghost.DefaultGhostMode = isPredicted ? GhostMode.Predicted : GhostMode.Interpolated;
    Netcode.RegisterPrefab(prefab.gameObject); // this is required because of the above runtime GhostMode setting
    await testWorld.TickMultipleAsync(1); // spawning doesn't happen until prefab has been acked, so need to wait a tick

    int ticksBeforeSpawn = isPredicted ? 3 : 6;
    int ticksBeforeDespawn = isPredicted ? 4 : 6;

    var serverObj = GameObject.Instantiate(prefab);
    await testWorld.TickMultipleAsync(ticksBeforeSpawn); // expect 4 ticks to spawn a predicted ghost client side

    Assert.AreEqual(1, PredictionCallbackHelper.ClientInstances.Count);
    Assert.AreEqual(1, PredictionCallbackHelper.ServerInstances.Count);

    GameObject.Destroy(serverObj.gameObject);
    await testWorld.TickMultipleAsync(ticksBeforeDespawn); // same for despawn, we expect the GO to be gone after 4 ticks
    Assert.AreEqual(0, PredictionCallbackHelper.ClientInstances.Count);
    Assert.AreEqual(0, PredictionCallbackHelper.ServerInstances.Count);
}

We have some more complex ones also available, but didn’t want this message to be too big :slight_smile:

do you use two instances of Unity? Do you use Multiplayer Play Mode? Does it work with Multiplayer Play Mode?

They have a quite nice test framework we’re currently investigating, see if we can reuse it. Thing is, multiprocess tests like this are quite slow though and can be a bit flaky if you’re not careful regarding timing. You can’t just “wait for the clone to be in playmode” without doing some timeout checks. But if your timeout are too short, you end up failing if your test instance is slower than usual on that day.

The nice thing with the above is you control everything, so you can be super precise. So we started with those kinds of automated tests. But having both multiprocess and in-process tests will be necessary. I might have more to talk about this in the next dev log :slight_smile:

And yes! We sort of have to test with Multiplayer PlayMode, because of single world host. You don’t have a binary server+client testing client flows anymore, you need a separate client and so we need editor clones for this.

I hope this answers all your questions, let me know if I missed anything.

3 Likes

Host migration next please? :face_with_hand_over_mouth:

1 Like

The entities side has host migration already. We’ll need to see what APIs we can do to wrap the few components required to configure it. But that’s not currently a priority, at least for the next few months. You would still be able to configure this through ECS APIs in your GameObject project though in the meantime, you wouldn’t be blocked.

I meant whether the transform state change detection is done by an entirely automated system, or whether it is done by having to flag the gameobject a certain way first (e.g. announcing what transform state changes are expected / need tracking for this one, as entities does at the moment). The question came from the idea that if I have a level with many non-moving networked objects, would I be able to tell it ‘hey uhh, dont bother tracking changes for these’.

Same. Heavily. I assume right now at minimum there is some level of overriding we can do with sending an RPC transform change, but I beg you to at least have a way to say “do not smooth this one”. (especially for a case of a nearby teleportation through a wall occurs, for example, this kind of stuff is desirable)

So thinking out loud, let’s say I spawn a missile and I want the muzzle firing effect to only play once, the intended flow would be

  1. spawn the object with Instantiate (and assume it has a GhostObject component)
  2. spawn effect in the script’s Start / equivalent method and check IsFirstTimeFullyPredictingTick?
  3. I assume that if I keep a reference for the effect, I can adjust its position regardless of if its a first time fully predicting tick, to correct what would happen?

As is tradition :sweat_smile: It’s good to see there are automated tests, though!
Thank you for sharing your current process! This is interesting to see specifically for questioning if the practice should be matched when creating integration tests for features in the game. Though single-world hosts are not necessary in my case and I prefer the dedicated server functionality!

Good! and hopefully it’s making strides! When I last tried MPPM + NFE, the biggest issue was the amount of backed frames whenever the second player hitched would cause a disconnection, which was awful to work with due to any material building on the second player. I hope that this hitch has since been improved against.

You’re awesome :grin: thank you for the thorough responses!

1 Like