Unity 基于Playable的动画系统学习笔记

传统的动画控制器非常复杂,而且容易混乱。但是我们可以使用 Playable 来构建自定义动画系统。

系统设计

我希望系统能在多个动画片段之间轻松切换状态,因此我需要一个 mixer 节点,还需要一个默认状态,例如 Idle。此外,我还希望有一个随机动画选择器来播放随机动画。

image

代码结构

首先,我们需要一个名为 AnimBehavior(动画行为)的抽象类,该类是所有动画节点的父类,我们可以在其中建立固有的子类,如 AnimUnit(动画单元)和 Mixer(混合器)。
其次,我创建了一个空节点 – AnimAdapter,它控制着 AnimBehavior。此外,AnimAdapter 也是一个节点,但它什么也不做,而不是控制行为。

image

AnimationBehavior.cs

在该类中,我们应将其抽象化,并需要几个虚拟方法:Enable()Disable()AddInput()Execute()

  1. Enable():启用节点,播放动画片段
  2. Disable(): 禁用节点,暂停动画片段
  3. AddInput():由于我们需要混合节点,它用于向混合节点添加片段
  4. 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
image
cur. speed = 1 / switch time
tar. weight = 1- cur. weight
2. non-general
image
因此,当新的附加片段出现时,我们需要降低当前片段和旧目标片段的权重。新目标的权重为 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

  • 13
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值