HelloWorld只有两个类:(AppDelegate>>CCApplication)&(HelloWorld>>CCLayer)。
main函数
1: int APIENTRY _tWinMain(HINSTANCE hInstance,
2: HINSTANCE hPrevInstance,
3: LPTSTR lpCmdLine,
4: int nCmdShow)
5: {
6: // create the application instance
7: AppDelegate app;
8:
9: return cocos2d::CCApplication::sharedApplication().run();
10: }
在简单的一个run之后,究竟有多少不能说的秘密呢,先把类结构和框架简单描述一下
如上图所示,整个渲染框架大致如上,CCApplication类模拟一个应用程序,负责程序相关的准备,驱动以及外部事件的响应,关键就是搞一个while(true)的死循环,让程序不停的渲染,而CCDirector则类似场景管理的作用,在cocos2d里面负责导演的角色,他来安排整个场景的matrix等相关展现效果,调用CCScene实现每一帧的渲染,而CCScene作为场景,只是通过数组的形式维护场景元素(演员),最终通过节点的draw渲染,完成每个演员的演出,从而完成一帧的绘制。
标准的游戏窗口的创建方式,以Windows为例(我的调试环境),相关代码如下:
1: bool AppDelegate::initInstance()
2: {
3: CCEGLView * pMainWnd = new CCEGLView();
4: CC_BREAK_IF(! pMainWnd
5: || ! pMainWnd->Create(TEXT("cocos2d: Hello World"), 480, 320));
6: }
7:
8: bool CCEGLView::Create(LPCTSTR pTitle, int w, int h)
9: {
10: // create window
11: m_hWnd = CreateWindowEx(
12: WS_EX_APPWINDOW | WS_EX_WINDOWEDGE, // Extended Style For The Window
13: kWindowClassName, // Class Name
14: pTitle, // Window Title
15: WS_CAPTION | WS_POPUPWINDOW | WS_MINIMIZEBOX, // Defined Window Style
16: 0, 0, // Window Position
17: 0, // Window Width
18: 0, // Window Height
19: NULL, // No Parent Window
20: NULL, // No Menu
21: hInstance, // Instance
22: NULL );
23:
24: // init egl
25: dm_pEGL = CCEGL::create(this);
26: }
27:
28: static CCEGL * create(CCEGLView * pWindow)
29: {
30: ...
31: }
代码也比较易懂,在最先的initInstance函数调用中,首先创建窗口,然后根据handle创建opengl来绑定该窗口,方便接下来opengl绘制。opengl es我没有用过,不过看代码,绑定窗口的函数基本和opengl没太多差别。
在这个环节中,我比较感兴趣,如果用户的窗口是外部创建好的而不需要重新new一个CCEGLView,这样的应用方式目前的接口是否能够满足?看了一下CCEGLView类里面貌似没有提供对应的接口或属性,如果真的没有,这个是窗口创建部分应该增加的,毕竟多数应用程序的窗口是在整个框架之中的。另外简单看了一下linux和android平台下的窗口实现方式,发现实现各不一样,linux貌似是用glfw来管理窗口和交互,android没看懂,感觉是一个虚拟的窗口,jni来进行封装,这个不太了解。
cocos2d-x在窗口View的层面上采取的是不同的平台使用不同的类,这本身导致了差异性和不一致性,坦白说这种方式降低了开发难度,但平台差异化的问增加了应用的难度,当然我不太清楚一致性的View在可行性上是否可以,不过个人认为是有一些优化的地方。
目前事件响应的代码如下,已有注视,请参考:
1: // Win实现
2: bool CCEGLView::Create(LPCTSTR pTitle, int w, int h)
3: {
4: wc.lpfnWndProc = _WindowProc; // 绑定事件函数_WindowProc
5: }
6:
7: static LRESULT CALLBACK _WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
8: {
9: if (s_pMainWindow && s_pMainWindow->getHWnd() == hWnd)
10: {
11: return s_pMainWindow->WindowProc(uMsg, wParam, lParam);// 调用窗口类事件处理的函数
12: }
13: else
14: {
15: return DefWindowProc(hWnd, uMsg, wParam, lParam);
16: }
17: }
18:
19: LRESULT CCEGLView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
20: {
21: ... // 最终实现代码
22: }
23:
24: // Linux实现
25: bool CCEGLView::Create(const char* pTitle, int iPixelWidth, int iPixelHeight, int iWidth, int iHeight, int iDepth)
26: {
27: //register the glfw key event
28: glfwSetKeyCallback(keyEventHandle);
29: //register the glfw char event
30: glfwSetCharCallback(charEventHandle);
31: //register the glfw mouse event
32: glfwSetMouseButtonCallback(mouseButtonEventHandle);
33: //register the glfw mouse pos event
34: glfwSetMousePosCallback(mousePosEventHandle);
35: }
正因为在View层面的不一致性,所以在设备事件监听上也是各自实现的,这个其实也不好,首先没有事件监听的统一化,这样各个平台重复的工作量也不小,而且随着外接设备的差异化,比如键盘,鼠标,手柄,触摸,还有现在的kinect,如果没有统一的类来封装,以后有点麻烦,其实可以参照OGRE中OIS的方式好好封装一下,相信对以后开发有很多好处。
一款游戏最重要的就是贴图了,HelloWorld程序其实就是一张图加一行文本,下面来分析一下纹理管理的方式。纹理管理主要涉及两个类:CCMenuItemImage&CCSprite。
CCMenuItemImage主要是一层外壳,里面负责选中,删除等上层交互的封装,而纹理管理的都是在CCSprite类中进行实现。我们先看一下外壳,总体把握一下。
1: bool HelloWorld::init()
2: {
3: // add a "close" icon to exit the progress. it's an autorelease object
4: CCMenuItemImage *pCloseItem = CCMenuItemImage::itemFromNormalImage(
5: "CloseNormal.png",
6: "CloseSelected.png",
7: this,
8: menu_selector(HelloWorld::menuCloseCallback) );
9: pCloseItem->setPosition( ccp(CCDirector::sharedDirector()->getWinSize().width - 20, 20) );
10:
11: // create menu, it's an autorelease object
12: CCMenu* pMenu = CCMenu::menuWithItems(pCloseItem, NULL);
13: pMenu->setPosition( CCPointZero );
14: this->addChild(pMenu, 1);
15: }
这里使用也比较简单,我比较关注的第一个,坐标Y轴是向上的,第二个,如何来管理图片的事件响应,这里就需要CCMenu这个类来进行管理了,如果我们直接addChild这个MenuItem也能够显示,但是却没有事件响应的效果,看来CCMenu这层增加了对事件响应的处理,不多说,直接上代码:
1: // 窗口注册触摸事件,实现窗口驱动委托类
2: void CCEGLView::setTouchDelegate(EGLTouchDelegate * pDelegate)
3: {
4: m_pDelegate = pDelegate;
5: }
6:
7: //Menu注册到触摸委托类来进行监听,实现委托类到具体菜单的驱动
8: void CCMenu::registerWithTouchDispatcher()
9: {
10: CCTouchDispatcher::sharedDispatcher()->addTargetedDelegate(this, kCCMenuTouchPriority, true);
11: }
12:
13: /**
14: 触摸的具体实现,实现具体菜单的触摸效果
15: */
16: virtual bool CCMenu::ccTouchBegan(CCTouch* touch, CCEvent* event);
17: virtual void CCMenu::ccTouchEnded(CCTouch* touch, CCEvent* event);
18: virtual void CCMenu::ccTouchCancelled(CCTouch *touch, CCEvent* event);
19: virtual void CCMenu::ccTouchMoved(CCTouch* touch, CCEvent* event);
如上,在事件处理中增加了对各个Menu的监听添加,从而完成了事件监听和驱动流程。给出CCMenu和CCMenuItem的类关系图
在CCLayer中有onEnter等函数,来支撑事件响应的判断,从而使得在CCLayer层以上的集成都能够有事件响应。
框架如上,保证了整个事件的响应,关键函数也已经包括,如果不明白可以调试代码,查看堆栈即可。
下面就专门讨论纹理实现的细节,先把关键代码提供如下:
1: // 纹理加载
2: CCSprite* CCSprite::spriteWithFile(const char *pszFileName)
3: {
4: CCSprite *pobSprite = new CCSprite();
5: if (pobSprite && pobSprite->initWithFile(pszFileName))
6: {
7: pobSprite->autorelease();
8: return pobSprite;
9: }
10: CC_SAFE_DELETE(pobSprite);
11: return NULL;
12: }
13:
14: bool CCTexture2D::initWithData(const void *data, CCTexture2DPixelFormat pixelFormat, unsigned int pixelsWide, unsigned int pixelsHigh, const CCSize& contentSize)
15: {
16: glGenTextures(1, &m_uName);
17: glBindTexture(GL_TEXTURE_2D, m_uName);
18: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)pixelsWide, (GLsizei)pixelsHigh, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
19: return true;
20: }
核心代码如上,完成纹理的加载,主要有两个成分:1解析图片,通过CFileData和CCImage类来解析,依赖的png和jpeg的第三方库的实现;2:纹理创建,标准的opengl的方法。
1: // 贴图
2: void CCSprite::draw(void)
3: {
4: ...
5: }
贴图也是在CCSprite中完成,继承自Node实现调用,也是opengl的标准渲染方式,不过没有vbo和fbo的优化方式,可能是对硬件差异化的安全处理吧。
综上所述cocos2d对纹理的应用主要也是集中在贴图,对模型,材质这些都还没有进行太多考虑,看到代码中有对粒子的支持,不过纹理压缩,mipmap,多重纹理等这些优化的也没有,而且代码写的也比较过程化,对以后重构代码也是一个有些麻烦的地方,当然对于二维应用问题不大,但是在以后三维应用,估计还是有不少隐患的。
另外还没有找到点线面的绘制方式,不过既然依赖opengl了,这些图形学的算法应该不太需要自己的处理了,反走样应该也能有较好支持,这个以后遇到了再看,简单找了一下没有找到。
文字就不多说了,其实也是贴图,就是需要通过CDC绘制一下图片,然后以贴图的形式完成文字的绘制,真是二三位一体化了。在linux下大致思路一样,通过FreeType来实现文字的绘制,最后实现贴图,基本代码如下:
1: // 创建文本标签:文本,字体,字号
2: CCLabelTTF* pLabel = CCLabelTTF::labelWithString("Hello World", "Arial", 24);
1: // 创建文字纹理函数
2: bool CCTexture2D::initWithString(const char *text, const char *fontName, float fontSize)
3: {
4: CCImage image;
5: image.initWithString(text, (int)dimensions.width, (int)dimensions.height, eAlign, fontName, (int)fontSize);
6: return initWithImage(&image);
7: }
8: // 生成文字图片,方便创建纹理
9: bool CCImage::initWithString(const char * pText, int nWidth = 0, int nHeight = 0,ETextAlign eAlignMask = kAlignCenter,const char *pFontName = 0,int nSize = 0)
10: {
11: ...
12: }
cocos2d的场景管理比较简单,本身CCScene也继承自CCNode,而CCDirector有点类似场景管理的作用,起到导演的作用。而场景元素并没有一个场景树的概念,基本组织结构如下:
1: class CC_DLL CCNode : public CCObject
2: {
3: #define CC_PROPERTY_READONLY(varType, varName, funName)\
4: protected: varType varName;\
5: public: virtual varType get##funName(void);
6:
7: CC_PROPERTY_READONLY(CCArray*, m_pChildren, Children)
8: };
9:
10: void CCNode::visit()
11: {
12: ccArray *arrayData = m_pChildren->data;
13: for( ; i < arrayData->num; i++ )
14: {
15: pNode = (CCNode*) arrayData->arr[i];
16: pNode->visit();
17: }
18:
19: // self draw
20: this->draw();
21: }
这样的场景管理本身还是和二维线性的绘制方式可以说是一马平川的方式,只是加了一个三维的外壳而已。其实对于这种场景管理,还是略显单薄,没有场景管理,以后范围裁剪,灯光,三维状态,深度,碰撞检测,阴影特效等处理起来都会增加复杂度,当然,这就要看cocos2d引擎设计的定位了,这些只是在阅读代码的时候想到的一些可能的不足。
简单的看完HelloWorld范例,个人觉得cocos2d的整体框架已经比较成熟,和一般的游戏开发框架基本相同,也比较容易上手,在目前的二维游戏里面具有很好的跨平台特性,确实很有优势。但是采用opengl es的引擎,对二维没什么问题,但是在三维上纹理,模型,场景管理,场景树这些都没有考虑,要想今后做三维游戏压力还是不小的。另外感觉camera的设计业略显简单,设备事件处理也有点乱,这些地方OGRE显得更为成熟和优雅,OGRE也显得略臃肿和复杂很多。
当然,作为一款这么多人应用的引擎,肯定有它优秀的地方,今后要学习的地方相信还有很多。也是初学者,上面的内容也难免有不对的地方,欢迎大家指正,多交流。
今天就先总结到这里,接下来主要了解一下Android平台,比较关心如何简化jni的开发以及动态库、静态库之间的结构关系。