unity 材质基础与材质动画

Render结构

  1. Render
    1. SkinMeshRender/MeshRender/ParticleSystemRender/...
      1. Material(s)
      2. shareMaterial(s)
        1. Shader
          1. Property
          2. KeyWord

API

Renderer.shareMaterial

共用材质,全都改,非运行时不能创建.material

render.shareMaterial.Get/SetXXX

Renderer.material

.material会实例化一份材质,runtime 允许多怪,MPB本来就可以每个不一样,但是keyword不可以

render.material.Get/SetXXX

Material选择

MPB本身是可以shareMaterial的,但keyword不可以

public static Material[] GetMaterials(Renderer renderer)
{
#if UNITY_EDITOR
     if (Application.isPlaying)
        return renderer.materials;//运行时允许多不同 所以.material
     else
        return renderer.sharedMaterials;//非运行时不能创建material
#else
        return renderer.materials;
#endif
}
MaterialPropertyBlock

降低内存使用,Editor使用此接口将使面板不可修改

var uMpb = new MaterialPropertyBlock()
renderer.GetPropertyBlock(uMpb)
uMpb.Setxxx(nameID, Value)
renderer.SetPropertyBlock(uMpb)

GetXXFormMPB/SetXXToMPB

引擎特供 单属性无需各处存放MaterialPropertyBlock

还原问题

需求:播放一个材质动画时,需要还原到开始播放前的样子,仅对某Property生效,没设置过MPB需要用Renderer.sharedMaterial.GetXXXX()获取材质属性原始值

  1. 使用Renderer.GetPropertyBlock()获取整个mpb
    若Get后,其他模块也改mpb,还原时会还原到开始播放时的样子,让这期间外部的设置无效
  2. 使用GetXXXFromMPB()获取单个属性的值
    但此接口存在问题,若没被设置过MPB获取到的将会是默认值如0

A:期望能有接口获取实际值面板值 (有mpb就获得mpb否则返回material原始值)

  1. 在CurveAnimator内记录自身改过什么MPB,若被改过就获取MPB,没改过就获取Renderer.Material.GetXX材质原始值
    缺点:存在风险,如果外部模块一起改同一属性,将会有错误,逻辑复杂和内存占用更多
  2. 引擎提供接口解决
     先 :  Has/GetPropertyInMPB
    然后 : HasProperty/materail.GetXX
Keyword

是Unity着色器中用来控制特定代码片段是否编译进最终着色器程序的一种技术。关键字通常用于实现各种可选效果,比如阴影、光照模型等。

compile
feature
Material.EnableKeyword(keyword);
Material.DisableKeyword(keyword);

Q:Animation动画不知道哪个是KeyWord没有获取接口,只SetProperty并不能起效,如果通过脚本提供的bool值并SetKeyWord进行K值无法热更,
A:最终依然K Property,但脚本在应用Property时会获取特性是否Keyword来设置KeyWord
renderer.sharedMaterial.shader.GetPropertyAttributes(propertyIndex)
[Keyword](a,b,c)

Animator结构

  1. Animator
    1. Layer[]
      1. IAnimation[]
        1. Node[]
          1. PropertyCurve []
            1. PropertyData
              1. name
              2. type
                1. Mat
                2. Transform
                3. RenderActor
                4. Active
            2. CurveData
              1. curve[]
              2. time, texture[]

CurveAnimator

namespace CurveAnimator
{
    // 曲线动画: 用于角色播放各种动画(材质,Transform,RenderActor,GameObject.Active/Visible)
    [ExecuteInEditMode]
    [DisallowMultipleComponent]
    public class CurveAnimator : MonoBehaviour
    {
        public Action<int, StateNotifyType, string> stateNotify;//Layer, state, animName
        public AnimatorContext context { get; protected set; }
        public enum UpdateMode
        {
            Normal = 0,
            Manual,
        }
        public UpdateMode updateMode = UpdateMode.Normal;
        public bool isPause = false;
        public int layerCount { get => _layers == null ? 0 : _layers.Length; }

        private AnimationLayer[] _layers;

        public void Start()
        {
            Init();
        }
        
        public void Clear()
        {
            context = null;
            _layers = null;
        }

        /// <summary>
        /// 获取Material原始参数,创建动画层,Clear后可以重新Init
        /// Start会自动调用,当需要额外设置可以提前调用
        /// </summary>
        /// <param name="numLayer">动画层数,用于多层动画同时表现,例如上下身移动射击,身体和武器不同表现</param>
        /// <param name="ignoreSetNode">是否忽视设置节点和节点的child,用于排除不想控制的节点</param>
        public void Init(int numLayer = 1, bool ignoreSetNode = false)
        {
            if (_layers != null)
                return;

            context = new AnimatorContext(gameObject, this, ignoreSetNode);
            _layers = new AnimationLayer[numLayer];
            for (int i = 0; i < numLayer; i++)
                _layers[i] = new AnimationLayer(context, i);
        }
        
        /// 重新获取所有节点和Cache节点的Renderer信息,可重复调用当换装时
        public void ReInitRender()
        {
            context.GetAllRendererInfo();
            context.GetCacheRendererInfo();
            //已播放的动画缓存的render也需重新init
            foreach (var layer in _layers)
            {
                foreach (var anim in layer.AnimationList.Values)
                    anim.InitProperty();
            }
        }

        /// (优化向接口)可以设置需要提前缓存的Renderer节点,固定节点0GC
        public void SetCacheRenderer(List<string> paths)
        {
            context.SetCacheRenderer(paths);
            context.GetCacheRendererInfo();
        }
        /// (优化向接口)可以提前初始化动画,让动画全程0GC
        public void PreAddAnimation(MultiAnimData multiAnimData, int layerIndex = 0)
        {
            if (_layers == null)
                return;
            _layers[Mathf.Clamp(layerIndex, 0, _layers.Length - 1)].PreAddAnim(multiAnimData);
        }

        /// <summary>
        /// 播放一个保存为Asset的动画数据,外部加载好Asset传进来
        /// </summary>
        /// <returns>是否播放成功</returns>
        public bool Play(CurveAnimAsset curveAnimAsset, int layerIndex = 0, float overrideDuration = 0)
        {
            if (_layers == null)
                return false;
            if (!curveAnimAsset)
                return false;
            return _layers[Mathf.Clamp(layerIndex, 0, _layers.Length - 1)].Play(curveAnimAsset, overrideDuration);
        }
        /// 播放一个动画数据类
        public bool Play(MultiAnimData multiAnimData, int layerIndex = 0, float overrideDuration = 0)
        {
            if (_layers == null)
                return false;
            if (multiAnimData == null)
                return false;
            return _layers[Mathf.Clamp(layerIndex, 0, _layers.Length - 1)].Play(multiAnimData, overrideDuration);
        }
        /// 播放一个已经存在Animator中的动画
        public bool Play(string animName, int layerIndex = 0, float overrideDuration = 0)
        {
            if (_layers == null)
                return false;
            if (string.IsNullOrEmpty(animName))
                return false;
            return _layers[Mathf.Clamp(layerIndex, 0, _layers.Length - 1)].Play(animName, overrideDuration);
        }
        
