文章目录
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
- 自定义的播放行为。可以改变播放中的行为。
实现播放动画的流程为:
- 创建PlayableGraph,即创建一个图。之后所有的输入输出都在graph中。
- 创建图的输出——输出到Animator:AnimationPlayableOutput。
- 创建Playable并进行它们在graph中的接设置。
- 将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中已经跑通了基本的实现,现在把它封装一下。
由于项目中不涉及到动画的分层,因此,只实现动画普通播放和淡入淡出。
封装后,提供的功能为:
- 根据AnimationClip播放动画
- 根据AnimationClip对动画进行淡入淡出的播放
- 可修改整体的动画播放速度
- 可以修改单个clip动画的播放速度
代码结构
- ICAnimState
- 定义外部如果访问的话,提供的字段。
namespace AnimationPlayer
{
public interface ICAnimState
{
bool isLoop { get; }
float length { get; }
float speed { get; }
}
}
- 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");
}
}
}
- CAnimTransition
- 动画淡入淡出时需要的数据
namespace AnimationPlayer
{
public class CAnimTransition
{
public CAnimClipState fromState;
public CAnimClipState toState;
public float transitionDuration;
public float fromeStateFadeOutTime;
}
}
- 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();
}
}
}
}
}
- 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
}
}
- 播放测试
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();
}
}
}
以上.