Skip to content

spine_flutter 的重叠绘制问题

Published: at 02:25

大家好,好久不见,我是某昨。

最近依然是在写 Flutter 方面的内容,这次的需求是用 Flutter 渲染 Spine 动画。如果你不知道 Spine 的话可以尝试搜索一下,简单来说就是一套成熟的 2D骨骼动画系统。

ToC

先行者

在全文之前我需要声明一下,整个框架的移植并不是我完成的,而是基于了先行者的成果。总的来说是基于以下这两个库:

spine_core

https://github.com/jtakakura/spine_core

这个库负责的是 Spine 文本内容的解析,对应的是 spine-ts/core 的部分。Spine 需要解析的内容是 atlasjson 文件,并由此生成 Skeleton 对象。这部分的代码非常稳定,至少到目前位置没出现什么这个层面的问题,感谢。

这个库也发布在了 Dart Package[1]

spine_flutter

https://github.com/jtakakura/spine_flutter

这个库负责的就是渲染方面的内容了,对应的是 spine-ts/canvas 的部分。master 分支的代码逻辑很乱,绕来绕去看着令人头秃,还把 spine_core 的所有代码都附上了。dev2 分支就干净多了,整体逻辑很清楚,但缺点是有些部分没有完成。我们的开发就是基于这个分支的。

问题

在整个开发过程中,我们总共有两个问题:千手观音和拼接处空白。

千手观音

@KAAAsS
@KAAAsS

可以很明显地发现,めぐる 的手臂多出了好几根,手也多出了好几只。这就是这篇文章解决的主要问题了。

拼接处空白

这个问题其实最后也没有解决,要想解决这个问题以我现在的水平恐怕是做不到的吧(叹)。问题的根源在于浮点数精度,导致三角形的绘画必定中间会出现白色,就像下面这样:

仔细观察 めぐる 鼻子附近的部分,可以看到非常明显的淡灰色细线,这就是问题所在。如果我们开启了 Debug Paint,可以发现这部分其实就是三角形的交界部分:

问题的原因应该算是找到了,那该怎么修复呢?如果我找到了解决方案的话,那应该就是下一篇博客的事了吧(笑)

开始之前

在开始之前,我们首先需要了解一些背景知识。

网格附件(mesh

这个是我们这次渲染的主角。上面提到了三角形的问题,而三角形的出现也恰恰和 mesh 有着密不可分的关系。

引用官网的话[2],网格支持在图片内设置多边形,之后可操纵多边形的顶点,以有效的方式让图片弯曲和变形。简单来说,就是通过多边形的形变产生动画效果。具体的方式看下面这张图就足够了:

是不是看到了大量的三角形?mesh 需要绘制的也就是这些三角形。

Path

Path,也就是路径,简单来说就是类似画笔一样的存在。通过 Path 当前点的不断移动,我们可以通过历史轨迹形成一条当前点的移动路径

这个路径可以直接绘制出来——也就是绘制线条;也可以填充它的内部——也就是绘制图形;还可以在它上面进行 transform——施加形变。

Paint

Paint 是一个 Flutter 的概念,表示的是当前将要绘制到 canvas 上的样式。没错,不同于 JavaScript 的全局样式,Flutter 中有着更细致的样式划分。这也使得 Flutter 的渲染不需要像 JS 渲染那样需要 save/restore 那么多次上下文。

Canvas

这个大家应该都知道吧,我就不解释了(逃

手怎么多了?

修复 Debug 模式

遇到问题要做的第一件事就是定位问题的发生点。为了看起来方便,我这里首先修复了 debugRendering 的部分:

if (_debugRendering) {
final Path path = Path()
..moveTo(x0, y0)
..lineTo(x1, y1)
..lineTo(x2, y2)
..lineTo(x0, y0);
canvas.drawPath(
path,
Paint()
..style = PaintingStyle.stroke
..color = Colors.green
..strokeWidth = 1,
);
}

这段代码就算没有了解过 canvas 的读者应该也能看明白吧。首先是创建了一个 Path,通过三个点构成了一个三角形;然后在 canvas 上用绿色、宽度为 1 的 stroke 画出了这条 Path

补全了这部分代码之后,我们来看一下效果:

可以发现,手和手臂的部分明显出现了三角形的重叠。于是我们可以断定:是手多画了

定位实际问题

接下来的这一步是最耗时的,也是最令人无奈的。在不断的测试中,我发现有时候减少 Slot 的绘制会使结果正常。最后也正是这个实验结果促使我进行了一波插桩分析,并且发现了令人惊喜的线索:

多绘制的身体部分都存在 "color": "ffffff00" 这一条目。

当我得出这个结论的时候,其实问题就已经解决了。因为作者注释掉了和这个有关的某几行代码:

这里也可以看到很明显的移植迹象:没有找到对应语言中的替代品,因此暂时注释处理。但在这里的注释就要了老命了。这使得所有的 Slot 都以 100% 透明度进行绘制,导致本该因透明度而被隐藏的 Slot 也被绘制了出来。因此解决方案也很简单,只要让 Paint 带上这个透明度就行了。

至此,千手迷案也就告一段落了。

除此之外

除了解决上面这个问题,其实我还做了一些更改,不过和这个相比就微不足道了。

spine_flutter 默认是从 assets 中加载素材的。需要加载的素材上文也提到了:atlasjson 和图片。而我实际应用中是从网络上获取的,因此我增加了直接提供资源内容的素材加载方式。当存在对应的素材时,就不在本地查找了。

结语

整个过程其实持续的时间挺长的。因为不了解,因此花了大量的时间去理解源码的内容,并且经过了挺长时间的比对才最终确定是基于什么移植的。确定了这个其实之后就是比对源码的过程了,于是我又花了挺多的时间比对两份有差异的源码……

中间有想过直接放弃治疗用 Webview 好了,感谢 Flutter 的复杂性拯救了我(

目前修改过的源码已经丢到 GitHub 上了:

改了一下名字,看上去舒服一点(

由于原作者已经两年没更新了,而且也没有在 Dart Packages 上发布这个包,我也就不 PR 过去了(笑

嘛,就是这样(


Previous Post
Flutter 后台运算与大量文本渲染
Next Post
Postfix 快速搭建只收不发的统一邮件验证系统