今天有个朋友在开发中遇到一个字符渲染的问题,原来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;
}
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);
}
}
递归调用,注意注意。