        public void Sample(string animName, float time, int layerIndex = 0)
        {
            if (_layers == null)
                return;
            if (string.IsNullOrEmpty(animName))
                return;

            _layers[Mathf.Clamp(layerIndex, 0, _layers.Length - 1)].Sample(animName, time);
        }

        private void Update()
        {
            if (updateMode != UpdateMode.Normal || isPause)
                return;

            _OnUpdate(Time.deltaTime);
        }
        public void Update(float deltaTime)
        {
            if (updateMode != UpdateMode.Manual)
                return;

            _OnUpdate(deltaTime);
        }
        private void _OnUpdate(float deltaTime)
        {
            if (_layers == null)
                return;

            for (int i = 0; i < _layers.Length; i++)
            {
                _layers[i].Update(deltaTime);
            }
        }

        private void LateUpdate()
        {
            if (updateMode != UpdateMode.Normal || isPause)
                return;

            OnLateUpdate();
        }
        public void OnLateUpdate()
        {
            if (_layers == null)
                return;
            for (int i = 0; i < _layers.Length; i++)
            {
                _layers[i].LateUpdate();
            }
        }

        /// <summary>
        /// 停止某个动画,一段直接结束,三段进入End
        /// </summary>
        /// <param name="curveAnimAsset">动画Asset</param>
        /// <param name="layerIndex"></param>
        /// <returns></returns>
        public bool Stop(CurveAnimAsset curveAnimAsset, int layerIndex = 0)
        {
            if (_layers == null)
                return false;
            if (!curveAnimAsset)
                return false;
            return _layers[Mathf.Clamp(layerIndex, 0, _layers.Length - 1)].Stop(curveAnimAsset);
        }
        public bool Stop(string animName, int layerIndex = 0)
        {
            if (_layers == null)
                return false;
            if (string.IsNullOrEmpty(animName))
                return false;
            return _layers[Mathf.Clamp(layerIndex, 0, _layers.Length - 1)].Stop(animName);
        }

        /// <summary>
        /// 移除某个动画,正在播放的会停止根据isRevert是否还原
        /// </summary>
        /// <param name="name"></param>
        /// <param name="layerIndex"></param>
        /// <param name="isRevert">覆盖配置</param>
        /// <param name="removeCache">是否移除Cache,移除后下次会重新播放</param>
        /// <returns></returns>
        public bool Remove(string name, int layerIndex = 0, bool isRevert = false, bool removeCache = true)
        {
            if (_layers == null)
                return false;

            return _layers[Mathf.Clamp(layerIndex, 0, _layers.Length - 1)].Remove(name, isRevert, removeCache);
        }

        /// 移除所有在播和要播动画,走正常结束和revert逻辑
        /// <param name="removeCache"></param>
        public void RemoveAll(bool removeCache = false)
        {
            if (_layers == null)
                return;

            for (int i = 0; i < _layers.Length; i++)
            {
                _layers[i].RemoveAll(removeCache);
            }
        }

        /// <summary>
        /// 移除在播和要播动画,并恢复到原始mpb
        /// </summary>
        /// <param name="resetCache">是否移除cache已播动画</param>
        public void ResetAnimator(bool resetCache = false)
        {
            if (_layers == null)
                return;

            for (int i = 0; i < _layers.Length; i++)
            {
                _layers[i].Reset(resetCache);
            }
            context.ResetAllRenderer();
        }

        public AnimationLayer GetLayer(int index)
        {
            if (index < 0 || _layers == null || index >= _layers.Length)
            {
                return null;
            }

            return _layers[index];
        }

        private void OnEnable()
        {
#if UNITY_EDITOR
            if (!Application.isPlaying)
                Init();
#endif
        }
        public enum StateNotifyType
        {
            // 状态进入
            Enter,

            // 退出(end播完或打断
            Exit,
        }

    }
}

CurveAnimatorUtil

using System;
using System.Collections.Generic;
using Unity.Profiling;
using UnityEngine;

namespace BattleCurveAnimator
{
    public static class CurveAnimatorUtil
    {
        //<shader, <property, keyword>>
        public static Dictionary<string, Dictionary<string,List<string>>> shaderPropetys = new Dictionary<string, Dictionary<string, List<string>>>();

        public delegate string[] StringSplitFunc(string str, params char[] separator);
        public static event StringSplitFunc _splitFunc;

        public static string[] targetShaders = null;
        public static string[] ignoreGoNames = null;

        public static ProfilerMarker animationLayerAddAnim = new ProfilerMarker("CurveAnim.AnimationLayer.AddAnim()");
        public static ProfilerMarker animationLayerPlay = new ProfilerMarker("CurveAnim.AnimationLayer.Play()");
        public static ProfilerMarker animationLayerStop = new ProfilerMarker("CurveAnim.AnimationLayer.Stop()");
        public static ProfilerMarker animationLayerSample = new ProfilerMarker("CurveAnim.AnimationLayer.Sample()");
        public static ProfilerMarker animationLayerLateUpdate = new ProfilerMarker("CurveAnim.AnimationLayer.LateUpdate()");

        public static ProfilerMarker animationInitData = new ProfilerMarker("CurveAnim.Animation.InitData()");
        public static ProfilerMarker animationBeforPlay = new ProfilerMarker("CurveAnim.Animation.BeforPlay()");
        public static ProfilerMarker animationPlay = new ProfilerMarker("CurveAnim.Animation.Play()");

        public static ProfilerMarker nodeCtor = new ProfilerMarker("CurveAnim.Node.Play()");
        public static ProfilerMarker nodeGetUncacheRender = new ProfilerMarker("CurveAnim.Node.GetUncacheRender()");

        public static ProfilerMarker materialPropertyCtor = new ProfilerMarker("CurveAnim.MaterialProperty.ctor()");
        public static ProfilerMarker materialPropertySetFloat = new ProfilerMarker("CurveAnim.MaterialProperty.SetFloat()");
        public static ProfilerMarker materialPropertySetVector = new ProfilerMarker("CurveAnim.MaterialProperty.SetVector()");
        public static ProfilerMarker materialPropertySetColor = new ProfilerMarker("CurveAnim.MaterialProperty.SetColor()");
        public static ProfilerMarker materialPropertySetTexture = new ProfilerMarker("CurveAnim.MaterialProperty.SetTexture()");
        public static ProfilerMarker materialPropertyRevert = new ProfilerMarker("CurveAnim.MaterialProperty.Revert()");

        public static ProfilerMarker utilGetPapeKeyword = new ProfilerMarker("CurveAnim.Util.GetPapeKeyword()");
        public static ProfilerMarker utilSetPapeKeyWord = new ProfilerMarker("CurveAnim.Util.SetPapeKeyWord()");

