传递给我一个AnimationClip,我就播放它,如果有上一个动画,那么就自动过渡。
自定义动画控制器
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;
public class Animation_Controller : MonoBehaviour
{
[SerializeField] Animator animator;
private PlayableGraph graph;
public AnimationMixerPlayable mixer;
private AnimationClipPlayable clipPlayable1;
private AnimationClipPlayable clipPlayable2;
private bool isFirstPlay = true;
private bool currentisClipPlayable1;
private void Start()
{
// 创建图
graph = PlayableGraph.Create("Animation");
// 设置图的时间模式
graph.SetTimeUpdateMode(DirectorUpdateMode.GameTime);
// 创建混合器
mixer = AnimationMixerPlayable.Create(graph,2);
// 创建Output
AnimationPlayableOutput playableOutput = AnimationPlayableOutput.Create(graph, "Animation", animator);
// 让混合器链接上OutPut
playableOutput.SetSourcePlayable(mixer);
}
/// <summary>
/// 播放动画
/// </summary>
public void PlayAnimation(AnimationClip animationClip)
{
//如果是第一次播放,不存在过度
if (isFirstPlay)
{
//直接连接clipPlayable1
clipPlayable1 = AnimationClipPlayable.Create(graph, animationClip);
graph.Connect(clipPlayable1, 0, mixer, 0);
isFirstPlay = false;
currentisClipPlayable1 = true;
}
//存在过渡
else
{
// 1 -> 2
if (currentisClipPlayable1)
{
//解除已有的2
graph.Disconnect(mixer, 1);
//设置新的动画放到2
clipPlayable2 = AnimationClipPlayable.Create(graph, animationClip);
graph.Connect(clipPlayable2, 0, mixer, 1);
mixer.SetInputWeight(1, 1);
}
// 2 -> 1
else
{
//解除已有的1
graph.Disconnect(mixer, 0);
//设置新的动画放到1
clipPlayable1 = AnimationClipPlayable.Create(graph, animationClip);
graph.Connect(clipPlayable1, 0, mixer, 0);
mixer.SetInputWeight(0, 1);
}
currentisClipPlayable1 = !currentisClipPlayable1;
//TODO:设置过渡
}
if (graph.IsPlaying() == false)
{
graph.Play();
}
}
private void OnDestroy()
{
graph.Destroy();
}
private void OnDisable()
{
graph.Stop();
}
}
通过外界传进来一个animationClip包进clipPlayable进行播放,主要逻辑就是动态添加删除mixer的两个输入端口来维护新旧动画,总是更新比较老的那个端口的ClipPlayable,举例来说,第一次播放动画,默认使用端口1,就是最新的,第二次播放,使用端口2,1和2上的动画混合,第三次播放,端口1是相对较老的,所以解除1上的连接,将新动画连接到1上(ClipPlayable是结构体,不存在GC),第四次播放,端口2是相对较老的,解除2的连接,将新动画连接到2上,通过currentisClipPlayable1标记老端口是谁(就两个端口,一个新,一个老,当前是1,1就是新的,当前不是1,1就是老的)。
public class TestPlayable : MonoBehaviour
{
[SerializeField] AnimationClip animationClip1;
[SerializeField] AnimationClip animationClip2;
[SerializeField] Animation_Controller animation_controller;
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Alpha1))
{
animation_controller.PlayAnimation(animationClip1);
}
if (Input.GetKeyDown(KeyCode.Alpha2))
{
animation_controller.PlayAnimation(animationClip2);
}
}
}
测试类,目前实现了新旧动画的切换(无过渡);
动画过渡
因为动画过渡涉及到时间变化量,我们用协程做。
/// <summary>
/// 动画过渡
/// </summary>
/// <param name="fixedTime">过渡的时间间隔,越小过渡速度越快</param>
/// <param name="currentIsClipPlayable1"></param>
/// <returns></returns>
private IEnumerator TransitionAnimation(float fixedTime,bool currentIsClipPlayable1)
{
//当前的权重
float currentWeight = 1;
float speed = 1 / fixedTime;
while (currentWeight > 0)
{
//yield return null; 不能放这
currentWeight = Mathf.Clamp01(currentWeight - Time.deltaTime * speed);
if (currentIsClipPlayable1) //当前播放端口1动画,从1过渡到2
{
mixer.SetInputWeight(0, currentWeight);
mixer.SetInputWeight(1, 1 - currentWeight);
}
else
{
mixer.SetInputWeight(0, 1 - currentWeight);
mixer.SetInputWeight(1, currentWeight);
}
yield return null;
}
}
yield return null的位置注意不能放前面,否则切换是停了一帧会有视觉暂留的BUG。
/// <summary>
/// 播放动画
/// </summary>
public void PlayAnimation(AnimationClip animationClip, float fixedTime = 0.25f)
{
......
//设置过渡
if (transitionCoroutine != null) StopCoroutine(transitionCoroutine);// 避免开启多个协程
transitionCoroutine = StartCoroutine(TransitionAnimation(fixedTime, currentisClipPlayable1));
currentisClipPlayable1 = !currentisClipPlayable1;
......
}
对应完善上层调用逻辑。
效果如下,能看到动画权重线颜色渐变,同时人物动画过渡。