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。只用纹理缓存与精灵帧缓存同时都释放的化,并且没有其它对象引用该纹理,该纹理才会得到释放。
相关文章推荐
- Cocos2d-x学习笔记之CCScene、CCLayer、CCSprite的默认坐标和默认锚点实验
- cocos2dx骨骼动画Armature源码剖析(三)
- iOS应用中存储用户设置的plist文件的创建与读写教程
- 分享自己写的一个贪吃蛇的游戏(Linux)
- 分享一个蛋疼的俄罗斯方块小游戏
- Linux下的字符界面扫雷游戏
- linux下的图形界面扫雷游戏(Gtk+2.0)
- cocos2dx 交叉编译 iconv库 protobuf库
- Cocos2d-x中PageView使用中的问题
- cmake clion 构建cocos2dx 应用程序并编译ios mac android
- 【笔记】试玩 cocos2d-x-3.0beta on android
- iOS MDM设备Authenticate、TokenUpdate及设备管控流程介绍
- iOS MDM设备Authenticate、TokenUpdate及设备管控流程介绍
- iOS MDM设备Authenticate、TokenUpdate及设备管控流程介绍
- 第三章 实现Eclipse Android与J2me平台切换(下)
- Mac OS X启动项详解
- cocos2dx出现 Fatal signal 11 (SIGSEGV) at 0x00000000 (code=1)
- cocos2d里面如何实现MVC(二)