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;
}
}
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());
@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.
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.
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
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.
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:)
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.