ECS:使用BlobAsset实现Animation动画

将一个方块的AnimationClip数据导出,然后以BlobAsset的方式在JobSystem中并行计算,以提升效率:

(1)编辑态,将AnimationClip转为SriptableObject数据,进行存储;

(2)运行时,将ScriptableObject数据转换为BlobAsset对象,供ComponentData使用;

(3)在JobSystem中使用BlobAsset驱动Entity的Matrix(T R S)修改。

编辑态:

记录动画数据的SriptableObject:

[CreateAssetMenu(menuName = "Test/CreaetAnimationData")]
public class AnimationData : ScriptableObject
{
    public float frameDelta;
    public int frameCount;
    public List<Vector3> positions;
    public List<Vector3> eulers;
    public List<Vector3> scales;
}

通过编辑器进行数据转换(fps30的数据采样率):

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public class ConvertAnimationDataEditor : EditorWindow
{
    private static EditorWindow window;

    [MenuItem("Test/ConverAnimationData")]
    static void Execute()
    {
        if (window == null)
            window = (ConvertAnimationDataEditor)GetWindow(typeof(ConvertAnimationDataEditor));
        window.minSize = new Vector2(300, 200);
        window.name = "动画转换工具";
        window.Show();
    }

    private AnimationClip clip;
    private AnimationData animAsset;

    private void OnGUI()
    {
        using (new GUILayout.HorizontalScope("box"))
        {
            GUILayout.Label("AnimClip:", GUILayout.Width(60f));
            clip = EditorGUILayout.ObjectField(clip, typeof(AnimationClip), false) as AnimationClip;
        }

        using (new GUILayout.HorizontalScope())
        {
            GUILayout.Label("SaveAsset:", GUILayout.Width(60f));
            animAsset = EditorGUILayout.ObjectField(animAsset, typeof(AnimationData), false) as AnimationData;
        }

        if(GUILayout.Button("Save"))
        {
            Save();
        }

    }

    private void Save()
    {
        var path = AssetDatabase.GetAssetPath(animAsset);
        var asset = AssetDatabase.LoadAssetAtPath<AnimationData>(path);

        asset.frameDelta = 1f / 30f;
        asset.frameCount = Mathf.CeilToInt(clip.length / asset.frameDelta);
        asset.positions = new List<Vector3>(asset.frameCount);
        asset.scales = new List<Vector3>(asset.frameCount);
        for(int i = 0; i < asset.frameCount; ++i)
        {
            asset.positions.Add(Vector3.zero);
            asset.scales.Add(Vector3.one);
        }

        foreach (var binding in AnimationUtility.GetCurveBindings(clip))
        {
            AnimationCurve curve = AnimationUtility.GetEditorCurve(clip, binding);

            string propName = binding.propertyName;

            float timer = 0f;
            float maxTime = clip.length;
            int index = 0;
            while(timer < maxTime && index < asset.frameCount)
            {
                switch (propName)
                {
                    case "m_LocalPosition.x":
                        {
                            var pos = asset.positions[index];
                            pos.x = GetValue(curve.keys, timer);
                            asset.positions[index] = pos;

                        }
                        break;
                    case "m_LocalPosition.y":
                        {
                            var pos = asset.positions[index];
                            pos.y = GetValue(curve.keys, timer);
                            asset.positions[index] = pos;
                        }
                        break;
                    case "m_LocalPosition.z":
                        {
                            var pos = asset.positions[index];
                            pos.z = GetValue(curve.keys, timer);
                            asset.positions[index] = pos;
                        }
                        break;
                    case "m_LocalScale.x":
                        {
                            var scale = animAsset.scales[index];
                            scale.x = GetValue(curve.keys, timer);
                            animAsset.scales[index] = scale;
                        }
                        break;
                    case "m_LocalScale.y":
                        {
                            var scale = asset.scales[index];
                            scale.y = GetValue(curve.keys, timer);
                            asset.scales[index] = scale;
                        }
                        break;
                    case "m_LocalScale.z":
                        {
                            var scale = asset.scales[index];
                            scale.z = GetValue(curve.keys, timer);
                            asset.scales[index] = scale;
                        }
                        break;
                }

                timer += asset.frameDelta;
                index++;
            }
        }

        EditorUtility.SetDirty(asset);
        AssetDatabase.SaveAssets();
    }

    private float GetValue(Keyframe[] frames, float time)
    {
        int pre = 0;
        int next = 0;
        for(int i = 0; i < frames.Length; ++i)
        {
            var frame = frames[i];
            if(time <= frame.time)
            {
                next = i;
                break;
            }
        }
        pre = Mathf.Max(0, next - 1);

        var preFrame = frames[pre];
        var nextFrame = frames[next];

        if(pre == next)
            return nextFrame.time;

        float ret = preFrame.value + (nextFrame.value - preFrame.value) * (time - preFrame.time) / (nextFrame.time - preFrame.time);
        return ret;
    }

}

 导出以后就会生成一个记录了动画数据的asset:

运行时:

运行时记录动画数据的结构体:

using Unity.Entities;
using Unity.Mathematics;

public struct AnimationBlobAsset
{
    public float frameDelta;
    public int frameCount;
    public BlobArray<float3> positions;
    public BlobArray<float3> eulers;
    public BlobArray<float3> scales;
}

注意使用的是Unity.Mathematics.float3而不是vector3,这样可以利用burst的simd优化。

将ScriptableObject对象的数据转化成为BlobAsset:

private BlobAssetReference<AnimationBlobAsset> animationBlob;

public void RegisterBlobAsset()
{
    AnimationData data = ...; // Asset资源加载

    using (BlobBuilder blobBuilder = new BlobBuilder(Allocator.Temp))
    {
        ref AnimationBlobAsset asset = ref blobBuilder.ConstructRoot<AnimationBlobAsset>();
        BlobBuilderArray<float3> positions = blobBuilder.Allocate(ref asset.positions, data.frameCount);
        BlobBuilderArray<float3> scales = blobBuilder.Allocate(ref asset.scales, data.frameCount);
        asset.frameDelta = data.frameDelta;
        asset.frameCount = data.frameCount;

        for (int i = 0; i < data.frameCount; ++i)
        {
            positions[i] = new float3(data.positions[i]);
            scales[i] = new float3(data.scales[i]);
        }

        animationBlob = blobBuilder.CreateBlobAssetReference<AnimationBlobAsset>(Allocator.Persistent);
    }
}

  上面的代码,就是创建BlobAsset的一个流程。

AnimationComponent数据的定义:

using Unity.Entities;
using Unity.Mathematics;

public struct Animation : IComponentData
{
    public BlobAssetReference<AnimationBlobAsset> animBlobRef;
    public float timer;
    public int frame;
    public float3 localPosition;
}

创建Entity时,给AnimationComponent设置数据:

// 设置ComponentData属性
entityManager.SetComponentData(entity, new Animation()
{
    animBlobRef = animationBlob,
    timer = 0f,
    frame = 0,
});
// ...

System的实现:

using Unity.Entities;
using Unity.Jobs;
using Unity.Transforms;
using Unity.Burst;

public partial class AnimationSystem : JobComponentSystem
{
    protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
        AnimateJob job = new AnimateJob()
        {
            deltaTime = UnityEngine.Time.deltaTime,
        };
        JobHandle handle = job.Schedule(this, inputDeps);
        return handle;
    }
}

public partial class AnimationSystem : JobComponentSystem
{
    [RequireComponentTag(typeof(Arrive))]
    [BurstCompile]
    private struct AnimateJob: IJobForEach<Animation, NonUniformScale>
    {
        public float deltaTime;

        public void Execute(ref Animation anim, ref NonUniformScale scale)
        {
            ref AnimationBlobAsset blob = ref anim.animBlobRef.Value;

            anim.timer += deltaTime;
            if(anim.timer < blob.frameDelta)
                return;

            while(anim.timer > blob.frameDelta)
            {
                anim.timer -= blob.frameDelta;
                anim.frame = (anim.frame + 1) % blob.frameCount;
            }

            anim.localPosition = blob.positions[anim.frame];
            scale.Value = blob.scales[anim.frame];
        }
    }
}

 

posted @ 2020-04-01 03:04  斯芬克斯  阅读(2428)  评论(0编辑  收藏  举报