Open johanzhu opened 2 months ago
感觉整体结构是不是有点奇怪,应该先整体看 Spine 组件需要给开发者提供什么能力,再考虑如何实现
addSeparateSlot 这个不要的结论推导不是很理解,首先应该是考虑开发者是否需要吧?而不是 “目前这个方法可以删除,没有对应的插件实现绘制顺序的调整,这个暂时可以删除掉”?
addSeparateSlot 这个不要的结论推导不是很理解,首先应该是考虑开发者是否需要吧?而不是 “目前这个方法可以删除,没有对应的插件实现绘制顺序的调整,这个暂时可以删除掉”?
这里我的理由确实没有写清楚,我补充一下。核心原因我认为是引擎API 应该与的编辑器功能有关联,unity 提供了一个专门的拆分组件,这个方法是为这个组件服务的。 目前暂时还没有增加这个拆分插件的计划,所以这个方法我认为目前不需要对外暴露。未来要不要暴露也待讨论,因为功能是希望用户通过编辑器的这个拆分插件来实现拆分,而不是用这个方法在运行时拆。
感觉整体结构是不是有点奇怪,应该先整体看 Spine 组件需要给开发者提供什么能力,再考虑如何实现
好的👌 RFC在结构上确实需要改进,我增加一个章节对 spine 提供的整体能力增加调研对比,以归纳出核心且必要的一些能力。基于整体API的对比调研结果,再结合我们的API来提出修改方案。
背景
过去的 Spine-runtime API 存在多个版本(SpineAnimation, SpineRenderer),杂乱且不统一。在 1.3 里程碑中,对 API 进行了初步的整合与优化。然而,当前的 API 距离最终版本仍有差距,尚需进一步的审视与优化,以确保在未来能够更好地满足开发者的需求,提供更优的开发体验。
整体 API 调研
为确保新的 Spine-runtime API 版本能够在功能性和易用性上满足开发者的需求,本次调研分析了多个流行的 Spine 运行时,包括 :Unity、Unreal Engine、Godot、Pixi 、Cocos 和 LayaAir。通过比较这些引擎在 API 设计、功能实现、扩展性等方面的不同,我们可以借鉴他们的优点,并识别现有 API 的改进方向,以实现更高效的一致性和可用性。
以下是各引擎的调研整理,**在对比不同的引擎 API 后,会整理出核心且必备的 API,然后结合目前 Galacean runtime 的 API 提出改造方案。** 以下是不同引擎API的调研和总结。
Unity Spine Runtime
概要
API 概览
Unity 对外暴露的 API 非常多且杂,但是基本上可以划分为以下几个类别:
Unity Spine Runtime 提供了 SkeletonAnimation 和 SkeletonRenderer 两个类来实现 spine 动画的渲染。二者是继承关系,且均为MonoBehaviour。
详细 API 如下:
SkeletonAnimation 对外暴露的 API 有:
SkeletonRenderer 对外暴露的 API 有:
在Unity 编辑器中
组件检查项中,和渲染有关的参数都统一放到了 Advanced 折叠块下,检查项与 API 是对应的~
除此之外的检查项有:
总结:
Unreal Engine
概要
API 概览
UE 同样提供了渲染组件和动画组件两个组件,蓝图提供的节点与组件暴露的方法略有差异。UE 暴露的 API 看似很多,但是都是基于 spineCore 的 AnimationState 和 Skeleton 对象的方法。
API 可以划分为这几类:
详细 API 如下:
动画组件 API:
蓝图节点:
代理方法:
渲染组件 API:
蓝图方法:
以上蓝图节点都是基于 SpineCore.Skeleton 对象提供的
在UE 编辑器中
UE 编辑器组件中只有这四个选项,分别是骨架数据,atlas 数据,以及两个预览的名称。
在蓝图编辑器中,能够使用组件暴露的蓝图节点:
总结:
Godot
概览
API 概览
Godot 的 API 可以分为以下几个类别:
详细 API 如下:
事件:
以下四个是四种混合模式下的材质设置与获取:
其他对外 API 是一些 debug 方法。这里就不贴了。
在编辑器中:
godot 提供的检查项有:
总结:
Pixi
概览
API 概览
Pixi 的 API 可以分为:
详细 API:
PS: hackTexture 相关的方法只在 Pixi-spine 中存在。官方运行时并未提供相关的动态替换纹理的方法。
总结:
Cocos
概览
API 概览
cocos 暴露的 API 非常多,主要分为以下几类:
详细 API:
在cocos编辑器中:
对比对外暴露的 API 能够发现,Animation,DefaultSkin 没有对应的 API。这两个检查项的 API 是 internal 的。
其余的检查项与 API 均能够对应。
总结:
Laya
概览
API 概览
Laya 的 API 主要分为以下几类:
详细 API:
在Laya编辑器中:
Laya 的检查项较少,只有:
上述检查项都能找到与之对应的 API。ExternalSKins 是比较特殊的一个 API,仅laya提供了这个API。但是文档中没有介绍这个检查项的功能。
总结:
根据以上 6 个引擎的 API 以及编辑器的检查项的调研,能够总结出必备的 API 类型有:
除此之外,其他类型的 API 都是额外的一些高级功能或者特殊处理。
下文中,会结合调研,总结目前 Galacean spine 的 API 要改什么,要加什么。
目前的 API 梳理
目前运行时对外的 API 如下:
根据上述调研结果,对已有 API 进行分类:
API 调整总览
spine 骨架/动画操作:目前的两个 API 能够满足开发者的诉求了,无需调整。理由如下:
骨架与动画操作底层都依赖 SpineCore 的 Skeleton 和 AnimationState API,直接暴露出这两个对象的是最简单最直接的方式。这种情况下,用户没有额外的学习成本。
调研的引擎中,cocos ,laya,ue 没有直接暴露 Skeleton 和 AnimationState 这两个对象,而是直接暴露的二次封装的方法,他们这样做的原因如下:
ue 是为了提供蓝图节点,暴露的函数与原本的 API 基本一致。
cocos 由于增加了动画烘焙缓存,所以无论是动画播放,还是附件替换,这些都进行了二次封装,但是功能和原生 API 是一致的。
laya 的纯粹是为了提供使用方法,部分方法会关闭 laya 的性能优化开关,所以也进行了封装。
综上,如果引擎没有特殊的实现或者操作,无需额外封装新的方法,cocos 和 laya 封装的方法,函数名/功能和原生的SpineCore API 也是一致的。
动画资产相关方法:根据调研,资产相关方法,目前存在的问题有:
生命周期相关方法:缺少较底层的生命周期方法,但使用场景不多,可以暂时先不增加。Spine 默认的动画事件足够满足开发诉求了。
渲染相关参数与方法: tint 和 pma 缺失,pma 非常需要,tint 不太常用。pma 本次里程碑会支持
其他:本次会增加一些性能优化的参数开关(预乘)
API 修改调整方案
以下是各 API 调整的详细方案,每个API 都包含修改的方向以及背后对各个引擎针对该API 的调研。
resource
修改方向
具体方案:
SpineAnimationRenderer 中的resource 的 getter 和 setter 标记为 deprecated。
SkeletonDataResource 名称修改为 SpineResource,内部存储: SkeletonData 和 AnimationData。
组件通过直接设置 Skeleton 和 AnimationState 的方式初始化
SpineAnimationRenderer 提供两个语法糖方法帮助快速初始化spine组件:
/**
SpineAnimationRenderer
component to an existingEntity
SpineResource
.Entity
to attach the component to.SpineResource
used to initialize theSpineAnimationRenderer
.SpineAnimationRenderer
component.针对资产 API 的调研:
Unity 在运行时能够做到动态切换,这段代码在 spine 实例化时也会调用。
除此之外,还提供了运行时实例化的功能,但是并不推荐这种方式:https://zh.esotericsoftware.com/spine-unity#%E9%AB%98%E7%BA%A7-%E8%BF%90%E8%A1%8C%E6%97%B6%E5%AE%9E%E4%BE%8B%E5%8C%96
优势: unity 提供了对外的 API 实现实例化以及动态替换素材,灵活性强,能够满足各种需求。
劣势:动态替换素材会重新构建 mesh,反复切换素材是比较 waste 的操作,严格上来说,不算劣势。这也是不同运行时都会面临的问题。
Pixi 没有暴露资产相关的 API 不可动态替换。只能通过 from 静态方法或者 contructor 来创建。
优势:from 静态方法能够很好的和 Pixi 的 Loader 相结合使用,但是前提是需要预加载 skeleton 和 atlas 素材。而通过 contructor 创建,则需要用户手动创建出 SkeletonData,灵活性更高但是需要引入更多的 paser 来预处理素材。
劣势:无法动态替换素材,预加载的方式非常多,为了实现预加载,多 page 需要在 loader options 传入非常多额外参数。
用户学习成本很高。具体可以参考官方提供的 example ,非常繁多。
ue 的实现其实与 unity 类似,但是官方文档中,没有告知用户运行时实例化和动态修改素材的方式。在代码中,还是存在对应的接口,
这使得能够通过蓝图,替换素材:
但是官方文档中提供的方式还是直接设置素材,而非蓝图:
优势:ue 的优势主要体现在能够结合蓝图一起使用。
劣势:与 unity 一样,当切换了素材时,同样会重新 buildMesh。
godot 的 SpineSprite 同样提供了方法动态修改 resource
语意非常明确: set_skeleton_data_res,并且提供了相应的回调函数。
在 SpineSprite.cpp 的回调实现中,修改素材后,同样会重新创建 Mesh
优势:与 unreal 和 unity 相同,godot 提供的组件功能也非常全面与灵活。但是文档中并没有告知用户动态替换素材的方法。
劣势:与 unreal 和 unity 相同,切换素材时,同样会重新初始化,构建 mesh。
都有动态替换 spine 资产的 API。但是 laya 的 API (source, url)语意不太明确
cocos:
素材加载: https://docs.cocos.com/creator/3.8/manual/zh/asset/spine.html#%E5%8A%A0%E8%BD%BD%E6%96%87%E6%9C%AC%E6%A0%BC%E5%BC%8F%E7%9A%84-spine-%E8%B5%84%E6%BA%90
laya:
cocos和laya 也能够运行时替换 spine 素材,但是都需要重新load素材,然后调用API加载。重新加载时,也都会重新buildMesh。
state & skeleton
这两个 API 放到一起说。
修改方向
调研:
unity 组件对外暴露了 spine-core 的这两个对象,左边是动画组件,暴露了 state:spine.AnimationState 对象,右边是渲染组件,暴露了 Spine.Skeleton 对象
ue 的实现和 unity 一样,也有两个组件一个是 skeleton 组件,一个是 animation 组件。后者继承于前者。
同样,也暴露了 skeleton state 的对象的 API,但是不是以对象的方式。而是把 API 拍平了挨个暴露出去,并且对于原本的 API 有二次封装,目的是为了更好地整合 Spine 动画系统,利用 UE 的内置特性,如蓝图、反射系统和垃圾回收机制。
提供了 get 方法来获取这两个对象。
API 与 spine-core 一致。
暴露了 spine-core 的 Skeleton 和 AnimationState 对象。
没有暴露这两个对象,但是基于这两个对象的方法,封装了常用的几个函数,比如:播放动画,替换附件,设置皮肤,还有一些 util 方法,比如:骨架归位,修改骨骼 Transform 等。之所以二次封装的理由上面也提到了,是因为运行时有一些额外的实现(动画烘焙,性能优化)。
addSeparateSlot
改造方向
目前这个方法可以删除,运行时的 API 需要在编辑器有对应功能,在添加分割插件前不需要这个 API
根据目前的调研,目前仅有 unity 提供了类似方法,用于处理一些特殊的遮挡情况:
针对拆分功能的调研
unity 的渲染组件中包含一个参数:separatorSlots
在 separatorSlots 中的插槽会用于单独创建一个独立的 subMesh。这个参数会被插件组件 SkeletonRenderSeparator 使用。SkeletonRenderSeparator 能够设置分离槽位的渲染顺序。
defaultState
改造方向
调研的引擎中,都有对应的参数来设置初始化的动画状态。不过部分引擎提供的参数只能够用于编辑器预览,无法应用到运行时。
调用后,个人认为,这些初始化的参数,直接压平放到运行时不合理,会让用户觉得这是提供出来用于修改皮肤和动画的的util API。 在保证初始化功能的前提下,为了不让用户对 API 有混淆,保留 defaultState 这一层,收拢所有初始化相关的参数。
针对默认状态 API 的调研:
unity 也能够设置初始化的 spine 状态,对应的 API 分别是:
AnimationName,还有一个单独的 loop 参数
initialSkinName
该参数在实例化时,会生效,用于设置 spine 的初始皮肤。
缩放只能够通过 flip 来设置初始的正反。
ue 没有提供对外的接口设置初始化的动画和皮肤,但是提供了两个设置项,用来预览动画和皮肤。
godot 没有提供初始化的 API ,而是提供了 preview_skin,preview_animation 用于设置预览的皮肤和动画。
如果脚本没有更新皮肤和动画,那么会直接应用 preview 这里设置的属性。
但是经过我测试,动画并没有应用成功,而且还搜索到类似的 bug:https://github.com/EsotericSoftware/spine-runtimes/issues/2530
没有提供动画,皮肤的初始化接口
没有对外暴露 初始化API,但是提供了内置的 API 且对应编辑器的接口:包括 Animation, SkinName。但是初始动画 的loop 则直接对外暴露。
setting
setting 目前管理了几个渲染相关参数,有useClipping(是否开启裁减) 和 zSpacing(层之间的间隙)。
改造方向
针对渲染参数的调研:
unity 类似的渲染参数是直接平铺在 Renderer 内的。
ue
有一个 DepthOffset 参数但是没有暴露出来是固定值
没有类似参数,阅读了代码似乎是靠 index 顺序来控制绘制顺序的
没有这两个参数,z 轴顺序是靠 mesh 的 zIndex 。
有一个 tint 开关参数,没有其他的渲染参数了
没有类似的渲染参数
新增实例化 API
目前只提供了 API 替换 spine 的 resource,但是没有提供 API 进行 atlas 的替换。
altas 素材的替换是常见的需求,详见 spine forum 帖子:
https://zh.esotericsoftware.com/forum/d/26252-swapping-atlases-based-on-screen-resolution
https://zh.esotericsoftware.com/forum/d/15659-how-to-change-the-quotactivequot-atlas-asset-at-runtime/2
https://zh.esotericsoftware.com/forum/d/18098-runtime-change-spineatlasasset/3
由于 1.3 没有实现 Spine atlas 素材的单独上传,所以替换 atlas 也没有实现。
调研 unity: unity 提供了一个 createRuntimeInstance 方法来创建一个 SkeletonDataAsset 对象,接收 skeleton 文件和 atlas 图集文件,创建新的SkeletonDataAsset:
优势:提供了方法在运行时创建并替换 spine 动画资产,灵活性强。 劣势:但是,运行时创建资产时,需要手动指定 skeleton 和 atlas 的关联关系。
ue: ue 没有对外提供更新的方法,但是引擎内部有对应的实现,当 atlas 和 skeleton 两个数据发生改变时,会重新调用 GetSkeletonData 来加载并重新创建 Skeleton 和 AnimationState 对象,这和 unity 的操作是一样的。只不过 unity 会在 initialize 方法中执行加载和创建的逻辑
劣势:ue 替换素材的方法没有对外暴露,无法运行时手动创建 spine 动画资产。
godot: goto 提供了一个 set_skeleton_data_res 方法用于设置 spine 资产。当资产修改后,会在内部调用一个更新方法,重新执行加载逻辑:
劣势:Godot 替换素材的 skeleton文件与atlas文件的关联关系无法修改,重新设置资产只能设置加载完毕的 skeleton_data_res 对象,灵活性没有 unity 高。
pixi pixi 动态替换 atlas,需要重新加载骨架和图集素材,调用 from 方法重新创建新的 spine 动画对象。
劣势:pixi 中,skeleton 和 atlas 的关联关系,只能手动建立。由于缺乏编辑器上传流程,假设文件不对应,会导致无法渲染或者渲染出错。
cocos cocos 替换 atlas 的方式和 unity 类似,也需要重新创建新的 skelentonData 素材:
优势:灵活性强,能够运行时修改 spine 动画资产,还可以自定义 atlas 关联的图片素材的路径。 劣势:不同资产的关联关系需要手动建立。假设文件不对应,会导致无法渲染或者渲染出错。
laya laya 没有提供替换 atlas 的方式,只能加载新的 spine 动画素材:
劣势:与 godot 类似,无法修改 skeleton 和 atlas 文件的关联关系。
结论: 综合调研,最好的方案需要支持以下功能:
具体方案: 1和2目前已经支持: Galacean 的编辑器资产和运行时使用的资产是通过 Loader 来完成转化的。目前skeletonDataAsset,spineAtlasAsset都有对应的 Loader。即已经存在方法在运行时创建运行时使用的资产了。( 这种情况下,素材的关联关系已经在上传素材时就确定好了) 这几种资产的运行时资产如下: skeletonDataAsset 的运行时资产是SkeletonDataResource spineAtlasAsset 的运行时资产是TextureAtlas texture的运行时资产是 Texture2D
3.提供新的创建运行时使用的资产的方法,并且支持手动建立资产关联关系。
createSpineResource(skeletonFile: string, atlas: TextureAtlas ): SpineResource {}
crreateTextureAtlas(atlasFile: string, textureFiles?:string[]): TextureAtlas {}
额外科普: 为什么 spine 动画再替换 atlas 后,需要重新初始化构建 mesh 呢? 重新初始化的原因如下:
至于为什么要重新构建mesh, 重新初始化后,相当于替换了一个新的 spine 素材。buffer 数据肯定会发生变化,所以调研的6款引擎都重新构建了新的 mesh。 那么可以针对替换 atlas 这种换肤场景,进行优化(不更新顶点)吗? 没必要。
生命周期方法
原生的几个方法已经能够满足开发需求了,高级的代理方法根据调研,目前只有 Unity 和 Godot 有提供。两个引擎提供的代理方法也不一样。所以现在就新增额外的生命周期方法不合适。暂时不额外添加。