It’s the new year! We’re back from the holidays and following from our previous entry, we’ll talk this time about accessing ECS singletons from GameObject logic, worlds and GameObject mapping. These are a few of the core features we have to land first to unlock the rest of the porting we’re doing.
Note: The features discussed below are experimental and currently hidden. This dev log is to keep you up to date on our development.
A Single Point of Entry for Singletons
Netcode for Entities users might be familiar with the various singleton components Netcode uses to expose various features like relevancy, server rewind, metrics, prefab management, etc. These are all a bit spread out and are hard to remember. You have to remember NetworkStreamDriver, PhysicsWorldHistorySingleton, GhostMetricsMonitor, GhostRelevancy, NetworkStreamConnection, etc.
For those looking at our implementations, you might notice a new Netcode static internal API added to Netcode for Entities’ most recent version. As we’re porting changes to it, you’ll see it grow and include more and more utilities that’ll become available when we remove our experimental scripting symbol.
This new Netcode api will be the main class you should remember when dealing with Netcode.
Without this, in order to connect from outside a system (from GameObject scripts for example), you’d need to call the following
ClientServerBootstrap.ClientWorld.EntityManager.CreateEntityQuery(typeof(NetworkStreamDriver)).GetSingleton<NetworkStreamDriver>().Connect(EntityManager, endpoint)
This will now get simplified to
Netcode.Connect(endpoint)
Some other examples
// Registering prefabs at runtime
Netcode.RegisterPrefab(myPrefab.gameObject);
// Relevancy
Netcode.Relevancy.Set(ghost, connection);
The idea is to have an API similar to what Physics provides, with its Physics.Raycast(...) and to have this callable from both GameObject and ECS flows. An ECS system should be able to connect using Netcode.Connect as well for example.
Single-World Host
This leads to the questions “how would this work with multiple worlds?”, “If I call Netcode.Connect, which world will it use?”.
Netcode for Entities users are already familiar with the binary client and server world setup required to have client hosted servers in Netcode. This offers a good separation between client and server logic and makes it easier to test locally, since your local client world runs the same logic as a standalone client world. This also makes it easy to port to dedicated servers, since your server world’s logic is already self contained.
However, it does require running serialization systems unnecessarily and running multiple times the same gameplay logic (once on the server world and 1+ on the client world for prediction), which is not great for resource constrained devices. It also means the GameObject to entity association we talked about in the previous post spawns both client and server GameObjects in the same Unity process. APIs like GameObject.FindObjectsByType and Physics.Raycast are not world aware and would return results from both worlds. This is not great. You don’t want your client GameObjects being able to interact with server GameObjects in the same process and vice-versa.
For these reasons, we’ve introduced a new “single-world host” mode for Netcode worlds. You can think of it as a server world with a subset of client systems running on it. With this, the host player no longer needs to serialize data to itself. Visible ghosts are no longer predicted or interpolated, they are simply the authoritative ghosts being rendered.
This is an additional mode for worlds and doesn’t replace the existing binary world setup.
Netcode for GameObjects users will find this pattern relatively familiar, this is already how “host” NetworkManagers work, where NetworkObjects act as both client and server at the same time.
Behavior Differences and Migration Considerations
For existing Netcode for Entities projects that want to benefit from this new mode, we will provide migration documentation. Here’s a few important highlights:
- When using single-world host mode, all ghosts are authoritative ghosts which means interpolation must be handled differently.
- Instead of changing the authoritative value, it’s recommended to just smooth the visuals when interpolating. For example, use
LocalToWorldfor transforms.
- Instead of changing the authoritative value, it’s recommended to just smooth the visuals when interpolating. For example, use
- You can use a fast path for RPCs in single-world host mode. Custom serialization should take advantage of that fast path with the new
IsPassthroughRPCandGetPassthroughActionData. - Partial Ticks: single-world host doesn’t support partial ticks. You can instead interpolate your ghosts between full ticks on the host.
- Sending snapshots on catchup ticks: in a server-only world, the server can send snapshot packets for each individual catchup tick if they all happen in the same frame. This isn’t the case for single-world host, where the host only sends one snapshot per frame.
- There’s a few important differences regarding input management, local connections and ownership we’ll describe more in our doc once out of experimental.
For Netcode for GameObjects users
Worlds are pretty similar to NetworkManagers in Netcode for GameObjects, but are not tied to a particular scene. They are in charge of managing your current session, connection, prefabs, networked objects lists, etc.
However, you can have multiple worlds in the same process. This is actually how our automated tests for the new GhostObject APIs work, by having one server world and 1+ client world connected to it. Same as our existing ECS side tests work.
Worlds Next Steps
This was one of the first steps required to get GhostObject GameObjects to work with Netcode for Entities. This also should provide nice optimizations for entities projects. Survival Kids had to bypass Netcode’s binary world setup with their own version of single-world host to release on their target platform, so this new mode will allow a supported way of doing this.
This is a pretty tentacular change and so we’re currently working on enabling most of our existing automated tests with this new mode, to make sure all flows are supported. Once that’s done, we’ll remove the experimental define and make this a first class citizen in Netcode.
A long term goal is to have more and more engine side GameObject APIs be world aware. As you can guess, this is a massive endeavour and so for now we’ll only support single-world host mode for the new GhostObject layer.
GameObject to Entity Mapping
This is more a “behind the curtains” type of change. Another team is currently working on engine side changes for entity + GameObject flows. In order to unblock our work, we’ve added a “fake” way of doing this by keeping a static GameObject to Entity hashmap to replicate the same behaviour. Once the feature is available engine side, we’ll be able to get rid of this internal map.
We’re currently generating entity prefabs on the fly when GameObject prefabs are registered. Since there’s no entity baking, we simply create the entity and then convert it to a Netcode prefab entity using the existing GhostPrefabCreation.ConvertToGhostPrefab.
No Spawn() Method
Since entities side new ghost spawns get picked up automatically by systems, we’re aiming for a no Spawn() method solution where you simply instantiate your GameObject to spawn it server side (and in prediction client side).
var myGhost = GameObject.Instantiate(myPrefab);
For example, at editor time you can now simply drag a ghost prefab in your server side scene and it’ll dynamically spawn it on any connected client.
We’re still investigating offline to online flows, so this might change.
Automatic Prefab Registration
In order to spawn your ghosts automatically client side, Netcode needs to be aware of the prefab it needs to use. We’re currently working on automatically detecting prefabs currently loaded in memory and having all of this prefab tracking be a behind the curtain thing. For cases where you DO want to control prefab registration, a “skip automatic prefab registration” option is included on your GhostObject to allow you to control this with Netcode.RegisterPrefab(...).
The idea would be to use the same manual registration flows when you use Addressables.
Next DevLog Entry
Next entry, we’ll talk about GhostBehaviours with their GhostField state replication, the new PredictedUpdate() method and input management.
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. There were great questions in the previous dev log so keep them coming, it’s nice to chat about this publicly!