creator中spine换肤纹理比例不一致导致的问题

662 阅读5分钟

bug源自一个很奇葩的需求,使用spine atlas进行换肤

正常来说,atals里面存在一个大小一模一样的相同纹理用来进行换肤

image.png

但是项目中因为各种原因,竟然出现了要换肤的atlas纹理的尺寸不一致

image.png

所以就导致换肤后出现,替换的地方纹理发生了缩小,没有和原始尺寸保持一致

image.png

测试例制作

制作测试例也非常简单,spine在导出纹理时,支持对纹理进行缩放

image.png

同时我开发了一个小工具,可以对spine atlas进行拆图,这样我就拿到了缩放后的散图文件,当然你也可以通过其他工具对散图进行缩放,只要能拿到缩放比例的散图文件即可。

问题排查

最开始我怀疑是uv的问题,后来仔细思考后,应该是xy顶点导致的,因为uv错误一般的表现是纹理发生错乱,这里很明显没有发生错乱,仅仅是大小有问题。

这里需要对creator引擎渲染spine的逻辑比较清楚,不了解的可以参考之前的文章

我尝试打印了计算后的顶点数据(x,y,u,v,color)的xy,发现

image.png

果然是x,y的位置计算出现了缩放,查看计算x,y的逻辑

image.png

var x = bone.worldX, y = bone.worldY;
var a = bone.a, b = bone.b,  // 坐标x
    c = bone.c, d = bone.d;  // 坐标y
var offsetX = 0, offsetY = 0;
offsetX = vertexOffset[RegionAttachment.OX1];
offsetY = vertexOffset[RegionAttachment.OY1];
worldVertices[offset]     = offsetX * a + offsetY * b + x; // x
worldVertices[offset + 1] = offsetX * c + offsetY * d + y; // y

仔细对比了堆栈变量,发现this.offset不一致,感觉可能是这里的问题。

image.png

又追查了下this.offset的逻辑来源

image.png

RegionAttachment.prototype.updateOffset = function () {
    // 一般来说,scaleX是1不会变化,底层也没有暴露接口,originalWidth是带透明度的尺寸
    var regionScaleX = this.width / this.region.originalWidth * this.scaleX;
    var regionScaleY = this.height / this.region.originalHeight * this.scaleY;
    // 中心点的偏移
    var localX = -this.width / 2 * this.scaleX + this.region.offsetX * regionScaleX;
    var localY = -this.height / 2 * this.scaleY + this.region.offsetY * regionScaleY;
    // region.width是剔除透明度后的尺寸
    var localX2 = localX + this.region.width * regionScaleX;
    var localY2 = localY + this.region.height * regionScaleY;
    
    var radians = this.rotation * Math.PI / 180;
    var cos = Math.cos(radians);
    var sin = Math.sin(radians);
    var localXCos = localX * cos + this.x;
    var localXSin = localX * sin;
    var localYCos = localY * cos + this.y;
    var localYSin = localY * sin;
    var localX2Cos = localX2 * cos + this.x;
    var localX2Sin = localX2 * sin;
    var localY2Cos = localY2 * cos + this.y;
    var localY2Sin = localY2 * sin;
    var offset = this.offset;
    offset[RegionAttachment.OX1] = localXCos - localYSin;
    offset[RegionAttachment.OY1] = localYCos + localXSin;
    offset[RegionAttachment.OX2] = localXCos - localY2Sin;
    offset[RegionAttachment.OY2] = localY2Cos + localXSin;
    offset[RegionAttachment.OX3] = localX2Cos - localY2Sin;
    offset[RegionAttachment.OY3] = localY2Cos + localX2Sin;
    offset[RegionAttachment.OX4] = localX2Cos - localYSin;
    offset[RegionAttachment.OY4] = localYCos + localX2Sin;
};

发现最后是和this.width有关系,刚好和我的实现关联上了

const tex2d: cc.Texture2D = frame.getTexture();
const { x, y, width, height } = frame.getRect(); // 宽高等于frame的原因导致的
this.width = width; // 屏蔽掉就正常了
this.height = height;// // 屏蔽掉就正常了
// width, height属性只在Region的计算中使用到了,Mesh没有使用

看了下MeshAttachMent的代码,好像没有关于this.offset的逻辑,实际测试的过程中发现MeshAttachment并没有受影响

其实关于localX的计算,我看的并不是非常清楚,只是直觉告诉我可能问题出现width身上,尝试后果然发现问题修复了。

spine atals 方案

replace1
  rotate: true
  xy: 426, 3
  size: 36, 40   # size是经过裁切过透明度,可以不一致
  orig: 225, 291 # origin一定要一致
  offset: 28, 24
  index: -1
replace2
  rotate: false
  xy: 402, 168
  size: 100, 153 # size是经过裁切过透明度,可以不一致
  orig: 225, 291 # origin一定要一致
  offset: 22, 23
  index: -1

rotaion的细节

image.png

这个情况旋转是和配置文件对应的

image.png

289.42-360=-70.58这个旋转,在spine编辑器中和配置文件中显示的不一样,存在一个转换关系

image.png

因为骨骼有旋转,visor旋转为0时对不上,所以还需要再旋转一下。

offset转换

spine里面atlas的offset的含义

我扩大像素后,将原图靠近左上角

image.png

spine裁切透明像素输出前后的差异

image.png

可以得出结论,这个offset就是裁切像素后,相对于左下角的偏移。

plist也会对透明像素进行裁切处理

<dict>
    <key>frame</key>
    <string>{{2,2},{132,126}}</string>
    <key>offset</key>
    <string>{-4,7}</string> // 没明白这个参数的具体含义
    <key>rotated</key>
    <false/>
    <key>sourceColorRect</key>
    <string>{{0,0},{132,126}}</string>// 裁切透明度后的尺寸
    <key>sourceSize</key>
    <string>{140,140}</string> // 带透明度的原始大小
</dict>

关于plist裁切透明像素后,计算偏移的问题

image.png

offset(34,-18)的含义,就是 trim后的图原点原来的图原点的偏移量,也就是上图中的2个中心点偏移量。

  • 34 = (131/2+99) - 261/2
  • 18 = (132/2+36) - 168/2坐标系问题,-18是以左下角为原点
offsetY= (trimHeight/2 + top) - sourceHeight/2
offsetY= (trimHeight- sourceHeight)/2 + top
换算成左下角
offsetY = (sourceHeight- trimHeight)/2 - top
top = (sourceHeight- trimHeight)/2 - offsetY
距离坐下角的Y
target= sourceHeight - (top + trimHeight)

官方对这个字段的解释spriteOffset

当我把这个带透明像素的图片放到spine,输出结果是

image.png

{99,36}对上了,不过是反向的36(0)

扩展问题

至此这个问题得到了彻底的修复,同时还发现另外一个问题,使用plist/spriteFrame尺寸不一样进行换肤也会存在同样的问题,同样mesh也不存在这个问题,原因同上。

image.png

c++适配

c++引擎在实现的时候也需要注意这个问题,主题逻辑和js engine非常类似,可以直接套用,RegionAttachmentwidth/height属性不能和SpriteFrame同步,不然也会出现类似的问题。