cocos2d-x 源碼剖析(21)

今天有個朋友在開發中遇到一個字符渲染的問題,原來cocos2d-x不支持LTR和RTL混合字符的渲染,我當初壓根沒有意識到還有這個問題的存在。跟蹤之後發現,cocos2d-x確實沒有支持,而且用ttf改起來頗爲費勁,關鍵是實現邏輯的地方太深了,沒有看到Android的情況,要做好移植很難。不過用bmf改起來還是比較方便的,能直接修改C++代碼,不用關心底層,能很好的移植。但是現實起來還是要做不少工作。這位同學是寫Lua的,基本上不狠下一番功夫是無望了。cocos2d-x xna上有個issues是關於這個的,但是這個分支已經死掉了。看,讀cocos2d-x的源碼還是很有用的吧。起來能快速判定問題所在,不是無頭蒼蠅瞎找一氣了。

在羣裏聊天的時候,發現有些cocos2d-x的代碼確實很爛。有些水平停留在只是會寫C++代碼的人也在上面提交關鍵代碼。縮進連一個起碼的要求都沒有。我一直不看好cocos studio,不支持mac不說,UI實在太難看的。我把這部分搞完就準備自己實現一個2d引擎,算作我遊戲開發的總結。一個真正易於擴展易於使用的引擎,如果有時間還會配套一些開發工具,比如TP和Animation之類的。

上一節講到了CCSpriteBatchNode,這節要講CCSprite了。CCSprite估計是遊戲中是用的最多的東西,他的作用就是顯示一張圖片。其他一些需要顯示圖片的工作也是他來做的。CCSprite的內容很簡單,我們之前基本上把關鍵點都講完了。CCSprite之所以看起來那麼多代碼就是應爲有兩種不同的渲染模式存在,而且是可以轉化的。這增加了不少的複雜度,這種設計就是典型的用if代替類的使用。他的初始化代碼可以分成3組:

static CCSprite* create(const char *pszFileName);
static CCSprite* createWithSpriteFrame(CCSpriteFrame *pSpriteFrame);
static CCSprite* createWithTexture(CCTexture2D *pTexture);


真正起作用的是第三個函數。這幾個函數有個漏洞,我clone一個sprite十分不方便。而這些東西用SpriteFrame就已經封裝好了的。

其實來說CCSprite的這些所有邏輯都是爲了正確的顯示一張圖片。draw函數是在非CCSpriteBatchNode中,顯示的時候調用的。那麼一些屬性生效的邏輯就不能在這裏,而是在visit函數中。我們之前看到過CCNode的visit的代碼,當時瞭解的不夠深入,現在看來draw函數千萬不要有任何非顯示的邏輯存在,切記切記。我們來看看visit中的transform這個函數,這個函數例行公事,做了一些通用邏輯,需要注意的是nodeToParentTransform這個函數,他就是哪些屬性生效的地方了:

