了解完Cocos2D-x的基本概念和概念类之后,是不是有一种蠢蠢欲动的冲动,想要探究Cocos2D-x是如何完成这一切的。接着我将通过对Cocos2D-x自代的HelloCpp项目进行分析,初步了解Cocos2D-x游戏的基本框架,揭开Cocos2D-x神秘的面纱。

 
   作为一个Hello World程序,HelloCpp的功能非常简单。打开一个OpenGL窗口,在里面显示了一张Cocos2D-x的log图片,在图片的上面写着"Hello World"。在右下角有一个按钮,用来退出程序。在下角显示了当前的帧率。如下图:
  
       HelloCpp项目提供了android、blackberry、ios、linux、mac和win32的工程,体现了Cocos2D-x的跨平台性。这里我们只针对win32工程。
  
       关于Cocos2D-x版本下载和环境配置非常简单,我这里就不再赘述。如有不明白的地方,可以上网搜索,有很多介绍这方面的文章。
 
       整个HelloCpp程序分为两个部分,这也是Cocos2D-x应用程序开发模板程序的共同结构。如下图:

   在Classes目录下,放着程序的主要部分,包括场景(CCScene)、布景(CCLayer)、精灵(CCSprite)等游戏元素相关的代码,而在每个工程的目录下(Win32是proj.win32)放着与平台相关的入口程序(proj.win32里就包含一个main.cpp/h)。
   先从入口开始,查看main.cpp代码,如下:
   int APIENTRY _tWinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPTSTR lpCmdLine,int nCmdShow)
   {
      UNREFERENCED_PARAMETER(hPrevInstance);//这是一个编译宏,用于告诉编译器,忽略未使用变量而产生的告警信息。
      UNREFERENCED_PARAMETER(lpCmdLine);
      AppDelegate app;      // 创建一个Cocos2D-x程序实例
      CCEGLView* eglView = CCEGLView::sharedOpenGLView(); 
      eglView->setFrameSize(960, 640 ); //处理windows窗体注册和创建
      return CCApplication::sharedApplication()->run(); //运行程序实例
   }
      写过win32程序的人,一定对_tWinMain函数不会感到陌生,Cocos2D-x也是从这里作为入口开始的。这个函数简洁到极致,只有寥寥几行代码,甚至看不到Windows的窗口注册和消息机制。因为Cocos2D-x把很多Windows窗口相关程序代码封装到CCEGLView中,该类被统一放到Cocos2D-x的win32平台中(cocos2d-2.0-x-2.0.2\cocos2dx\platform\win32目录)。这样做的目的,无疑是为了方便跨平台。
     一般游戏程序都需要一个主循环,那Cocos2D-x的主循环在哪里呢?它就藏在CCApplication的成员函数run里,下面查一下处理代码:
