zoukankan      html  css  js  c++  java
  • 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];
            }
        }
    }
  • 相关阅读:
    12月12日学习日志
    12月11日学习日志
    12月10日学习日志
    linux下安装git
    ubuntu上安装mysql
    扩展虚拟机容量
    【linux】你需要以 root 身份执行此命令
    Ubuntu新建Django工程错误:ModuleNotFoundError: No module named 'distutils.core'
    LeetCode26. 删除排序数组中的重复项
    LeetCode27. 移除元素
  • 原文地址:https://www.cnblogs.com/sifenkesi/p/12610079.html
Copyright © 2011-2022 走看看