【Unity3d】【动画】基于Playable的动态播放AniamtionClip播放器

0.原因和目标

Unity提供了的Animator和AnimatorController用来播放动画。其中,AnimatorController作为动画控制器,需要提前配置好每个动画State。
但是对于实际开发过程中,提前配置好动画状态的方式不够”灵活“。
假如,某个模型有n个animationClip,并且在运行时,不是全部都使用。
如果用了AnimatorController的播放控制方式,就需要提前将所有可能要播放的animationClip都配置上。
因此,能不能实现某种,可以动态加载animationClip并且播放动画的方式?

Unity中提供了动画相关的Playable,可以供开发者使用。基于此,实现一个可以动态播放animationClip的动画播放器。

1.原理和demo

Unity中提供了如下播放动画相关的类型:

  • PlayableGraph
    • 图,对各种Playable进行关联
  • AnimationPlayableOutput
    • 最终操作Animator。将计算结果输出到Animator。
  • AnimationClipPlayable
    • 获得AnimationClip的Playable,即动画clip的播放曲线。
  • AnimationMixerPlayable
    • 动画混合器,可以设置不同InputIndex的权重
  • AnimationLayerMixerPlayable
    • 动画层级混合器。主要是可以设置当前layer的Mask、Addtive。
  • PlayableBehaviour
    • 自定义的播放行为。可以改变播放中的行为。

实现播放动画的流程为:

  1. 创建PlayableGraph,即创建一个图。之后所有的输入输出都在graph中。
  2. 创建图的输出——输出到Animator:AnimationPlayableOutput。
  3. 创建Playable并进行它们在graph中的接设置。
  4. 将graph中的playable的最终节点设置为AnimationPlayableOutput的source。
    animationPlayableOutput.SetSourcePlayable(finalPlayable,0)

参考Unity文档中提供的demoPlayableGraph.Connect

1.播放demo1
TestAnimationPlayer

    using UnityEngine;
    using UnityEngine.Animations;
    using UnityEngine.Playables;

    namespace AnimationPlayer
    {
        public class TestAnimationPlayer : MonoBehaviour
        {
            public bool          playClip;
            public AnimationClip clip;
            public AnimationClip clip2;

            private PlayableGraph _graph;

            void Update()
            {
                if (playClip)
                {
                    playClip = false;
                    TestPlay(clip,clip2);
                }
            }

            public void TestPlay(AnimationClip clip,AnimationClip clip2)
            {
                // 1. 创建graph
                _graph = PlayableGraph.Create($"AnimationPlayer_{this.gameObject.name}");
                // 2. 创建输出
                AnimationPlayableOutput animOutput =
                    AnimationPlayableOutput.Create(_graph, "AnimationOut", this.GetComponent<Animator>());

                // 3. 创建自定义的播放行为
                var blendBehaviour = ScriptPlayable<TestAnimationBlendBehaviour>.Create(_graph, new TestAnimationBlendBehaviour(), 2);

                // 4. 根据animationClip创建两个clipPlayable
                var clipPlayable  = AnimationClipPlayable.Create(_graph, clip);
                var clipPlayable2 = AnimationClipPlayable.Create(_graph, clip2);

                // 5. 创建一个动画的混合器
                var mixer = AnimationMixerPlayable.Create(_graph, 2);
                blendBehaviour.GetBehaviour().mixerPlayable = mixer;

                // 6. 将clipPlayable连接到混合器。 
                _graph.Connect(clipPlayable, 0, mixer, 0);
                _graph.Connect(clipPlayable2, 0, mixer, 1);

                // 7. 将混合器连接到自定义播放行为
                _graph.Connect(mixer, 0, blendBehaviour, 0);

                // 8. 设置自定义的播放行为作为输出的源
                animOutput.SetSourcePlayable(blendBehaviour, 0);

                // 9. 播放
                _graph.Play();
            }
        }
    }
自定义播放行为:TestAnimationBlendBehaviour

    using UnityEngine;
    using UnityEngine.Animations;
    using UnityEngine.Playables;

    namespace AnimationPlayer
    {
      public class TestAnimationBlendBehaviour:PlayableBehaviour
      {
        public AnimationMixerPlayable mixerPlayable;
        public override void PrepareFrame(Playable playable, FrameData info)
        {
          var value = Mathf.PingPong((float)playable.GetTime(), 1);
          // 动态对mixerPlayable的两个输入权重进行修改
          mixerPlayable.SetInputWeight(0,value);
          mixerPlayable.SetInputWeight(1,1-value);
        }
      }
    }