int CCApplication::run()
{
    PVRFrameEnableControlWindow(false);
 
    // Main message loop:
    MSG msg;
    LARGE_INTEGER nFreq;
    LARGE_INTEGER nLast;
    LARGE_INTEGER nNow;
 
    QueryPerformanceFrequency(&nFreq);
    QueryPerformanceCounter(&nLast);
 
    // Initialize instance and cocos2d.
    if (!applicationDidFinishLaunching())
    {
        return 0;
    }
 
    CCEGLView* pMainWnd = CCEGLView::sharedOpenGLView();
    pMainWnd->centerWindow();
    ShowWindow(pMainWnd->getHWnd(), SW_SHOW);
 
    //主消息循环
    while (1)
    {
        if (! PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            // Get current time tick.
            QueryPerformanceCounter(&nNow);
 
            // If it's the time to draw next frame, draw it, else sleep a while.
            if (nNow.QuadPart - nLast.QuadPart > m_nAnimationInterval.QuadPart)
            {
                nLast.QuadPart = nNow.QuadPart;
                CCDirector::sharedDirector()->mainLoop();            }
            else
            {
                Sleep(0);
            }
            continue;
        }
 
        if (WM_QUIT == msg.message)
        {
            // Quit message loop.
            break;
        }
 
        // Deal with windows message.
        if (! m_hAccelTable || ! TranslateAccelerator(msg.hwnd, m_hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
 
    return (int) msg.wParam;
}
     这段代码主要在处理windows消息机制,熟悉windows程序开发的应该很眼熟。我们重点关注CCDirector::sharedDirector()->mainLoop(),这行代码是Cocos2D-x的主循环处理,完成场景的渲染。
 
  下面深入每一个类进行探索,看一看Cocos2D-x的运行机制。
  首先要查看的类是AppDelegate类,头文件定义如下:
class  AppDelegate : private cocos2d::CCApplication
{
public:
    AppDelegate();
    virtual ~AppDelegate();
    virtual bool applicationDidFinishLaunching(); //程序启动时调用
    virtual void applicationDidEnterBackground(); //程序被切换到后台处于非激活时调用
    virtual void applicationWillEnterForeground();//程序被切换到前台处于激活状态时调用
};
  当我最初看到这个AppDelegate时,还以为使用类似C#中的代理机制。但查看头文件定义才知道,它是私有继承于CCApplication。而CCApplication就是一个App,AppDelegate可以理解为MyApp。这里采用私有继承的目的是隐藏接口。这里AppDelegate实现了父类CCApplication的三个虚函数。而这三个虚函数是CCApplication从CCApplicationProtocol接口继承而来,但它并未实现这些接口,所以不能直接创建CCApplication实例。
  applicationDidFinishLaunching函数非常关键中,因为它将加载Cocos2D-x的概念类和类之间的关联关系,代码如下:
    bool AppDelegate::applicationDidFinishLaunching() {
    CCDirector *pDirector = CCDirector::sharedDirector(); //这里得到一个导演
    pDirector->setOpenGLView(CCEGLView::sharedOpenGLView());
    TargetPlatform target = getTargetPlatform();
    if (target == kTargetIpad)
    {
        // ipad
       
        CCFileUtils::sharedFileUtils()->setResourceDirectory("iphonehd");
       
        // don't enable retina because we don't have ipad hd resource
        CCEGLView::sharedOpenGLView()->setDesignResolutionSize(960, 640, kResolutionNoBorder);
    }
    else if (target == kTargetIphone)
    {
        // iphone
       
        if (pDirector->enableRetinaDisplay(true))
        {
            // iphone hd
            CCFileUtils::sharedFileUtils()->setResourceDirectory("iphonehd");
        }
        else
        {
            CCFileUtils::sharedFileUtils()->setResourceDirectory("iphone");
        }
    }
    else
    {
        // android, windows, blackberry, linux or mac
        // use 960*640 resources as design resolution size
        CCFileUtils::sharedFileUtils()->setResourceDirectory("iphonehd");
        CCEGLView::sharedOpenGLView()->setDesignResolutionSize(960, 640, kResolutionNoBorder);
    }
 
    // turn on display FPS
    pDirector->setDisplayStats(true);
    // set FPS. the default value is 1.0/60 if you don't call this
    pDirector->setAnimationInterval(1.0 / 60);
    // create a scene. it's an autorelease object
    CCScene *pScene = HelloWorld::scene(); //这里得到一个场景
    // run
    pDirector->runWithScene(pScene); //导演启动场景渲染
    return true;
}
     当程序启动后,applicationDidFinishLaunching函数将会被调用,函数内将获得一个导演和创建一个场景,然后导演调用runWithScene函数渲染该场景。
     这里只有导演和场景,还少了布景层和精灵。它们在哪里创建的呢?一切奥秘就藏在HelloWorld里,在这里将解答我们的疑问。看一下HelloWorld的头文件,如下:
class HelloWorld : public cocos2d::CCLayer
{
public:
    // Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone
    virtual bool init(); 
 
    // there's no 'id' in cpp, so we recommand to return the exactly class pointer
    static cocos2d::CCScene* scene();
   
    // a selector callback
    void menuCloseCallback(CCObject* pSender);
 
    // implement the "static node()" method manually
    CREATE_FUNC(HelloWorld);
};
    可以看到HelloWorld类继承自布景层CCLayer。在它的头文件里定义了四个成员函数。前三个成员函数,很容易理解它们的用途。而最后一个有点奇怪,需要解释一下。CREATE_FUNC其实是一个宏定义,如下:
#define CREATE_FUNC(__TYPE__) \
static __TYPE__* create() \
{ \
    __TYPE__ *pRet = new __TYPE__(); \
    if (pRet && pRet->init()) \
    { \
        pRet->autorelease(); \
        return pRet; \
    } \
    else \
    { \
        delete pRet; \
        pRet = NULL; \
        return NULL; \
    } \
}
    这个宏为HelloWorld添加一个静态成员函数create,并返回这个类自身的指针。在创建的时候,自动调用了类的init函数对类进行初始化,并且使用autorelease。(autorelease是Cocos2D-x的内存管理机制,欲详细了解请查看Cocos2D-x内存管理专题)来自动释放对象实例。
   还是关注HelloWorld实例的创建,答案就在下面这行代码里。
CCScene *pScene = HelloWorld::scene(); //这里获取一个场景
    这行代码明明是获取一个场景,HelloWorld实例又在哪里创建的呀。还是看一看HelloWorld的scene函数的实现,如下:
CCScene* HelloWorld::scene()
{
    // 'scene' is an autorelease object
    CCScene *scene = CCScene::create(); //这里真正创建一个场景CCScene
   
    // 'layer' is an autorelease object
    HelloWorld *layer = HelloWorld::create(); //创建一个布景CCLayer
 
    // add layer as a child to scene
    scene->addChild(layer);//将布景添加到场景里
 
    // return the scene
    return scene;//返回创建的场景
}
    现在一切都清楚了,原来在scene函数内创建了场景和布景,而且将布景添加到场景中并返回了场景。
    这里似乎只有场景和布景,还差什么呢?是精灵。那精灵又是在哪里创建的呢?答案是在HelloWorld的初始化init函数里。还是给出init函数的实现代码,如下:
bool HelloWorld::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !CCLayer::init() )
    {
        return false;
    }
   
    CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize();
    CCPoint origin = CCDirector::sharedDirector()->getVisibleOrigin();
 
    /////////////////////////////
    // 2. add a menu item with "X" image, which is clicked to quit the program
    //    you may modify it.
 
    // add a "close" icon to exit the progress. it's an autorelease object
    CCMenuItemImage *pCloseItem = CCMenuItemImage::create(
                                        "CloseNormal.png",
                                        "CloseSelected.png",
                                        this,
                                        menu_selector(HelloWorld::menuCloseCallback));
   
    if (CCApplication::sharedApplication()->getTargetPlatform() == kTargetIphone)
    {
        pCloseItem->setPosition(ccp(visibleSize.width - 20 + origin.x, 20 + origin.y));
    }
    else
    {
        pCloseItem->setPosition(ccp(visibleSize.width - 40 + origin.x, 40 + origin.y));
    }
 
    // create menu, it's an autorelease object
    CCMenu* pMenu = CCMenu::create(pCloseItem, NULL);
    pMenu->setPosition(CCPointZero);
    this->addChild(pMenu, 1);
 
    /////////////////////////////
    // 3. add your codes below...
 
    // add a label shows "Hello World"
    // create and initialize a label
    CCLabelTTF* pLabel = CCLabelTTF::create("Hello World", "Arial", 24);
 
    // position the label on the center of the screen
    pLabel->setPosition(ccp(visibleSize.width/2 + origin.x, visibleSize.height - 50 + origin.y));
 
    // add the label as a child to this layer
    this->addChild(pLabel, 1);
 
    // add "HelloWorld" splash screen"
    CCSprite* pSprite = CCSprite::create("HelloWorld.png"); //这里创建精灵
 
    // position the sprite on the center of the screen
    pSprite->setPosition(ccp(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
 
    // add the sprite as a child to this layer
    this->addChild(pSprite, 0);//将精灵添加到布景中
    return true;
}
    这段代码有点长,它将在HelloWorld类创建时被调用。它将给HelloWorld添加一个图像菜单(关闭按钮)、一个Label标签以及一个精灵。
    HelloWorld还有一个函数menuCloseCallback,这是一个事件响应函数。它用来响应关闭按钮被按下时的事件。它的处理很简单就是关闭程序。
 
    现在整个Cocos2D-x的基本框架讲解完了。它虽然很简单,但是却是Cocos2D-x开发的基础。
   
    刚开始接触Cocos2D-x代码时,里面有一些惯用法和宏定义可能不是很适应,不过慢慢就会习惯了。下面例举HelloWorld中使用的一些惯用法和宏,如下:
    ccp(visibleSize.width - 20 + origin.x, 20 + origin.y)//这里ccp是辅助宏,用来创建CCPoint。#define ccp(__X__,__Y__) cocos2d::CCPointMake((float)(__X__), (float)(__Y__))
    CCSprite* pSprite = CCSprite::create("HelloWorld.png");//使用create而不是new创建对象实例
    CCLayer::init();//使用init初始化一个对象
    CCDirector *pDirector = CCDirector::sharedDirector();//shared前缀表示一个类的单例(单例设计模式)
-------------------------------------------------------------------------------------------------------------------

注:本人在本博客的原创文章采用创作共用版权协议http://creativecommons.org/licenses/by-nc-sa/2.5/cn/), 要求署名、非商业用途和保持一致。要求署名包含注明我的网名及文章来源(我的博客地址:http://www.cnblogs.com/binbingg)。

[原创]cocos2d-x研习录-第二阶 基本框架的更多相关文章

  1. [原创]cocos2d-x研习录-第二阶 概念类之导演类(CCDirector)

    CCDirector类是游戏的组织和控制中心(总指挥),它控制着主屏幕的显示.场景的切换和显示,以及游戏的开始.结束和暂停.它的继承关系图如下:    CCDirector继承自基类CCObject, ...

  2. [原创]cocos2d-x研习录-第二阶 概念类之节点类(CCNode)

    节点类CCNode在基本概念中并不存在,它是为了建立基本概念之间的关联关系而抽象出来的中间辅助类.这个类在Cocos2D-x中极为重要,它为概念类之间搭建了一座宏伟的桥梁.它的继承关系图如下:     ...

  3. [原创]cocos2d-x研习录-第二阶 基本概念

    在Cocos2D-x引擎中,有几个非常重要的概念:导演(CCDirector).摄像机(CCCamera).场景(CCSecen).布景(CCLayer).精灵(CCSPrite)和动作(CCActi ...

  4. [原创]cocos2d-x研习录-第二阶 概念类之摄相机类(CCCamera)

    在Cocos2D-x中,每个CCNode都拥有一个摄像机类CCCamera.只有通过CCCamera,CCNode才会被渲染出来.当CCNode发生缩放.旋转和位置变化时,都需要覆盖CCCamera, ...

  5. [原创]cocos2d-x研习录-第二阶 概念类之精灵类(CCSprite)

    上一节说布景层CCLayer是小容器,那么精灵类CCSprite就是容器添加的内容,它是构成游戏的主要元素.精灵这个名称应该是游戏专用,它表示游戏中玩家操作的主角.敌人.NPC(Non Player ...

  6. [原创]cocos2d-x研习录-第二阶 概念类之布场层类(CCLayer)

    上面说场景CCScene相当于一个大容器,那么布景层类CCLayer就是大容器里的若干个小容器.每个游戏场景CCScene会有很多层CCLayer,每一层CCLayer负责各自的任务.看一下CCLay ...

  7. [原创]cocos2d-x研习录-第二阶 概念类之场景类(CCScene)

    场景类CCScene是Cocos2D-x在屏幕显示的内容,相当于游戏关卡或界面.CCDirector任何时候只能显示一个场景CCScene,游戏中可能存在若干场景,CCDirector通过场景切换达到 ...

  8. [原创]cocos2d-x研习录—前言

    我认为很多开发者面对层出不穷的新技术.新思想和新理念,最为之苦恼的是找不到行之有效的学习方法,对于知识的本质缺乏认识,虽阅读了大量教材,却无法将其融入自己的知识体系,并搭建自己的知识树.不可否认,教材 ...

  9. [原创]cocos2d-x研习录-第一阶 背景介绍 之 cocos2d家族史

    Cocos2D是一个2D开源游戏引擎,它最早是由Ricardo Quesada(阿根廷人,社区简称Riq)和他的朋友们用Python开发的,用于开发2D游戏和基于2D图形的任何应用.最早引擎的名字源自 ...

随机推荐

  1. Logistic 分类器与 softmax分类器

    首先说明啊:logistic分类器是以Bernoulli(伯努利) 分布为模型建模的,它可以用来分两种类别:而softmax分类器以多项式分布(Multinomial Distribution)为模型 ...

  2. Delphi TTable 组件

    TTable 是 TDataSet 的派生类,它是基于 BDE 数据库引擎的数据集组件,也是一个较简单的数据组件,可以直接从数据库中获取数据表的数据,只需设置连接的数据库属性(Database) 和所 ...

  3. Python之路 day2 初识字典

    #Author:ersa ''' key-value 键值对 字典是无序的,不需要下标,有key 字典的查找.修改.添加.判断.删除 ''' info = { 'stu1101': "Ten ...

  4. Laravel 5 使用中的问题记录(持续更新)

    1.更新了blade模板却没有更新缓存 通过使用ftp上传文件到服务器,更新了blade模板,却没有更新缓存,经查,原因是系统时间的影响,通过ftp上传的模板文件修改时间与缓存文件的时间不一致,导致模 ...

  5. (33)odoo中产品价格字段

    打开product.template 和 product.product 模型发现有很多关于价格描述的字段 product.template:        price        list_pri ...

  6. Mysql:The table‘xxxx’is full

    下午跑程序,在插入mysql时突然报错: "The table'xxxx'is full" 而之前一直没问题的. 上网查了一下,都说临时表的问题,需要设置"tmp_tab ...

  7. Codeforces Round #371 (Div. 2) C. Sonya and Queries

    题目链接 分析:01trie树,很容易就看出来了,也没什么好说的.WA了一发是因为没有看见如果数字位数大于01序列的时候01序列也要补全0.我没有晚上爬起来打,白天发现过的人极多. /******** ...

  8. Android AutoLayout全新的适配方式 堪称适配终结者(转)

    一.概述 相信Android的开发者对于设配问题都比较苦恼,Google官方虽然给出了一系列的建议,但是想要单纯使用这些建议将设备很轻松的做好,还是相当困难的.个人也比较关注适配的问题,之前也发了几篇 ...

  9. LeetCode 175 Combine Two Tables mysql,left join 难度:0

    https://leetcode.com/problems/combine-two-tables/ Combine Two Tables Table: Person +-------------+-- ...

  10. [Weekly] 2014.03.01-2014.03.08

    这周写过好多东西,虽然还没有完全弄明白线段树,但是progress还是有的! 不过有时候真的很想哭,因为自己的梦想连别人看看韩剧.无所事事还要分量轻,实在不明白政治课的Teamwork意义何在,花两分 ...