您的位置:首页 > 移动开发 > Cocos引擎

cocos2dx-深度解析plist文件(二)(CCSpriteFrameCache怎么从解析出的数据创建精灵帧的)

2016-06-29 17:31 447 查看

cocos2dx-深度解析plist文件(二)(CCSpriteFrameCache怎么从解析出的数据创建精灵帧的)

精灵帧的创建

CCSpriteFrameCache::addSpriteFramesWithDictionary(CCDictionary* dictionary, CCTexture2D *pobTexture)里面的CCDICT_FOREACH(framesDict, pElement)完成了对key为frame的字典的遍历,代码如下

CCDictionary* frameDict = (CCDictionary*)pElement->getObject();
std::string spriteFrameName = pElement->getStrKey();
CCSpriteFrame* spriteFrame = (CCSpriteFrame*)m_pSpriteFrames->objectForKey(spriteFrameName);


上面代码先从pElement中获得一个字典frameDict,然后获得它的key,其key是精灵帧的名字。一个精灵帧的XML形式如下如下

<key>1.png</key>
<dict>
<key>frame</key>
<string>{{2,868},{110,102}}</string>
<key>offset</key>
<string>{1,-15}</string>
<key>rotated</key>
<false/>
<key>sourceColorRect</key>
<string>{{24,39},{110,102}}</string>
<key>sourceSize</key>
<string>{156,150}</string>
</dict>


上面1.png是字典的key,dict是字典对象的value.如果if (spriteFrame)那么就continue。如果找不到就会执行下面代码

// create frame
spriteFrame = new CCSpriteFrame();
spriteFrame->initWithTexture(pobTexture,
frame,
rotated,
offset,
sourceSize
);


上面代码根据解析出的数据创建一个精灵帧。下面研究下精灵帧如何通过这些数据被创建的。

spriteFrame->initWithTexture代码如下

bool CCSpriteFrame::initWithTexture(CCTexture2D* pobTexture, const CCRect& rect, bool rotated, const CCPoint& offset, const CCSize& originalSize)
{
m_pobTexture = pobTexture;

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

m_obRectInPixels = rect;
m_obRect = CC_RECT_PIXELS_TO_POINTS(rect);
m_obOffsetInPixels = offset;
m_obOffset = CC_POINT_PIXELS_TO_POINTS( m_obOffsetInPixels );
m_obOriginalSizeInPixels = originalSize;
m_obOriginalSize = CC_SIZE_PIXELS_TO_POINTS( m_obOriginalSizeInPixels );
m_bRotated = rotated;

return true;
}


上面创建了m_obRect等变量后面都有用处。上一篇分析中提到pobTexture是由解析plist后创建的,pobTexture已经加入了纹理缓存,并且计数为1。这个时候该纹理是未使用纹理,可以调用纹理缓存的removeUnusedTextures直接删除掉的。上面的initWithTexture里面被没有做什么,如果纹理存在,就把纹理的计数加一,这样表示有一个对象引用该纹理了,如果纹理不存在,就不做什么,后面就简单的记录下一些信息,这些信息用于创建精灵的。通过精灵帧创建精灵的代码如下

CCSprite* CCSprite::createWithSpriteFrame(CCSpriteFrame *pSpriteFrame)
{
CCSprite *pobSprite = new CCSprite();
if (pSpriteFrame && pobSprite && pobSprite->initWithSpriteFrame(pSpriteFrame))
{
pobSprite->autorelease();
return pobSprite;
}
CC_SAFE_DELETE(pobSprite);
return NULL;
}
bool CCSprite::initWithSpriteFrame(CCSpriteFrame *pSpriteFrame)
{
CCAssert(pSpriteFrame != NULL, "");

bool bRet = initWithTexture(pSpriteFrame->getTexture(), pSpriteFrame->getRect());
setDisplayFrame(pSpriteFrame);

return bRet;
}


上面分两步完成精灵的初始化,第一步通过initWithTexture(pSpriteFrame->getTexture(), pSpriteFrame->getRect())设置纹理信息,第二步通过 setDisplayFrame(pSpriteFrame)

initWithTexture(pSpriteFrame->getTexture(), pSpriteFrame->getRect())代码如下

bool CCSprite::initWithTexture(CCTexture2D *pTexture, const CCRect& rect)
{
return initWithTexture(pTexture, rect, false);
}

