【目标】:调试通过《cocos2D-x权威指南》中实例3.1.4:通过节点控制屏幕中的全体渲染对象
【参考】:
《cocos2D-x权威指南》 (也就是我现在正在学的书,纯入门级不解释)
cocos2D-x源码: cocos2dx\base_nodes\CCNode.cpp
【第一部分】:helloWorld模板的超入门级架构解读心得
作为超级菜鸟,刚拿到模板代码的时候,研究了好久才找到在哪里动笔改。实际上模板架构还是很简单的:
1) main.cpp: _tWinMain 就是 win32程序入口不解释。
对比一般的WIN32程序流程:注册wndclass -> CreateWindow & ShowWindow -> 消息队列循环
可以看到这里的流程如下: CCEGLView::sharedOpenGLView(); - > CCApplication::sharedApplication()->run();
很显然,CCEGLView::sharedOpenGLView() 是创建窗口,而 CCApplication::sharedApplication()->run(); 完成绘制和消息队列功能
2) AppDelegate: tWinMain的开头会调用 AppDelegate app; 构造一个 AppDelegate 的实例,而 AppDelegate 是继承与 CCApplication 的,在CCApplication的初始化代码中,可以看到
CCApplication::CCApplication() : m_hInstance(NULL) , m_hAccelTable(NULL) { m_hInstance = GetModuleHandle(NULL); m_nAnimationInterval.QuadPart = 0; CC_ASSERT(! sm_pSharedApplication); sm_pSharedApplication = this; }
将全局实例 sm_pSharedApplication 设置为了 AppDelegate app,所以后面调用 CCApplication::sharedApplication()->run() 实际上都是调用的 AppDelegate 的方法。
3) HelloWorldScene : AppDelegate::applicationDidFinishLaunching() 中构造并启动。可以参考其中的调用
【第二部分】:坐标系
1、 源程序无比简单,就是在helloWorld模板下,创建一个父节点CCNode anode,将场景中所有节点作为其子节点添加,然后通过对父节点的操作来达到操纵子节点的目的。
和初始代码相比,只需要将原有的 this->addChild(XXX, z); 改为 aNode->addChild(XXX, z);
2、注意到3.1.4.4 中对整体旋转的处理:
aNode->setPosition(ccp(200, 200)); aNode->setRotation(90.0);
只能围绕默认锚点(0,0)进行旋转,比较SB。
既然一个sprite可以通过设置锚点来实现中心旋转,那么作为父节点的CCNode为什么不可以呢?
3、坐标系
参考
http://www.cnblogs.com/pengyingh/articles/2433081.html
http://www.2cto.com/kf/201208/148630.html
http://article.ityran.com/archives/3367
这里有这几个概念,只说明我这个初学者的看法,很可能不正确。
1)世界坐标系:即原 WINDOWS 中的 MM_TEXT。原点左上,正方向为右下。根据参考文档中所述,触摸等事件使用的是该坐标系。
2)GL坐标系:即OPENGL坐标系,右手坐标系,原点左下,正方向为右上前。
【TODO:需要澄清的事实:坐标系的单位?】
在WINDOW上既然是MM_TEXT,那么就应该是以PX为单位了(实测确实如此),不过不知道android和IOS上是以什么为单位的。根据我自己的DEFY的显示情况,应该也是以PX为单位。
(PS:关于android PX DPI DENSITY DIP 可以参考 http://blog.csdn.net/zhuojiuyihu/article/details/7292669 )
3)节点坐标系:父节点提供给子节点的相对坐标系,自然场景可以参考物理学中的相对坐标系。其情况又可以分为两种:
3.1: 原点和正方向(不考虑锚点)
aNode->convertToWorldSpace(testPoint); aNode->convertToNodeSpace(testPoint);
不考虑父节点 aNode 的锚点,以aNode的“原设定”左下为原点,“原设定”的右上为正方向。
注意这里指的是“原设定”,这是因为父节点本身可能会旋转,如果发生旋转的话,节点坐标系也会随着旋转。
3.2: 原点和正方向(考虑锚点)
aNode->convertToWorldSpaceAR(testPoint); aNode->convertToNodeSpaceAR(testPoint);
考虑父节点的锚点,以锚点为原点,“原设定”的右上为正方向。坐标系同样会旋转,不过旋转是以锚点为中心,所以在这个方法中,原点倒是不会随旋转而乱跑。
3.3:单位
对于节点坐标系的单位,又需要特别说明,因为会随着 setScale 而变化。
cin >> scale; aNode->setScale( ((float)scale)/10 ); CCPoint anchorPoint = aNode->getAnchorPointInPoints(); aNode->setPosition(ccp(200, 200)); CCPoint testPoint = ccp(10, 10); CCPoint nodePoint = aNode->convertToNodeSpace(testPoint);
当setscale(1) 时输出结果是:(-190, -190); 当 setScale(0.5) 时,输出结果是 (-380, -380),也就是说坐标系的单位值也变成了原来的0.5倍。
另外需要注意的是,setPosition 使用的显然不是节点坐标系,由于这里锚点设置的是默认的原点(0,0), 所以无论输入什么 scale, 其左下角原点在世界坐标系上的位置都是固定的。
这里和锚点牵扯的比较厉害,下面研究了锚点再来看一个稍微复杂一些的例子。
【第三部分:锚点】
名称还是挺形象的,我理解就是图像转换的中心点。需要注意的是,和原点并不是一个概念。
1、设置锚点的方法
第二部分提到书上 3.1.4.4 这个示例只是按照默认锚点旋转,对于图像旋转而言,并不是很好用。
书上有这样一句话:“只有CCNode节点使用贴图的情况下,锚点才有意义。”
不过在某些情况下,一个本身无贴图的CCNode节点,可能是作为几个有贴图的子节点的集中管理器出现的,那么提供一个锚点来进行操作还是挺有意义的。所以还是要尝试启用这个功能。现在我们的代码中的 aNode 就是这样一个CCNode,那么下面要为他设置锚点,然后再进行旋转。
如果我们直接使用一般节点的锚点设置方法是无效的:
aNode->setAnchorPoint(ccp(0.5,0.5)); aNode->setRotation(90.0);
会发现界面完全没有动。
查阅CCNode.cpp 源代码,可以看到
CCAffineTransform CCNode::nodeToParentTransform(void) { if (m_bIsTransformDirty) { // Translate values float x = m_tPosition.x; float y = m_tPosition.y; if (m_bIgnoreAnchorPointForPosition) { x += m_tAnchorPointInPoints.x; y += m_tAnchorPointInPoints.y; } // Rotation values float c = 1, s = 0; if (m_fRotation) { float radians = -CC_DEGREES_TO_RADIANS(m_fRotation); c = cosf(radians); s = sinf(radians); } bool needsSkewMatrix = ( m_fSkewX || m_fSkewY ); // optimization: // inline anchor point calculation if skew is not needed if (! needsSkewMatrix && !m_tAnchorPointInPoints.equals(CCPointZero)) { x += c * -m_tAnchorPointInPoints.x * m_fScaleX + -s * -m_tAnchorPointInPoints.y * m_fScaleY; y += s * -m_tAnchorPointInPoints.x * m_fScaleX + c * -m_tAnchorPointInPoints.y * m_fScaleY; }
可以看到,在实现旋转的时候,关键的几个变量是 m_bIgnoreAnchorPointForPosition 和 m_tAnchorPointInPoints。
m_bIgnoreAnchorPointForPosition 可以通过 ignoreAnchorPointForPosition(true) 来打开,
至于 m_tAnchorPointInPoints 则需要再设置setAnchorPoint "之前"调用 setContentSize(size) 来设置 size,这是因为 m_tAnchorPointInPoints 是根据 setAnchorPoint 设置进去的相对值和 ContentSize 的值相乘得到的,而一个没有设置贴图的 CCNode , 其contentSize 是 0, 所以如果直接设置 setAnchorPoint, 会发现用 getAnchorPointInPoints 查出来的锚点值仍然是0。
具体来说,代码如下:
CCSize size = CCDirector::sharedDirector()->getWinSize(); aNode->ignoreAnchorPointForPosition(true); aNode->setContentSize(size); //这里的aNode实际装载的是整个界面,所以size也是整个winSize aNode->setAnchorPoint(ccp(0.5,0.5)); aNode->setRotation(90.0);
这样就可以实现旋转了。
可以看到这样设置锚点还挺麻烦的,肯定不是最好的方法,我看到后面scene节点和layer节点设置要容易的多,可能是更好的办法。
2. 锚点、旋转和节点坐标系
缩放和坐标系前面讨论过了,这里主要讨论旋转。实际上,节点坐标系是随着节点一起旋转的。只看一个例子,基本上就可以理解了:
aNode->ignoreAnchorPointForPosition(true); aNode->setContentSize(size); aNode->setAnchorPoint(ccp(0.5,0.5)); CCPoint anchorPoint = aNode->getAnchorPointInPoints(); cout << "x = " << anchorPoint.x << "; y = " << anchorPoint.y << endl; aNode->setPosition(ccp(200, 200)); aNode->setRotation(90.0); CCPoint testPoint = ccp(10, 10); CCPoint nodePoint = aNode->convertToNodeSpace(testPoint); cout << "node point x = "<< nodePoint.x << "; y = " << nodePoint.y << endl;
输出的结果是 x = 590; y = -270 (辅助信息:eglView->setFrameSize(480, 320);) 容易看到坐标轴实际上随着图片转动了