        public static void SetTargetShader(string[] target)
        {
            targetShaders = target;
        }
        public static void SetIgnoreGoName(string[] ignore)
        {
            ignoreGoNames = ignore;
        }

        public static bool IsTargetShader(string shaderName)
        {
            if (targetShaders == null)
                return true;

            foreach (var s in targetShaders)
            {
                if (shaderName.Contains(s))
                    return true;
            }
            return false;
        }

        public static bool IsTargetGo(GameObject targetOjb)
        {
            if (ignoreGoNames == null)
                return true;

            var targetObjParent = targetOjb.transform;
            while (targetObjParent != null)
            {
                foreach (var name in ignoreGoNames)
                {
                    if (targetObjParent.name == name)
                    {
                        //Debug.LogError("Ignore:" + targetObjParent.name, targetOjb);
                        return false;
                    }
                }
                targetObjParent = targetObjParent.parent;
            }
            return true;
        }

        public static float GetFloat(Renderer renderer, int name)
        {
            if (renderer.HasPropertyInMPB(name))
                return renderer.GetFloatFromMPB(name);
            if (GetMaterials(renderer)[0].HasProperty(name))
                return GetMaterials(renderer)[0].GetFloat(name);
            LogProxy.LogWarningFormat("[CurveAnimator]获取{0}获取材质property:{1}失败", renderer, name);
            return 0f;
        }
        public static Vector4 GetVector(Renderer renderer, int name)
        {
            if (renderer.HasPropertyInMPB(name))
                return renderer.GetVectorFromMPB(name);
            if (GetMaterials(renderer)[0].HasProperty(name))
                return GetMaterials(renderer)[0].GetVector(name);
            LogProxy.LogWarningFormat("[CurveAnimator]获取{0}获取材质property:{1}失败", renderer, name);
            return Vector4.zero;
        }
        public static Color GetColor(Renderer renderer, int name)
        {
            if (renderer.HasPropertyInMPB(name))
                return renderer.GetColorFromMPB(name);
            if (GetMaterials(renderer)[0].HasProperty(name))
                return GetMaterials(renderer)[0].GetColor(name);
            LogProxy.LogWarningFormat("[CurveAnimator]获取{0}获取材质property:{1}失败", renderer, name);
            return Color.white;
        }
        public static Texture GetTexture(Renderer renderer, int name)
        {
            if (renderer.HasPropertyInMPB(name))
                return renderer.GetTextureFromMPB(name);
            if (GetMaterials(renderer)[0].HasProperty(name))
                return GetMaterials(renderer)[0].GetTexture(name);
            LogProxy.LogWarningFormat("[CurveAnimator]获取{0}获取材质property:{1}失败", renderer, name);
            return null;
        }

        public static bool GetPapeKeyword(Renderer renderer, string shaderName, string property)
        {
            using (utilGetPapeKeyword.Auto())
            {
                //先找是否有记录
                if (shaderPropetys.TryGetValue(shaderName, out var shaderPropKeywords))
                {
                    if (shaderPropKeywords.TryGetValue(property, out var keywords))
                    {
                        if (keywords != null)
                        {
                            //GetKeywordOriValue(renderer, property);
                            return true;
                        }
                        else
                        {
                            return false;
                        }
                    }
                }

                //取特性
                int propertyIndex = renderer.sharedMaterial.shader.FindPropertyIndex(property);
                if (propertyIndex != -1)
                {
                    string[] attributes = renderer.sharedMaterial.shader.GetPropertyAttributes(propertyIndex);
                    //判断是否Keyword
                    foreach (var attr in attributes)
                    {
                        if (attr.Contains("PapeKeyword"))
                        {
                            //否则开始解析
                            var attrContent = Spilt(attr, '(', ')');
                            if (attrContent.Length >= 2)
                            {
                                if (!shaderPropetys.ContainsKey(shaderName))
                                {
                                    shaderPropetys[shaderName] = new Dictionary<string, List<string>>();
                                }
                                var propKeywords = shaderPropetys[shaderName];
                                if (!propKeywords.ContainsKey(property))
                                {
                                    propKeywords[property] = new List<string>();
                                }

                                var keywordContent = Spilt(attrContent[1], ',');
                                for (int j = 0; j < keywordContent.Length; j++)
                                {
                                    propKeywords[property].Add(keywordContent[j]);
                                }
                            }
                            else
                            {
                                LogProxy.LogError($"[shader]:{shaderName}  Attributes PapeKeyWord 缺少Keyword:" + attr);
                            }
                            return true;
                        }
                    }
                }

                if (!shaderPropetys.ContainsKey(shaderName))
                    shaderPropetys[shaderName] = new Dictionary<string, List<string>>();
                shaderPropetys[shaderName][property] = null;
                return false;
            }
        }

        public static void SetPapeKeyword(Material[] materials, string shaderName, string property, float value)
        {
            using (utilSetPapeKeyWord.Auto())
            {
                if (shaderPropetys.TryGetValue(shaderName, out var shaderPropKeywords))
                {
                    if (shaderPropKeywords.TryGetValue(property, out var saveKeywords) && saveKeywords != null)
                    {
                        for (int j = 0; j < saveKeywords.Count; j++)
                        {
                            foreach (var mat in materials)
                                SetKeyword(mat, saveKeywords[j], value == j + 1);
                        }
                    }
                }
            }
        }
        public static void SetKeyword(Material m, string keyword, bool state)
        {
            if (m == null)
                return;
            if (state)
                m.EnableKeyword(keyword);
            else
                m.DisableKeyword(keyword);
        }

        public static Material[] GetMaterials(Renderer renderer)
        {
#if UNITY_EDITOR
            if (Application.isPlaying)
                return renderer.materials;//运行时允许多怪 所以.material
            else
                return renderer.sharedMaterials;//非运行时不能创建material
#else
                return renderer.materials;
#endif
        }

        public static void LogError(object message, UnityEngine.Object context = null)
        {
#if UNITY_EDITOR
            Debug.LogWarning(message, context);
#else
            LogProxy.LogWarning(message);
#endif
        }

        //TODO 有待长空提供function
        public static void SetSplitFunc(StringSplitFunc func)
        {
            _splitFunc = func;
        }

        public static string[] Spilt(string str, params char[] separator)
        {
            if(_splitFunc != null)
            {
                return _splitFunc.Invoke(str, separator);
            }
            else
            {
                return str.Split(separator);
            }
        }
    }
}

AnimatorContext

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

namespace BattleCurveAnimator
{
    public class AnimatorContext
    {
        public static string rootPath = "$root"; //设定$root表示自身,避免与root路径重复
        public static string bodyPath = "Body";//补丁:怪物没有Body节点,怪物Body=$root
        
        public GameObject go { get; protected set; }
        public string pName { get; }
        public CurveAnimator animator { get; }
        private bool _ignoreSetNode;

        //root,默认存在,用于resetMaterial
        private List<MaterialPropertyBlock> rootOrigMpbs= new List<MaterialPropertyBlock>();
        private List<Renderer> rootRenderers= new List<Renderer>();
        private List<Material[]> rootMaterials= new List<Material[]>();
        private List<string> rootShaderNames= new List<string>();

