在Cutscene之间进行的动画融合,Cutscene 动画Clip没有根动画选项,根据官方BakePosition的原理,复刻一个
核心思想是在Enter的时候,将动画全部根据时间存储位置,BakeRootMotion();
然后再正式的Update中 应用ApplyBakedRootMotion(time);
Slate官方给的是在Track中进行的根位置烘焙,而我们是要在Clip中进行烘焙,所以在细节上有一些需要注意的地方,
- 推进烘焙时间的函数 EvaluateTrackClips 其中的Enter 与Exit 不能推进,否则会出现递归,栈溢出
- Update中不能推进Graph时间,否则烘焙位置会出现偏差,人物会漂移
- 使用不当 人物会螺旋升天。。。
参考代码
using Sirenix.OdinInspector;
using Slate;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;
[Name("播放动画Playable")]
[Attachable(typeof(AnimTrack))]
public class PlayAnimPlayableClip : CutsceneClip<Animator>, IDirectable
{
private const int ROOTMOTION_FRAMERATE = 60;
public AnimationClip animationClip;
[LabelText("播放速度")]
[SerializeField]
public float PlaySpeed = 1;
[LabelText("循环播放(动画长度超过原动作片段时,循环动作)")]
[SerializeField]
public bool Loop = false;
[HideInInspector]
[SerializeField] private float _length = 1 / 30f;
private AnimationClipPlayable playableClip;
private PlayableGraph playableGraph;
public bool useRootMotion = true;
private bool useBakedRootMotion;
[SerializeField, HideInInspector]
public bool isRootMotionPreBaked;
[SerializeField, HideInInspector]
private List<Vector3> rmPositions;
[SerializeField, HideInInspector]
private List<Quaternion> rmRotations;
private Animator _animator;
public Animator animator
{
get
{
if (_animator == null || _animator.gameObject != actor.gameObject)
{
_animator = ActorComponent;
}
return _animator;
}
}
protected override void OnCreate()
{
Refresh();
}
protected override bool OnInitialize()
{
return base.OnInitialize();
}
protected override void OnEnter()
{
playableGraph = PlayableGraph.Create();
var playableOutput = AnimationPlayableOutput.Create(playableGraph, "Animation", ActorComponent);
playableClip = AnimationClipPlayable.Create(playableGraph, animationClip);
playableOutput.SetSourcePlayable(playableClip);
playableGraph.Play();
playableClip.SetPlayState(PlayState.Paused);
if (useRootMotion)
{
BakeRootMotion();
}
}
private void BakeRootMotion()
{
if (isRootMotionPreBaked)
{
animator.applyRootMotion = false;
useBakedRootMotion = true;
return;
}
var rb = animator.GetComponent<Rigidbody>();
if (rb != null)
{
rb.MovePosition(animator.transform.localPosition);
rb.MoveRotation(animator.transform.localRotation);
}
useBakedRootMotion = false;
animator.applyRootMotion = true;
rmPositions = new List<Vector3>();
rmRotations = new List<Quaternion>();
var tempActiveClips = 0;
var updateInterval = (1f / ROOTMOTION_FRAMERATE);
for (var time = startTime - updateInterval; time <= endTime + updateInterval; time += updateInterval)
{
EvaluateTrackClips(time, time - updateInterval, ref tempActiveClips);
if (tempActiveClips > 0)
{
playableGraph.Evaluate(updateInterval);
}
var pos = rb != null ? rb.position : animator.transform.localPosition;
var rot = rb != null ? rb.rotation : animator.transform.localRotation;
rmPositions.Add(pos);
rmRotations.Add(rot);
}
animator.applyRootMotion = false;
useBakedRootMotion = true;
}
void EvaluateTrackClips(float time, float previousTime, ref int tempActiveClips)
{
IDirectable clip = this;
if (time >= clip.startTime && previousTime < clip.startTime)
{
tempActiveClips++;
}
if (time >= clip.startTime && time <= clip.endTime)
{
clip.Update(time - clip.startTime, previousTime - clip.startTime);
}
if ((time > clip.endTime || time >= this.endTime) && previousTime <= clip.endTime)
{
tempActiveClips--;
}
}
protected override void OnUpdate(float time)
{
var curClipLength = animationClip.length;
float normalizedBefore = time * PlaySpeed;
if (Loop && time > curClipLength)
{
normalizedBefore = time * PlaySpeed % curClipLength;
}
playableClip.SetTime(normalizedBefore);
if (useRootMotion && useBakedRootMotion)
{
ApplyBakedRootMotion(time);
}
}
private void ApplyBakedRootMotion(float time)
{
var frame = Mathf.FloorToInt(time * ROOTMOTION_FRAMERATE);
var nextFrame = frame + 1;
nextFrame = nextFrame < rmPositions.Count ? nextFrame : rmPositions.Count - 1;
var tNow = frame * (1f / ROOTMOTION_FRAMERATE);
var tNext = nextFrame * (1f / ROOTMOTION_FRAMERATE);
var posNow = rmPositions[frame];
var posNext = rmPositions[nextFrame];
var pos = Vector3.Lerp(posNow, posNext, Mathf.InverseLerp(tNow, tNext, time));
animator.transform.localPosition = pos;
var rotNow = rmRotations[frame];
var rotNext = rmRotations[nextFrame];
var rot = Quaternion.Lerp(rotNow, rotNext, Mathf.InverseLerp(tNow, tNext, time));
animator.transform.localRotation = rot;
}
protected override void OnExit()
{
base.OnExit();
}
[Button("刷新", 40)]
public override void Refresh()
{
length = animationClip.length / PlaySpeed;
}
public override string info
{
get { return animationClip != null ? animationClip.name : base.info; }
}
public override float length
{
get { return _length; }
set { _length = value; }
}
public override bool canCrossBlend
{
get { return false; }
}
}
|