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

来源:互联网 发布:windows优化大师64位 编辑:程序博客网 时间:2024/05/19 07:09

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 framespriteFrame = 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 initializerbool 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纹理坐标是左下00x向右增长,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。只用纹理缓存与精灵帧缓存同时都释放的化,并且没有其它对象引用该纹理,该纹理才会得到释放。

0 0
原创粉丝点击