        //cache,外部设置,用于提前缓存运行时0gc
        private List<string> cachePaths;
        private Dictionary<string, List<Renderer>> cacheRenderers;
        private Dictionary<string, List<Material[]>> cacheMaterials;
        private Dictionary<string, List<string>> cacheShaderNames;

        public AnimatorContext(GameObject go, CurveAnimator animator, bool ignoreSetNode)
        {
            if (go == null)
                return;

            this.go = go;
            this.animator = animator;
            pName = go.transform.parent != null ? go.transform.parent.name : go.name;
            _ignoreSetNode = ignoreSetNode;
            GetAllRendererInfo();
        }

        //初始化时,获取所有renderOrig,用于Reset  
        public void GetAllRendererInfo()
        {
            var renders = go.GetComponentsInChildren<Renderer>(true);
            if (renders == null) return;

            rootOrigMpbs.Clear();
            rootRenderers.Clear();
            rootMaterials.Clear();
            rootShaderNames.Clear();
            for (var k = renders.Length - 1; k >= 0; k--)
            {
                if (_ignoreSetNode && !CurveAnimatorUtil.IsTargetGo(renders[k].gameObject)) continue;
                var material = renders[k].sharedMaterial;
                if (material == null)
                    continue;
                var shaderName = material.shader.name;
                if (!CurveAnimatorUtil.IsTargetShader(shaderName))
                    continue;
                var mpb = new MaterialPropertyBlock();
                renders[k].GetPropertyBlock(mpb);
                rootRenderers.Add(renders[k]);
                rootMaterials.Add(CurveAnimatorUtil.GetMaterials(renders[k]));
                rootShaderNames.Add(shaderName);
                rootOrigMpbs.Add(mpb);
            }
        }
        public void ResetAllRenderer()
        {
            for (int i = 0; i < rootRenderers.Count; i++)
            {
                if (rootRenderers[i] == null) continue;//可能会删掉
                rootRenderers[i].SetPropertyBlock(rootOrigMpbs[i]);
            }
        }

        //设置Cache节点路径
        public void SetCacheRenderer(List<string> paths)
        {
            cachePaths = paths;
        }
        //获取Cache节点 提前缓存优化GC
        public void GetCacheRendererInfo()
        {
            if(cachePaths == null || cachePaths.Count == 0) return;
            
            cacheRenderers = new Dictionary<string, List<Renderer>>();
            cacheMaterials = new Dictionary<string, List<Material[]>>();
            cacheShaderNames = new Dictionary<string, List<string>>();
            foreach (var cacheName in cachePaths)
            {
                var cacheTransform = go.transform.Find(cacheName);
                if (cacheTransform == null) continue;

                var renderers = cacheTransform.GetComponentsInChildren<Renderer>(true);
                if (renderers.Length == 0) continue;

                cacheRenderers[cacheName] = new List<Renderer>();
                cacheMaterials[cacheName] = new List<Material[]>();
                cacheShaderNames[cacheName] = new List<string>();
                for (int k = renderers.Length - 1; k >= 0; k--)
                {
                    if (_ignoreSetNode && !CurveAnimatorUtil.IsTargetGo(renderers[k].gameObject)) continue;
                    Material material = renderers[k].sharedMaterial;
                    if (material == null) continue;
                    string shaderName = material.shader.name;
                    if (!CurveAnimatorUtil.IsTargetShader(shaderName)) continue;

                    cacheRenderers[cacheName].Add(renderers[k]);
                    cacheMaterials[cacheName].Add(CurveAnimatorUtil.GetMaterials(renderers[k]));
                    cacheShaderNames[cacheName].Add(shaderName);
                }
            }
        }

        public bool GetCacheRenderer(string path, out List<Renderer> rs, out List<Material[]> ms, out List<string> ss)
        {
            rs = null;
            ms = null;
            ss = null;
            if (path == rootPath)
            {
                rs = rootRenderers;
                ms = rootMaterials;
                ss = rootShaderNames;
                return true;
            }
            //X3Battle补丁:怪物没有Body节点,怪物Body=$root
            if (path == bodyPath && cacheRenderers != null && !cacheRenderers.ContainsKey(bodyPath))
            {
                rs = rootRenderers;
                ms = rootMaterials;
                ss = rootShaderNames;
                return true;
            }
            if (cacheRenderers != null && cacheRenderers.TryGetValue(path, out rs) &&
                cacheMaterials != null && cacheMaterials.TryGetValue(path, out ms) &&
                cacheShaderNames != null && cacheShaderNames.TryGetValue(path, out ss))
            {
                return true;
            }

            rs = new List<Renderer>();
            ms = new List<Material[]>();
            ss = new List<string>();
            return false;
        }
    }
}

AnimationLayer

using System.Collections.Generic;
using UnityEngine;

namespace BattleCurveAnimator
{
    // 管理多个动画层级,用于支持同时播放多个动画,例如武器和身体
    public class AnimationLayer
    {        
        public AnimatorContext context { get; protected set; }

        public List<IAnimation> PlayingAnimations => _playingAnims;
        public Dictionary<string, IAnimation> AnimationList => _cacheAnims;

        protected int _layerIndex;
        protected float _deltaTime;

        //播放完的列表会存下来 可以用名字直接播里面的动画
        protected Dictionary<string, IAnimation> _cacheAnims = new Dictionary<string, IAnimation>();
        //正在播放列表
        protected List<IAnimation> _playingAnims = new List<IAnimation>();
        //准备播放列队(动画,是否播放/sample时间)
        protected Dictionary<IAnimation, AnimParam> _requestAnims = new Dictionary<IAnimation, AnimParam>(4);
        protected Dictionary<IAnimation, float> _sampleAnims = new Dictionary<IAnimation, float>(4);

        protected struct AnimParam
        {
            public bool isPlay;
            public float overrideDuration;

            public AnimParam(bool isPlay, float overrideDuration = 0)
            {
                this.isPlay = isPlay;
                this.overrideDuration = overrideDuration;
            }
        }

        public AnimationLayer(AnimatorContext Context, int index, CurveAnimAsset[] assets = null)
        {
            this.context = Context;
            _layerIndex = index;
            for (int i = 0; assets != null && i < assets.Length; i++)
            {
                _AddAnim(assets[i].multiAnimData.name, assets[i].multiAnimData.anims);
            }
        }

        public void PreAddAnim(MultiAnimData multiAnimData)
        {
            _AddAnim(multiAnimData.name, multiAnimData.anims);
        }

        protected IAnimation _AddAnim(string animName, AnimData[] anims)
        {
            if (anims == null || anims.Length <= 0)
                return null;
            if (_cacheAnims.TryGetValue(animName, out var result))
            {
                //PapeGames.X3.LogProxy.Log($"Curve Animator播放重复的Anim:({animName})!");
                return result;
            }
            using (CurveAnimatorUtil.animationLayerAddAnim.Auto())
            {
                if (anims.Length > 1)
                    _cacheAnims.Add(animName, result = new MultiAnimation(animName, anims, context));
                else
                    _cacheAnims.Add(animName, result = new Animation(animName, anims[0], context));
            }

            return result;
        }

