Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Resolved How to work with standard animator with entities

Discussion in 'Entity Component System' started by FederalRazer89, Sep 8, 2021.

  1. FederalRazer89

    FederalRazer89

    Joined:
    Nov 24, 2019
    Posts:
    83
    Hello

    I have trying to find documentation on how I would use animation in ECS, but at the moment I cant find enough documentation on how implement it. So I think I will do some kind of hybrid approach until there is more documentation and the DOTS animation is more stable.

    But I having some troubles finding resources that can explain how some of the methods work. If anyone can recommend some links or github that shows/explains how ECS and Monobehaviour could work, that would be nice.

    Or if there is a simple way for an entity to send data to GameObject, and just have a GameObject copy position, rotation and receive some float values to update the animator. I already have a ECS controller working.

    I am very new to ECS and I am starting to understand the logic. Because I am not sure how to use animations in ECS or with ECS I not sure of how much time I would need to convert my project. If I can see a working example then know what to work with.


    Versions

    Unity 2020.3.9f1
    Hybrid Renderer 0.11.0-preview.44
    Entities 0.17.0-preview.42
    Jobs Version 0.8.0-preview.23
     
    apkdev likes this.
  2. apkdev

    apkdev

    Joined:
    Dec 12, 2015
    Posts:
    263
    This does seem like the sanest way to get stuff done at the moment.

    One way would be to pass simulation data to the MonoBehaviour from your system's OnUpdate(), eg. from a non-bursted, non-jobified Entities.ForEach. You can use System State Components to react to entity creation/destruction and appropriately spawn/despawn the animated character GameObject.

    You can use a managed IComponentData to store a reference to the GameObject. This way you can use it in a ForEach directly. Haven't actually used this feature myself though, but it seems straightforward enough.

    Then there's hybrid components. They could be a nice alternative if you're using the conversion workflow. This way out of the box you get a GameObject, with your hybrid components added to it, that gets spawned automatically with the entity and their transforms are synced. But in my experience these have performance and GC issues currently - hope Unity fixes that in the next release.

    You can also look up your component data directly in the MonoBehaviour's Update() using the EntityManager. May be useful in some cases. (Probably wouldn't go this way though?)

    I haven't actually needed to do this yet. Hopefully I haven't messed anything up. Not sure what is the best/blessed/cleanest/most performant method and I'm interested to find out how everyone else does it.

    Edit: See my posts in another thread.
     
    Last edited: Sep 12, 2021
    FederalRazer89 likes this.
  3. FederalRazer89

    FederalRazer89

    Joined:
    Nov 24, 2019
    Posts:
    83
    Will see if i can find some way to work with this. If i don't figure out how to go this route might try test the other method that you mentioned. If i can see any example of working example then it would help a lot
     
  4. FederalRazer89

    FederalRazer89

    Joined:
    Nov 24, 2019
    Posts:
    83
    I have read och looked at github example, but I am getting stuck. Because I can’t find a example that show ECS updating a Gameobject so I can use that general approach to interact with animator, UI and others.

    This link below kind of do what I want to, but the on of the answers contains 4 scripts that I am not sure of what is needed and how i can use it.
    https://forum.unity.com/threads/using-classic-animator-with-ecs-subscenes.1086356/#post-7000478


    And then I looked at Unitys own github example
    https://github.com/Unity-Technologi...ECSSamples/Assets/StressTests/HybridComponent

    The WireframeGizmo in the github example have a AddHybridComponent which from my observation looks like it add every frame. And this confuses me.

    I have also looked at ConvertToEntity, but I can’t see how I can interact with the converted Gameobject.

    Anyone that can prove a simple example how ECS can update a GameObject?
     
  5. MaNaRz

    MaNaRz

    Joined:
    Aug 24, 2017
    Posts:
    117
    What I am doing is to spawn a Gameobject based on a Prefab reference in a ManagedComponent.
    When im instantiating the Gameobject im saving the animator reference into the same ManagedComponent.
    My gamesystems determine what animation should be played which is written to the IComponentData "AnimationData".
    Then i can do something like this to get it into Mono World:


    Entities.WithoutBurst().ForEach((ManagedComponent hybridAnimation, in AnimationData animationData) =>
    {
    hybridAnimation.animator.SetBool("isRunning", animationData.isRunning);
    }).Run();


    Note that for Managed / Hybrid components you dont write "ref" or "in" in the ForEach parameters.
     
  6. FederalRazer89

    FederalRazer89

    Joined:
    Nov 24, 2019
    Posts:
    83
    MaNaRz Thanks the response could you show an example with ManagedComponent, I get an error when I just change the struct to a class in my IComponentData.

    The error on AnimationData:
    Entities.ForEach uses managed IComponentData AnimationData&. This is only supported when using .WithoutBurst() and .Run().

    Raw Input Data, Rotation Data and Move Data works.
    my current setup on the gameobject:
    Exemple.png

    AnimationData

    Code (CSharp):
    1. [GenerateAuthoringComponent]
    2. public class AnimationData : IComponentData
    3. {
    4.     public Entity entity;
    5.    
    6.     [HideInInspector] public float VelocityX;
    7.     [HideInInspector] public float VelocityY;
    8.  
    9.     [HideInInspector] public float AnimationState;
    10.  
    11. }
    12.  
    13.  
    AnimatonUpdateSystem


    Code (CSharp):
    1. public class AnimatonUpdateSystem : SystemBase
    2. {
    3.  
    4.     protected override void OnUpdate()
    5.     {
    6.  
    7.         Entities.WithAll<AnimationData>()
    8.         .ForEach((in AnimationData animationData, in Translation translation, in RotationData rotationData) =>
    9.         {
    10.            
    11.             //animationData.VelocityX = 1f;
    12.  
    13.  
    14.         }).WithBurst().Run();
    15.     }
    16. }
    Anyone that can tell my what i am doing wrong?
     
  7. apkdev

    apkdev

    Joined:
    Dec 12, 2015
    Posts:
    263
    Since AnimationData is now a class, you cannot use it in jobs that are Burst-compiled. You need to use
    .WithoutBurst()
    in your ForEach. From the docs:

     
  8. FederalRazer89

    FederalRazer89

    Joined:
    Nov 24, 2019
    Posts:
    83
    Thanks kind of felt like a idiot when i saw that. But now its no errors coming up.

    Still i cant access the animator.

    Updated the code a bit and trying on a skinned mesh, but the animator.SetFloat("Velocity X", 1f) dont do anything when i press play and the character just Idle.
    In Monobehaviuor it would just be:
     gameObject.GetComponent<Animator>().animator.SetFloat("Velocity X", 1f)



    My skinned gameobject
    upload_2021-9-18_21-44-42.png

    upload_2021-9-18_21-45-45.png

    AnimationData script:

    Code (CSharp):
    1. [GenerateAuthoringComponent]
    2. public class AnimationData : IComponentData
    3. {
    4.     public Entity entity;
    5.  
    6.     [HideInInspector] public float VelocityX;
    7.     [HideInInspector] public float VelocityY;
    8.  
    9.     [HideInInspector] public float AnimationState;
    10.  
    11.     public Animator animator;
    12.  
    13. }

    AnimatonUpdateSystem script:

    Code (CSharp):
    1. public class AnimatonUpdateSystem : SystemBase
    2. {
    3.  
    4.     protected override void OnUpdate()
    5.     {
    6.  
    7.         Entities.WithAll<AnimationData>()
    8.         .ForEach((AnimationData animationData, in Translation translation, in RotationData rotationData) =>
    9.         {
    10.  
    11.             animationData.animator.SetFloat("Velocity X", 1f);
    12.             animationData.animator.SetFloat("Velocity Y", 1f);
    13.  
    14.  
    15.         }).WithoutBurst().Run();
    16.     }
    17. }
    Any suggestion is appreciated
     
  9. MaNaRz

    MaNaRz

    Joined:
    Aug 24, 2017
    Posts:
    117
    The problem most likely is that that you do not have an animator in the first place. Your gameobject is destroyed in the Conversion and all MonoBehaviours with it.
    My solution to this was to spawn the Gameobject after the Conversion and link the Gameobject to the Entity with systems that copy the Transform and Rotation from Entity to Gameobject as well as writing to the Animator.

    I needed to do all of that because im using the 2DAnimation package but in your case maybe you could look into the Animation Package specifically for ECS. Maybe that solves your problems easier but i do not have any experience with it.
     
    apkdev likes this.
  10. FederalRazer89

    FederalRazer89

    Joined:
    Nov 24, 2019
    Posts:
    83
    Can you show an example of how you link the Gameobject and the Entity, sounds like that might work for my needs?

    The ECS Animation Package is something i will check out after the documentation and other learning resources is more available. Just want to move most of my logic to ECS
     
  11. MaNaRz

    MaNaRz

    Joined:
    Aug 24, 2017
    Posts:
    117
    The Code that spawns the GameObject:
    Code (CSharp):
    1.  
    2. Entities.WithoutBurst().WithNone<IsBootstrapped>().ForEach((Entity entity, HybridArtAComp hybridArtAComp) =>
    3.         {
    4.             hybridArtAComp.artInstance = MonoBehaviour.Instantiate(hybridArtAComp.artPrefab);
    5.  
    6.             hybridArtAComp.animator = hybridArtAComp.artInstance.GetComponent<Animator>();
    7.                
    8.             ecb.AddComponent(entity, new IsBootstrapped());
    9.         }).Run();
    10.  
    HybridArtAComp is a ManagedComponent where i can store the reference to the GameObject (artInstance) i spawned from the Prefab to be able to destroy it later on when my Entity dies. It also holds the reference to the Animator of the artInstance so i don't need to call Monobehaviour.GetComponent() in other Systems.
    The IsBootstrapped tag makes sure this is only ever called once per entity in the first frame where that entity spawns.

    Then to sync the artInstance with the Entity position i just do something like this:
    Code (CSharp):
    1.  
    2. Entities.WithoutBurst().ForEach((Entity entity, HybridArtAComp hybridArtAComp, in LocalToWorld localToWorld) =>
    3.         {
    4.                 hybridArtAComp.artInstance.transform.position = localToWorld.Position;
    5.         }).Run();
    6.  
     
    apkdev likes this.
  12. FederalRazer89

    FederalRazer89

    Joined:
    Nov 24, 2019
    Posts:
    83
    In your implementation can you interact with the animator.SetFloat() from your ECS script?

    Is it possible for me to see how hybridArtAComp is made, not sure of how i should reference the GameObject. If i can understand this then i think i will know how to procced.
     
  13. apkdev

    apkdev

    Joined:
    Dec 12, 2015
    Posts:
    263
    If you spawn the GameObject at runtime (as opposed to putting it in a subscene), like in MaNaRz's example, you should be able to access it normally, use
    animator.SetFloat()
    , and so on. It's just a plain old GameObject spawned from a plain old prefab.
     
    tjumma, FederalRazer89 and MaNaRz like this.
  14. MaNaRz

    MaNaRz

    Joined:
    Aug 24, 2017
    Posts:
    117
    My HybridArtAComp really is nothing special and actually done "wrong" but it worked like this anyway:

    Code (CSharp):
    1. public class HybridArtAComp : MonoBehaviour, IConvertGameObjectToEntity
    2. {
    3.     public GameObject artPrefab;
    4.     [HideInInspector] public GameObject artInstance;
    5.     [HideInInspector] public Animator animator;
    6.  
    7.  
    8.     public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    9.     {
    10.             dstManager.AddComponentObject(entity,this);
    11.     }
    12. }
    In this case im adding a MonoBehaviour to my Entity which is rather bad (I think it even generates a warning) but i couldn't be bothered fixing it yet. What i should actually be doing is creating a HybridArt managed IComponentData in the Conversion and add this to the Entity. Both ways would result in exactly the same System Code i showed before.

    In my first post i showed how you can write to the Animator from inside a System.
     
  15. FederalRazer89

    FederalRazer89

    Joined:
    Nov 24, 2019
    Posts:
    83
    I discovered that my test system doesn't run with AnimationData as an argument in the Entities.ForEach, if i remove it and just use Translation or RotationData it runs. And i get no error so i don't know were to start investigate.
    What is wrong? Do i need to add Managed Components in a different way?


    Code (CSharp):
    1. public class ConvertTestSystem : SystemBase
    2. {
    3.  
    4.  
    5.     protected override void OnUpdate()
    6.     {
    7.  
    8.         Entities.ForEach(( AnimationData animationData, in RotationData rotationData) =>
    9.         {
    10.          
    11.             Debug.Log("working");
    12.  
    13.  
    14.         }).WithoutBurst().Run();
    15.     }
    16. }
    AnimationData

    Code (CSharp):
    1. [GenerateAuthoringComponent]
    2. public class AnimationData : IComponentData
    3. {
    4.     public Entity entity;
    5.  
    6.     [HideInInspector] public float VelocityX;
    7.     [HideInInspector] public float VelocityY;
    8.  
    9.     [HideInInspector] public float AnimationState;
    10.  
    11.     public Animator animator;
    12.  
    13. }
     
  16. MaNaRz

    MaNaRz

    Joined:
    Aug 24, 2017
    Posts:
    117
    Hmm i have no idea about the internals of GenerateAuthoringComponent but i would guess if you write the Authoring yourself - like in my example - it should work. If you are afraid because i said i did something not really clean here is the correct version:

    Code (CSharp):
    1. public class HybridArtAComp : MonoBehaviour, IConvertGameObjectToEntity
    2. {
    3.     public GameObject artPrefab;
    4.  
    5.     public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    6.     {
    7.             if (artPrefab != null)
    8.             {
    9.                 dstManager.AddComponentObject(entity,new HybridArtManaged
    10.                 {
    11.                     artPrefab = artPrefab
    12.                 });
    13.             }
    14.     }
    15. }
    16.  
    17. public class HybridArtManaged : IComponentData
    18. {
    19.     public GameObject artPrefab;
    20.     public GameObject artInstance;
    21.     public Animator animator;
    22. }
     
    Last edited: Sep 21, 2021
    FederalRazer89 likes this.
  17. FederalRazer89

    FederalRazer89

    Joined:
    Nov 24, 2019
    Posts:
    83
    Solved the problem with ConvertTestSystem, and it was just me forgetting to attach the test component to my GameObject.

    MaNaRz after reading through your code and testing a several of stuff i eventually got it to work and i somewhat understand how it works. I got it to work with the Convert To Entity script, so i can just use monobehaviour to spawn it or just drag it into the editor. Going to test and see if translation works as intended.

    Thanks for all the help and examples it really helped me.

    This part i am a bit unsure of. Do you use IsBootstrapped() as a tag or is it something else in it?




    Bellow is how i got this to work
    upload_2021-9-21_20-50-34.png


    Code (CSharp):
    1. public class ConvertTest : MonoBehaviour, IConvertGameObjectToEntity {
    2.  
    3.  
    4.     public GameObject myPrefab;
    5.     public GameObject controllerInstance;
    6.     public Animator convertAnimator;
    7.  
    8.     public Entity controllerEntity;
    9.  
    10.     public void Convert(Entity convertEntity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    11.     {
    12.         dstManager.AddComponentObject(convertEntity, new AnimationData {characterPrefab = myPrefab });
    13.  
    14.     }
    15.  
    16. }

    Code (CSharp):
    1. public class SpawnPlayerSystem : SystemBase //TODO change name
    2. {
    3.  
    4.     protected override void OnUpdate()
    5.     {
    6.  
    7.         Entities.ForEach((Entity entity, ConvertTest convertTest, AnimationData animationData) =>
    8.         {
    9.             //convertTest.controllerInstance = MonoBehaviour.Instantiate(convertTest.myPrefab);
    10.             convertTest.convertAnimator = convertTest.controllerInstance.GetComponent<Animator>();
    11.  
    12.  
    13.         }).WithoutBurst().Run();
    14.  
    15.         Entities.ForEach((Entity entity, ConvertTest convertTest, AnimationData animationData, in LocalToWorld localToWorld) =>
    16.         {
    17.             convertTest.controllerInstance.transform.position = localToWorld.Position;
    18.         }).WithoutBurst().Run();
    19.     }
    20. }

    Code (CSharp):
    1. public class ConvertTestSystem : SystemBase
    2. {
    3.  
    4.     protected override void OnUpdate()
    5.     {
    6.         float deltaTime = Time.DeltaTime;
    7.         Entities.WithAll<AnimationData>()
    8.         .ForEach((Entity entity, ConvertTest convertTest, AnimationData animationData) =>
    9.         {
    10.  
    11.             if (animationData.VelocityX >= 1)
    12.             {
    13.                 animationData.VelocityX = -1f;
    14.             }
    15.             if (animationData.VelocityY >= 1)
    16.             {
    17.                 animationData.VelocityY = -1f;
    18.             }
    19.  
    20.             convertTest.convertAnimator.SetFloat("Velocity X", animationData.VelocityX);
    21.             convertTest.convertAnimator.SetFloat("Velocity Y", animationData.VelocityY);
    22.  
    23.             animationData.VelocityX = animationData.VelocityX + (1f * deltaTime);
    24.             animationData.VelocityY = animationData.VelocityY + (1f * deltaTime);
    25.  
    26.         }).WithoutBurst().Run();
    27.     }
    28. }

    Code (CSharp):
    1. public class AnimationData : IComponentData
    2. {
    3.     public GameObject characterPrefab;
    4.     public GameObject animationInstance;
    5.     public Animator animator;
    6.  
    7.     public float VelocityX;
    8.     public float VelocityY;
    9.  
    10. }
     
  18. MaNaRz

    MaNaRz

    Joined:
    Aug 24, 2017
    Posts:
    117
    Glad to hear it is working for you now!

    Yes. This Tag makes sure every entity is just Processed once when it spawns. In my usecase i cannot spawn everything when the game first starts.

    I see you are using "Convert and Inject". I would strongly advise against it. What you should ultimately use is Subscenes that handle the Conversion and they only work in "Convert and Destroy" mode. Because you are Injecting the Gameobject to ECS you can directly get that Animator from the Initial GameObject and dont need to spawn a new one. so this:

    Code (CSharp):
    1. Entities.ForEach((Entity entity, ConvertTest convertTest, AnimationData animationData) =>
    2.         {
    3.             //convertTest.controllerInstance = MonoBehaviour.Instantiate(convertTest.myPrefab);
    4.             convertTest.convertAnimator = convertTest.controllerInstance.GetComponent<Animator>();
    5.         }).WithoutBurst().Run();
    should not be necassary at all. Especially not every frame. In your version you could Inject the Animator directly into the foreach because the Entity basically has a Link to the Injected Gameobject with the Animator Component.
    Like this:
    Code (CSharp):
    1. Entities.ForEach((Animatior animatior) =>
    2.         {
    3.             // Do Stuff (animator.SetBool ...)
    4.         }).WithoutBurst().Run();
    But again i strongly recommend to not use the outdated Inject workflow.
     
    FederalRazer89 likes this.
  19. FederalRazer89

    FederalRazer89

    Joined:
    Nov 24, 2019
    Posts:
    83
    At the moment this workflow is fine, so i can have some way to interact with systems that might not be ready or easy enough to use in ECS.
    If i chose convert and destroy it will give a warning and not animate the character. So i am not sure how i would approach this without a gameobject with a animator, unless i go with ECS animation.

    Shader [Standard] on [Ch35] does not support skinning. This can result in incorrect rendering.
    Please see documentation for Linear Blend Skinning Node and Compute Deformation Node in Shader Graph.


    But i want to focus on getting game logic prototyp done first.

    Ether way thanks for the help, now i feel like i can move most logic to ECS.
     
  20. samanjimbo

    samanjimbo

    Joined:
    Feb 13, 2022
    Posts:
    1
    Hi.I know it's a resolved question but since English is not my first language I'm a bit confused.can you tell me the order of the things you're doing? :( sry
     
    bb8_1 likes this.
  21. FederalRazer89

    FederalRazer89

    Joined:
    Nov 24, 2019
    Posts:
    83
    Do you need a step by step guide of how to make the animator work with ECS? I can show the way i made it work when i get home, but i am not sure if its the most efficient but it works.
     
    bb8_1 likes this.
  22. FederalRazer89

    FederalRazer89

    Joined:
    Nov 24, 2019
    Posts:
    83
    Check how i did access the animator through ECS, and this is som old code that i plan to revisit when i am done with some other stuff. And this is still version 0.17 so some things might needed to be corrected.

    First you add a Monobehaviourconvert script with the animator selected and add the Convert to Entity script from ECS.

    Then you can you can access Monobehaviour code through ECS Entities.ForEach loop.

    upload_2022-9-8_17-33-34.png

    upload_2022-9-8_17-40-7.png



    Code (CSharp):
    1. [System.Serializable]
    2.         public sealed class CharacterControllerMonoConvert : MonoBehaviour, IConvertGameObjectToEntity
    3.     {
    4.  
    5.         public Animator prefabAnimator;
    6.  
    7.  
    8.          public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    9.             {
    10.              dstManager.AddComponent<CopyTransformToGameObject>(entity);
    11.  
    12.         }
    13.     }

    Code (CSharp):
    1.        
    2.    
    3.     public class CharacterHybridSystem : SystemBase
    4.     {
    5.         protected override void OnUpdate()
    6.         {
    7.  
    8.  
    9.             Entities.ForEach((CharacterHybridComponent characterHybridComponent, CharacterControllerMonoConvert cCMonoConvert, in PlayerControllerComponent cControllerC, in CharacterDataComponent cDataC, in Rotation rotation) =>
    10.             {
    11.  
    12.                 cCMonoConvert.prefabAnimator.SetFloat("Velocity X", cControllerC.inputHorizontal);
    13.                 cCMonoConvert.prefabAnimator.SetFloat("Velocity Y", cControllerC.inputVertical);
    14.  
    15.                 cCMonoConvert.prefabAnimator.SetBool("IsCrouching", cControllerC.crouch);
    16.                 cCMonoConvert.prefabAnimator.SetBool("IsPhrone", cControllerC.phrone);
    17.  
    18.                 float cameraHorizontalRotation = cControllerC.mouseY;
    19.                 float cameraVerticalRotation = cControllerC.mouseX;
    20.  
    21.                 //Add shooting animation Animation Rigging
    22.  
    23.  
    24.                 if (cDataC.isJumping || cDataC.inAir)
    25.                 {
    26.  
    27.                     cCMonoConvert.prefabAnimator.SetBool("InAir", cDataC.inAir);
    28.                 }
    29.                 else
    30.                 {
    31.                     cCMonoConvert.prefabAnimator.SetBool("StartJumping", cDataC.isJumping);
    32.                     cCMonoConvert.prefabAnimator.SetBool("InAir", cDataC.inAir);
    33.                 }
    34.  
    35.  
    36.             }).WithoutBurst().Run();
    37.  
    38.  
    39.         }
    40.     }
    41.  
    42. [/SPOILER]
    43.  
    44. Hope this can help people to learn ECS
     
    bb8_1 likes this.
  23. instriker_911

    instriker_911

    Joined:
    Mar 31, 2022
    Posts:
    27
    Hi.

    I had the same use case, and I'm running on Entities 0.51.1 . After looking at the Boid demo, the animation of the shark that I look was incomplete, mainly because there were no transitions handling.

    Reading the post and making sense of all the comments, I though a good summary would be great to help getting started. Here is the code I ended up with if it can help anyone, mainly an adaptation based on the other comments above.

    The solution is based on:
    • Using a pure ECS entity converted with subscenes or Convert & Destroy
    • Inject the a pure GameObject at runtime for each entities that is responsible for the animation only
    What this helps with is:
    • Working with pure entities data for most of the systems
    • Can use standard unity Animator until we have a better solution with ECS.
    • While isolating the GameObject animation changes to be made in the future
    • Getting started quickly and avoiding spending effort when we know a better solution will come in the future
    What this doesn't helps is:
    • Getting the best ECS performance for animation right now.

    Authoring Part

    First, I have an authoring component attached on my entities in subscenes (or using the Conver to Entity with Convert & Destroy). I add a GameObject prefab with that only have the Rendering + Animator

    upload_2022-9-13_21-53-51.png

    The Prefab:
    upload_2022-9-13_21-55-16.png

    Code (CSharp):
    1.  
    2. public class AnimatableAuthoring : MonoBehaviour, IConvertGameObjectToEntity, IDeclareReferencedPrefabs
    3. {
    4.     [Tooltip("GameObject Prefab that can be spawn to create an animated Hybrid GameObject at runtime.")]
    5.     public GameObject animationPrefab;
    6.  
    7.     public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    8.     {
    9.         dstManager.AddComponentObject(entity, new Animatable { animationPrefab = animationPrefab });
    10.     }
    11.  
    12.     public void DeclareReferencedPrefabs(List<GameObject> referencedPrefabs)
    13.     {
    14.         referencedPrefabs.Add(animationPrefab);
    15.     }
    16. }
    17.  
    18. public class Animatable : IComponentData
    19. {
    20.     public GameObject animationPrefab;
    21. }
    22.  
    23.  
    Core Animation System

    The core AnimationSystem create an Animated managed component for all Animatable entities at runtime. The Animated component is responsible to manage the reference to the Animator and to handle the GameObject lifecycle.

    Code (CSharp):
    1.  
    2.  
    3. public class Animated : IComponentData, IEquatable<Animated>, IDisposable
    4. {
    5.     public GameObject gameObject;
    6.     public Animator animator;
    7.  
    8.     public bool Equals(Animated other)
    9.     {
    10.         if (other == null)
    11.         {
    12.             return false;
    13.         }
    14.  
    15.         if (this == other)
    16.         {
    17.             return true;
    18.         }
    19.  
    20.         return object.Equals(gameObject, other.gameObject);
    21.     }
    22.  
    23.     public override int GetHashCode()
    24.     {
    25.         return gameObject?.GetHashCode() ?? 0;
    26.     }
    27.  
    28.     public void Dispose()
    29.     {
    30.         UnityEngine.Object.Destroy(gameObject);
    31.     }
    32. }
    33.  
    34. public partial class AnimationSystem : SystemBase
    35. {
    36.     protected override void OnUpdate()
    37.     {
    38.         Entities
    39.             .WithoutBurst()
    40.             .WithNone<Animated>()
    41.             .WithStructuralChanges()
    42.             .ForEach((Entity entity, Animatable animatable) =>
    43.             {
    44.                 var animationInstance = GameObject.Instantiate(animatable.animationPrefab);
    45.                 animationInstance.name = $"{animatable.animationPrefab.name} - {entity}";
    46.                 var animator = animationInstance.gameObject.GetComponentInChildren<Animator>();
    47.                 if (animator == null)
    48.                 {
    49.                     Debug.LogError($"Failed to find Animator in prefab {animatable.animationPrefab.name}");
    50.                     return;
    51.                 }
    52.                 EntityManager.AddComponentData(entity, new Animated
    53.                 {
    54.                     gameObject = animationInstance,
    55.                     animator = animator,
    56.                 });
    57.                 EntityManager.AddComponentObject(entity, animationInstance.GetComponent<Transform>());
    58.                 EntityManager.AddComponentData(entity, new CopyTransformToGameObject());
    59.             })
    60.             .Run();
    61.     }
    62. }
    63.  
    64.  
    Game Specific Animation System

    Once the core setup is done, all we need to do is to create the game specific animation state logic (jobs / systems) updating pure ECS components:

    Code (CSharp):
    1.  
    2.  
    3. public struct CreatureAnimationState : IComponentData
    4. {
    5.     public bool isWalking;
    6. }
    7.  
    8. [UpdateInGroup(typeof(PresentationSystemGroup))]
    9. public partial class CreatureAnimationStateSystem : SystemBase
    10. {
    11.     protected override void OnUpdate()
    12.     {
    13.         Entities
    14.             .ForEach((ref CreatureAnimationState animationData, in NavAgentComponent navAgentComponent) =>
    15.             {
    16.                 animationData.isWalking = math.any(navAgentComponent.velocity);
    17.             })
    18.             .Schedule();
    19.     }
    20. }
    21.  
    22.  
    and finally a throwaway Hybrid animation system that bridge the between the pure ECS components and the animator calls:

    Code (CSharp):
    1.  
    2.  
    3. [UpdateInGroup(typeof(PresentationSystemGroup))]
    4. [UpdateAfter(typeof(CreatureAnimationStateSystem))]
    5. public partial class CreatureAnimationPresentationSystem : SystemBase
    6. {
    7.     private static readonly int attackId = Animator.StringToHash("Attack");
    8.     private static readonly int walkingId = Animator.StringToHash("Walking");
    9.     private static readonly int diedId = Animator.StringToHash("Died");
    10.  
    11.     protected override void OnUpdate()
    12.     {
    13.         Entities
    14.             .WithoutBurst()
    15.             .ForEach((Animated animated, in CreatureAnimationState animationData) =>
    16.             {
    17.                 animated.animator.SetBool(walkingId, animationData.isWalking);
    18.             })
    19.             .Run();
    20.     }
    21. }
    22.  
    Notes:

    The code is not fully optimised, as we EntityCommandBuffer could be used, while using a GameObject pool could also helps.
     
    bb8_1 and FederalRazer89 like this.
  24. instriker_911

    instriker_911

    Joined:
    Mar 31, 2022
    Posts:
    27
    Update of the above solution: I migrated to the 1.0.0-exp.8 entitites version.

    So here is the updated code above:

    For the new baking systeme:

    Code (CSharp):
    1. [DisallowMultipleComponent]
    2. public class AnimatableAuthoring : MonoBehaviour
    3. {
    4.     [Tooltip("GameObject Prefab that can be spawn to create an animated Hybrid GameObject at runtime.")]
    5.     public GameObject animationPrefab;
    6.  
    7.     private void OnDrawGizmos()
    8.     {
    9.         var drawSize = 1.5f;
    10.         Gizmos.color = new Color(1.0f, 1.0f, 0f);
    11.         Gizmos.DrawCube(this.transform.position + new Vector3(0, drawSize / 2, 0), Vector3.one * drawSize);
    12.     }
    13.  
    14.     public class Baker : Baker<AnimatableAuthoring>
    15.     {
    16.         public override void Bake(AnimatableAuthoring authoring)
    17.         {
    18.            AddComponentObject(new Animatable { animationPrefab = authoring.animationPrefab });
    19.         }
    20.     }
    21. }
    We lost the CopyTransformToGameObject (now CompanionLink replace it, but I failed to make the CompanionLink work with an Animator), so the new system:

    Code (CSharp):
    1. [UpdateInGroup(typeof(LateSimulationSystemGroup))]
    2. [RequireMatchingQueriesForUpdate]
    3. public partial class AnimationSystem : SystemBase
    4. {
    5.     protected override void OnUpdate()
    6.     {
    7.         Entities
    8.             .WithNone<Animated>()
    9.             .WithStructuralChanges()
    10.             .WithoutBurst()
    11.             .ForEach((Animatable animatable, in Entity entity) =>
    12.             {
    13.                 // PERF : could use a the GameObject Pooling??
    14.                 var animationInstance = GameObject.Instantiate(animatable.animationPrefab);
    15.                 animationInstance.name = $"{animatable.animationPrefab.name} - {entity}";
    16.                 var animator = animationInstance.gameObject.GetComponentInChildren<Animator>();
    17.                 if (animator == null)
    18.                 {
    19.                     Debug.LogError($"Failed to find Animator in prefab {animatable.animationPrefab.name}");
    20.                     return;
    21.                 }
    22.                 EntityManager.AddComponentData(entity, new Animated
    23.                 {
    24.                     gameObject = animationInstance,
    25.                     animator = animator,
    26.                 });
    27.             })
    28.             .Run();
    29.  
    30.         Entities
    31.             .WithoutBurst()
    32.             .ForEach((Animated animated, in Translation translation, in Rotation rotation) =>
    33.             {
    34.                 // CopyTransformToGameObject is gone, and CompanionLink is only set under certain condition
    35.                 // But when we meet them, it seems to break the animator, so I just simulate the old CopyTransformToGameObject behavior here.
    36.                 var transform = animated.gameObject.transform;
    37.                 transform.position = translation.Value;
    38.                 transform.rotation = rotation.Value;
    39.             })
    40.             .Run();
    41.     }
    42. }
    The Animated classs is the same code, but using the newest, I got issues while in the editor when switching between edit / play mode, so the updated version:

    Code (CSharp):
    1. public class Animated : IComponentData, IEquatable<Animated>, IDisposable
    2. {
    3.     public GameObject gameObject;
    4.     public Animator animator;
    5.  
    6.     public bool Equals(Animated other)
    7.     {
    8.         if (other == null)
    9.         {
    10.             return false;
    11.         }
    12.  
    13.         if (this == other)
    14.         {
    15.             return true;
    16.         }
    17.  
    18.         return object.Equals(gameObject, other.gameObject);
    19.     }
    20.  
    21.     public override int GetHashCode()
    22.     {
    23.         return gameObject?.GetHashCode() ?? 0;
    24.     }
    25.  
    26.     public void Dispose()
    27.     {
    28.         if (gameObject == null)
    29.         {
    30.             return;
    31.         }
    32.  
    33. #if UNITY_EDITOR
    34.         var canDestroy = !PrefabUtility.IsPartOfAnyPrefab(gameObject);
    35.         if (!canDestroy)
    36.         {
    37.             return;
    38.         }
    39.  
    40.         if (Application.isPlaying)
    41.         {
    42.             UnityEngine.Object.Destroy(gameObject);
    43.         }
    44.         else
    45.         {
    46.             UnityEngine.Object.DestroyImmediate(gameObject);
    47.         }
    48. #else
    49.         UnityEngine.Object.Destroy(gameObject);
    50. #endif
    51.     }
    52. }
    And those classes stayed the same:
    • Animatable Component
    • The application specific systems (just need to update the standard updated, eg.: using the [RequireMatchingQueriesForUpdate])
     
    demilich1 and bb8_1 like this.
  25. Ryunis

    Ryunis

    Joined:
    Dec 23, 2014
    Posts:
    24
    Ok, since this is the closest thread I've found relating to my question, I'm just gonna ask here.
    Can an object like this, with a Renderer and Animator, be baked into a subscene directly? I want to be able to edit the object directly in the hierarchy, not spawn it from a prefab. Is this still possible with Entities 1.0 or has this been removed along with the deprecation of runtime conversion?
     
  26. instriker_911

    instriker_911

    Joined:
    Mar 31, 2022
    Posts:
    27
    I cannot confirm it cannot be done. But after upgrading to 1.0.0-exp8, I tried to make it work without success. After the investigations I did, using the AddComponentObject to reference a GameObject worked well with a prefab (so the solution above). I can't remember the exact issues I got when adding the object itself (or a child game object), but I think I had conflicts with either the default baking system wanting to handle my meshes or the fact that CompanionLinks are internal and wasn't added (when adding a child). I would need to re investigate to confirm, but I'm still hoping for a real animation ecs package in the future to migrate and limit the throwaway efforts.

    It's possible that I missed something, but even though I tried, the animator always failed to animate the GameObject when injected. I might give it an other tentative in the future, but the solution I have at the moment is sufficient for my actual case. But if someone find how to do it, I would also appreciate to know how.

    The alternatives I found what https://docs.unity3d.com/Packages/com.unity.animation@0.9/manual/index.html for the ecs animations, but AFAIK it's still under heavy development and not compatible with the 1.0.0 release (ref: https://forum.unity.com/threads/dots-1-0-exp.1341962/). Updating the core ecs is alreayd painful time consuming, so I'm not ready to go into that path yet.

    Other demo was https://github.com/Unity-Technologi.../tree/master/ECSSamples/Assets/Advanced/Boids but after taking a deeper look to the animations, it's somewhat limited, and it would take large efforts to implement as I wished. The animation work in the demo using animation sampling, but there is no equivalent of "animator". This mean you lose all the transition handling. So you most likely need lot of effort on throwable code to make something interesting unless you are aiming for simple animations.
     
    tjumma likes this.