Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice

Current best way to handle animations in ecs?

Discussion in 'DOTS Animation' started by vectorized-runner, Jun 2, 2020.

  1. vectorized-runner

    vectorized-runner

    Joined:
    Jan 22, 2018
    Posts:
    398
    I have been trying to work out animating skinned meshes with Entities for a while now. I downloaded the AnimationSamples project and after crashing multiple times I was able to make it work... But it only works in HDRP and I'm not able to modify their code much it's very advanced to me.

    Currently the best way I was able to come up with was a system where a Monobehaviour Spawner simultaneously spawns both Entities for handling transform-animation data and GameObjects for rendering and a ComponentSystem syncs Entity position-animation with Gameobjects each frame.

    Is there a better approach than this? Here is my code if you could give me some feedback.

    Edit: Added array Index to Hybrid component of entity in order to access transforms correctly.

    Code (CSharp):
    1. using Unity.Collections;
    2. using Unity.Entities;
    3. using Unity.Mathematics;
    4. using Unity.Transforms;
    5. using UnityEngine;
    6. using UnityEngine.Jobs;
    7. public struct Hybrid : IComponentData
    8. {
    9.     public int Index;
    10. }
    11. public class HybridSpawner : MonoBehaviour
    12. {
    13.     public GameObject Object;
    14.     public int Width;
    15.     public int Height;
    16.     public float Spacing;
    17.     [HideInInspector]
    18.     public TransformAccessArray TransformAccessArray;
    19.     [HideInInspector]
    20.     public GameObject[] GameObjects;
    21.     public NativeArray<Entity> Entities;
    22.     EntityManager EntityManager => World.DefaultGameObjectInjectionWorld.EntityManager;
    23.     public static HybridSpawner Instance { get; private set; }
    24.     void Awake()
    25.     {
    26.         if(Instance == null)
    27.             Instance = this;
    28.         else
    29.             Destroy(gameObject);
    30.     }
    31.     void OnDisable()
    32.     {
    33.         if(Entities.IsCreated)
    34.             Entities.Dispose();
    35.    
    36.         if (TransformAccessArray.isCreated)
    37.             TransformAccessArray.Dispose();
    38.     }
    39.     void Start()
    40.     {
    41.         var count = Width * Height;
    42.         GameObjects = new GameObject[count];
    43.         var transforms = new Transform[count];
    44.         Entities = new NativeArray<Entity>(count, Allocator.Persistent);
    45.         var archetype = EntityManager.CreateArchetype(
    46.             ComponentType.ReadOnly<Translation>(),
    47.             ComponentType.ReadOnly<Hybrid>());
    48.         EntityManager.CreateEntity(archetype, Entities);
    49.         for(int i = 0; i < count; i++)
    50.         {
    51.             GameObjects[i] = Instantiate(Object, transform);
    52.             transforms[i] = GameObjects[i].transform;
    53.             var x = i % Width;
    54.             var y = i / Width;
    55.             var position = new float3(x * Spacing, 0f, y * Spacing);
    56.             EntityManager.SetComponentData(Entities[i], new Translation
    57.             {
    58.                 Value = position,
    59.             });
    60.          
    61.             EntityManager.SetComponentData(Entities[i], new Hybrid
    62.             {
    63.                 Index = i,
    64.             });
    65.         }
    66.    
    67.         TransformAccessArray = new TransformAccessArray(transforms);
    68.     }
    69. }
    Code (CSharp):
    1. using Unity.Burst;
    2. using Unity.Collections;
    3. using Unity.Entities;
    4. using Unity.Jobs;
    5. using Unity.Transforms;
    6. using UnityEngine.Jobs;
    7.  
    8. [BurstCompile]
    9. struct TransformSyncJob : IJobParallelForTransform
    10. {
    11.     [DeallocateOnJobCompletion]
    12.     public NativeArray<LocalToWorld> LocalToWorldArray;
    13.  
    14.     public void Execute(int index, TransformAccess transform)
    15.     {
    16.         transform.position = LocalToWorldArray[index].Position;
    17.         transform.rotation = LocalToWorldArray[index].Rotation;
    18.     }
    19. }
    20.  
    21. [UpdateAfter(typeof(TransformSystemGroup))]
    22. public class HybridTransformSyncSystem : JobComponentSystem
    23. {
    24.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    25.     {
    26.         if(HybridSpawner.Instance == null)
    27.             return inputDeps;
    28.  
    29.         var transformAccessArray = HybridSpawner.Instance.TransformAccessArray;
    30.         var entities = HybridSpawner.Instance.Entities;
    31.         var localToWorldData = GetComponentDataFromEntity<LocalToWorld>(true);
    32.         var hybridData = GetComponentDataFromEntity<Hybrid>(true);
    33.         var localToWorldArray = new NativeArray<LocalToWorld>(entities.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
    34.  
    35.         var gatherPositionsJobHandle = Job.WithCode(() =>
    36.            {
    37.                for(int i = 0; i < entities.Length; i++)
    38.                {
    39.                    localToWorldArray[hybridData[entities[i]].Index] = localToWorldData[entities[i]];
    40.                }
    41.            })
    42.            .WithReadOnly(hybridData)
    43.            .WithReadOnly(localToWorldData)
    44.            .WithBurst()
    45.            .Schedule(inputDeps);
    46.    
    47.         var assignPositionsJobHandle = new TransformSyncJob
    48.         {
    49.             LocalToWorldArray = localToWorldArray,
    50.         }.Schedule(transformAccessArray, gatherPositionsJobHandle);
    51.    
    52.         return assignPositionsJobHandle;
    53.     }
    54. }
    55.  
     
    Last edited: Jun 5, 2020
  2. aveakrause

    aveakrause

    Joined:
    Oct 3, 2018
    Posts:
    70
    Walter_Hulsebos likes this.
  3. Kelevra

    Kelevra

    Joined:
    Dec 27, 2012
    Posts:
    87
    Hello, @velenrendlich !
    I would say that if you want to work with an Animator it's the only way for now.
    Also, you can replace your HybridSyncSystem with this.

    Code (CSharp):
    1. using Unity.Transforms;
    2.  
    3. EntityManager.AddComponentObject(entity, gameObject.GetComponent<Transform>());
    4.  
    5. // Choose which is right for you
    6. EntityManager.AddComponentData(entity, new CopyTransformToGameObject());
    7.  
    8. // Or
    9. EntityManager.AddComponentData(entity, new CopyTransformFromGameObject());
     
  4. vectorized-runner

    vectorized-runner

    Joined:
    Jan 22, 2018
    Posts:
    398
    Wow, I didn't know that component even existed, but I can't find it and I'm using Entities 0.11.0 yet it still shows up in the docs.
     
  5. JakHussain

    JakHussain

    Joined:
    Oct 20, 2016
    Posts:
    318
    @velenrendlich check if you're using the correct namespaces at the top of your code. In the docs, all types are listed under their namespace which can be found on the left
     
  6. vectorized-runner

    vectorized-runner

    Joined:
    Jan 22, 2018
    Posts:
    398
    Yeah I just found it, it was because I didn't include Unity.Transforms.Hybrid in my assembly definition. The docs says it's in Unity.Transforms namespace though.
     
    Egad_McDad and JakHussain like this.
  7. Thygrrr

    Thygrrr

    Joined:
    Sep 23, 2013
    Posts:
    700
    I made an authoring component that can sample animation curves into BlobAssets, and then I created a few tweening components and systems to perform simple procedural animations for me along these curves, from a curve and keyframe asset type I made.

    It's not much, but it's honest work.
     
    turick00 and vectorized-runner like this.
  8. Stroustrup

    Stroustrup

    Joined:
    May 18, 2020
    Posts:
    142
    dots animation package is probably your best bet, its not as difficult as it may seem at first. plus once you get the hang of animation & data flow graph, dsp graph will come naturally afterwards. they're not too dissimilar.

    essentially you make an imaginary graph like with shader graph & vfx graph, but instead of dragging nodes, you have to instantiate nodes via script and connect them.

    how it works is you connect the time counter (DeltaTimeNode) to the animation node (ClipPlayerNode) containing an animation clip. then you connect the output, which contains the animation data at any instance in time, to to the translation component of the entity. there are other nodes like animation blend and root motion too for fancier animations

    also, contrary to what it says on the github page, it works with both urp and hdrp

    anyway to get started with no code to begin with, create a shadergraph shader that you will use for you rig with the following bit attached to your master node:


    next, get your character and attach the rig component from the animation package and the myfirstanimation script from the sample. fill the bones array with the bones of your character



    that is all you have to do to get started





    if your character comes out as an abomination, try varying the index offset of your material. also your character might only be visible in playmode
     

    Attached Files:

    Last edited: Jun 5, 2020
  9. vectorized-runner

    vectorized-runner

    Joined:
    Jan 22, 2018
    Posts:
    398
    I ended up going with default pipeline GPU Skinning with this amazing project https://github.com/chengkehan/GPUSkinning.

    Currently 5K Skinned Mesh units and only 30 batches. Their position is synced with entities with the script I posted above and I get 60 fps easily. Now only downside is 5K monobehaviour updates for GPU Skinning, if I could convert these scripts to job-burst compile in some way I'm good to go for like 50K+ units.
     
  10. Endlesser

    Endlesser

    Joined:
    Nov 11, 2015
    Posts:
    89
    Hi there@Stroustrup, could u tell a bit more Dots Anim in URP using and setting here? or point me the right dir/info to get deeper about it.
    And,I've got a bug following your method go through "MyFirstAnimationClip" as a basic,the abominations are not getting away through changing the index offset value in URP, it just gets close but still a little deformities.in HDRP all is OK.
    Much appreciated:)

    upload_2020-8-21_13-45-21.png
    upload_2020-8-21_13-45-49.png
    upload_2020-8-21_13-46-23.png
     
  11. Endlesser

    Endlesser

    Joined:
    Nov 11, 2015
    Posts:
    89
    Well I think I got it fixed :p,in a not so performent way I guess. Duplicate "SimpleVertexSkinning" material and put them into each skinned mesh renderer, then just tune those "index offset" till anim goes right.
    upload_2020-8-21_15-35-27.png

    And, as Stroustrup shared, URP does fit in.
    upload_2020-8-21_15-41-9.png
     
  12. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
    Hello,

    I made a tutorial video that explain hybrid animation using 1.0, if anyone is interested:


    If covers Managed Components and Reactive Systems to keep GO and Entity in synch.

    EDIT : Just made an update to the code : check the pinned comment in the video and the git repository !
     
    Last edited: Nov 9, 2022
  13. AnaWilliam850

    AnaWilliam850

    Joined:
    Dec 23, 2022
    Posts:
    36
    I didn't know that type exist! thank you
     
  14. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
    These components no longer exists in 1.0 and transform V2.
    You have to sync the position yourself.
     
    UniqueCode likes this.