2.播放demo2,分层混合

只为试试分层混合,因此只创建两个层级混合就可以了。
在每一个层级中,直接只放一个AnimationClipPlayable(也可以在层里面做clip的混合,demo1已经有了)


        public void TestPlayLayerAnim()
        {
            _graph = PlayableGraph.Create($"AnimationPlayer_{this.gameObject.name}");
            AnimationPlayableOutput animOutput =
                AnimationPlayableOutput.Create(_graph, "AnimOut", this.GetComponent<Animator>());

            var blendBehaviour = ScriptPlayable<TestAnimationBlendBehaviour>.Create(_graph, new TestAnimationBlendBehaviour(), 2);

            var clipPlayable  = AnimationClipPlayable.Create(_graph, clip);
            var clipPlayable2 = AnimationClipPlayable.Create(_graph, clip2);

            var mixer = AnimationMixerPlayable.Create(_graph, 2);
            blendBehaviour.GetBehaviour().mixerPlayable = mixer;


            // 层级0
            var layerMixer0 = AnimationLayerMixerPlayable.Create(_graph, 1);
            layerMixer0.SetLayerAdditive(1,false);
            
            // 层级1
            var layerMixer1 = AnimationLayerMixerPlayable.Create(_graph, 1);
            layerMixer1.SetLayerAdditive(1,true);

            // 将clipPlayable与层级关联
            _graph.Connect(clipPlayable, 0, layerMixer0, 0);
            _graph.Connect(clipPlayable2, 0, layerMixer1, 0);
            
            // 将层级与混合器关联
            _graph.Connect(layerMixer0, 0, mixer, 0);
            _graph.Connect(layerMixer1, 0, mixer, 1);

            // 将混合器与自定义的播放行为关联
            _graph.Connect(mixer, 0, blendBehaviour, 0);

            animOutput.SetSourcePlayable(blendBehaviour, 0);

            _graph.Play();
        }

2.封装

从demo中已经跑通了基本的实现,现在把它封装一下。
由于项目中不涉及到动画的分层,因此,只实现动画普通播放和淡入淡出。
封装后,提供的功能为:

  1. 根据AnimationClip播放动画
  2. 根据AnimationClip对动画进行淡入淡出的播放
  3. 可修改整体的动画播放速度
  4. 可以修改单个clip动画的播放速度
