Using high performance ECS
How to use Unity's Entity Component System together with this package.
Unity's ECS (Entity Component System) is a different way of building game code that, in many cases, allows much higher performance, compared to the classical approach of using MonoBehaviours. ECS has downsides, however, such as higher code complexity and more boilerplate in many cases, and it is seldom useful for a very small number of items (whether that is NPCs, buttons or bullets).
ECS really shines when it can operate on a large number of items (in our case, agents), as it can parallelize calculations, and make use of unity's Burst compiler for batch-processing.
Contents
- Using ECS for movement scripts
- When to use ECS, and when not to
- Interacting with a baked FollowerEntity
- Other baked components
- ECS physics
- Performance
Using ECS for movement scripts
The A* Pathfinding Project supports using ECS when using the FollowerEntity movement script. With it, you can achieve good performance even with thousands of agents.
There are two ways of using it:
Use the FollowerEntity as a normal MonoBehaviour. Behind the scenes, it will always use ECS anyway, and you'll get most of the performance benefit out-of-the-box. This makes it easy to integrate with a classical MonoBehaviour workflow.
For even higher performance, place the FollowerEntity in a subscene. Then it will be automatically baked into a pure entity, without any overhead from gameObjects and MonoBehaviours. However, then you'll have to write ECS-code to interact with it.
Other built-in movement scripts have no support for ECS.
For an example of subscenes, check out the included example scene High performance ECS
When to use ECS, and when not to
I recommend using ECS when you have a large number of agents (at least 100, likely more), so that the reduced overhead is actually noticable. Before that, you'll just be adding extra complexity to your game for pretty small wins. Keep in mind that the FollowerEntity always uses ECS behind the scenes, so you get most of the performance benefit out-of-the-box.
If you are already using ECS for the rest of the game, then of course using ECS for the movement scripts is also reasonable.
When in doubt, test both approaches and profile your game, and weigh that against the additional code complexity.
In any case, I strongly recommend starting out with MonoBehaviours to prototype things.
Interacting with a baked FollowerEntity
When used in a subscene, you cannot use any properties, fields, or methods on the FollowerEntity in play mode, since the component is baked away and won't even exist at runtime. Instead, you can either use ECS components directly, or you can use the wrapper FollowerEntityProxy which acts almost identically to the FollowerEntity component, but uses an entity as the source instead.
Using the FollowerEntityProxy
When you have a baked entity in a given world (how to get a reference to one is out of scope for this tutorial, check out Unity's ECS documentation), you can create a proxy wrapper to easily access most data:
var follower = new FollowerEntityProxy(world, entity);
The interface is almost identical to the FollowerEntity MonoBehaviour component.
follower.maxSpeed = 5;
follower.destination = new Vector3(1, 2, 3);
if (follower.currentNode.Tag == 1) {
Debug.Log("The agent is right now traversing a node with tag 1");
}
This proxy can only be used on the main thread, and not in jobs. In those cases, you'll need to use ECS components directly.
Accessing ECS components directly
The FollowerEntity is baked into many components. You can find a list of them at FollowerEntity Component List. All settings of the movement script are reflected in one of those components.
For example, to change the agent's speed, destination and read the current node, like above, we can do:
// Read and then write back
In addition to being a bit more clunky, this has one important difference compared to using the proxy or MonoBehaviour:
var data = world.EntityManager.GetComponentData<MovementSettings>(entity);
data.follower.speed = 5;
world.EntityManager.SetComponentData(entity, data);
world.EntityManager.SetComponentData<DestinationPoint>(entity, new DestinationPoint {
destination = new Vector3(1, 2, 3),
facingDirection = Vector3.zero,
});
var currentNode = world.EntityManager.GetComponentData<ManagedState>(entity).pathTracer.startNode;
if (currentNode.Tag == 1) {
Debug.Log("The agent is right now traversing a node with tag 1");
}
When using the proxy or MonoBehaviour to change properties, the code will try VERY hard to ensure that all other related properties are as up-to-date as possible. For example, when changing the destination of the agent, the reachedDestination property will immediately update to reflect this. When directly accessing ECS components, no such update is done. Therefore, related properties may be out of date until the next simulation loop for the agent runs (typically the next frame). This means it's also faster, of course. You'll just have to be aware of it.
Unsure about in which component to find a given setting? Check the source code for the property on the FollowerEntityProxy that you are interested in. Most implementations are only a few lines, and you'll be able to see exactly how to access it.
Other baked components
There are a few other commonly used components which can be baked, among those are:
AIDestinationSetter (bakes into a DestinationEntity ECS component)
MoveInCircle (bakes into a DestinationMoveInCircle ECS component)
They work essentially the same when baked as when they are used as MonoBehaviours.
ECS physics
This package does not currently have any support for the com.unity.physics package. All physics is done using Unity's built-in PhysX engine.
If you are simulating multiple worlds simultaneously (e.g. on a server) and have multiple physics scenes, you can use the PhysicsSceneRef component to specify which physics scene to use for ground collision and such.
Performance
When the FollowerEntity component is used in a subscene, performance is very good in most cases. Almost everything is done in separate worker threads, and the main thread is freed up to handle all your other game logic. It also parallelizes most computations, to make use of multiple CPU cores.
In the above screenshot, you can see an annotated profiler capture from a standalone build of the example scene running with 5000 agents at around 120-200 fps. Your mileage may vary depending on your hardware. This particular test was done on a pretty high-end computer. Rendering in this test is also extremely simple, and in a real game you will likely have a lot more overhead from rendering and all kinds of other things.
Best practices for good performance, for more tips on performance when using the FollowerEntity component.