bug源自一个很奇葩的需求,使用spine atlas进行换肤
正常来说,atals里面存在一个大小一模一样的相同纹理用来进行换肤
但是项目中因为各种原因,竟然出现了要换肤的atlas纹理的尺寸不一致
所以就导致换肤后出现,替换的地方纹理发生了缩小,没有和原始尺寸保持一致
测试例制作
制作测试例也非常简单,spine在导出纹理时,支持对纹理进行缩放
同时我开发了一个小工具,可以对spine atlas进行拆图,这样我就拿到了缩放后的散图文件,当然你也可以通过其他工具对散图进行缩放,只要能拿到缩放比例的散图文件即可。
问题排查
最开始我怀疑是uv的问题,后来仔细思考后,应该是xy顶点导致的,因为uv错误一般的表现是纹理发生错乱,这里很明显没有发生错乱,仅仅是大小有问题。
这里需要对creator引擎渲染spine的逻辑比较清楚,不了解的可以参考之前的文章。
我尝试打印了计算后的顶点数据(x,y,u,v,color)的xy,发现
果然是x,y的位置计算出现了缩放,查看计算x,y的逻辑
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不一致,感觉可能是这里的问题。
又追查了下this.offset的逻辑来源
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的细节
这个情况旋转是和配置文件对应的
289.42-360=-70.58这个旋转,在spine编辑器中和配置文件中显示的不一样,存在一个转换关系
因为骨骼有旋转,visor旋转为0时对不上,所以还需要再旋转一下。
offset转换
spine里面atlas的offset的含义
我扩大像素后,将原图靠近左上角
spine裁切透明像素输出前后的差异
可以得出结论,这个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裁切透明像素后,计算偏移的问题
offset(34,-18)的含义,就是 trim后的图原点和原来的图原点的偏移量,也就是上图中的2个中心点偏移量。
34 = (131/2+99) - 261/218 = (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)
当我把这个带透明像素的图片放到spine,输出结果是
和{99,36}对上了,不过是反向的36(0)
扩展问题
至此这个问题得到了彻底的修复,同时还发现另外一个问题,使用plist/spriteFrame尺寸不一样进行换肤也会存在同样的问题,同样mesh也不存在这个问题,原因同上。
c++适配
c++引擎在实现的时候也需要注意这个问题,主题逻辑和js engine非常类似,可以直接套用,RegionAttachment的width/height属性不能和SpriteFrame同步,不然也会出现类似的问题。