        public bool Play(CurveAnimAsset asset, float overrideDuration = 0)
        {
            var anim = _AddAnim(asset.multiAnimData.name, asset.multiAnimData.anims);
            if (anim == null)
                return false;

            _requestAnims[anim] = new AnimParam(true, overrideDuration);
            return true;
        }
        public bool Play(MultiAnimData multiAnimData, float overrideDuration = 0)
        {
            var anim = _AddAnim(multiAnimData.name, multiAnimData.anims);
            if (anim == null)
                return false;

            _requestAnims[anim] = new AnimParam(true, overrideDuration);
            return true;
        }
        public bool Play(string animName, float overrideDuration = 0)
        {
            _cacheAnims.TryGetValue(animName, out var anim);
            if (anim == null)
                return false;

            _requestAnims[anim] = new AnimParam(true, overrideDuration);
            return true;
        }
        private bool _Play(IAnimation result, float overrideDuration = 0)
        {
            if (result == null)
                return false;

            foreach(var playing in _playingAnims)
            {
                if(result == playing && result is MultiAnimation && !result.isEnding)
                {
                    return false;//同动画 多段 播放阶段 再次播放时不做处理
                }
            }
            using (CurveAnimatorUtil.animationLayerPlay.Auto())
            {
                //先让之前的动画Revert掉 然后Play时取原始的表现
                if (_playingAnims.Count > 0)
                    _playingAnims[_playingAnims.Count - 1].Revert();
                result.BeforPlay();//先获取初始材质
                result.Play(overrideDuration);//立即更新,防止EndRever的设置会闪一下
                context.animator.stateNotify?.Invoke(_layerIndex, CurveAnimator.StateNotifyType.Enter, result.name);
                if (_playingAnims.IndexOf(result) < 0)
                {
                    _playingAnims.Add(result);
                }
            }
            return true;
        }

        public void Sample(string name, float time)
        {
            if (_cacheAnims.TryGetValue(name, out var anim))
            {
                _sampleAnims[anim] = time;
            }
        }
        public void Update(float dt)
        {
            _deltaTime = dt;
        }
        public void LateUpdate()
        {
            foreach(var animParam in _requestAnims)
            {
                if(animParam.Value.isPlay)
                {
                    _Play(animParam.Key, animParam.Value.overrideDuration);
                }
                else
                {
                    _Stop(animParam.Key);
                }
            }
            _requestAnims.Clear();
            using (CurveAnimatorUtil.animationLayerSample.Auto())
            {
                foreach (var sampleAnim in _sampleAnims)
                {
                    if (_playingAnims.Contains(sampleAnim.Key))
                        sampleAnim.Key.Sample(sampleAnim.Value);
                }
                _sampleAnims.Clear();
            }

            using (CurveAnimatorUtil.animationLayerLateUpdate.Auto())
            {
                for (int i = _playingAnims.Count - 1; i >= 0; i--)
                {
                    var _animation = _playingAnims[i];
                    if (_animation.isAutoUpdate)
                    {
                        _animation.isBackground = i != _playingAnims.Count - 1;
                        _animation.Update(_deltaTime);
                    }
                    if (_animation.isWaitRemove)
                    {
                        _Remove(_animation);
                        continue;
                    }
                }
            }
        }


        public bool Stop(CurveAnimAsset asset)
        {
            return Stop(asset.multiAnimData.name);
        }
        public bool Stop(string name)
        {
            if (_cacheAnims.TryGetValue(name, out var anim))
            {
                if (_playingAnims.Contains(anim) || _requestAnims.ContainsKey(anim))
                {
                    _requestAnims[anim] = new AnimParam(false);
                    return true;
                }
            }

            return false;
        }
        protected void _Stop(IAnimation anim)
        {
            using (CurveAnimatorUtil.animationLayerStop.Auto())
            {
                //仅播放时才stop 否则存在顺序问题 获取的是
                if (anim.isPlaying)
                    anim.Stop();
            }
        }

        public bool Remove(string name, bool isRevert = false, bool removeCache = true)
        {
            if (_cacheAnims.TryGetValue(name, out var anim))
            {
                if (isRevert || anim.isEndRevert)
                    anim.Revert();
                _Remove(anim);
                if (removeCache)
                    _cacheAnims.Remove(name);
                return true;
            }

            return false;
        }
        private void _Remove(IAnimation anim)
        {
            _playingAnims.Remove(anim);
            context.animator.stateNotify?.Invoke(_layerIndex, CurveAnimator.StateNotifyType.Exit, anim.name);
        }

        public void RemoveAll(bool removeCache = false)
        {
            for (int i = _playingAnims.Count - 1; i >= 0; i--)
            {
                if (_playingAnims[i].isEndRevert)
                    _playingAnims[i].Revert();
                _Remove(_playingAnims[i]);
            }
            _playingAnims.Clear();
            _requestAnims.Clear();
            _sampleAnims.Clear();
            if (removeCache)
                _cacheAnims.Clear();
        }

        public void Reset(bool resetCache = false)
        {
            for (int i = _playingAnims.Count - 1; i >= 0; i--)
            {
                _Remove(_playingAnims[i]);
            }
            _playingAnims.Clear();
            _requestAnims.Clear();
            _sampleAnims.Clear();
            if (resetCache)
                _cacheAnims.Clear();
        }
    }
}

IAnimation

using UnityEngine;

namespace BattleCurveAnimator
{
    public enum State
    {
        None,
        Begin,
        Update,
        End,
    }
    public enum AnimIndex
    {
        Begin = 0,
        Loop,
        End
    }

    public interface IAnimation
    {
        string name { get; }
        //如果是后台模式,动画更新时,属性不会被设置
        bool isBackground { get; set; }
        bool isPlaying { get; }
        bool isEnding { get; }
        bool isWaitRemove { get; }
        bool isAutoUpdate { get; }
        bool isEndRevert { get; }

        //初始化属性,获取操作的render,允许重新获取
        void InitProperty();
        //播放前,记录获取原始数值
        void BeforPlay();
        //应用第一帧
        void Play(float overrideDuration = 0);
        void Sample(float time);
        void Update(float dt);
        //停止播放/进入End阶段
        void Stop();
        //恢复到播放前的材质 例如:被顶掉,播放结束且需要还原
        void Revert();
    }
}

Animation

using UnityEngine;