CCAffineTransform CCNode::nodeToParentTransform(void)
{
    if (m_bTransformDirty) 
    {
 
// Translate values
        float x = m_obPosition.x;
        float y = m_obPosition.y;
 
        if (m_bIgnoreAnchorPointForPosition) 
        {
            x += m_obAnchorPointInPoints.x;
            y += m_obAnchorPointInPoints.y;
        }
 
// Rotation values
// Change rotation code to handle X and Y
// If we skew with the exact same value for both x and y then we're simply just rotating
        float cx = 1, sx = 0, cy = 1, sy = 0;
        if (m_fRotationX || m_fRotationY)
        {
            float radiansX = -CC_DEGREES_TO_RADIANS(m_fRotationX);
            float radiansY = -CC_DEGREES_TO_RADIANS(m_fRotationY);
            cx = cosf(radiansX);
            sx = sinf(radiansX);
            cy = cosf(radiansY);
            sy = sinf(radiansY);
        }
 
        bool needsSkewMatrix = ( m_fSkewX || m_fSkewY );
 
// optimization:
// inline anchor point calculation if skew is not needed
// Adjusted transform calculation for rotational skew
        if (! needsSkewMatrix && !m_obAnchorPointInPoints.equals(CCPointZero))
        {
            x += cy * -m_obAnchorPointInPoints.x * m_fScaleX + -sx * -m_obAnchorPointInPoints.y * m_fScaleY;
            y += sy * -m_obAnchorPointInPoints.x * m_fScaleX +  cx * -m_obAnchorPointInPoints.y * m_fScaleY;
        }
 
// Build Transform Matrix
// Adjusted transform calculation for rotational skew
        m_sTransform = CCAffineTransformMake( cy * m_fScaleX,  sy * m_fScaleX,
            -sx * m_fScaleY, cx * m_fScaleY,
            x, y );
 
// XXX: Try to inline skew
// If skew is needed, apply skew and then anchor point
        if (needsSkewMatrix) 
        {
            CCAffineTransform skewMatrix = CCAffineTransformMake(1.0f, tanf(CC_DEGREES_TO_RADIANS(m_fSkewY)),
                tanf(CC_DEGREES_TO_RADIANS(m_fSkewX)), 1.0f,
                0.0f, 0.0f );
            m_sTransform = CCAffineTransformConcat(skewMatrix, m_sTransform);
 
// adjust anchor point
            if (!m_obAnchorPointInPoints.equals(CCPointZero))
            {
                m_sTransform = CCAffineTransformTranslate(m_sTransform, -m_obAnchorPointInPoints.x, -m_obAnchorPointInPoints.y);
            }
        }
 
        if (m_bAdditionalTransformDirty)
        {
            m_sTransform = CCAffineTransformConcat(m_sTransform, m_sAdditionalTransform);
            m_bAdditionalTransformDirty = false;
        }
 
        m_bTransformDirty = false;
    }
 
    return m_sTransform;
}


我的數學不好就不做解釋了,這個轉換作用於OpenGL的繪製矩陣而不是自己的頂點座標,這點要清楚。需要明晰下面兩個函數的不同:

virtual void setScaleX(float fScaleX);
void setFlipX(bool bFlipX);

在CCSpriteBatchNode的visit中,transform也是被調用了的,所以這些都有效果。至於真正的顯示,就就本上和繪製紋理的情況類似了。在CCSpriteBatchNode中管理Children的方式和普通的CCSprite不同,所以CCSprite的一些邏輯都做了特殊的處理。我們來看看CCSprite添加到CCSpriteBatchNode的轉化過程:

void CCSprite::setBatchNode(CCSpriteBatchNode *pobSpriteBatchNode)
{
  m_pobBatchNode = pobSpriteBatchNode; // weak reference
 
  // self render
  if( ! m_pobBatchNode ) {
    m_uAtlasIndex = CCSpriteIndexNotInitialized;
    setTextureAtlas(NULL);
    m_bRecursiveDirty = false;
    setDirty(false);
 
    float x1 = m_obOffsetPosition.x;
    float y1 = m_obOffsetPosition.y;
    float x2 = x1 + m_obRect.size.width;
    float y2 = y1 + m_obRect.size.height;
    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 );
 
  } else {
      // using batch
    m_transformToBatch = CCAffineTransformIdentity;
      setTextureAtlas(m_pobBatchNode->getTextureAtlas()); // weak ref
  }
}

意外的簡潔,做事的函數交到這裏來做了:

void CCSpriteBatchNode::appendChild(CCSprite* sprite)
{
  m_bReorderChildDirty=true;
  sprite->setBatchNode(this);
  sprite->setDirty(true);
 
  if(m_pobTextureAtlas->getTotalQuads() == m_pobTextureAtlas->getCapacity()) {
    increaseAtlasCapacity();
  }
 
  ccArray *descendantsData = m_pobDescendants->data;
 
  ccArrayAppendObjectWithResize(descendantsData, sprite);
 
  unsigned int index=descendantsData->num-1;
 
  sprite->setAtlasIndex(index);
 
  ccV3F_C4B_T2F_Quad quad = sprite->getQuad();
  m_pobTextureAtlas->insertQuad(&quad, index);
 
    // add children recursively
 
  CCObject* pObj = NULL;
  CCARRAY_FOREACH(sprite->getChildren(), pObj)
  {
    CCSprite* child = (CCSprite*)pObj;
    appendChild(child);
  }
}

遞歸調用,注意注意。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章