// designated initializer
bool CCSprite::initWithTexture(CCTexture2D *pTexture, const CCRect& rect, bool rotated)
{
if (CCNodeRGBA::init())
{
m_pobBatchNode = NULL;

m_bRecursiveDirty = false;
setDirty(false);

m_bOpacityModifyRGB = true;

m_sBlendFunc.src = CC_BLEND_SRC;//设置混合方式
m_sBlendFunc.dst = CC_BLEND_DST;

m_bFlipX = m_bFlipY = false;//不翻转

// default transform anchor: center
setAnchorPoint(ccp(0.5f, 0.5f));//设置锚点

// zwoptex default values
m_obOffsetPosition = CCPointZero;

m_bHasChildren = false;

// clean the Quad
memset(&m_sQuad, 0, sizeof(m_sQuad));//4个顶点数据清0

// Atlas: Color
ccColor4B tmpColor = { 255, 255, 255, 255 };//设置顶点颜色,着色器里与纹理像素相乘得到片元颜色
m_sQuad.bl.colors = tmpColor;
m_sQuad.br.colors = tmpColor;
m_sQuad.tl.colors = tmpColor;
m_sQuad.tr.colors = tmpColor;

// shader program
setShaderProgram(CCShaderCache::sharedShaderCache()->programForKey(kCCShader_PositionTextureColor));//设置着色器

// update texture (calls updateBlendFunc)
setTexture(pTexture);//保存纹理指针
setTextureRect(rect, rotated, rect.size);

// by default use "Self Render".
// if the sprite is added to a batchnode, then it will automatically switch to "batchnode Render"
setBatchNode(NULL);

return true;
}
else
{
return false;
}
}


上面代码通过获得精灵帧存储的纹理,以及纹理范围来初始化精灵的纹理信息

pSpriteFrame->getTexture()代码如下

CCTexture2D* CCSpriteFrame::getTexture(void)
{
if( m_pobTexture ) {
return m_pobTexture;
}

if( m_strTextureFilename.length() > 0 ) {
return CCTextureCache::sharedTextureCache()->addImage(m_strTextureFilename.c_str());
}
// no texture or texture filename
return NULL;
}


上面代码描述了如果精灵帧的m_pobTexture存在,就直接返回,如果不存在,就创建纹理然后返回该纹理。pSpriteFrame->getRect()代码如下

inline const CCRect& getRect(void) { return m_obRect; }


它把m_obRect返回给精灵,这个是纹理的范围,描述了用于渲染出精灵的纹理的位置和大小,一个左下点位置与一个矩形区域。

上面代码做了注释,下面对setTextureRect(rect, rotated, rect.size)做下分析,代码如下

void CCSprite::setTextureRect(const CCRect& rect, bool rotated, const CCSize& untrimmedSize)
{
m_bRectRotated = rotated;

setContentSize(untrimmedSize);//设置节点m_obContentSize(描述了节点的原始大小),设置锚点m_obAnchorPointInPoints像素大小,不是float
setVertexRect(rect);// m_obRect = rect;m_obRect是纹理的范围
setTextureCoords(rect);//设置纹理的坐标

CCPoint relativeOffset = m_obUnflippedOffsetPositionFromCenter;

// issue #732
if (m_bFlipX)
{
relativeOffset.x = -relativeOffset.x;
}
if (m_bFlipY)
{
relativeOffset.y = -relativeOffset.y;
}
//设置纹理坐标的偏移
m_obOffsetPosition.x = relativeOffset.x + (m_obContentSize.width - m_obRect.size.width) / 2;
m_obOffsetPosition.y = relativeOffset.y + (m_obContentSize.height - m_obRect.size.height) / 2;

// rendering using batch node
if (m_pobBatchNode)
{
// update dirty_, don't update recursiveDirty_
setDirty(true);
}
else
{
// self rendering

// Atlas: Vertex
float x1 = 0 + m_obOffsetPosition.x;
float y1 = 0 + m_obOffsetPosition.y;
float x2 = x1 + m_obRect.size.width;
float y2 = y1 + m_obRect.size.height;

// Don't update Z.
m_sQuad.bl.vertices = vertex3(x1, y1, 0);
m_sQuad.br.vertices = vertex3(x2, y1, 0);
m_sQuad.tl.vertices = vertex3(x1, y2, 0);
m_sQuad.tr.vertices = vertex3(x2, y2, 0);
}
}


在plist中得到了精灵帧的需要的纹理的原始大小,与它的剪切后的纹理的大小,剪切是为了让整张纹理变小。还得到了纹理的范围,所以最后精灵帧关联创建的精灵,它的大小等于精灵帧的原始大小,它渲染用的纹理是裁剪后的纹理,同时纹理的位置对于节点有偏移,其实这个计算不是在这里完成的。而是在CCSprite::initWithSpriteFrame(CCSpriteFrame *pSpriteFrame)的setDisplayFrame(pSpriteFrame);中完成的,它的代码如下:

void CCSprite::setDisplayFrame(CCSpriteFrame *pNewFrame)
{
m_obUnflippedOffsetPositionFromCenter = pNewFrame->getOffset();

CCTexture2D *pNewTexture = pNewFrame->getTexture();
// update texture before updating texture rect
if (pNewTexture != m_pobTexture)
{
setTexture(pNewTexture);
}

// update rect
m_bRectRotated = pNewFrame->isRotated();
setTextureRect(pNewFrame->getRect(), m_bRectRotated, pNewFrame->getOriginalSize());
}


m_obUnflippedOffsetPositionFromCenter = pNewFrame->getOffset();获得了精灵帧偏移m_obOffset,这个是在解析是得到的,可以返回前面看一下。

CCTexture2D *pNewTexture = pNewFrame->getTexture();是获得精灵帧对应的纹理。

m_bRectRotated = pNewFrame->isRotated();获得是否纹理在plist对应的整个纹理中旋转了,旋转都是固定的往左旋转90度,可以看下TexturePacker这个工具生成的图片里的子图片是怎么旋转的。

setTextureRect(pNewFrame->getRect(), m_bRectRotated, pNewFrame->getOriginalSize());三个参数:精灵帧关联的纹理的范围,这个是裁剪后的纹理,被裁掉的是透明的区域,不影响显示。是否旋转、精灵帧引用的纹理的原始图片大小。一般图片多大,创建的精灵就有多大,直接一张图片创建精灵,图片的四周透明区域并不会被裁减,这个时候精灵的大小跟纹理大小是一样的。而用TexturePacker等工具对图片打包后,它会压缩纹理的大小,裁剪那些可以忽略的透明区域,所以用plist批量创建精灵帧后,在用这个精灵帧创建精灵,精灵的大小一般不等于纹理大小。继续分析上面的CCSprite::setTextureRect(const CCRect& rect, bool rotated, const CCSize& untrimmedSize)。rect为CCSpriteFrame的m_obRect,

rotated为m_bRectRotated,untrimmedSize为pNewFrame->getOriginalSize(),即获得原始图片大小m_obOriginalSize。m_obUnflippedOffsetPositionFromCenter为纹理相对于节点的偏移,上面分析了。下面把这4个变量带入setTextureRect进行分析。分析如下:

setContentSize(untrimmedSize);设置精灵节点大小,自然要用未裁剪的原始大小,为m_obOriginalSize。

setVertexRect(rect);设置用于渲染的四边形的四个顶点位置,它要跟纹理一样大,因为纹理按顶点位置进行纹理映射的,至于纹理左下角跟顶点左下角对应还是左上角对应,那是纹理坐标决定的。它的大小为m_obRect,即裁剪大小。

setTextureCoords(rect);代码如下:

