找回密码
 立即注册
查看: 201|回复: 0

[Unity] Playables学习整理 - 1

[复制链接]
发表于 2023-1-27 14:15 | 显示全部楼层 |阅读模式
环境

    Unity 2020、Unity 2021
Playables的定义


Playables包含三个部分:Playable、PlayableOutput、PlayableGraph

图1 PlayableGraph


图2 PlayableOutput


图3 Playable

【Playable】代表各种可播放物的数据与行为。参考上图,除了Output外的所有节点都是Playable。
【PlayableOutput】它将输入Playable处理后的的状态信息,反应到场景中的Unity对象上,从而使播放效果呈现出来。
【PlayableGraph】它是Playables这套系统的容器与管理器。它负责创建、销毁各种Playable、PlayableOutput的节点,也管理节点间的关系。
<hr>从类的角度看Playables(逻辑/行为部分)


图4 Playables类图

PlayableGraph中的节点只能是PlayableOutput或Playable类型(它们的子类型也可以,都归属到这两种类型里)。PlayableOutput与Playable的结构、实现方式都是相似的,只是用途不同,并且在目前的项目使用中基本用不到自定义的PlayableOutput,因此 后面只针对Playable做一些说明
Playable是所有可播放物(见图3)的基类型(不是基类),因此它们可以隐式转换为Playable(通过PlayableHandle实现)。Playable只能由Unity定义好后给我使用,我们无法完全自定义一个Playable结构出来,因为要用到C#侧的internal接口,并在native层实现具体的逻辑。
Unity为我们提供了拓展Playable的能力,那就是ScriptPlayable<T>:IPlayable与PlayableBehaviour:IPlayableBehaviour。
ScriptPlayable<T>是Unity定义好的一个Playable子类型,它只是一个空壳,所有的逻辑都由PlayableBehaviour来实现,而IPlayableBehaviour是我们可以自己实现拓展的。从设计模式角度来看,ScriptPlayable<T>就是一个适配器(Adapter),它将IPlayableBehaviour转换为IPlayable给PlayableGraph使用。
(反过来说,我们要拓展Playable,只能实现IPlayableBehaviour或者继承PlayableBehaviour,来定义自己的Behaviour类。并用ScriptPlayable<T>将Behaviour进行包装后,传递给PlayableGraph使用。)
PlayableHandle这个结构在其他文章中几乎没有提到过,但是它很重要。Handle是Playable是这套系统访问native层的接口,它持有相关对象的指针。PlayableHandle是访问所有Playable的Handle,无论是基类型Playable还是子类型AnimationClipPlayable、AnimationMixerPlayable,都借助PlayableHandle访问native里的接口。
<hr>分析Unity的设计点


下面列举几个,自己猜测出来的,Unity团队在Playables这套系统中的设计点:

1、Playable与PlayableOutput
它们都是空壳子,真正的接口在其对应的Handle里(并且功能的实现是在native层)。相关的Extensions类也只是把Handle的接口开放出来。定义这些空壳,是为了在C#层能够使用native提供的功能。

2、IPlayable与IPlayableOutput
这两个interface的存在是为了在C#中提供接口约束能力,并不承担实际功能或代码设计的目的。因为Playables相关的基础接口都采用struct实现,而struct是不能继承的。Unity底层通过Handle结构模拟了一种struct继承的实现方式(struct相互转换),并通过定义interface对一些接口的使用进行强制约束。

3、如何实现Playable与其子类型的相互转换
如前所述,所有的Playable结构(struct)都是空壳,只提供了访问native的接口,它们的实质都在于PlayableHandle。只要PlayableHandle保存的指针是同一个,创建新的基类型或者子类型的实例,即可完成相互转换。
为了更符合面向对象的用法,Unity在每个子类型中都通过声明转换操作符(implicit、explicit)的方式,实现Playable与子类型的相互转换。例:
public static implicit operator Playable(AnimationMixerPlayable playable);public static explicit operator AnimationMixerPlayable(Playable playable);
4、IPlayableBehaviour存在的意义
这里其实我没搞很明白,甚至有些迷惑!!
PlayableBehaviour是实现(implement)了IPlayableBehaviour的C#类,注意这里是类(class)了,不是结构体(struct)。从这个小点也可以看出,它本身不是Playable,而是用于拓展Playable的另一套东西。
PlayableBehaviour是一个抽象类(abstract),它为所有IPlayableBehaviour的接口都只提供了空实现,即:空的虚方法(virtual)。这里我能理解到的用途是 简化拓展的代码量,因为IPlayableBehaviour提供的是一组生命周期的接口(类似Awake、Start、Update),我们只需要实现自己需要的几个即可,没必要把所有接口都写出来。PlayableBehaviour的存在,就为我们提供了,可以按需实现的便利。
Unity官方Scripting API文档里介绍PlayableBehaviour,说它是所有自定义Playable脚本的基类。言下之意,我们拓展Playable时,应该继承PlayableBehaviour,而不是实现IPlayableBehaviour。但是有两个点凑在一起,使得这个设定很奇怪:
    在C#中(反编译)能看到的Playable相关模块里,没有直接使用PlayableBehaviour的地方。PlayableHandle与ScriptPlayable<T>使用的都是IPlayableBehaviour。PlayableBehaviour中有个方法,在IPlayableBehaviour中并没有定义:public virtual void PrepareData(Playable playable, FrameData info)。文档中说当Playable延迟时,会调用PrepareData,但什么情况下算延迟不知道;在PrepareData中应该做些什么事情也不知道。

令人困惑的也是两点:

  • 既然设定PlayableBehaviour为所以自定义的基类,那么为啥还要定义IPlayableBehaviour?
    这里不需要模拟继承;IPlayableBehaviour里的接口又不全。所有Playable接口,接收的都是IPlayableBehaviour类型,那么PrepareData是如何被调用的?
参考文章


Playable API:定制你的动画系统
The PlayableGraph
Playable

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-6-16 21:33 , Processed in 0.088058 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表