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];
        }
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM