关于 2dx v3.7 uiscale9sprite的bug

传图和格式化代码好麻烦,请移步博客:
http://www.cnblogs.com/songcf/p/4755193.html

刚把引擎重js binding v3.0升级到v3.7,发现了一些bug,这里先说说关于scale9的,其他的以后有空了慢慢整理了再发帖

  1. 关于capInsets

bool Scale9Sprite::updateWithSprite(Sprite* sprite,
const Rect& textureRect,
bool rotated,
const Vec2 &offset,
const Size &originalSize,
const Rect& capInsets)
{
//…

// Set the given rect’s size as original size
_spriteRect = rect;
_offset = offset;
_spriteFrameRotated = rotated;
_originalSize = size;
_preferredSize = size;
// if(!capInsets.equals(Rect::ZERO)) //此处的if判断应该去掉,直接赋值给_capInsetsInternal
{
_capInsetsInternal = capInsets;
}
if (_scale9Enabled)
{
this->createSlicedSprites();
}

//…
}

原因:
setSpriteFrame时,默认传入的capInsets是zero,注意_insetRight ,_insetBottom 的值

void Scale9Sprite::setSpriteFrame(SpriteFrame * spriteFrame, const Rect& capInsets)
{
    Sprite * sprite = Sprite::createWithTexture(spriteFrame->getTexture());
    this->updateWithSprite(sprite,
                           spriteFrame->getRect(),
                           spriteFrame->isRotated(),
                           spriteFrame->getOffset(),
                           spriteFrame->getOriginalSize(),
                           capInsets);


    // Reset insets

this->_insetLeft = capInsets.origin.x; // == 0
this->_insetTop = capInsets.origin.y;// == 0
this->_insetRight = _originalSize.width - _insetLeft - capInsets.size.width; //== width
this->_insetBottom = _originalSize.height - _insetTop - capInsets.size.height;// ==height
}

然后按顺序调用setInsetLeft(0), setInsetTop(0), setInsetRight(0), setInsetBottom(0),
4个函数都会调用updateCapInset,并在其中调用setCapInsets,在setCapInsets中调用updateWithSprite和重新计算_insetRight ,_insetBottom的值,
那么问题来了,执行到setInsetRight(0)时:

void Scale9Sprite::setCapInsets(const Rect& capInsets)
{
    Size contentSize = this->_contentSize;
    this->updateWithSprite(this->_scale9Image,
                           _spriteRect,
                           _spriteFrameRotated,
                           _offset,
                           _originalSize,
                           capInsets); // 这里传入的capInsets = (0,0, origin.width,0),此值不等于zero,在updateWithSprite中会赋值给_capInsetsInternal
    this->_insetLeft = capInsets.origin.x;
    this->_insetTop = capInsets.origin.y;
    this->_insetRight = _originalSize.width - _insetLeft - capInsets.size.width;
    this->_insetBottom = _originalSize.height - _insetTop - capInsets.size.height;
    this->setContentSize(contentSize);
}

那么再在最后一次调用 setInsetBottom(0)时,capInsets==zero,不会覆盖_capInsetsInternal,那么_capInsetsInternal就成了一个错误的临时值,其实我们要的应该是最后一次调用后的值,即使用默认的九宫格缩放

// If there is no specified center region
if ( _capInsetsInternal.equals(Rect::ZERO) )
{
// log("… cap insets not specified : using default cap insets …");
_capInsetsInternal = Rect(width /3, height /3, width /3, height /3);
}

当_capInsetsInternal == (0,0, origin.width,0)时,默认值也不会被使用,那么九宫格缩放当然是错误的啦。

那么问题又来了,谁会连续这样调用这4个接口呢:setInsetLeft(0), setInsetTop(0), setInsetRight(0), setInsetBottom(0)。这里(使用cocosbuilder的时候):

void Scale9SpriteLoader::onHandlePropTypeFloat(Node * pNode, Node * pParent, const char * pPropertyName, float pFloat, CCBReader * ccbReader) {
if(strcmp(pPropertyName, PROPERTY_INSETLEFT) == 0) {
((cocos2d::ui::Scale9Sprite *)pNode)->setInsetLeft(pFloat);
} else if(strcmp(pPropertyName, PROPERTY_INSETTOP) == 0) {
((cocos2d::ui::Scale9Sprite *)pNode)->setInsetTop(pFloat);
} else if(strcmp(pPropertyName, PROPERTY_INSETRIGHT) == 0) {
((cocos2d::ui::Scale9Sprite *)pNode)->setInsetRight(pFloat);
} else if(strcmp(pPropertyName, PROPERTY_INSETBOTTOM) == 0) {
((cocos2d::ui::Scale9Sprite *)pNode)->setInsetBottom(pFloat);
} else {
NodeLoader::onHandlePropTypeFloat(pNode, pParent, pPropertyName, pFloat, ccbReader);
}
}

所以updatewithsprite中,// if(!capInsets.equals(Rect::ZERO)) //此处的if判断应该去掉,直接赋值给_capInsetsInternal ,避免多次setCapInset时使用中间的临时值。

======================================================华丽的分割线==================================================

  1. createSlicedSprites接口中计算offsetPosition

void Scale9Sprite::createSlicedSprites()
{
float width = _originalSize.width;
float height = _originalSize.height;

Vec2 offsetPosition(ceil(_offset.x + (_originalSize.width - _spriteRect.size.width) / 2),
ceil(_offset.y + (_originalSize.height - _spriteRect.size.height) / 2));
//…
}

此处计算偏移时为什么要向上取整,总之我不是很明白,希望有人能解释一下,谢谢
导致的问题(怎么传图???):
生成的九宫格图片偏移了几个像素,这个问题一般情况下不明显,我是在使用plist拼图时发现, 9宫格图片下方出现了其他图片的条纹,然后自己图片的上方少了几个像素。
然后找到上面的代码,去掉ceil后,发现恢复了一些,把ceil换成floor后,发现正常了。
那么问题来了:
按我的理解此处不应该使用ceil,也不应该使用floor,但为什么使用floor后才是正常的。有空的朋友可以使用texturepacker打个纹理图集测试一下,把shape outlines勾选上就能看到图片的矩形区域,看看你们创建出来的scale9sprite矩形区域是否发生了偏移。

=====================================华丽的分割线=================================================

楼主能否提供测试代码和测试图片?

关注一下等后续

这里发图片太麻烦,测试图片看这里:http://www.cnblogs.com/songcf/p/4755193.html

@821416394 网盘上传个资源,比如 plist和 png 我要测试一下。

“此处计算偏移时为什么要向上取整,总之我不是很明白,希望有人能解释一下,谢谢“

这个主要是为了解决 spritsheet里面的 crop 问题。

@子龙山人
mainscene_res/btn_sign.png
res.zip (973 KB)

— Begin quote from ____

引用第5楼821416394于2015-08-25 09:57发表的 :
@子龙山人
mainscene_res/btn_sign.png
res.zip (973 KB) http://www.cocoachina.com/bbs/job.php?action=topost&tid=320932&pid=1376255

— End quote

这里确实不需要用 ceil,我把 ceil 去掉后就 ok 了。你在博客里面提到要改成 floor,why?

楼主的代码是 ok 的,需要把 ceil 改成 floor,我再验证下其它测试例 。

我测试平台是 mac

— Begin quote from ____

引用第5楼821416394于2015-08-25 09:57发表的 :
@子龙山人
mainscene_res/btn_sign.png
res.zip (973 KB) http://www.cocoachina.com/bbs/job.php?action=topost&tid=320932&pid=1376255

— End quote

你一个 bug 修复的 PR 在这里:

https://github.com/cocos2d/cocos2d-x/pull/13563

第二个问题还需要更多地验证。

感谢楼主反馈 bug。

按照我的理解,计算中心点偏移这里 ceil 和 floor都不应该使用,但是都不使用时,还是有一小点像素偏差,要使用floor才会正确,山人大湿能否解释一下为什么这里要取整

我们的纹理信息从 plist取出来以后是要从像素转换成点的:

bool SpriteFrame::initWithTexture(Texture2D* texture, const Rect& rect, bool rotated, const Vec2& offset, const Size& originalSize)
{
_texture = texture;

if (texture)
{
    texture->retain();
}

_rectInPixels = rect;
_rect = CC_RECT_PIXELS_TO_POINTS(rect);
_offsetInPixels = offset;
_offset = CC_POINT_PIXELS_TO_POINTS( _offsetInPixels );
_originalSizeInPixels = originalSize;
_originalSize = CC_SIZE_PIXELS_TO_POINTS( _originalSizeInPixels );
_rotated = rotated;

return true;

}

这里CC_POINT_PIXELS_TO_POINTS会把原来的整数的偏移转换成浮点数,因为 contenScaleFactor 可能是2.1333这种数值。

然后用point 的单位去计算出Scale9Sprite 未 crop 的大小时,是需要取整的:

originalRect = Rect(_spriteRect.origin.x - offsetPosition.y,
_spriteRect.origin.y - offsetPosition.x,
_originalSize.width, _originalSize.height);

好的、 谢谢,下面的各种矩形计算有点复杂,有空了再仔细看看

https://github.com/cocos2d/cocos2d-x/issues/13564