Render结构
- Render
- SkinMeshRender/MeshRender/ParticleSystemRender/...
- Material(s)
- shareMaterial(s)
- Shader
- Property
- KeyWord
- Shader
- SkinMeshRender/MeshRender/ParticleSystemRender/...
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()获取材质属性原始值
- 使用Renderer.GetPropertyBlock()获取整个mpb
若Get后,其他模块也改mpb,还原时会还原到开始播放时的样子,让这期间外部的设置无效 - 使用GetXXXFromMPB()获取单个属性的值
但此接口存在问题,若没被设置过MPB获取到的将会是默认值如0
A:期望能有接口获取实际值面板值 (有mpb就获得mpb否则返回material原始值)
- 在CurveAnimator内记录自身改过什么MPB,若被改过就获取MPB,没改过就获取Renderer.Material.GetXX材质原始值
缺点:存在风险,如果外部模块一起改同一属性,将会有错误,逻辑复杂和内存占用更多 - 引擎提供接口解决
先 : 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结构
- Animator
- Layer[]
- IAnimation[]
- Node[]
- PropertyCurve []
- PropertyData
- name
- type
- Mat
- Transform
- RenderActor
- Active
- CurveData
- curve[]
- time, texture[]
- PropertyData
- PropertyCurve []
- Node[]
- IAnimation[]
- Layer[]
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;
}

965

被折叠的 条评论
为什么被折叠?