namespace BattleCurveAnimator
{
    public class Animation : IAnimation
    {
        public string name { get; set; }
        public AnimData data { get; set; }
        public AnimatorContext context { get; set; }
        public float currentTime { get => _curTime; }
        public float realDuration { get => _overrideDuration > 0 ? _overrideDuration : data.length; }
        public bool isPlaying { get => _curTime >= 0 && _state != State.End; }
        public bool isEnding { get => _state == State.End; }
        public bool isAutoUpdate { get; set; }
        public bool isEndRevert { get => data.isEndRevert; }
        public bool isBackground { get; set; }//如果是后台模式,动画更新时,属性不会被设置
        public bool isWaitRemove { get => _state == State.End || !data.isLoop && _curTime >= realDuration; }//Stop或者时间到了就移除

        protected Node[] _nodes;
        protected float _curTime = -1;
        protected float _overrideDuration = 0;
        protected State _state = State.None;

        public Animation() { }

        public Animation(string name, AnimData data, AnimatorContext context)
        {
            this.name = name;
            this.data = data;
            this.context = context;
            InitData();
        }

        public void InitData()
        {
            using (CurveAnimatorUtil.animationInitData.Auto())
            {
                isAutoUpdate = true;
                _nodes = new Node[data.nodeDatas.Length];
                for (int i = 0; i < data.nodeDatas.Length; i++)
                {
                    _nodes[i] = new Node(this.data.nodeDatas[i], context, this.name);
                }
            }
        }

        public void InitProperty()
        {
            for (int i = 0; i < data.nodeDatas.Length; i++)
            {
                _nodes[i].InitProperty();
            }
        }

        public void BeforPlay()
        {
            using (CurveAnimatorUtil.animationBeforPlay.Auto())
            {
                for (int i = 0; i < _nodes.Length; i++)
                {
                    _nodes[i].BeforPlay();
                }
            }
        }

        public void Play(float overrideDuration = 0)
        {
            using (CurveAnimatorUtil.animationPlay.Auto())
            {
                _curTime = 0;
                _overrideDuration = overrideDuration;
                _state = State.Begin;
                isAutoUpdate = true;
                if (!isBackground)
                {
                    for (int i = 0; i < _nodes.Length; i++)
                    {
                        _nodes[i].Play();
                    }
                }
            }
        }

        public void Sample(float time)
        {
            if (isEnding)
                return;
            
            _curTime = time;
            isAutoUpdate = false;
            Update(0);
        }

        public void Update(float dt)
        {            
            if (_state == State.Begin)
            {
                _state = State.Update;
                _ApplyProps();
            }
            else if (_state == State.Update)
            {
                _curTime += dt;
                if (_curTime >= data.length)
                {
                    _curTime = data.isLoop ? _curTime % data.length : data.length;
                }
                _ApplyProps();

                if (isWaitRemove)
                {
                    Stop();
                }
            }
        }

        public void Stop()
        {
            _state = State.End;
            for (int i = 0; i < _nodes.Length; i++)
            {
                if (data.isEndRevert && !isBackground)
                    _nodes[i].Revert();
            }
        }

        public void Revert()
        {
            for (int i = 0; i < _nodes.Length; i++)
            {
                _nodes[i].Revert();
            }
        }

        protected void _ApplyProps()
        {
            //如果是后台模式,属性不进行设置
            if (isBackground)
            {
                return;
            }

            for (int i = 0; i < _nodes.Length; i++)
            {
                _nodes[i].Update(_curTime);
            }
        }
    }
}

MultiAnimation

using UnityEngine;

namespace BattleCurveAnimator
{
    public class MultiAnimation : IAnimation
    {
        public string name { get; set; }
        public AnimData[] animDatas { get; set; }
        public AnimatorContext context { get; set; }
        public AnimIndex animIndex => _index;
        public Animation curAnim { get => _anims[(int)_index]; }
        public bool isPlaying { get => _index != AnimIndex.End || curAnim.isPlaying; }
        public bool isEnding { get => _index == AnimIndex.End; }
        public bool isAutoUpdate { get; set; }
        public bool isEndRevert { get => _anims[_anims.Length - 1].data.isEndRevert; }
        public bool isBackground
        {
            get => _anims[(int)_index].isBackground;
            set
            {
                foreach (var anim in _anims)
                {
                    anim.isBackground = value;
                }
            }
        }
        public bool isWaitRemove { get => !isPlaying; }

        protected Animation[] _anims;
        protected AnimIndex _index = AnimIndex.End;

        public MultiAnimation() { }

        public MultiAnimation(string name, AnimData[] animDatas, AnimatorContext context)
        {
            this.name = name;
            this.animDatas = animDatas;
            this.context = context;
            InitData();
        }
        public void InitData()
        {
            isAutoUpdate = true;
            _anims = new Animation[this.animDatas.Length];
            for (int i = 0; i < this.animDatas.Length; i++)
            {
                _anims[i] = new Animation(name, this.animDatas[i], context);
            }
        }

        public void InitProperty()
        {
            foreach (var anim in _anims)
            {
                anim.InitProperty();
            }
        }

        public void BeforPlay()
        {
            //即使不Revert也要记录,因为被其他动画顶掉也要Revert
            _anims[(int)AnimIndex.Begin].BeforPlay();
        }

        public void Play(float overrideDuration = 0)
        {
            isAutoUpdate = true;
            _index = AnimIndex.Begin;
            curAnim.Play();
        }

        //TODO Sample与Update整理到一个方法
        public void Sample(float time)
        {
            var curAnimTime = time;
            isAutoUpdate = false;
            if (time < animDatas[0].length)
            {
                _index = AnimIndex.Begin;
            }
            else if (time < animDatas[1].length + animDatas[0].length)
            {
                if (_index != AnimIndex.Loop)
                {
                    _index = AnimIndex.Loop;
                    curAnim.Play();
                }
                curAnimTime = time - animDatas[0].length;
            }
            else
            {
                _index = AnimIndex.End;
                curAnimTime = time - animDatas[0].length - animDatas[1].length;
            }

            curAnim.Sample(curAnimTime);//TODO 确保跑到最后一帧(因为有可能关键帧K到最后)
        }

        public void Update(float dt)
        {
            curAnim.Update(dt);
            if (!curAnim.isWaitRemove)//时间是否结束
            {
                return;
            }

            if (_index == AnimIndex.End)//时间结束,并且是End
            {
                if (!isBackground)
                    Revert();
            }
            else if (!curAnim.data.isLoop)//进入Loop
            {
                _index += 1;
                curAnim.Play();
            }
        }

        public void Stop()
        {
            _index = AnimIndex.End;
            curAnim.Play();
        }

        public void Revert()
        {
            if (isEndRevert)
                _anims[(int)AnimIndex.Begin].Revert();
        }
    }
}

Node

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

namespace BattleCurveAnimator
{
    //每个Node操作多个Property
    public class Node
    {
        protected NodeData _data;
        protected PropertyCurve[] _propCurves;

        protected AnimatorContext _context;
        protected string _realPath;
        protected Transform _realTransform;
        protected string _animName;

