溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

【Cocos2dx開發(fā)】精靈

發(fā)布時(shí)間:2020-10-03 21:41:14 來源:網(wǎng)絡(luò) 閱讀:863 作者:龍顏碩 欄目:開發(fā)技術(shù)

Cocos2dx開發(fā)】精靈

寫在前面——有不對(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)的類圖。

【Cocos2dx開發(fā)】精靈

1

   如圖1中所示,CCSprite繼承自CCTextureProtocolCCNodeRGBA類,而CCNodeRGBA有繼承自CCNodeCCRGBAProtocol類。將上面的關(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”。

【Cocos2dx開發(fā)】精靈

2

   好的,我們先在已經(jīng)創(chuàng)建好的Cocos2dxDemo工程中添加一個(gè)精靈上去。

【Cocos2dx開發(fā)】精靈

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;
}

可以看出在CCSpritecreate接口中,我們首先創(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;
}

這里我們可以看出,通過CCTextureCacheaddImage,我們會(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í)了。


批處理圖片的學(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)出。

【Cocos2dx開發(fā)】精靈

4

【Cocos2dx開發(fā)】精靈

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),ImageinitWithImageFile接口只調(diào)用到了一次,而這一次其實(shí)就是“grossini.png”大圖加載的時(shí)候調(diào)用的。

   好的,今天的學(xué)習(xí)就先到這里吧@_@

向AI問一下細(xì)節(jié)

免責(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)容。

AI