留个档,Unity Animator state节点的Motion动态替换AnimationClip

这篇具有很好参考价值的文章主要介绍了留个档,Unity Animator state节点的Motion动态替换AnimationClip。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

·由于Unity没有提供直接替换的API,所以在仅限的API下进行逻辑操作。

·替换的原理是差不多的,利用AnimatorOverrideController,进行运行时的覆盖。

·网上搜索很多文章是利用 名字字符串作为hash的key来进行替换。不满足我自己项目中的需求,于是利用GetOverrides 和 ApplyOverrides,封装了这个功能。

思考过程

·因为Animator的操作是UnityeEditor级别的,所以需要AnimatorOverrideController来辅助操作。

· AnimatorOverrideController的两个接口的特性会针对一个数据结构List<KeyValuePair<AnimationClip, AnimationClip>>。 查API就可以知道, 是from to的概念。
我自己做了测试,理解上可以认为, KeyValuePair里的key指的是原本Animator中的动画文件。 value是需要替换的AnimationClip。

·因为需要进行替换,节点里的动画不能为空。不然就没法拿来替换了。

·我要实现的模块,可以提供接口,是针对state节点的名字,进行操作的,需要一个映射关系。


所以,这个模块的设计,那么就会是Animator一开始会有一套默认的AnimationClip存在于各个state中。这一步我认为是静态过程即可。
我还需要一个state名字和默认clip之间的对应关系, 这个静态即可, 这在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);
    }

}


使用

在用的时候留个档,Unity Animator state节点的Motion动态替换AnimationClip,C#技术,Unity技术,unity,游戏引擎,c#
· 我会先通过UnityEditor ContextMenu按钮的方式把 映射关系和默认clip保存写到序列化。那么这个prefab就可以作为一个单独的对象加载了。 他可以是一个骨骼的prefab也可以是其他。

·Runtime时,MarkReplace进行state名字对应的AnimationClip的设定,最后Flush,进行批量应用刷新。


程序学无止尽。
欢迎大家沟通,有啥不明确的,或者不对的,也可以和我私聊
我的QQ 334524067 神一般的狄狄文章来源地址https://www.toymoban.com/news/detail-526546.html

到了这里,关于留个档,Unity Animator state节点的Motion动态替换AnimationClip的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Unity Animator cpu性能测试

    场景中共有4000个物体,挂在40个animtor 上,每个Animator控制100个物体的动画。 Unity Profiler. unity 2019.4.40f1 手机 没有挂在Animator时: Enable 状态: Play状态: 挂载Animator,但处于disable 状态: 以上现象,可以看出Animator 没有挂载和挂载为启用时,都会有部分cup处理,但时间都很短(

    2024年02月08日
    浏览(51)
  • Unity-Animation&Animator

    Animation窗口 直译就是动画窗口 它主要用于在unity内部创建和修改动画 所有在场最中的对象都可以通过Animation窗口为其制作动画 原理: 制作动画时:记录在固定时间点对象挂载的脚本的变量变化 播放动画时:将制作动画时记录的数据在固定时间点进行改变,产生动画效果 动画时

    2024年03月13日
    浏览(56)
  • Unity animator动画倒放的方法

    在Unity中, 我们有时候不仅需要 animator 正放的效果,也需要倒放的效果。但我们在实际制作动画的时候可以只制作一个正放的动画,然后通过代码控制倒放。 实现方法其实很简单,只需要把 animator 动画的 speed 设置为-1即为倒放, speed 设置为1即为正放: 比如我制作了一个从

    2024年01月23日
    浏览(48)
  • Unity Animation -- 使用Animator控制动画

            在很多实际场景中,我们经常需要根据特定的事件(比如玩家输入,敌人受到攻击等)来播放不同的动画。这需要我们了解一下Animator,Animator Controller和基础的动画状态机。         首先我们来创建一个简单的开门动画,示例中的门的模型来自官方教程。其实我们也

    2023年04月27日
    浏览(74)
  • Unity Animator人物模型动画移动偏移

    模型动画出现移动方向偏移 !修改Animation中的Root Transform Rotation(根变换位置)、Root Transform Rotation(x,y,z)(旋转),Bake Info Pose修改为Original。可以解决 !!但是,使用动画移动函数时将无法移动,原因是锁定根变换位置和循环位置 !!!所以只要修改依据为原始或者微调偏离值,

    2024年02月15日
    浏览(43)
  • Unity Animator获取当前播放动画片段

    Animator获取当前播放片段,获取错误,跟当前播放的片段不一致的原因。 一般使用下面API,获取当前动画机播放片段: 这里有一个机制,如果上一句代码是播放一个动画片段,下一句代码立即去获取当前播放片段信息,是获取不到的,需要等待几十ms的延迟,才能正确获取到

    2024年02月12日
    浏览(52)
  • Unity动画控制器animator.CrossFade

    1.CrossFade虽然可以不用任何逻辑来链接而直接跳转,但是CrossFade只能覆盖其他动画,当当前动画播放完毕而没有跳出这个动画时再次调用CrossFade将会失败。造成动画依旧停在原位。 attack动画名称 0.1f由其他动画转入此动画需0.1秒来过渡。

    2024年02月15日
    浏览(49)
  • Unity为人物模型 添加动效Animator

    码字不易,转载请注明出处^ o ^ Unity导入一个人物模型后,一般还需要让它动起来。并且,还需要有一定的状态管理,比如按A键就做跑的动作,按B键就做打人的动作,等等。 本文就从无到有,讲一下如何快速导入一个人物模型,然后动起来。 本文着重讨论的是人物模型,因

    2024年02月02日
    浏览(34)
  • Unity Animation、Animator 的使用(超详细)

    选中待提添加动画的物体, 在 Animation 窗口下 注意 :如果选中的物体无Animation/Animator组件,会自动添加Animator组件。 预览:启用/禁用场景预览模式。 录制:启用/禁用关键帧记录模式。 转到动画剪辑开头。 转到上一个 keyframe(关键帧)。 播放动画剪辑。 转到下一个keyfra

    2024年02月08日
    浏览(55)
  • Unity获取Animation和Animator的时长

    我们有时候会在协程中等待动画播放完毕,再去执行下一步的操作,但是由于不知道动画的播放时长,每次都要去看动画然后默数秒数(PS:作者以前是这样做的)。终于突发奇想我为什么不能直接获取动画的时长呢? 下面直接上代码

    2024年02月09日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包