        public Node(NodeData data, AnimatorContext context, string animName)
        {
            using (CurveAnimatorUtil.nodeCtor.Auto())
            {
                _data = data;
                _context = context;
                _animName = animName;

                //确定路径对象
                _realPath = string.IsNullOrEmpty(data.redirectPath) ? data.path : data.redirectPath;
                if (_realPath == AnimatorContext.rootPath)
                {
                    _realTransform = context.go.transform;
                }
                else
                {
                    var realTransform = context.go.transform.Find(_realPath);
                    if (realTransform != null)
                    {
                        _realTransform = realTransform;
                    }
                    else if (_realPath == AnimatorContext.bodyPath)
                    {
                        //TODO 怪物没Body 所以可能找不到 此处业务逻辑 不该这么处理 应该模型上如此
                        _realTransform = context.go.transform;
                    }
                    else if (realTransform == null)
                    {
                        CurveAnimatorUtil.LogError($"[CurveAnimator] {context.pName}播放:{animName},获取路径失败:{_realPath},找相关美术确认节点路径是否存在", context.go);
                    }
                }

                InitProperty();
            }
        }

        public void InitProperty()
        {
            if (_propCurves == null)
                _propCurves = new PropertyCurve[_data.propCurves.Length];
            for (int i = 0; i < _data.propCurves.Length; i++)
            {
                if (_data.propCurves[i].property.type == PropertyType.Material)
                {
                    if (_realTransform == null) continue;
                    bool isCache = _context.GetCacheRenderer(_realPath, out var renderers, out var materials, out var shaderNames);

                    using (CurveAnimatorUtil.nodeGetUncacheRender.Auto())
                    {
                        if (!isCache)
                        {
                            if (_data.isIncludeChild)
                            {
                                if (_realTransform != null)
                                {
                                    var allRenderers = _realTransform.GetComponentsInChildren<Renderer>(true);
                                    if (allRenderers.Length == 0)
                                    {
                                        CurveAnimatorUtil.LogError($"[CurveAnimator] {_context.go.name}的重定向到:{_realPath},获取子Render数量为0", _context.go);
                                        continue;
                                    }
                                    for (int k = allRenderers.Length - 1; k >= 0; k--)
                                    {
                                        Material material = allRenderers[k].sharedMaterial;
                                        if (material == null)
                                            continue;
                                        string shaderName = material.shader.name;
                                        bool isTargetShader = CurveAnimatorUtil.IsTargetShader(shaderName);
                                        if (!isTargetShader)
                                            continue;
                                        renderers.Add(allRenderers[k]);
                                        materials.Add(CurveAnimatorUtil.GetMaterials(allRenderers[k]));
                                        shaderNames.Add(shaderName);
                                    }
                                }
                            }
                            else
                            {
                                var r = _realTransform.GetComponent<Renderer>();
                                if (r == null)
                                {
                                    CurveAnimatorUtil.LogError($"[CurveAnimator] {_context.pName}播放:{_animName}Path:{_realPath}获取Render组件失败,找相关美术确认是否正确", _context.go);
                                    continue;
                                }
                                if (!CurveAnimatorUtil.IsTargetShader(r.sharedMaterial.shader.name))
                                    continue;
                                renderers.Add(r);
                                materials.Add(CurveAnimatorUtil.GetMaterials(r));
                                shaderNames.Add(r.sharedMaterial.shader.name);
                            }
                        }
                    }
                    _propCurves[i] = new PropertyCurve(_context, _data.propCurves[i], renderers, materials, shaderNames);
                }
                else if (_data.propCurves[i].property.type == PropertyType.Transform ||
                        _data.propCurves[i].property.type == PropertyType.Active ||
                        _data.propCurves[i].property.type == PropertyType.RenderActor)
                {
                    _propCurves[i] = new PropertyCurve(_context, _data.propCurves[i], _realPath);
                }
                else
                {
                }
            }
        }

        public void BeforePlay()
        {
            for (int i = 0; i < _propCurves.Length; i++)
            {
                _propCurves[i]?.BeforePlay();
            }
        }
        
        public void SetWeight(float v)
        {
            for (int i = 0; i < _propCurves.Length; i++)
            {
                _propCurves[i]?.SetWeight(v);
            }
        }
        public void SetPriority(int v)
        {
            for (int i = 0; i < _propCurves.Length; i++)
            {
                _propCurves[i]?.SetPriority(v);
            }
        }

        public void Play()
        {
            for (int i = 0; i < _propCurves.Length; i++)
            {
                _propCurves[i]?.Apply(0);
            }
        }

        public void Sample(float time)
        {
            for (int i = 0; i < _propCurves.Length; i++)
            {
                _propCurves[i]?.Apply(time);
            }
        }
        public void Update(float time)
        {
            for (int i = 0; i < _propCurves.Length; i++)
            {
                _propCurves[i]?.Apply(time);
            }
        }
        public void Revert()
        {
            for (int i = 0; i < _propCurves.Length; i++)
            {
                _propCurves[i]?.Revert();
            }
        }
    }
}

change

