Current best way to handle animations in ecs?

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.

using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
using UnityEngine.Jobs;
public struct Hybrid : IComponentData
{
    public int Index;
}
public class HybridSpawner : MonoBehaviour
{
    public GameObject Object;
    public int Width;
    public int Height;
    public float Spacing;
    [HideInInspector]
    public TransformAccessArray TransformAccessArray;
    [HideInInspector]
    public GameObject[] GameObjects;
    public NativeArray<Entity> Entities;
    EntityManager EntityManager => World.DefaultGameObjectInjectionWorld.EntityManager;
    public static HybridSpawner Instance { get; private set; }
    void Awake()
    {
        if(Instance == null)
            Instance = this;
        else
            Destroy(gameObject);
    }
    void OnDisable()
    {
        if(Entities.IsCreated)
            Entities.Dispose();
   
        if (TransformAccessArray.isCreated)
            TransformAccessArray.Dispose();
    }
    void Start()
    {
        var count = Width * Height;
        GameObjects = new GameObject[count];
        var transforms = new Transform[count];
        Entities = new NativeArray<Entity>(count, Allocator.Persistent);
        var archetype = EntityManager.CreateArchetype(
            ComponentType.ReadOnly<Translation>(),
            ComponentType.ReadOnly<Hybrid>());
        EntityManager.CreateEntity(archetype, Entities);
        for(int i = 0; i < count; i++)
        {
            GameObjects[i] = Instantiate(Object, transform);
            transforms[i] = GameObjects[i].transform;
            var x = i % Width;
            var y = i / Width;
            var position = new float3(x * Spacing, 0f, y * Spacing);
            EntityManager.SetComponentData(Entities[i], new Translation
            {
                Value = position,
            });
          
            EntityManager.SetComponentData(Entities[i], new Hybrid
            {
                Index = i,
            });
        }
   
        TransformAccessArray = new TransformAccessArray(transforms);
    }
}
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Transforms;
using UnityEngine.Jobs;

[BurstCompile]
struct TransformSyncJob : IJobParallelForTransform
{
    [DeallocateOnJobCompletion]
    public NativeArray<LocalToWorld> LocalToWorldArray;

    public void Execute(int index, TransformAccess transform)
    {
        transform.position = LocalToWorldArray[index].Position;
        transform.rotation = LocalToWorldArray[index].Rotation;
    }
}

[UpdateAfter(typeof(TransformSystemGroup))]
public class HybridTransformSyncSystem : JobComponentSystem
{
    protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
        if(HybridSpawner.Instance == null)
            return inputDeps;

        var transformAccessArray = HybridSpawner.Instance.TransformAccessArray;
        var entities = HybridSpawner.Instance.Entities;
        var localToWorldData = GetComponentDataFromEntity<LocalToWorld>(true);
        var hybridData = GetComponentDataFromEntity<Hybrid>(true);
        var localToWorldArray = new NativeArray<LocalToWorld>(entities.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);

        var gatherPositionsJobHandle = Job.WithCode(() =>
           {
               for(int i = 0; i < entities.Length; i++)
               {
                   localToWorldArray[hybridData[entities[i]].Index] = localToWorldData[entities[i]];
               }
           })
           .WithReadOnly(hybridData)
           .WithReadOnly(localToWorldData)
           .WithBurst()
           .Schedule(inputDeps);
    
        var assignPositionsJobHandle = new TransformSyncJob
        {
            LocalToWorldArray = localToWorldArray,
        }.Schedule(transformAccessArray, gatherPositionsJobHandle);
    
        return assignPositionsJobHandle;
    }
}

Unity is still working on the DOTS animation package. It still doesn’t have documentation. https://docs.unity3d.com/Packages/com.unity.animation@0.4/manual/index.html

I started poking around with it, but it’s just not ready to be used.

1 Like

Hello, @vectorized-runner !
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.

using Unity.Transforms;

EntityManager.AddComponentObject(entity, gameObject.GetComponent<Transform>());

// Choose which is right for you
EntityManager.AddComponentData(entity, new CopyTransformToGameObject());

// Or
EntityManager.AddComponentData(entity, new CopyTransformFromGameObject());

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.

@vectorized-runner 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

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.

2 Likes

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.

2 Likes

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



4 Likes

I ended up going with default pipeline GPU Skinning with this amazing project GitHub - 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.

4 Likes

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:)

6228357--685299--upload_2020-8-21_13-45-21.png
6228357--685302--upload_2020-8-21_13-45-49.png
6228357--685305--upload_2020-8-21_13-46-23.png

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.
6228564--685341--upload_2020-8-21_15-35-27.png

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

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 !

6 Likes

I didn’t know that type exist! thank you

These components no longer exists in 1.0 and transform V2.
You have to sync the position yourself.

1 Like