序文
・Unityは直接代替となるAPIを提供していないため、限られたAPIの下で論理演算を実行します。
·置換の原理も同様で、ランタイムカバレッジを行うにはAnimatorOverrideControllerを使用します。
·オンラインで検索された多くの記事は、名前の文字列を置換のハッシュ キーとして使用します。これは私自身のプロジェクトのニーズを満たしていなかったため、GetOverrides と applyOverrides を使用してこの関数をカプセル化しました。
思考プロセス
・Animatorの動作はUnityEditorレベルで行われるため、その動作を補助するためにAnimatorOverrideControllerが必要となります。
· AnimatorOverrideController の 2 つのインターフェイスの特性は、データ構造 List<KeyValuePair<AnimationClip,AnimationClip>> をターゲットとします。API を確認すると from to の概念であることがわかります。
私自身もテストしてみましたが、KeyValuePair のキーが元の Animator のアニメーション ファイルを参照していることがわかりました。value は、置換する必要があるアニメーションクリップです。
・置換が必要なため、ノード内のアニメーションを空にすることはできません。そうしないと、交換する方法がありません。
·実装したいモジュールは、状態ノードの名前で動作し、マッピング関係を必要とするインターフェイスを提供できます。
したがって、このモジュールの設計では、Animator が最初に各状態のデフォルトのアニメーションクリップのセットを持つことになります。このステップは静的なプロセスだと思います。
また、ステート名とデフォルトのクリップとの対応関係も必要ですが、これは静的であるため、UnityEditor で簡単に処理できます。そしてそれをシリアル化するだけです。
次にロジックを書きます
/*
* Create by fox.huang 黄文叶
* time: 2023.6.24
*
* UnityAPI提供的替换逻辑,只能通过指定Clip的名字,或者指定Clip对象进行替换
* 针对State的修改,是在UnityEditor下的,也就是runTime不能用。所以这个类的目的是实现State映射clip的修改。
*
* 注意:为了区分唯一性,同layer内,不能出现重名的节点(在使用子状态机的时候,会出现同名情况)
*/
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor.Animations;
using UnityEditor;
using System.IO;
#endif
/// <summary>
/// Animator节点上的AnimationClip替换组件。
/// </summary>
public class AnimatorClipReplaceComponent : MonoBehaviour
{
[System.Serializable]
public class Pack
{
//这边的序列化应该灰显的 懒得写了,就这么用呗。
[SerializeField] public string m_strName = null; // 节点的名字
[SerializeField] public int m_nLayer = 0;
[SerializeField] public AnimationClip m_aniClipDefault = null; //默认的动画clip
/// <summary>
/// 构造
/// </summary>
/// <param name="strName">节点名字</param>
/// <param name="aniClipDefault">默认动画名字</param>
public Pack(string strName, int nLayer, AnimationClip aniClipDefault)
{
m_strName = strName;
m_nLayer = nLayer;
m_aniClipDefault = aniClipDefault;
}
}
#if UNITY_EDITOR
[Tooltip("创建填充用AnimationClip的路径(空节点也认为是有效节点,创建默认clip用作替换依据)")]
[SerializeField] string m_strClipAssetCreatePath = "Assets/Art/Animation";
/// <summary>
/// 刷新当前的列表
/// </summary>
[ContextMenu("Refresh Default State Node List")]
private void RefreshList()
{
Animator ani = this.GetComponent<Animator>();
if (ani == null)
{
Debug.LogError("AnimatorClipReplaceHelper RefreshList, can not find component Animator in "
+ this.gameObject.name);
return;
}
string strAssetPath = AssetDatabase.GetAssetPath(ani.runtimeAnimatorController);
AnimatorController animatorController = AssetDatabase.LoadAssetAtPath<AnimatorController>(strAssetPath);
if (animatorController == null)
{
Debug.LogError("AnimatorClipReplaceHelper RefreshList, can not load asset : "
+ strAssetPath);
return;
}
//创建默认clip的文件夹
CreateFolder(m_strClipAssetCreatePath);
//开始遍历AnimatorCrontroller
var layers = animatorController.layers;
int nLayerCount = layers.Length;
m_listInfos = new List<Pack>();
for (int i = 0; i < nLayerCount; i++)
{
var oneLayer = layers[i];
CollectStates(oneLayer.stateMachine.states, i);
CollectStateMachines(oneLayer.stateMachine.stateMachines, i);
}
//重新保存一次AnimatorController
EditorUtility.SetDirty(animatorController);
AssetDatabase.SaveAssets();
}
private void CreateFolder(string strPath)
{
string strFullPath = strPath.Replace("Assets", Application.dataPath);
if (!Directory.Exists(strFullPath))
{
Directory.CreateDirectory(strFullPath);
}
}
/// <summary>
/// 递归收集节点们的信息
/// </summary>
private void CollectStates(ChildAnimatorState[] states, int nLayer)
{
int nCount = states.Length;
for (int i = 0; i < nCount; i++)
{
var oneState = states[i].state;
string strName = oneState.name;
if (oneState.motion == null)
{
AnimationClip newClip = new AnimationClip();
string strLocalPath = m_strClipAssetCreatePath + "aniclip_def_" +
strName.ToLower() + "_" + i.ToString() +
".anim";
AssetDatabase.CreateAsset(newClip, strLocalPath);
AssetDatabase.ImportAsset(strLocalPath);
oneState.motion = AssetDatabase.LoadAssetAtPath<AnimationClip>(strLocalPath);
}
m_listInfos.Add(new Pack(strName, nLayer, (AnimationClip)oneState.motion));
}
}
/// <summary>
/// 递归收集子状态机的内容
/// </summary>
private void CollectStateMachines(ChildAnimatorStateMachine[] group, int nLayer)
{
if (group == null) // 安全判断
{
return;
}
int nCount = group.Length;
if (nCount == 0) // 安全判断
{
return;
}
for (int i = 0; i < nCount; i++)
{
var one = group[i].stateMachine;
CollectStates(one.states, nLayer);
CollectStateMachines(one.stateMachines, nLayer);
}
}
#endif
/// <summary>
/// Animator节点名字对应的信息集合
/// </summary>
[SerializeField] List<Pack> m_listInfos = null;
private Dictionary<string, AnimationClip> m_dicDefaultClips = null;
[SerializeField] Animator m_animator = null;
private List<KeyValuePair<AnimationClip, AnimationClip>> m_listRuntime
= new List<KeyValuePair<AnimationClip, AnimationClip>>();
private AnimatorOverrideController m_aoc = null;
private void Awake()
{
//创建一个AnimatorOverrideController作为替换的容器
if (m_animator == null)
{
m_animator = this.GetComponent<Animator>();
}
if (m_animator == null)
{
Debug.LogError("AnimatorClipReplaceHelper Awake, no Animator : " + this.gameObject.name);
return;
}
m_aoc = new AnimatorOverrideController(m_animator.runtimeAnimatorController);
m_aoc.name = "aoc_" + this.gameObject.name;
m_animator.runtimeAnimatorController = m_aoc;
//获取到当前clip的列表
m_aoc.GetOverrides(m_listRuntime);
m_dicDefaultClips = new Dictionary<string, AnimationClip>();
//序列化信息 转换成 键值对
int nCount = m_listInfos.Count;
for (int i = 0; i < nCount; i++)
{
var pack = m_listInfos[i];
string strKeyName = pack.m_strName + pack.m_nLayer.ToString();
if (m_dicDefaultClips.ContainsKey(strKeyName))
{
Debug.LogWarning("AnimatorClipReplaceHelper Awake, already has node : " + pack.m_strName);
continue;
}
m_dicDefaultClips.Add(strKeyName, pack.m_aniClipDefault);
}
}
/// <summary>
/// 对一个节点的AnimationCLip进行替换
/// </summary>
/// <param name="strStateName">节点名</param>
/// <param name="aniClip">动画Clip(为null的时候还原到默认)</param>
public void MarkReplace(string strStateName, AnimationClip aniClip, int nLayer = 0)
{
strStateName += nLayer.ToString();
AnimationClip aniClipDefault;
if (!m_dicDefaultClips.TryGetValue(strStateName, out aniClipDefault))
{
Debug.LogWarning("AnimatorClipReplaceHelper MarkReplace, no state be find " +
strStateName + " in layer: " + nLayer);
return;
}
if (aniClip == null)
{
aniClip = aniClipDefault;
}
int nCount = m_listRuntime.Count;
for (int i = 0; i < nCount; i++)
{
if (m_listRuntime[i].Key == aniClipDefault)
{
m_listRuntime[i] = new KeyValuePair<AnimationClip, AnimationClip>(aniClipDefault, aniClip);
break; // 这里break,因为获取到的kv list是去重的
}
}
}
/// <summary>
/// 把之前记录的修改,刷新到控件上
/// </summary>
public void Flush()
{
m_aoc.ApplyOverrides(m_listRuntime);
}
}
使用
これを使用するときは
、まずマッピング関係とデフォルトのクリップを UnityEditor の ContextMenu ボタンを使用してシリアル化に保存します。その後、このプレハブを別のオブジェクトとしてロードできます。それはスケルトンプレハブまたはその他のものである可能性があります。
・実行時、MarkReplaceは状態名に対応するAnimationClipを設定し、最後にFlushがアプリケーションのバッチリフレッシュを実行します。
プログラミングには終わりがありません。
誰でも気軽にコミュニケーションできます。不明な点や間違いがある場合は、個人的にチャットすることもできます。My
QQ 334524067 God-like Didi