//获取绑定关系
            EditorCurveBinding[] curveBindings = AnimationUtility.GetCurveBindings(clip);
            EditorCurveBinding[] objectReferenceBindings =
                AnimationUtility.GetObjectReferenceCurveBindings(clip);

            //处理普通属性
            //一种是材质属性 material._color
            //一种是普通属性 m_localPostion
            foreach (var curveBinding in curveBindings)
            {
                string path = curveBinding.path;
                string propertyName = curveBinding.propertyName;

                TimelineUtil.CreateNodeData(nodeDataList, path);

                List<PropCurveData> propCurveDataList =
                    TimelineUtil.FindPropCurveDataList(propCurveDataListDic, path);
                AnimationCurve animationCurve =
                    AnimationUtility.GetEditorCurve(clip, curveBinding);

                //目前 m_Enabled 不支持转换
                if (propertyName == "m_Enabled")
                {
                    EditorUtility.DisplayDialog("材质动画装换", "不支持Enable,联系程序" , "确定");
                    continue;
                }

                //目前 ControlChildren 不支持控制子节点
                if (propertyName == "ControlChildren")
                {
                    continue;
                }

                //处理 Transform 属性的转换
                if (ChangeTransform(curveBinding, propertyName, propCurveDataList, animationCurve))
                {
                    continue;
                }

                //处理RenderActor的转换
                if (ChangeRenderActor(curveBinding, propertyName, propCurveDataList, animationCurve))
                {
                    continue;
                }

                //处理 m_IsActive 的转换
                if (propertyName == "m_IsActive")
                {
                    PropCurveData activePropCurveData =
                        TimelineUtil.FindPropCurveData(propertyName, propCurveDataList);
                    if (activePropCurveData == null)
                    {
                        activePropCurveData = TimelineUtil.CreatePropCurveData(curveBinding.type,
                            PropertyType.Active, PropertyDataType.Float, propertyName, 1);
                        propCurveDataList.Add(activePropCurveData);
                        activePropCurveData.curve.floatCurves[0] = animationCurve;
                    }

                    continue;
                }

                //处理 Material 属性
                if (ChangeMaterial(propertyName, propCurveDataList, animationCurve, curveBinding.type))
                {
                    continue;
                }
                
                EditorUtility.DisplayDialog("材质动画装换", $"不支持proerty:{propertyName},联系程序" , "确定");
            }

            //处理脚本属性
            //目前只支持matTexAnimhelper脚本
            foreach (var objectBinding in objectReferenceBindings)
            {
                string path = objectBinding.path;
                string propertyName = objectBinding.propertyName;

                TimelineUtil.CreateNodeData(nodeDataList, path);

                List<PropCurveData> propCurveDataList =
                    TimelineUtil.FindPropCurveDataList(propCurveDataListDic, path);
                ObjectReferenceKeyframe[] objectCurves =
                    AnimationUtility.GetObjectReferenceCurve(clip, objectBinding);

                //处理tex 属性
                ChangeTex(propertyName, propCurveDataList, objectCurves, objectBinding.type);
            }
            
            //复制重定项节点信息
            foreach (var info in asset.nodeInfos)
            {
                foreach (NodeData nodeData in nodeDataList)
                {
                    if (nodeData.path == info.path)
                    {
                        nodeData.redirectPath = "";
                        if (!string.IsNullOrEmpty(info.redirectPath))
                        {
                            nodeData.redirectPath = info.redirectPath;
                        }
                        else if (info.IsBodys)
                        {
                            nodeData.redirectPath = "Body";
                        }
                        else if (info.IsWeapon)
                        {
                            nodeData.redirectPath = "Weapon";
                        }
                        else if (info.IsRoots)
                        {
                            nodeData.redirectPath = "$root";
                        }

                        if (nodeData.redirectPath != "")
                        {
                            nodeData.isIncludeChild = true;
                        }
                    }
                }
            }
            
            //复制动画曲线
            foreach (NodeData nodeData in nodeDataList)
            {
                propCurveDataListDic.TryGetValue(nodeData.path,
                    out List<PropCurveData> propCurveDataList);
                if (propCurveDataList != null)
                {
                    nodeData.propCurves = propCurveDataList.ToArray();
                }
                else
                {
                    nodeData.propCurves = new PropCurveData[0];
                }
            }
            
            data.nodeDatas = nodeDataList.ToArray();
            data.isEndRevert = isEndRevert;
            
            return data;
        }

End

内容概要:本文聚焦于“面向电网频率稳定的VSG惯量阻尼协同自适应控制策略”研究,深入探讨虚拟同步发电机(VSG)在微电网中的关键作用。通过Simulink仿真平台Matlab编程实现,提出一种能够动态协同调节惯量和阻尼参数的自适应控制方法,旨在有效提升高比例可再生能源接入背景下电网的频率稳定性。研究系统阐述了VSG的核心控制原理,构建了惯量阻尼的动态调节机制,详细分析了系统的动态响应特性,并通过仿真实验验证了所提策略的有效性,成功解决了传统VSG因参数固定而导致的动态响应速度稳态稳定性之间的矛盾问题。; 适合人群:具备电力系统分析、自动控制理论等专业知识背景,熟练掌握Matlab/Simulink仿真工具,从事新能源并网技术、微电网运行控制、电力系统稳定性研究等方向的研究生、高校科研人员及电力行业工程技术人员。; 使用场景及目标:①为微电网中VSG控制策略的优化设计提供理论依据和技术方案;②支撑高渗透率可再生能源电力系统的频率稳定控制研究工程实践;③为相关领域的学术论文复现、创新算法开发及科研项目申报提供完整的仿真模型代码参考;④助力科研人员深入理解VSG动态特性并开展二次创新研究。; 阅读建议:建议学习者结合提供的Simulink仿真模型Matlab源代码进行同步研习,重点剖析惯量阻尼协同调控的逻辑架构及自适应算法的具体实现流程。可通过调整控制器关键参数,观察并分析系统在不同扰动下的频率响应曲线变化,从而深刻掌握VSG的内在动态规律控制精髓。
内容概要:本文系统性地介绍了基于ARIMA-CNN-LSTM混合模型的时间序列预测方法,融合传统统计学模型ARIMA深度学习网络CNN、LSTM的优势,构建高精度预测框架。其中,ARIMA用于捕捉时间序列的线性成分平稳特征,CNN有效提取局部时序模式和空间特征,LSTM则擅长建模长期依赖关系非线性动态。该混合模型特别适用于风电功率、光伏发电、电力负荷、股票价格等非平稳、非线性复杂时序数据的预测任务。文中提供了完整的Python代码实现流程,涵盖数据预处理、模型搭建、训练优化结果评估,强调模型的可复现性和工程实用性,有助于深入理解多模型融合机制实际应用技巧。; 适合人群:具备一定Python编程基础和机器学习知识,从事数据分析、人工智能、电力系统、金融工程、气象预测等相关领域的研究人员、工程技术人员及研究生。; 使用场景及目标:①应用于风电、光伏、负荷、股价等复杂时间序列的高精度预测;②深入理解ARIMA、CNN、LSTM三种模型的特性及其在混合架构中的协同机制;③掌握混合预测模型的设计思路、实现方法参数调优策略,为科研项目或工业级预测系统开发提供可靠的技术方案。; 阅读建议:建议读者结合文中提供的Python代码进行动手实践,逐步调试并理解各模块的功能实现,重点关注数据划分、残差建模、特征融合超参数调整等关键环节。同时,可通过文末提供的网盘链接获取完整代码数据集,以确保实验的可复现性,并鼓励在此基础上进行模型改进创新应用。
内容概要:LTK8315是一款单通道H桥有刷直流电机驱动器,工作电压范围为2.5V至12V,具备320mΩ低导通电阻和最高3.5A峰值电流驱动能力,支持PWM调速控制。该芯片通过INA和INB两个逻辑输入实现电机正转、反转、制动待机功能,并集成欠压保护(UVLO)、过热保护(TSD)及自动故障恢复机制,提升系统安全性。器件支持低功耗休眠模式,当INA和INB持续为低电平时进入休眠,显著降低静态功耗。采用SOP8封装,适用于小家电、玩具、电子锁、机器人等小型机电设备中对直流或步进电机绕组的驱动控制。; 适合人群:电子工程技术人员、嵌入式硬件开发者、电机控制系统设计人员,以及从事小型智能设备研发的工程师;具备基本电路知识和电机控制基础的设计人员尤为适合。; 使用场景及目标:①用于小功率直流电机的方向速度控制,如智能锁电机启停、玩具车运动控制;②配合微控制器实现PWM调速和节能管理;③作为步进电机双驱动方案中的一相驱动单元,其他驱动器协同工作;④在电池供电设备中利用休眠模式延长续航。; 阅读建议:在实际应用中需注意电源端外接足够容量的去耦电容以抑制电压波动,避免因瞬态电流过大导致芯片损坏;设计时应将VM电源电容尽量靠近芯片布局,减少寄生电感影响;建议参考典型应用电路完成整体系统验证。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值