Hello everyone. When it comes to ECS, I think
today's topic covers one of the most anticipated features of ECS and
covers one of its biggest drawback. How can I animate my entities? So before we start to learn how to do
this with the hybrid workflow, go to the GitHub repository linked in the
description below and download the seventh folder to import it into your project. Once you've imported, the folder
into your project you can open the animate scenes and have
a look at the hierarchy. If we look at the subscene, you can
see we have several GameObjects that will be converted. The first one, direct
conversion, contains a game object with an animator, a sphere collider, audio
source, rigid body, and particle system. The second one is much simpler. We only have one authoring component,
a custom authoring component that we will see a bit later. And that references a prefab GameObject,
and also the physics colliders. The other ones are similar to this
one, with the same game object, and some additional authoring like the
limited lifetime that we've seen in the previous episode, or another one just to
tags this entity to be able to log the animation it's currently playing as
we'll see in a few minutes. And the last one, which is actually just a normal
GameObject prefab, has the same component as the direct conversion. So this one is not part of the subscene. So if we enter play mode, you will
see that we have five entities. The two that we had in the scene and
three other one that just appeared. You can notice that they are animated. If we look at the console, we
can see they are actually playing the idle normal animation. So we have three entities that are,
logging the animation and which are actually these three entities. If I keep playing, this entity will
disappear and get destroyed instantly without any further animation. And that's because I have
the limited lifetime component authoring component on it. So five seconds after
spawning, it disappears. And if we look a bit more closely, we can
see that this entity here is not animated. So why is it not animated
compared to the three others? So that's because this one is actually
an entity when in fact, the three other slimes that are animated are GameObjects
that are synchronized with an entity. So the principle of the hybrid
workflow is to separate the simulation from the presentation. So we will handle all the simulation
in ECS with the ECS world, and we will handle all that is presentation
related meaning the animation, the audio source, particle system, and so on. Are all remaining in the game
object world, mono behavior world. Now, if I look at my entity hierarchy,
I can see my three entities. So this one, which has direct
destruction, this one that has delayed destruction and the direct conversion. So this one is an entity,
but it's not animated. Because it doesn't have the
game object representation. So the rendering of this entity is
actually handled by ECS and you can see that we have the skin mesh renderer here and the mesh
information on this entity directly. Whereas if we look at the direct
destruction, for instance, we don't see any information about
the rendering and the render mesh. What we see on the other hand,
are all the components we had as monobehaviour components. So you can see the transform
component, the sphere collider, rigid body, and the animator. If we look at the delayed destruction,
we also have one component that is a custom component, which
is the destruction manager. So in fact, here, what we have
is on one side, an entity that performs a simulation and a game
object that perform the rendering. And we have on the entity, references
directly to the component of the presentation game object. So one of them is the destruction
manager, which is a script that I implemented myself. So if I take a look at the
direct destruction entity and I delete it, you can see that this
entity disappeared directly. If I delete the delayed
destruction and remove it. You can see that it plays the
death animation of the slime now let's dive into the
systems and how we make this. So for the authoring component, I have
two very simple authoring components. The first one is that just adds a
tag component for a system to be able to log the animation that are
currently playing on our entities. And the second one, which is actually the
most important one for us today, which is the GameObject companion authoring. So it's the component that allows us to
reference a prefab GameObject and store it as part of a component on entity. So we've seen that entities can only have
component with data, meaning that they cannot store class instance, or they
cannot store lists or other reference types. We actually have a notion of manage
component that are the same as IComponentData, the difference being that instead
of being struct, there are classes. So you can actually store classes
or, monobehavior components or, any other non-blittable data on an
entity or, associated to an entity through a managed component with the,
Class implementing IComponentData. This allows us to have some compatibility
features like we've just seen, but it also means that as part of, the iteration
on this component, we cannot use burst compilation and we cannot use jobs. So. If you are using a managed component,
you will be limited to main thread and non bursted code only. Looking at our components that
we have just seen, we have an additional component, which is the
presentation game object cleanup. It's very similar to the one we just seen
because it's a class component and it's implementing an ICcleanupComponentData. So it's a little different from
an IComponentData because this component will not be removed from
the entity if the entity is destroyed. That means that if I queue a command
to destroy an entity and that entity still has components that implement
the IComponentData they will remain, so the entity will still exist
and it will only be associated to the components that
implement this interface. So this will be very useful for
us to manage the synchronization of the lifecycle between the
entity and the GameObject. Next, we have our MonoBehaviours,
and for the MonoBehaviour I have implemented two MonoBehaviours. The first one, which is
the destruction manager. It's a simple MonoBehaviour
that we will put on our prefab. Game Object Prefab and that basically
allows us to invoke, several unity events and delay the destruction
of the game objects by a given number of seconds to let the time
to run the animation, for instance. And the other one is one I
call the entity game object. And this one will allow me to link or to add a monobehavior to my game object. That will be my presentation game
object, and it will store a reference to the entity that is associated
to the presentation game object and the world this entity lives in. So we'll see that we will just
assign this field on a system. And when we are destroying the game
object, we are actually calling the destroy on the entity also so
that if the entity is destroyed, the game object is destroyed. And if the game object is destroyed,
the entity itself is also destroyed. Next, we have our prefabs. And for the prefabs, we have the
actual game object presentation prefab. I have two of them because I have
one with direct destruction and one with delayed destruction. The only difference being, the destruction
manager that we've just seen that allows me to play the animation die when the
game object will be destroyed or just before the game object is destroyed. And now we get to the meat of the
video with the different systems. So I actually have four systems. One will be in charge of logging the
animation that is currently playing. One will be in charge of instantiating
or creating the presentation GameObject at runtime and doing the link between
the GameObject and the entity. Next, we will have another system
that will be in charge of keeping in synchronization, the entity
position with the game object position so that we can simulate our entity
movement and have the game object presentation follow that movement. And the last one is the, GameObject
cleanup that will use the, ICleanupComponentData to actually perform the destruction
of the game object on ECS if the entity is destroyed. So let's first take a look at how we
instantiate our presentation game object. So for that, we have a system
it's still an I system, but since we will be using
managed component, we are not implementing burst, or adding the
burst attribute to the system. For system, I'm using a system
API as always, with a foreach, but it's a little bit different
because I'm not iterating over the components directly, but I'm
iterating directly over the entities. And for that, I am not using the
usual query bracket components. I'm using actually a query builder. That states that I want all the
entities that have the local to world and presentation GO component that we've
just seen and that doesn't have any of the presentation GO cleanup component. And then I build it and
allocate a temporary array to get the array of entities. We'll see in just a few lines
why I'm doing it like this We actually want to get the reference
to the GameObject prefab stored in the presentation GameObject. So to get that, we need to use the managed
API from the system API and get the actual component, the class component PresentationGO. From that, I can get the prefab reference
and instantiate my game object, keeping track of it, keeping a reference to it. And then I can loop
through all the components. So this will loop through all the
mono behaviors and components that are attached to this game object. And for every one of them, I will
use the entity manager and use the add component object on the entity. And add the component directly. So here I'm actually adding
a MonoBehaviour to an entity. And that's why I'm using actually
the two array Allocator temp here and iterating over the entities and not
the components themselves, because if I were iterating over the components,
I would need to use the entity command buffer to perform these changes. And this method does not exist
for the entity command buffer. It exists, but only takes in, entity
query and not a single entity. So to be able to do it for a single
entity, I need to use the entity manager. And that's why I'm using here as a query
builder and not as a query bracket components. So once I've added to my entity, all
the behaviors of my game objects, so reference to all the monobehaviors
of my game object, I can actually add my entity to my game object. So on my game object, I will
add another monobehavior. And this MonoBehaviour will keep track of the entity that
is associated to that GameObject. And the last thing I'm doing is to
get the position of my entity and assigning it to the game object. I just instantiated so that they are at
the same position in the world space. Now, what we just did is what
we called a reactive system. In fact, this system will only run once for the entity, because once we
have run the system on an entity, we will add the presentation GameObject
cleanup component, which will keep a copy of the GameObject instance. And therefore we won't
match the query anymore. So that's a reactive system. That also means that
this code will not run. And that's why we need another system to
keep over time our entity synchronized. With our game object or rather the game
object synchronized with our entity. So for that, we have still a
ISystem and we will still be using a managed component, the transform
component of our game object. So we still can't use burst, but this
time we can use the system API query. And in that case. I'm using the read only reference
to the local world, because I just want the position of my entity. So I'm simulating the movement
of my entity on the ECS side. And I'm copying that new position to
the GameObject through the transform. And to get the Unity engine components, so, all our monobehaviors we
can use again, as part of the query, we can use SystemAPI.ManageAPI.UnityEngineComponent brackets, and the name of the component we want. And again, I'm just copying the,
position of my entity to my GameObject. And we can do something very similar for
any other MonoBehaviour component we have. So for instance, for the animator,
what I'm doing here is actually querying just for the animator
component with the entity access. And here I'm logging the entity, which
entity I'm iterating on, and, the name of the current animation clip. And finally, the last thing that remains
is in case our entity is destroyed, we need to clean up the game object so that
we just don't destroy the entity and leave the presentation game object in the scene. So for that, we use the Presentation
game object cleanup system. And for this one, I'm doing something
similar to the other systems. I'm this time using the begin
initialization entity command buffer system to queue the
destruction of my entity. So I'm getting this singleton and
requiring it for the system to update. And as part of the for each statement,
I'm using the system API query to get the presentation game object cleanup that
doesn't have the presentation game object. So as a reminder, this one was
containing a reference to the prefab game object and this one to the
actual instance of that prefab. So. If the presentation GameObject instance
is not null, meaning that the GameObject still exists, I will continue the system. Otherwise, I'm just
looping to the next entity. Then I'm checking if this instance of
game object as the destruction manager component, and if it does, I'm invoking
the destroy on the destruction manager. So this will allow me to call all the
Unity events and delay the destruction of the game object itself until all the event I want to
process have been processed. If, there is no component destruction
manager on that game object, I simply just destroy immediately the, game object. And the last thing I do
again here at this point, why do we have the presentation game
cleanup and not the presentation GameObject ? It's because our entity has been
destroyed, but since this one is an ICleanupComponentData, the
presentation go component has been removed from the entity. But the entity still
exists with this component. So to actually finally destroy the
entity, I need to move all the ICleanupComponentData that exists on that entity. And for me, for this
system, it's this one. So I queue the removal of the presentation
clean up component for that entity. Next, what remains for us to see is
how we can handle UI interaction and UI display for our enemies, health
bar for instance, and also how we can manage our scenes to have a menu
scene and a level picker and be able to, do, a complete tower defense game. As usual, thank you for watching. I hope you enjoyed the video. To support me, don't forget
to like and share the video. And if you want to provide me more
direct support, you can either use the Asset Store affiliate link in the
description below or go to my Ko fi page and make a single donation or
a monthly subscription as you wish. Thank you again for watching. I will see you in the next video for
UI interaction and UI Health Bar. See you in the next video.