大家好,好久不见,我是某昨。
最近依然是在写 Flutter
方面的内容,这次的需求是用 Flutter
渲染 Spine
动画。如果你不知道 Spine
的话可以尝试搜索一下,简单来说就是一套成熟的 2D
ToC
先行者
在全文之前我需要声明一下,整个框架的移植并不是我完成的,而是基于了先行者的成果。总的来说是基于以下这两个库:
spine_core
https://github.com/jtakakura/spine_core
这个库负责的是 Spine
文本内容的解析,对应的是 spine-ts/core
的部分。Spine
需要解析的内容是 atlas
和 json
文件,并由此生成 Skeleton
对象。这部分的代码非常稳定,至少到目前位置没出现什么这个层面的问题,感谢。
这个库也发布在了 Dart Package
上[1]。
spine_flutter
https://github.com/jtakakura/spine_flutter
这个库负责的就是渲染方面的内容了,对应的是 spine-ts/canvas
的部分。master
分支的代码逻辑很乱,绕来绕去看着令人头秃,还把 spine_core
的所有代码都附上了。dev2
分支就干净多了,整体逻辑很清楚,但缺点是有些部分没有完成。我们的开发就是基于这个分支的。
问题
在整个开发过程中,我们总共有两个问题:千手观音和拼接处空白。
千手观音
可以很明显地发现,めぐる 的手臂多出了好几根,手也多出了好几只。这就是这篇文章解决的主要问题了。
拼接处空白
这个问题其实最后也没有解决,要想解决这个问题以我现在的水平恐怕是做不到的吧(叹)。问题的根源在于浮点数精度,导致三角形的绘画必定中间会出现白色,就像下面这样:
仔细观察 めぐる 鼻子附近的部分,可以看到非常明显的淡灰色细线,这就是问题所在。如果我们开启了 Debug Paint
,可以发现这部分其实就是三角形的交界部分:
问题的原因应该算是找到了,那该怎么修复呢?如果我找到了解决方案的话,那应该就是下一篇博客的事了吧(笑)
开始之前
在开始之前,我们首先需要了解一些背景知识。
网格附件(mesh
)
这个是我们这次渲染的主角。上面提到了三角形的问题,而三角形的出现也恰恰和 mesh
有着密不可分的关系。
引用官网的话[2],网格支持在图片内设置多边形,之后可操纵多边形的顶点,以有效的方式让图片弯曲和变形。简单来说,就是通过多边形的形变产生动画效果。具体的方式看下面这张图就足够了:
是不是看到了大量的三角形?mesh
需要绘制的也就是这些三角形。
Path
Path
,也就是路径,简单来说就是类似画笔一样的存在。通过 Path
当前点的不断移动,我们可以通过历史轨迹形成一条当前点的移动路径。
这个路径可以直接绘制出来——也就是绘制线条;也可以填充它的内部——也就是绘制图形;还可以在它上面进行 transform
——施加形变。
Paint
Paint
是一个 Flutter
的概念,表示的是当前将要绘制到 canvas
上的样式。没错,不同于 JavaScript
的全局样式,Flutter
中有着更细致的样式划分。这也使得 Flutter
的渲染不需要像 JS
渲染那样需要 save/restore
那么多次上下文。
Canvas
这个大家应该都知道吧,我就不解释了(逃
手怎么多了?
修复 Debug
模式
遇到问题要做的第一件事就是定位问题的发生点。为了看起来方便,我这里首先修复了 debugRendering
的部分:
这段代码就算没有了解过 canvas
的读者应该也能看明白吧。首先是创建了一个 Path
,通过三个点构成了一个三角形;然后在 canvas
上用绿色、宽度为 1 的 stroke
画出了这条 Path
。
补全了这部分代码之后,我们来看一下效果:
可以发现,手和手臂的部分明显出现了三角形的重叠。于是我们可以断定:是手多画了。
定位实际问题
接下来的这一步是最耗时的,也是最令人无奈的。在不断的测试中,我发现有时候减少 Slot
的绘制会使结果正常。最后也正是这个实验结果促使我进行了一波插桩分析,并且发现了令人惊喜的线索:
多绘制的身体部分都存在 "color": "ffffff00"
这一条目。
当我得出这个结论的时候,其实问题就已经解决了。因为作者注释掉了和这个有关的某几行代码:
这里也可以看到很明显的移植迹象:没有找到对应语言中的替代品,因此暂时注释处理。但在这里的注释就要了老命了。这使得所有的 Slot
都以 100%
透明度进行绘制,导致本该因透明度而被隐藏的 Slot
也被绘制了出来。因此解决方案也很简单,只要让 Paint
带上这个透明度就行了。
至此,千手迷案也就告一段落了。
除此之外
除了解决上面这个问题,其实我还做了一些更改,不过和这个相比就微不足道了。
spine_flutter
默认是从 assets
中加载素材的。需要加载的素材上文也提到了:atlas
、json
和图片。而我实际应用中是从网络上获取的,因此我增加了直接提供资源内容的素材加载方式。当存在对应的素材时,就不在本地查找了。
结语
整个过程其实持续的时间挺长的。因为不了解,因此花了大量的时间去理解源码的内容,并且经过了挺长时间的比对才最终确定是基于什么移植的。确定了这个其实之后就是比对源码的过程了,于是我又花了挺多的时间比对两份有差异的源码……
目前修改过的源码已经丢到 GitHub
上了:
由于原作者已经两年没更新了,而且也没有在 Dart Packages
上发布这个包,我也就不 PR
过去了(笑
嘛,就是这样(