For some time now, I’ve had some questions on how to do some basic gameplay setups with an ECS approach. I feel I’m pretty familiar with the concept and theory of ECS style approaches but for certain setups an ECS approach feels greatly like a hindrance rather than a benefit.
So lets discuss a very simple gameplay example. Imagine we have an RPG game, that has a rich gear system (think diablo, divinity, witcher, BG, etc…). You can easily imagine that the characters are composed of multiple meshes due to the customization needs. For example in Divinity: Original Sin 2, the Red Prince is shown below with two completely different sets of gear. Each piece of gear is an individual mesh skinned on the same skeleton.
Now since we’re making an RPG, we must have a cloaks. So we also want to have a cloth mesh that is simulated attached to the character. The cloak is not part of the character skeleton since not all characters have a cloak and so you don’t want to artificial bloat the poses. The cloak therefore is a separate mesh with it’s own skeleton which is then attached to the character, often to a specific bone, let’s say: Spine_4.
And Finally, we want to be able to equip and use crazy weapons that are animated as part of the attack animation. Consider the whip in Darksiders 3 shown below, whenever the character attacks the whip animates to suit her animation.
Now in the Darksiders 3, I’m going to assume that since the whip is always equipped it’s part of the main characters skeleton, but in most RPGs you are constantly switching weapons so we probably want the weapon skeleton separate from the character’s skeleton. I’m not going to go into too much detail here but that’s how I’ve done it in the past. The weapon therefore is similar to the cloth in that it’s a separate mesh and skeleton and attached to the character at a specific bone i.e. hand_R.
So conceptually, assuming a bunch of customizable character pieces, our character looks something like this:
In the above image the animation system update generates 3 poses on three separate animation skeletons (character, cape, weapon). This is done since the prop animations are often linked to the specific character animations and so in complex blending/layering scenarios, you definitely want proper sync and blending. Running multiple state machines independently that will pretty much perform the same logic is a waste of time not to mention a pain in the ass to maintain and keep synchronized on the author side. Additionally on the code front, you need to provide mechanisms to synchronize the runtime of those various state machines. I’ve had a few comments on twitter of people that seem to think we play a single animation at a time in games. Those days are looooong gone especially for AAA game, we are constantly blending, layering, IK’ing anims to produce the final result.
Modern animation systems are relatively complex state machines. In many cases consisted of several thousand nodes. At work our graphs are around 30K nodes, at Ubi on certain productions the numbers exceeded 60K. The animation system will result in the 3 animation poses.
I’m assuming the concept of having separate animation and render skeletons as frankly each have different responsibilities and often bones counts. For example, deformation and procedural bones are usually not present in the animation skeleton as they are entirely dependent on the mesh used and often various meshes have completely different deformation bones. If the character is heavy armor with pauldrons you might need a set driven key solution to correct skinning whereas for a leather shirt, simple twist/poke joints are enough.
Since we have different skeletons for anim and rendering, we need a pose transfer solution. This is usually achieved with a simple bone map table. Once the pose is transferred to the mesh, we can run the mesh deformation solvers before finally submitting the bones and mesh to the renderer to draw.
This is a relatively straightforward and common problem so lets try to implement this in an ECS system. The only restriction being that you are only allowed to have a single component of any given type on an entity. This approach seems to be the most popular today (for some very good reasons). An example (and perhaps naive) setup of this in an ECS is shown below:
So we have 8 entities, with 27 components spread amongst them. How about the systems? We need 7 systems, these are shown below including the set of components that they each operate on.
Let’s do a quick breakdown of each system:
The anim system is responsible for calculating the various poses (character, cape, weapon). Usually when it comes to animating props, the prop anims are embedded in the character anims to aid with synchronization, state machine setup and pipeline.
The pose transfer system is responsible for transferring the pose from the anim system to the mesh component using a bone map table.
The cloth pose transfer system is responsible for transferring the pose from the anim system into the cloth system since we can blend between anim and sim for the cloth.
The cloth system is responsible for performing a cloth simulation and blending between anim and physics, as well as transferring the resulting pose onto a mesh.
The cross entity pose transfer system transfers an anim pose from one entity to the mesh component on another entity. This is similar to the pose transfer system except it needs additional information about the source entity (you could fold it into the pose transfer system and check which component is present and so branch inside the system). I’ve chosen to keep it separate for argument’s sake. The entire role of the master anim system component is to specify the link to the entity that actually contains the anim pose for this entity.
The attachment system updates transform hierarchies across entities. It basically updates the transform component on an entity to be relative to an attachment point on another entity. Let’s just assume we only support one level of transform parenting or this gets even more messy fast.
Let’s we consider the attachment system update show below:
To update the transform for entity H, I need the transform for entity A. This will be looked up during the update, so lets hope it’s already in the cache and we don’t have too many transform components (dubious). I also need to get the mesh component then read its pose to find the global transform for the socket bone. Only then can I update the transform of H. In doing this we are hitting a bunch of different memory locations.
The cross entity pose transfer system update functions similarly in that we need to get the anim component of entity A, read the anim pose, then get the bone map table, then get the mesh pose and update the bones.
Additionally our system have dependencies on one another in terms of what data each one needs to generate so that the next can proceed. I’ve drawn a basic pipeline diagram of the order in which the systems need to execute.
The anim system has to run first, then once it has generated a pose, can we run all the pose transfer systems. Once all the poses are set, we can run the cloth, and attachment systems. This is a strong order that has to be defined somewhere so either you end up putting hard dependencies between systems or you need to create an abstract priority system that allows users to specific priorities. As the number of systems grow, I can imagine managing the update order could get tricky, especially when it comes to the mess that is gameplay code.
Is it just me or does the above seem like a LOT of complexity to do a relatively simple character update? Not only that but we’ve gone and sprinkled a bunch of functionality across several systems increasing the cognitive load for anyone coming into this system.
Additionally, whereas in other models, a character is usually self contained in that I can find all meshes that belong to a given character. In this scenario, I would need to perform backwards lookups for all master anim components and all attachment components that reference A.
Or I could create a component on A, that lists all “related” entities to A. If I do that then I have circular dependencies between the entities. And honestly when it comes to circular dependencies, fuck that idea.
I’m sure with some more thought I can create some relationship entities or mapping systems to track dependencies but something is wrong when I’m having to build complex machinery and introduce further complexity to solve basic problems. Let’s not even talk about performance cause in this scenario I’m having a hard time understanding the cache benefits given the scope of the operations each system might need to perform per component set update. We often don’t have that many dynamic objects in our games, maybe 50ish in the average AAA game. Most of our environments are static in terms of physics and mobility (ignoring shader trickery and VFX). I’m fully on board with ECS like approaches for things like particles systems, broadphase occlusion culling, drawlist creation, etc…
But for main character updates, I’m not convinced. If we leave performance aside, ECS’s often have the benefits of decoupling systems and promoting re-usability via composition, I get that but in the above scenario, I dont find this particularly elegant or efficient in terms of workflows and I’m a programmer, asking a designer or an LD wrap their head around this will not be trivial. Additionally, while we have decoupled several related concepts, it just adds a cognitive load on the me in that I now have a lot of moving pieces that I need to keep in my head and remember how they slot together…
What am I missing?!