Mastering Entity Animation in Unity ECS : Hybrid Workflow Explained

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
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.
Info
Channel: WAYN Games
Views: 465
Rating: undefined out of 5
Keywords: Entity component system, ECS Physics, unity dots 2024, unity dots tutorial 2024, Unity DOTS, how to make a game with unity dots, unity dots 1.0, ECS vs Mono, Unity dots tutorial, Unity job system, unity dots, unity ecs, unity ecs 1.0, unity ecs tutorial, unity esc, WAYN Games, Unity dots animation, character animation unity, dots animation, how to animate with dots, humanoid animation unity, unity dots tutorial 2021, unity ecs 1.0 release date
Id: va6xGjKBxbU
Channel Id: undefined
Length: 19min 15sec (1155 seconds)
Published: Sat Mar 30 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.