您好,登錄后才能下訂單哦!
寫在前面——有不對(duì)的地方,煩請(qǐng)大家批評(píng)指正,我也會(huì)繼續(xù)努力提高自己。如果轉(zhuǎn)載請(qǐng)注明出處,謝謝大家支持——Forward。
我的微博——秦京一夢(mèng)
在上一篇博客中,Forward對(duì)場景以及各種場景切換特效進(jìn)行了一個(gè)入門級(jí)的學(xué)習(xí)分享,但是只有一個(gè)場景是不能完成游戲開發(fā)的。用畫家畫畫來做比喻,有了場景就好像畫家拿到了一頁畫紙,而要完成一幅壯麗的圖畫,還需要在這頁畫紙上通過合理的分配,填充上不同的元素。這里所說的元素,類比到游戲開發(fā)中,就是場景中的精靈。
今天我們就來學(xué)習(xí)一下Cocos2dx中的精靈這一概念。按照習(xí)慣,我們首先來看看CCSprite這個(gè)類相關(guān)的類圖。
圖 1
如圖1中所示,CCSprite繼承自CCTextureProtocol和CCNodeRGBA類,而CCNodeRGBA有繼承自CCNode和CCRGBAProtocol類。將上面的關(guān)系翻譯成自然語言,我們就很好理解CCSprite這個(gè)類了——所謂CCSprite精靈,就是用來描述紋理和顏色信息的節(jié)點(diǎn)。為了我們能夠更深入的了解CCSprite精靈類,還需要進(jìn)一步深入的學(xué)習(xí)。
查看CCSprite類源碼,可以看到,里面提供了很多API,常用的比如創(chuàng)建、設(shè)置紋理信息、設(shè)縮放、旋轉(zhuǎn)、坐標(biāo)位置、錨點(diǎn)信息、隱藏與顯示的設(shè)置、子節(jié)點(diǎn)的添加與移除等等,具體的Forward在這里不再贅述,相信大家都能看懂。
接下來我們通過程序來進(jìn)行進(jìn)一步的學(xué)習(xí)。這里使用Cocos2dx例子中的資源“grossini”。
圖 2
好的,我們先在已經(jīng)創(chuàng)建好的Cocos2dxDemo工程中添加一個(gè)精靈上去。
圖 3
那么這個(gè)精靈在加載的過程中,我們都做了哪些工作呢?
首先,我們加載動(dòng)畫的代碼清單如下:
CCSprite* pSprite = CCSprite::create("grossini.png"); pSprite->setPosition(ccp(240,160)); this->addChild(pSprite);
進(jìn)入精靈類的create接口,代碼清單如下:
CCSprite* CCSprite::create(constchar*pszFileName) { CCSprite *pobSprite = new CCSprite(); if(pobSprite && pobSprite->initWithFile(pszFileName)) { pobSprite->autorelease(); returnpobSprite; } CC_SAFE_DELETE(pobSprite); returnNULL; }
可以看出在CCSprite的create接口中,我們首先創(chuàng)建了一個(gè)精靈類對(duì)象,然后通過initWithFile接口來對(duì)這個(gè)精靈做初始化,就Forward前面說的一樣,所謂精靈,就是用來描述紋理和顏色信息的節(jié)點(diǎn),創(chuàng)建CCSprite對(duì)象的過程我們就得到了這個(gè)節(jié)點(diǎn),初始化就相當(dāng)于給這個(gè)節(jié)點(diǎn)填充上對(duì)應(yīng)的紋理和其它必需的信息的過程。繼續(xù)跟進(jìn)~~
boolCCSprite::initWithFile(constchar *pszFilename) { CCAssert(pszFilename != NULL, "Invalid filename for sprite"); CCTexture2D *pTexture =CCTextureCache::sharedTextureCache()->addImage(pszFilename); if(pTexture) { CCRect rect = CCRectZero; rect.size =pTexture->getContentSize(); return initWithTexture(pTexture, rect); } // don't releasehere. // when loadtexture failed, it's better to get a "transparent" sprite then acrashed program //this->release(); returnfalse; }
這里我們可以看出,通過CCTextureCache的addImage,我們會(huì)得到一張紋理信息,并且最終用來加載到CCSprite上的就是張紋理。
繼續(xù)進(jìn)入到addImage接口中——
CCTexture2D *CCTextureCache::addImage(constchar * path) { CCAssert(path != NULL, "TextureCache: filep_w_picpath MUST not be NULL"); CCTexture2D * texture = NULL; CCImage* pImage = NULL; // Split updirectory and filename // MUTEX: // Needed sinceaddImageAsync calls this method from a different thread //pthread_mutex_lock(m_pDictLock); std::string pathKey = path; pathKey =CCFileUtils::sharedFileUtils()->fullPathForFilename(pathKey.c_str()); if(pathKey.size() == 0) { returnNULL; } texture =(CCTexture2D*)m_pTextures->objectForKey(pathKey.c_str()); std::string fullpath = pathKey; //(CCFileUtils::sharedFileUtils()->fullPathFromRelativePath(path)); if (!texture) { std::string lowerCase(pathKey); for (unsignedint i = 0; i< lowerCase.length(); ++i) { lowerCase[i] =tolower(lowerCase[i]); } // all p_w_picpathsare handled by UIImage except PVR extension that is handled by our own handler do { if(std::string::npos != lowerCase.find(".pvr")) { texture = this->addPVRImage(fullpath.c_str()); } elseif (std::string::npos != lowerCase.find(".pkm")) { // ETC1 file format, only supportted onAndroid texture = this->addETCImage(fullpath.c_str()); } else { CCImage::EImageFormateImageFormat = CCImage::kFmtUnKnown; if(std::string::npos != lowerCase.find(".png")) { eImageFormat =CCImage::kFmtPng; } else if (std::string::npos != lowerCase.find(".jpg") || std::string::npos !=lowerCase.find(".jpeg")) { eImageFormat =CCImage::kFmtJpg; } else if (std::string::npos != lowerCase.find(".tif") || std::string::npos !=lowerCase.find(".tiff")) { eImageFormat =CCImage::kFmtTiff; } else if (std::string::npos != lowerCase.find(".webp")) { eImageFormat =CCImage::kFmtWebp; } pImage = newCCImage(); CC_BREAK_IF(NULL == pImage); boolbRet = pImage->initWithImageFile(fullpath.c_str(), eImageFormat); CC_BREAK_IF(!bRet); texture = new CCTexture2D(); if(texture && texture->initWithImage(pImage) ) { #ifCC_ENABLE_CACHE_TEXTURE_DATA //cache the texture file name VolatileTexture::addImageTexture(texture, fullpath.c_str(),eImageFormat); #endif m_pTextures->setObject(texture, pathKey.c_str()); texture->release(); } else { CCLOG("cocos2d: Couldn't create texture for file:%s inCCTextureCache", path); } } } while(0); } CC_SAFE_RELEASE(pImage); //pthread_mutex_unlock(m_pDictLock); returntexture; }
可能這個(gè)接口的實(shí)現(xiàn)略微顯得有點(diǎn)長,但并不影響我們的代碼閱讀與理解。這段代碼主要實(shí)現(xiàn)了在一張Hash表中去查找對(duì)應(yīng)的紋理信息是否已經(jīng)加載,如果找到就將對(duì)應(yīng)紋理返回
pathKey =CCFileUtils::sharedFileUtils()->fullPathForFilename(pathKey.c_str()); if(pathKey.size() == 0) { returnNULL; } texture = (CCTexture2D*)m_pTextures->objectForKey(pathKey.c_str());
否則的話就要先創(chuàng)建一個(gè)Image然后通過這個(gè)Image來完成一張新的紋理的創(chuàng)建,并將這張新創(chuàng)建的紋理加入到Hash表中。
pImage = newCCImage(); CC_BREAK_IF(NULL == pImage); boolbRet = pImage->initWithImageFile(fullpath.c_str(), eImageFormat); CC_BREAK_IF(!bRet); texture = new CCTexture2D(); if(texture &&texture->initWithImage(pImage) ) { #ifCC_ENABLE_CACHE_TEXTURE_DATA //cache the texture file name VolatileTexture::addImageTexture(texture, fullpath.c_str(),eImageFormat); #endif m_pTextures->setObject(texture, pathKey.c_str()); texture->release(); }
接著,我們進(jìn)入到CCImage類的initWithImageFile接口中。
boolCCImage::initWithImageFile(constchar * strPath, EImageFormat eImgFmt/* = eFmtPng*/) { bool bRet =false; #ifdefEMSCRIPTEN // Emscriptenincludes a re-implementation of SDL that uses HTML5 canvas // operationsunderneath. Consequently, loading p_w_picpaths via IMG_Load (an SDL // API) will be alot faster than running libpng et al as compiled with // Emscripten. SDL_Surface *iSurf = IMG_Load(strPath); int size =4 * (iSurf->w * iSurf->h); bRet = _initWithRawData((void*)iSurf->pixels, size, iSurf->w,iSurf->h, 8, true); unsignedint *tmp = (unsignedint *)m_pData; intnrPixels = iSurf->w * iSurf->h; for(int i = 0; i < nrPixels; i++) { unsignedchar *p = m_pData + i * 4; tmp[i] = CC_RGB_PREMULTIPLY_ALPHA(p[0], p[1], p[2], p[3] ); } SDL_FreeSurface(iSurf); #else unsignedlong nSize = 0; std::string fullPath =CCFileUtils::sharedFileUtils()->fullPathForFilename(strPath); unsignedchar* pBuffer =CCFileUtils::sharedFileUtils()->getFileData(fullPath.c_str(), "rb", &nSize); if (pBuffer!= NULL && nSize > 0) { bRet = initWithImageData(pBuffer,nSize, eImgFmt); } CC_SAFE_DELETE_ARRAY(pBuffer); #endif// EMSCRIPTEN returnbRet; }
在#else到#endif一段,我們應(yīng)該很清楚地看到,在每次調(diào)用initWithImageFile接口來初始化Image對(duì)象的時(shí)候都會(huì)去通過“rb”方式讀取文件。至此,我們可以說對(duì)一個(gè)精靈的創(chuàng)建到紋理加載全過程已經(jīng)有了比較深入的學(xué)習(xí)了。
Forward對(duì)上面的Demo程序作了一些修改——
mIndex = 1; mElapseTime = 0.0f; pSprite = NULL; pSprite = CCSprite::create("grossini.png"); pSprite->setPosition(ccp(240,160)); this->addChild(pSprite); this->schedule(schedule_selector(HelloWorld::Tick));
首先我們?cè)趫鼍吧咸砑恿艘粋€(gè)精靈對(duì)象,然后通過schedule接口注冊(cè)了一個(gè)回調(diào),具體的回調(diào)代碼清單如下。
voidHelloWorld::Tick(float dt) { mElapseTime += dt; if (mElapseTime <= 0.3f) { return; } mElapseTime = 0.0f; int tIndex = mIndex % 14 + 1; mIndex++; char strImageName[64] = {0}; sprintf(strImageName,"grossini_dance_%02d.png",tIndex); pSprite->initWithFile(strImageName); }
翻譯為自然語言就是,每隔0.3秒,我們對(duì)之前創(chuàng)建的grossini_dance對(duì)象做一次紋理修改,編譯運(yùn)行——grossini_dance果然動(dòng)起來了。
但當(dāng)我們斷點(diǎn)調(diào)試的時(shí)候,會(huì)發(fā)現(xiàn)在每次使用的紋理圖片在第一次使用的時(shí)候都會(huì)執(zhí)行到這里。
bool bRet =pImage->initWithImageFile(fullpath.c_str(), eImageFormat); CC_BREAK_IF(!bRet);
上面我們提到“調(diào)用initWithImageFile接口來初始化Image對(duì)象的時(shí)候都會(huì)去通過“rb”方式讀取文件”而文件操作一般都是比較耗時(shí)的,在小的Demo程序中可能看不到這一點(diǎn)對(duì)效率造成的影響,但是當(dāng)我們?cè)谧龅氖且粋€(gè)真正的游戲項(xiàng)目時(shí),這里的時(shí)間消耗就不能忽略甚至與不能容忍了。
這就引出了我們接下來要說的另外一問題。當(dāng)我們?cè)诘谝淮问褂脠D2中“grossini_dance_01.png”到“grossini_dance_14.png”這一套序列圖片的紋理信息的時(shí)候,我們需要對(duì)一張圖片進(jìn)行一次文件讀取。
解決這個(gè)問題的思路是,我們通過將一張張碎圖拼到一張大圖上去,所有紋理在第一次加載圖片的時(shí)候就已經(jīng)讀進(jìn)內(nèi)存,以后直接使用,通過降低文件讀取次數(shù)來提高效率。這里Forward使用TexturePacker工具(不了TP打包工具的同學(xué)可以去網(wǎng)上查找一下相關(guān)資料,網(wǎng)上資源很豐富哦~^_^),將十四張碎圖拼接成一張大圖并導(dǎo)出。
圖 4
圖 5
如圖5所示,我們?cè)趯?dǎo)出后,得到了一張拼接好的大圖和一個(gè)plist文件。打開這個(gè)plist文件我們會(huì)發(fā)現(xiàn),其實(shí)它就是用來描述每一張碎圖在這張大圖的上的位置,像素寬高等等信息的。
下面我們就用這套資源來重新完成上面的那個(gè)簡易動(dòng)畫。代碼清單如下。
mIndex = 1; mElapseTime = 0.0f; pSprite = NULL; CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("grossini.plist"); pSprite = CCSprite::create("grossini.png"); pSprite->setPosition(ccp(240,160)); this->addChild(pSprite); this->schedule(schedule_selector(HelloWorld::Tick));
與之前的不同之處在于,這里使用CCSpriteFrameCache加載了我們導(dǎo)出的plist文件,其實(shí)這里就是將plist文件描述的“grossiniDemo.png”大圖加載到內(nèi)存并通過plist文件描述對(duì)其劃分為一個(gè)個(gè)小的CCSpriteFrame。
voidHelloWorld::Tick(float dt) { mElapseTime += dt; if (mElapseTime <= 0.3f) { return; } mElapseTime = 0.0f; int tIndex = mIndex % 14 + 1; mIndex++; char strImageName[64] = {0}; sprintf(strImageName,"grossini_dance_%02d.png",tIndex); pSprite->initWithSpriteFrame(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(strImageName)); }
然后在使用的時(shí)候,我們直接通過每個(gè)碎圖名來獲取對(duì)應(yīng)紋理信息即可。
OK!繼續(xù)斷點(diǎn)調(diào)試——我們會(huì)發(fā)現(xiàn),Image的initWithImageFile接口只調(diào)用到了一次,而這一次其實(shí)就是“grossini.png”大圖加載的時(shí)候調(diào)用的。
好的,今天的學(xué)習(xí)就先到這里吧@_@
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。