代码结构
  1. ICAnimState
  • 定义外部如果访问的话,提供的字段。

    namespace AnimationPlayer
    {
      public interface ICAnimState
      {
        bool  isLoop { get; }
        float length { get; }
        float speed  { get; }

      }
    }

  1. CAnimClipState
  • 封装的播放器内部使用
  • 根据AnimationClip创建了Playable
  • 可以提供动画开始播放和结束播放的通知

    using UnityEngine;
    using UnityEngine.Animations;
    using UnityEngine.Playables;

    namespace AnimationPlayer
    {
        public class CAnimClipState : ICAnimState
        {
            public AnimationClip clip   { get; private set; }
            public bool          isLoop => clip.isLooping;
            public float         length => clip.length;
            public float         speed  { get; set; } = 1f;

            public AnimationClipPlayable clipPlayable { get; private set; }
            public float                 playTimer;
            public bool                  isPlaying;


            private CAnimClipState(AnimationClip clip, PlayableGraph graph)
            {
                this.clip    = clip;
                clipPlayable = AnimationClipPlayable.Create(graph, clip);
            }

            public static CAnimClipState Create(AnimationClip clip, PlayableGraph graph)
            {
                return new CAnimClipState(clip, graph);
            }

            public void OnStateStart()
            {
                isPlaying = true;
                // 可以通知动画开始播放
                Debug.Log($"{clip.name} start play");
            }

            public void OnStateEnd()
            {
                isPlaying = false;
                // 可以通知动画播放结束
                Debug.Log($"{clip.name} end play");
            }
        }
    }

  1. CAnimTransition
  • 动画淡入淡出时需要的数据

    namespace AnimationPlayer
    {
        public class CAnimTransition
        {
            public CAnimClipState fromState;
            public CAnimClipState toState;
            public float          transitionDuration;

            public float fromeStateFadeOutTime;
          }
    }
  
  1. CAnimPlayBehaviour 自定义播放行为(动画混合)
    • 实现了播放动画和淡入淡出播放动画

    using UnityEngine;
    using UnityEngine.Animations;
    using UnityEngine.Playables;

    namespace AnimationPlayer
    {
      public class CAnimPlayBehaviour : PlayableBehaviour
      {
              public  PlayableGraph          graph;
              public  AnimationMixerPlayable mixerPlayable;
              private CAnimClipState         _curPlayState;
              private float                  _playSpeed = 1f;
              private bool                   _isInTransition;
              private float                  _transitionTimer;
              private CAnimTransition        _animTransition;


              public void SetPlaySpeed(float playSpeed)
              {
                  _playSpeed = playSpeed;
              }

              public void Play(CAnimClipState state)
              {
                  _curPlayState = state;
                  mixerPlayable.DisconnectInput(0);
                  mixerPlayable.DisconnectInput(1);
                  
                  // connectInput可以使用graph,也可以使用mixerPlayable
                  // graph.Connect(_curPlayState.clipPlayable, 0, mixerPlayable, 0);
                  mixerPlayable.ConnectInput(0,_curPlayState.clipPlayable,0);
                  
                  mixerPlayable.SetInputWeight(0, 1f);

                  mixerPlayable.SetTime(0);
                  _curPlayState.clipPlayable.SetTime(0);
                  _curPlayState.playTimer = 0f;
                  _curPlayState.OnStateStart();
              }

              public void CrossFade(CAnimClipState state, float fadeDuration)
              {
                  var lastState = _curPlayState;
                  if (lastState == null)
                  {
                      Play(state);
                      return;
                  }

                  if (_curPlayState == state)
                  {
                      return;
                  }

                  _animTransition           = _animTransition ?? new CAnimTransition();
                  _curPlayState             = state;
                  _animTransition.fromState = lastState;
                  _animTransition.toState   = _curPlayState;

                  _isInTransition = true;
                  var lastLeftTime = lastState.length - lastState.playTimer % lastState.length;
                  _animTransition.transitionDuration    = Mathf.Min(lastLeftTime, fadeDuration);
                  _animTransition.fromeStateFadeOutTime = _animTransition.transitionDuration;
                  mixerPlayable.DisconnectInput(0);
                  mixerPlayable.DisconnectInput(1);

                  // connectInput可以使用graph,也可以使用mixerPlayable
                  // graph.Connect(_curPlayState.clipPlayable, 0, mixerPlayable, 0);
                  // graph.Connect(lastState.clipPlayable, 0, mixerPlayable, 1);
                  mixerPlayable.ConnectInput(0,_curPlayState.clipPlayable,0);
                  mixerPlayable.ConnectInput(1,lastState.clipPlayable,0);

                  mixerPlayable.SetInputWeight(0, 0);
                  mixerPlayable.SetInputWeight(1, 1);

                  _curPlayState.clipPlayable.SetTime(0);
                  _curPlayState.playTimer = 0f;
                  mixerPlayable.SetTime(0);
                  _curPlayState.OnStateStart();
              }

              public override void PrepareFrame(Playable playable, FrameData info)
              {
                  if (_curPlayState == null) return;

                  if (_isInTransition)
                  {
                      _animTransition.fromeStateFadeOutTime -= info.deltaTime;
                      var fadeOutWeight = _animTransition.fromeStateFadeOutTime / _animTransition.transitionDuration;
                      if (fadeOutWeight > 0.001f)
                      {
                          mixerPlayable.SetInputWeight(0, 1 - fadeOutWeight);
                          mixerPlayable.SetInputWeight(1, fadeOutWeight);
                      }
                      else
                      {
                          mixerPlayable.SetInputWeight(0, 1f);
                          mixerPlayable.SetInputWeight(1, 0f);
                          if (!_animTransition.fromState.isLoop)
                          {
                              _animTransition.fromState.OnStateEnd();
                          }

                          _animTransition.fromState          = null;
                          _animTransition.toState            = null;
                          _animTransition.transitionDuration = 0f;

                          _isInTransition = false;
                      }
                  }


                  _curPlayState.playTimer += info.deltaTime;
                  if (_curPlayState.isLoop)
                  {
                      return;
                  }

                  if (_curPlayState.playTimer >= _curPlayState.length)
                  {
                      _curPlayState.OnStateEnd();
                      _curPlayState = null;
                  }
              }

              public void Evaluate(float deltaTime)
              {
                  if (_curPlayState != null)
                  {
                      if (!graph.IsPlaying())
                      {
                          graph.Play();
                      }

                      graph.Evaluate(deltaTime * _playSpeed * _curPlayState.speed);
                  }
                  else
                  {
                      if (graph.IsPlaying())
                      {
                          graph.Stop();
                      }
                  }
              }
          }
    }
  1. CAnimationPlayer 动画播放器

    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Animations;
    using UnityEngine.Playables;

    namespace AnimationPlayer
    {
        public class CAnimationPlayer
        {
            private Dictionary<object, CAnimClipState> _stateMap  = new Dictionary<object, CAnimClipState>();
            private float                              _playSpeed = 1f;

                public CAnimationPlayer(Animator animator)
                {
                    _animator = animator;
                    _Initialize();
                }

                public float playSpeed
                {
                    get => _playSpeed;
                    set
                    {
                        if (_playSpeed != value)
                        {
                            _playSpeed = value;
                            _player.SetPlaySpeed(_playSpeed);
                        }
                    }
                }

                public void Play(AnimationClip clip, float speed = 1f)
                {
                    if (!_stateMap.TryGetValue(clip, out var state))
                    {
                        state = CAnimClipState.Create(clip, _graph);
                        _stateMap.Add(clip, state);
                    }

                    state.speed = speed;
                    DoPlay(state);
                }

                public void CrossFade(AnimationClip clip, float fadeDuration = 0.2f, float speed = 1f)
                {
                    if (!_stateMap.TryGetValue(clip, out var state))
                    {
                        state = CAnimClipState.Create(clip, _graph);
                        _stateMap.Add(clip, state);
                    }

                    state.speed = speed;
                    DoCrossFade(state, fadeDuration);
                }

                private void DoPlay(CAnimClipState state)
                {
                    _player.Play(state);
                }

                private void DoCrossFade(CAnimClipState state, float duration = 0.2f)
                {
                    _player.CrossFade(state, duration);
                }

                #region Core Logic

                private PlayableGraph      _graph;
                private bool               _graphInitialized;
                private Animator           _animator;
                private CAnimPlayBehaviour _player;

                private void _Initialize()
                {
                    if (_graphInitialized)
                    {
                        return;
                    }

                    _graphInitialized = true;
                    _graph            = PlayableGraph.Create($"CAnimationPlayer_{this._animator.name}");

                    // 自定义更新方式
                    _graph.SetTimeUpdateMode(DirectorUpdateMode.Manual);
                    var animOutPut    = AnimationPlayableOutput.Create(_graph, "CAnimation", _animator);
                    var mixerPlayable = AnimationMixerPlayable.Create(_graph, 2);
                    var mixBehaviour  = ScriptPlayable<CAnimPlayBehaviour>.Create(_graph, 2);
                    _player               = mixBehaviour.GetBehaviour();
                    _player.graph         = _graph;
                    _player.mixerPlayable = mixerPlayable;

                    _graph.Connect(mixerPlayable, 0, mixBehaviour, 0);
                    animOutPut.SetSourcePlayable(mixBehaviour);
                }

                public void Update()
                {
                    _player.Evaluate(Time.deltaTime);
                }

                #endregion
            }
    }

  1. 播放测试

    using UnityEngine;

    namespace AnimationPlayer
    {
        public class TestCAnimationPlayer : MonoBehaviour
        {
            public bool          playClip;
            public bool          crossFade;
            public AnimationClip clip1;
            public AnimationClip clip2;
            public float         playSpeed = 1f;

            private CAnimationPlayer _animPlayer;
            private Animator         _animator;

            private void Awake()
            {
                _animator   = GetComponent<Animator>();
                _animPlayer = new CAnimationPlayer(_animator);
            }

            public void Update()
            {
                _animPlayer.playSpeed = playSpeed;
                if (playClip)
                {
                    playClip = false;
                    _animPlayer.Play(clip1);
                }

                if (crossFade)
                {
                    crossFade = false;
                    _animPlayer.CrossFade(clip2);
                }
            }

            void LateUpdate()
            {
                _animPlayer.Update();
            }
        }
    }

以上.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

延澈左

小小心意

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值