void CCSprite::setTextureCoords(CCRect rect)
{
rect = CC_RECT_POINTS_TO_PIXELS(rect);

CCTexture2D *tex = m_pobBatchNode ? m_pobTextureAtlas->getTexture() : m_pobTexture;
if (! tex)
{
return;
}

float atlasWidth = (float)tex->getPixelsWide();获得纹理宽高
float atlasHeight = (float)tex->getPixelsHigh();

float left, right, top, bottom;

if (m_bRectRotated)//左边旋转90度
{
#if CC_FIX_ARTIFACTS_BY_STRECHING_TEXEL
left    = (2*rect.origin.x+1)/(2*atlasWidth);
right    = left+(rect.size.height*2-2)/(2*atlasWidth);
top        = (2*rect.origin.y+1)/(2*atlasHeight);
bottom    = top+(rect.size.width*2-2)/(2*atlasHeight);
#else
left    = rect.origin.x/atlasWidth;
right    = (rect.origin.x+rect.size.height) / atlasWidth;
top        = rect.origin.y/atlasHeight;
//gl纹理坐标是左下00,x向右增长,y向上增长,这里bottom比top大,是因为加载纹理图片数据的接口把左上角认为是00位置,
//内存里存的第一个像素是左上角点,gl会把它认为左下角,这样纹理渲染出来就y方向反了,所以这里要对调bottom与top的值
bottom    = (rect.origin.y+rect.size.width) / atlasHeight;
#endif // CC_FIX_ARTIFACTS_BY_STRECHING_TEXEL

if (m_bFlipX)
{
CC_SWAP(top, bottom, float);
}

if (m_bFlipY)
{
CC_SWAP(left, right, float);
}
//设置四边形四个顶点的纹理坐标值
m_sQuad.bl.texCoords.u = left;
m_sQuad.bl.texCoords.v = top;
m_sQuad.br.texCoords.u = left;
m_sQuad.br.texCoords.v = bottom;
m_sQuad.tl.texCoords.u = right;
m_sQuad.tl.texCoords.v = top;
m_sQuad.tr.texCoords.u = right;
m_sQuad.tr.texCoords.v = bottom;
}
else
{
#if CC_FIX_ARTIFACTS_BY_STRECHING_TEXEL
left    = (2*rect.origin.x+1)/(2*atlasWidth);
right    = left + (rect.size.width*2-2)/(2*atlasWidth);
top        = (2*rect.origin.y+1)/(2*atlasHeight);
bottom    = top + (rect.size.height*2-2)/(2*atlasHeight);
#else
left    = rect.origin.x/atlasWidth;
right    = (rect.origin.x + rect.size.width) / atlasWidth;
top        = rect.origin.y/atlasHeight;
bottom    = (rect.origin.y + rect.size.height) / atlasHeight;
#endif // ! CC_FIX_ARTIFACTS_BY_STRECHING_TEXEL

if(m_bFlipX)
{
CC_SWAP(left,right,float);
}

if(m_bFlipY)
{
CC_SWAP(top,bottom,float);
}

m_sQuad.bl.texCoords.u = left;
m_sQuad.bl.texCoords.v = bottom;
m_sQuad.br.texCoords.u = right;
m_sQuad.br.texCoords.v = bottom;
m_sQuad.tl.texCoords.u = left;
m_sQuad.tl.texCoords.v = top;
m_sQuad.tr.texCoords.u = right;
m_sQuad.tr.texCoords.v = top;
}
}


CCPoint relativeOffset = m_obUnflippedOffsetPositionFromCenter;m_obUnflippedOffsetPositionFromCenter的值在函数setDisplayFrame由 m_obUnflippedOffsetPositionFromCenter = pNewFrame->getOffset();求得,它是纹理范围相对于节点范围的偏移。

m_obOffsetPosition.x = relativeOffset.x + (m_obContentSize.width - m_obRect.size.width) / 2;为什么纹理在节点中的偏移不直接是relativeOffset呢,而是relativeOffset加上纹理在节点中居中的位置距左下的偏移呢?现在要得到正确的纹理在之前图片上的局部位置,relativeOffset的偏移实现对于纹理的居中位置的,最后还要加上居中时距左下角的偏移,这样就可以得到一个纹理块在以前图片中的位置时,相对图片左下角的偏移。

OK,setDisplayFrame分析完了,它获得精灵帧从plist里获得的节点大小,纹理大小,纹理偏移,然后让精灵关联纹理,设置精灵的纹理坐标、顶点坐标。最后渲染结点时将用这些新的信息来渲染这个节点。

上面就是对精灵帧创建精灵的分析,在setDisplayFrame前面一句initWithTexture(pSpriteFrame->getTexture(), pSpriteFrame->getRect())也做了纹理处理,但是里面好多跟setDisplayFrame重复的函数调用,并且不调用setDisplayFrame的话结果就不对。最后得出结论是精灵帧存储了用于渲染纹理的相关信息,包含纹理引用,纹理范围是否旋转信息,以及最终创建的精灵的大小。

CCSprite::setTexture(CCTexture2D *texture)调用了CC_SAFE_RETAIN(texture);让纹理引用计数加一,也就是说此时这个纹理计数为3,创建并放入纹理缓存后为1,创建精灵帧后计数为2,创建精灵后计数为3。所以关联纹理的对象都会导致纹理计数加1.那些用plist创建的纹理,它们一开始计数就为2,如果用纹理缓存移除无用纹理的话,纹理还有计数1被精灵帧保持着。只用精灵帧缓存的CCSpriteFrameCache::removeSpriteFrames(void)移除精灵帧也会让纹理计数还剩1。只用纹理缓存与精灵帧缓存同时都释放的化,并且没有其它对象引用该纹理,该纹理才会得到释放。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息