传统的动画控制器非常复杂,而且容易混乱。但是我们可以使用 Playable 来构建自定义动画系统。
系统设计
我希望系统能在多个动画片段之间轻松切换状态,因此我需要一个 mixer
节点,还需要一个默认状态,例如 Idle
。此外,我还希望有一个随机动画选择器来播放随机动画。
![image](https://i-blog.csdnimg.cn/blog_migrate/da7564e69c15f0475bc42578fee11b5e.png)
代码结构
首先,我们需要一个名为 AnimBehavior
(动画行为)的抽象类,该类是所有动画节点的父类,我们可以在其中建立固有的子类,如 AnimUnit
(动画单元)和 Mixer
(混合器)。
其次,我创建了一个空节点 – AnimAdapter
,它控制着 AnimBehavior
。此外,AnimAdapter
也是一个节点,但它什么也不做,而不是控制行为。
![image](https://i-blog.csdnimg.cn/blog_migrate/ffb8a9f7d81cc77a401f0536c7a340c9.png)
类
AnimationBehavior.cs
在该类中,我们应将其抽象化,并需要几个虚拟方法:Enable()
、Disable()
、AddInput()
和Execute()
。
Enable()
:启用节点,播放动画片段Disable()
: 禁用节点,暂停动画片段AddInput()
:由于我们需要混合节点,它用于向混合节点添加片段Execute()
:节点按帧执行,因此这些方法需要在PrepareFrame()
中处理
public virtual void Enable()
{
enabled=true;
remainTime = GetAnimLength();
}
public virtual void Disable()
{
enabled=false;
}
public virtual void Execute(Playable playable,FrameData info)
{
if(enabled==false)return;
remainTime = remainTime > 0 ? remainTime - info.deltaTime : 0f;
}
AnimationAdapter.cs
这是一个空节点,唯一的作用是控制行为。它控制行为节点是否需要启用、禁用或执行。它还存储了一个关于动画行为的实例。
public class AnimAdapter : PlayableBehaviour
{
public AnimBehaviour animBehaviour;
public void Init(AnimBehaviour animBehaviour)
{ this.animBehaviour = animBehaviour;
}
public void Enable()
{ animBehaviour?.Enable();
}
public void Disable()
{ animBehaviour?.Disable();
}
public override void PrepareFrame(Playable playable, FrameData info)
{ animBehaviour?.Execute(playable,info);
}
public float GetAnimEnterTime()
{ return animBehaviour.GetEnterTime();
}}
AnimUnit. cs
这是一个特定的剪辑类,它继承自 AnimationBehavior.cs
每个剪辑都有一个 AnimUnit
节点,您可以在该类中存储剪辑的任何信息,如动画长度或输入时间。在本动画系统中,我们通过该类来操作剪辑。
public class AnimUnit : AnimBehaviour
{
private AnimationClipPlayable clipPlayable;
private AnimationClip clip;
private double animStopTime;
public AnimUnit(PlayableGraph graph,AnimationClip clip,float enterTime=0f):base(graph,enterTime)
{
this.clip = clip;
clipPlayable = AnimationClipPlayable.Create(graph,clip);
animAdapter.AddInput(clipPlayable,0,1.0f);
Disable();
}
public override void Enable()
{
base.Enable();
animAdapter.SetTime(animStopTime);
clipPlayable.SetTime(animStopTime);
animAdapter.Play();
clipPlayable.Play();
}
public override void Disable()
{
base.Disable();
animAdapter.Pause();
clipPlayable.Pause();
animStopTime = animAdapter.GetTime();
}
public override float GetAnimLength()
{
return clip.length;
}
}
Mixer. cs
这是一个自定义混合器节点,我希望在两个动画片段切换时设置阻尼,并允许被第三个附加动画打断。我们可以考虑这两种情况:
1. general
cur. speed = 1 / switch time
tar. weight = 1- cur. weight
2. non-general
因此,当新的附加片段出现时,我们需要降低当前片段和旧目标片段的权重。新目标的权重为 1-cur.weight-del.weight
normal speed = 1 / switch time
decline speed = 2 / switch time
target weight = 1 - cur. weight - del. weight
我们使用片段索引来切换动画,因此我们的想法是建立两个索引:当前索引和目标索引,我们还需要一个列表来存储多个下降索引,因为有时可能会有多个新的目标,我们需要多次切换。例如,如果有 4 个片段 [1,2,3,4],当 1 和 2 切换时,我们让系统切换到 3,同时我们需要比较 1 和 2 的权重,如果 weight(1) > weight(2)
,我们将weight 2
添加到下降列表,如果 weight(1) < weight(2)
,这意味着 1 和 2 的切换即将结束,所以现在 2 是 cur,我们将 weight 1
添加到下降列表,weight 1
的权重是 1-weight(cur)-weight(del.list)
。
RandomSelector
该节点用于随机选择片段,我们还需要一个混合器节点,以便在片段之间切换动画。
我们可以存储一个名为 currentIndex
的字段,通过设置混合器的 currentIndex
来选择片段。然后将权重设置为 0 至 1。
public void Select()
{
CurIndex = Random.Range(0, ClipCount);
}
public override void Enable()
{
if (CurIndex < 0 || CurIndex > ClipCount) return;
mixer.SetInputWeight(CurIndex, 1.0f);
AnimHelper.Enable(mixer.GetInput(CurIndex));
animAdapter.SetTime(0);
animAdapter.Play();
mixer.SetTime(0);
mixer.Play();
}
Root. cs
这是 AnimUnit
的根节点,我使用该节点启用或禁用属于它的所有节点。
public override void Enable()
{
for (int i = 0; i < animAdapter.GetInputCount(); i++)
{ var adapter = AnimHelper.GetAdapter(animAdapter.GetInput(i));
adapter.Enable();
}}
public override void Disable()
{
for (int i = 0; i < animAdapter.GetInputCount(); i++)
{ var adapter = AnimHelper.GetAdapter(animAdapter.GetInput(i));
adapter.Disable();
}}
以上就是动画系统的全部内容,基于B站鸢尾零音大佬的视频实现,我觉得还是很值得学习的!
项目地址: https://github.com/x739809514/AnimationSystem
#GameSystems