欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Cocos2d-x入门教程(详细的实例和讲解)

程序员文章站 2023-08-29 19:00:10
智能终端上的游戏目前风头正劲,试问哪个智能手机上没有几款企鹅公司出品的游戏呢!之前从未涉猎过游戏开发,但知道游戏开发前要挑选一款合适的游戏引擎,自己从头开始敲代码的时代已经...

智能终端上的游戏目前风头正劲,试问哪个智能手机上没有几款企鹅公司出品的游戏呢!之前从未涉猎过游戏开发,但知道游戏开发前要挑选一款合适的游戏引擎,自己从头开始敲代码的时代已经out了。在寻觅游戏引擎之前,我需要回答三道摆在我面前的选择题:

    1、2d引擎还是3d引擎?
    2、平台专用引擎还是跨平台引擎?
    3、收费引擎还是开源引擎?

作为入门级选手,2d游戏显然更适合上手一些,另外适合果果这个年龄段的幼教类的游戏也多以2d游戏居多。3d游戏本身也太难了,不仅要 programming能力,还要3d建模能力,这些学习起来周期就太长了;一直是ubuntu fans,手头没有mac book,这样开发ios程序变成一件糟心的事,在ubuntu下搭建ios app开发环境繁杂的很,即便是虚拟机也懒得尝试。但从游戏体验来看,还是在ipad上玩更好一些,因此最好引擎能跨平台,以便后续迁移到ios上;开源 和用开源惯了,收费的引擎目前不在考虑范围之内。综上,我要寻找的是一款开源的、跨平台的mobile 2d game engine。

于是我找到了cocos2d-x!cocos2d-x是cocos2d-iphone的c++跨平台分支,由于是国人创立的,在国内有着较大的用 户群,引擎资料也较多,社区十分活跃。国内已经出版了多本有关cocos2d-x的中文书籍,比如《cocos2d-x高级开发教程:制作自己的 “捕鱼达人”》 、《cocos2d-x权威指南》 等都还不错。更重要的是cocos2d-x自带了丰富的例子,供初学者“临摹学习”,其中cocos2d-x-2.2.2/samples/cpp /testcpp这个例子几乎涵盖了该引擎的绝大多数功能。下面就开启cocos2d-x的入门之旅(for android)。

一、引擎安装

试验环境:

复制代码 代码如下:
   ubuntu 12.04.1 x86_64
   gcc 4.6.3
   javac 1.7.0_21
   java "1.7.0_21" hotspot 64-bit server vm
   adt-bundle-linux-x86_64-20131030.zip
   android-ndk-r9d-linux-x86_64.tar.bz2
 

cocos2d-x官网目前提供2.2.2稳定版以及3.0beta2版的下载(当然你也可以下载到更老的版本)。由于3.0改变较大,资料不 多,且对编译器等版本的要求较高(需要支持c++ 11标准),因此这里依旧以2.2.2版本作为学习目标。cocos2d-x-2.2.2下载后解压到某个目录:比如/home1/tonybai/android-dev/cocos2d-x-2.2.2。 如果仅是用cocos2d-x开发android版本游戏,则不需要做什么编译工作。android game project会在project build时自动用ndk的编译器编译c++代码,并与ndk链接。如果你想早点看看cocos2d-x sample中的例子运行起来到底是什么样子的,你可以在ubuntu下编译出linux版本的游戏:在cocos2d-x-2.2.2下执行make-all-linux-project.sh即可。编译需要一段时间,编译成 功后,我们可以进入到“cocos2d-x-2.2.2/samples/cpp/hellocpp/proj.linux/bin/release” 下执行“hellocpp”这个可执行文件,一个最简单的cocos2d-x游戏就会展现在你的面前了。

android sample project的构建稍微复杂些:

首先在eclipse中添加libcocos2dx library project from existed code(注意:不copy到workspace,原地建立)。该project的代码路径为cocos2d-x-2.2.2/cocos2dx/platform /android/java。在project.properties和androidmanifest.xml适当修改你所使用的api版本, 以让编译通过。我这里用的是 target=android-19。

然后,设置ndk_root环境变量(比如export ndk_root='/home1/tonybai/android-dev/adt-bundle-linux-x86_64/android-ndk-r9c'), 供build_native.sh使用。

最后添加游戏project。在eclipse中添加hellocpp project from existed code,位置cocos2d-x-2.2.2/samples/cpp/hellocpp/proj.android(注 意:不copy到workspace中,原地建立)。在hellocpp的project.properties中添加“android.library.reference.1=../../../../cocos2dx/platform/android /java”。同样别忘了在project.properties和androidmanifest.xml适当修改你所使用 的api版本,以让编译通过。

如果一切顺利的话,你会在console窗口看到“**** build finished ****”。problems窗口显示“0 errors“。 启动android模拟器,run application,同样的hellocpp画面会呈现在模拟器上。

cocos2d-x是建构在opengl技术之上的。对于android平台而言,android sdk已经完全封装了opengl es 1.1/2.0的api(android.opengl.*;javax.microedition.khronos.egl.*;javax.microedition.khronos.opengles.*), 引擎完全可以建立在这个之上,无需c++代码。但cocos2d-x是一个跨平台的2d游戏引擎,核心选择了用c++代码实现(ios提供的c绑 定,不提供java绑定;android则提供了java和c绑定),因此 在开发android平台的2d游戏时,引擎部分是sdk与ndk交相互应,比如glthread的创建和管理用的是sdk的 glsurfaceview和glthread,但真正的surface绘制部分则是回调cocos2d-x用c++编写的绘制实现(链接ndk 中的库)。

二、cocos2d-x android工程代码组织结构

以samples/cpp/helloapp的android工程为例,android版的cocos2d-x工程与普通android应用程序 差别 不大,核心部分只是多了一个jni目录和一个build_native.sh脚本文件。其中jni目录下存放的是java和c++调用转换的“胶 水”代码;build_native.sh则是用于编译jni下c++代码以及 cocos2dx_static library代码的构建脚本。

hellocpp的构建过程摘要如下:

复制代码 代码如下:

**** build of configuration default for project hellocpp ****

bash /home1/tonybai/android-dev/cocos2d-x-2.2.2/samples/cpp/hellocpp/proj.android/build_native.sh
ndk_root = /home1/tonybai/android-dev/adt-bundle-linux-x86_64/android-ndk-r9c
cocos2dx_root = /home1/tonybai/android-dev/cocos2d-x-2.2.2/samples/cpp/hellocpp/proj.android/../../../..
app_root = /home1/tonybai/android-dev/cocos2d-x-2.2.2/samples/cpp/hellocpp/proj.android/..
app_android_root = /home1/tonybai/android-dev/cocos2d-x-2.2.2/samples/cpp/hellocpp/proj.android
+ /home1/tonybai/android-dev/adt-bundle-linux-x86_64/android-ndk-r9c/ndk-build -c /home1/tonybai/android-dev/cocos2d-x-2.2.2/samples/cpp/hellocpp/proj.androidndk_module_path=/home1/tonybai/android-dev/cocos2d-x-2.2.2/samples/cpp/hellocpp/proj.android/../../../..:/home1/tonybai/android-dev/cocos2d-x-2.2.2/samples/cpp/hellocpp/proj.android/../../../../cocos2dx/platform/third_party/android/prebuilt
using prebuilt externals
android ndk: warning:/home1/tonybai/android-dev/cocos2d-x-2.2.2/samples/cpp/hellocpp/proj.android/../../../../cocos2dx/android.mk:cocos2dx_static: local_ldlibs is always ignored for static libraries 
make: entering directory `/home1/tonybai/android-dev/cocos2d-x-2.2.2/samples/cpp/hellocpp/proj.android'
[armeabi] compile++ thumb: hellocpp_shared <= main.cpp
[armeabi] compile++ thumb: hellocpp_shared <= appdelegate.cpp
[armeabi] compile++ thumb: hellocpp_shared <= helloworldscene.cpp
[armeabi] compile++ thumb: cocos2dx_static <= ccconfiguration.cpp
[armeabi] compile++ thumb: cocos2dx_static <= ccscheduler.cpp
 … …
[armeabi] compile++ thumb: cocos2dx_static <= cctouch.cpp
[armeabi] staticlibrary  : libcocos2d.a
[armeabi] compile thumb  : cpufeatures <= cpu-features.c
[armeabi] staticlibrary  : libcpufeatures.a
[armeabi] sharedlibrary  : libhellocpp.so
[armeabi] install        : libhellocpp.so => libs/armeabi/libhellocpp.so
make: leaving directory `/home1/tonybai/android-dev/cocos2d-x-2.2.2/samples/cpp/hellocpp/proj.android'

**** build finished ****


指挥ndk编译的则是jni下的android.mk文件,其角色类似于makefile。

三、cocos2d-x android工程代码阅读

单独将如何阅读代码拿出来,是为了后面分析引擎的驱动流程做准备工作。学习类似cocos2d-x这样的游戏引擎,仅仅停留在游戏逻辑层代码是不 能很好的把握引擎本质的,因此适当的挖掘引擎实现实际上对于理解和使用 引擎都是大有裨益的。

以一个cocos2d-x android工程为例,它的游戏逻辑代码以及涉及的引擎代码涵盖在一下路径下(还是以hellocpp的android工程为例):

复制代码 代码如下:

    项目层:
        * cocos2d-x-2.2.2/samples/cpp/hellocpp/proj.android/src  主activity的实现;
        * cocos2d-x-2.2.2/samples/cpp/hellocpp/proj.android/jni/hellocpp  cocos2dxrenderer类的nativeinit实现,用于引出application的入口;
        * cocos2d-x-2.2.2/samples/cpp/hellocpp/classes 你的游戏逻辑,以c++代码形式呈现;

    引擎层:
        * cocos2d-x-2.2.2/cocos2dx/platform/android/java/src 引擎层对android activity、glsurfaceview以及render的封装
        * cocos2d-x-2.2.2/cocos2dx/platform/android/jni 对应上面封装的native method实现
        * cocos2d-x-2.2.2/cocos2dx、cocos2d-x-2.2.2/cocos2dx/platform、cocos2d-x- 2.2.2/cocos2dx/platform/android   cocos2dx引擎的核心实现(针对android平台)

后续的代码分析也将从这两个层次、六处位置出发。

四、从activity开始

之前多少了解了一些android app开发的知识,android app都是始于activity的。游戏也是app的一种,因此在android平台上,cocos2d-x游戏也是从activity开始的。于是 activity,确切的说是cocos2dxactivity是我们这次引擎驱动机制分析的出发点。

回顾android activity的lifecycle,activity启动的顺序是:activity.oncreate -> activity.onstart() -> activity.onresume()。接下来我们将按照 这条主线进行引擎驱动机制的分析。

hellocpp.java中的hellocpp这个activity完全无所作为,仅仅是继承其父类cocos2dxactivity的实现罢 了。

复制代码 代码如下:

// hellocpp.java
public class hellocpp extends cocos2dxactivity{
    protected void oncreate(bundle savedinstancestate){
        super.oncreate(savedinstancestate);
    }
    … …
}

我们来看cocos2dxactivity类。
复制代码 代码如下:

// cocos2dxactivity.java

@override
protected void oncreate(final bundle savedinstancestate) {
    super.oncreate(savedinstancestate);
    scontext = this;
    this.mhandler = new cocos2dxhandler(this);
    this.init();
    cocos2dxhelper.init(this, this);
}

public void init() {
        // framelayout
        viewgroup.layoutparams framelayout_params =
            new viewgroup.layoutparams(viewgroup.layoutparams.fill_parent,
                                       viewgroup.layoutparams.fill_parent);
        framelayout framelayout = new framelayout(this);
        framelayout.setlayoutparams(framelayout_params);

        … …
        // cocos2dxglsurfaceview
        this.mglsurfaceview = this.oncreateview();

        // …add to framelayout
        framelayout.addview(this.mglsurfaceview);
        … …
        this.mglsurfaceview.setcocos2dxrenderer(new cocos2dxrenderer());
        … …

        // set framelayout as the content view
        setcontentview(framelayout);
}

从上面代码可以看出,oncreate调用的init方法才是cocos2dxactivity初始化的核心。在init方法 中,cocos2dxactivity创建了一个framelayout实例,并将该实例作为content view赋给了cocos2dxactivity的实例。framelayout实例也并不孤单,一个设置了cocos2dxrenderer实例的 glsurfaceview被added to it。而cocos2d-x引擎的初始化已经悄悄地在这几行代码间完成了,至于初始化的细节我们后续再做分析。

接下来是onresume方法,它的实现如下:

复制代码 代码如下:

    @override
    protected void onresume() {
        super.onresume();

        cocos2dxhelper.onresume();
        this.mglsurfaceview.onresume();
    }


onresume调用了view的onresume()。
复制代码 代码如下:

// cocos2dxglsurfaceview:
    @override
    public void onresume() {
        super.onresume();

        this.queueevent(new runnable() {
            @override
            public void run() {
                cocos2dxglsurfaceview.this.mcocos2dxrenderer.handleonresume();
            }
        });
    }


cocos2dxglsurfaceview将该事件打包放到队列里,扔给了另外一个线程去执行(后续会详细说明这个线程),对应的方法在 cocos2dxrenderer class中。
复制代码 代码如下:

    public void handleonresume() {
        cocos2dxrenderer.nativeonresume();
    }

render实际上调用的是native方法。
复制代码 代码如下:

 jniexport void jnicall java_org_cocos2dx_lib_cocos2dxrenderer_nativeonresume() {
        if (ccdirector::shareddirector()->getopenglview()) {
            ccapplication::sharedapplication()->applicationwillenterforeground();
        }
    }

applicationwillenterforeground方法在你的appdelegate.cpp中;

void appdelegate::applicationwillenterforeground() {
    ccdirector::shareddirector()->startanimation();//

    // if you use simpleaudioengine, it must resume here
    // simpleaudioengine::sharedengine()->resumebackgroundmusic();
}

这里仅是重新获得了一下时间罢了。

五、render thread(渲染线程) - glthread

游戏引擎要兼顾ui事件和屏幕帧刷新。android的opengl应用采用了ui线程(main thread) +  渲染线程(render thread)的模式。activity活在main thread(主线程)中,也叫做ui线程。该线程负责捕获与用户交互的信息和事件,并与渲染(render)线程交互。比如当用户接听电话、切换到其他 程序时,渲染线程必须知道发生了 这些事件,并作出即时的处理,而这些事件及处理方式都是由主线程中的activity以及其装载的view传递给渲染线程的。我们在cocos2dx的框 架代码中看不到渲染线程的诞生过程,这是因为这一过程是在android sdk层实现的。

我们回顾一下cocos2dxactivity.init方法的关键代码:

复制代码 代码如下:

    // cocos2dxglsurfaceview
    this.mglsurfaceview = this.oncreateview();

    // …add to framelayout
    framelayout.addview(this.mglsurfaceview);
    this.mglsurfaceview.setcocos2dxrenderer(new cocos2dxrenderer());

    // set framelayout as the content view
    setcontentview(framelayout);

cocos2dxglsurfaceview是 android.opengl.glsurfaceview的子类。在android 上做原生opengl es 2.0编程的人应该都清楚glsurfaceview的重要性。但渲染线程并非是在cocos2dxglsurfaceview实例化时被创建的,而是在 setrenderer的时候。

我们来看cocos2dxglsurfaceview.setcocos2dxrenderer的实现:

复制代码 代码如下:

    public void setcocos2dxrenderer(final cocos2dxrenderer renderer) {
        this.mcocos2dxrenderer = renderer;
        this.setrenderer(this.mcocos2dxrenderer);
    }

setrender是cocos2dxglsurfaceview父类glsurfaceview实现的方法。在android sdk glsurfaceview.java文件中,我们看到:
复制代码 代码如下:

       public void setrenderer(renderer renderer) {
        checkrenderthreadstate();
        if (meglconfigchooser == null) {
            meglconfigchooser = new simpleeglconfigchooser(true);
        }
        if (meglcontextfactory == null) {
            meglcontextfactory = new defaultcontextfactory();
        }
        if (meglwindowsurfacefactory == null) {
            meglwindowsurfacefactory = new defaultwindowsurfacefactory();
        }
        mrenderer = renderer;
        mglthread = new glthread(mthisweakref);
        mglthread.start();
    }

glthread的实例是在这里被创建并开始执行的。至于渲染线程都干了些什么,我们可以通过其run方法看到:

复制代码 代码如下:

        @override
        public void run() {
            setname("glthread " + getid());
            if (log_threads) {
                log.i("glthread", "starting tid=" + getid());
            }

            try {
                guardedrun();
            } catch (interruptedexception e) {
                // fall thru and exit normally
            } finally {
                sglthreadmanager.threadexiting(this);
            }
        }

run方法并没有给我们带来太多有价值的东西,真正有价值的信息藏在guardedrun方法中。guardedrun是这个源文件中规模最为庞 大的方法,但抽取其核心结构后,我们发现它大致就是一个死循环,以下是摘要式的伪代码:

复制代码 代码如下:

while (true) {
   synchronized (sglthreadmanager) {
       while (true) {
           …. …
           if (! meventqueue.isempty()) {
               event = meventqueue.remove(0);
               break;
           }
        } 
   }//end of synchronized (sglthreadmanager)

    if (event != null) {
       event.run();
       event = null;
       continue;
   } 

   if needed
       view.mrenderer.onsurfacecreated(gl, meglhelper.meglconfig);

   if needed
       view.mrenderer.onsurfacechanged(gl, w, h);

   if needed
       view.mrenderer.ondrawframe(gl);
}


在这里我们看到了event、renderer的三个回调方法onsurfacecreated、onsurfacechanged以及 ondrawframe,后续我们会对这三个函数做详细分析的。

六、游戏逻辑的入口

在hellocpp的classes下有好多c++代码文件(涉及具体的游戏逻辑),在hellocpp的android project jni目录下也有jni胶水代码,那么这些代码是如何和引擎一起互动生效的呢?

上面讲到过,涉及到画面的一些渲染都是在glthread中进行的,这涉及到onsurfacecreated、 onsurfacechanged以及ondrawframe三个方法。我们看看 cocos2dxrenderer.onsurfacecreated方法的实现,该方法会在surface被首次渲染时调用:

复制代码 代码如下:

    public void onsurfacecreated(final gl10 pgl10, final eglconfig peglconfig) {
        cocos2dxrenderer.nativeinit(this.mscreenwidth, this.mscreenheight);
        this.mlasttickinnanoseconds = system.nanotime();
    }

该方法继续调用hellocpp工程jni目录下的nativeinit代码:
复制代码 代码如下:

void java_org_cocos2dx_lib_cocos2dxrenderer_nativeinit(jnienv*  env, jobject thiz, jint w, jint h)
{
    if (!ccdirector::shareddirector()->getopenglview())
    {
        cceglview *view = cceglview::sharedopenglview();
        view->setframesize(w, h);

        appdelegate *pappdelegate = new appdelegate();
        ccapplication::sharedapplication()->run();
    }
    else
    {
        ccglinvalidatestatecache();
        ccshadercache::sharedshadercache()->reloaddefaultshaders();
        ccdrawinit();
        cctexturecache::reloadalltextures();
        ccnotificationcenter::sharednotificationcenter()->postnotification(event_come_to_foreground, null);
        ccdirector::shareddirector()->setgldefaultvalues();
    }
}

这似乎让我们看到了游戏逻辑的入口了:

复制代码 代码如下:

    cceglview *view = cceglview::sharedopenglview();
    view->setframesize(w, h);

    appdelegate *pappdelegate = new appdelegate();
    ccapplication::sharedapplication()->run();


继续追踪ccapplication::run方法:
复制代码 代码如下:

int ccapplication::run()
{
    // initialize instance and cocos2d.
    if (! applicationdidfinishlaunching())
    {
        return 0;
    }

    return -1;
}

applicationdidfinishlaunching,没错这就是游戏逻辑的入口了。我们得回到samples代码目录中去找到对应方法 的实现。

复制代码 代码如下:

//cocos2d-x-2.2.2/samples/cpp/hellocpp/classes/appdelegate.cpp

bool appdelegate::applicationdidfinishlaunching() {
    // initialize director
    ccdirector* pdirector = ccdirector::shareddirector();
    cceglview* peglview = cceglview::sharedopenglview();

    pdirector->setopenglview(peglview);
    ccsize framesize = peglview->getframesize();
    … …

    // 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中我们做了很多引擎参 数的设置。接下来大管家ccdirector实例登场,并运行了helloworld scene的实例。但这依旧是初始化的一部分,虽然方法名让人听起来像是某种持续连贯行为:

复制代码 代码如下:

//cocos2d-x-2.2.2/cocos2dx/ccdirector.cpp

void ccdirector::runwithscene(ccscene *pscene)
{
    … …
    pushscene(pscene);
    startanimation();
}

void ccdisplaylinkdirector::startanimation(void)
{
    if (cctime::gettimeofdaycocos2d(m_plastupdate, null) != 0)
    {
        cclog("cocos2d: displaylinkdirector: error on gettimeofday");
    }

    m_binvalid = false;
}


两个方法均只是初始化了某些数据成员变量,并未真正将引擎驱动起来。


七、驱动引擎

之所以游戏画面是运动的,那是因为屏幕以较高的帧数刷新的缘故,这样人眼就会看到连续的动作,就和电影的放映原理是一样的。在cocos2d-x 引擎中这些驱动屏幕刷新的代码在哪里呢?

我们回顾一下之前谈到的glthread线程,我们说过画面渲染的工作都是由它来完成的。glthread的核心是guardedrun函数,该 函数以“死循环”的方式调用cocos2dxrender.ondrawframe方法对画面进行持续渲染。

我们来看看引擎实现的cocos2dxrender.ondrawframe方法:

复制代码 代码如下:

public void ondrawframe(final gl10 gl) {
        /*
         * fps controlling algorithm is not accurate, and it will slow down fps
         * on some devices. so comment fps controlling code.
         */

        /*
        final long nowinnanoseconds = system.nanotime();
        final long interval = nowinnanoseconds – this.mlasttickinnanoseconds;
        */

        // should render a frame when ondrawframe() is called or there is a
        // "ghost"
        cocos2dxrenderer.nativerender();

        /*
        // fps controlling
        if (interval < cocos2dxrenderer.sanimationinterval) {
            try {
                // because we render it before, so we should sleep twice time interval
                thread.sleep((cocos2dxrenderer.sanimationinterval – interval) / cocos2dxrenderer.nanosecondspermicrosecond);
            } catch (final exception e) {
            }
        }

        this.mlasttickinnanoseconds = nowinnanoseconds;
        */
    }


这个方法实现得比较奇怪,似乎修改过多次,但最后还是决定只保留了一个方法调用: cocos2dxrenderer.nativerender()。从注释掉的代码来看,似乎是想在这个方法中通过thread.sleep来控制 render thread渲染的帧率。但由于控制的不理想,索性就不控制了,让guardedrun真正变成了dead loop。但从hellocpp sample运行时的状态显示,画面始终保持在60帧左右,让人十分诧异。据说cocos2d-x 3.0版本重新设计了渲染这块的机制。(后记:在android上虽然没有帧数控制,但真正的渲染帧率实际上还受到"垂直同步"信号 – vertical sync的影响。在游戏中,也许强劲的显卡迅速的绘制完一屏的图像,但是没有垂直同步信号的到达,显卡无法绘制下一屏,只有等vsync信号到达,才可以绘制。这样fps实际上要要受到操作系统刷新率值的制约)。

nativerender从命名来看,这显然是一个c++编写的函数实现。我们只能到jni目录下寻找。

复制代码 代码如下:

cocos2d-x-2.2.2/cocos2dx/platform/android/jni/ java_org_cocos2dx_lib_cocos2dxrenderer.cpp

    jniexport void jnicall java_org_cocos2dx_lib_cocos2dxrenderer_nativerender(jnienv* env) {
        cocos2d::ccdirector::shareddirector()->mainloop();
    }


nativerender也很简洁,直接调用了ccdirector的mainloop,也就是说每帧渲染过程中真正干活地是 ccdirector::mainloop。到此我们终于找到了引擎渲染的驱动器:glthead::guardedrun,以“死循环”的方式刷新着画面,让我们感受到“动”的魅力。

八、mainloop

进一步我们来看看mainloop所做的工作。mainloop是ccdirector类的一个纯虚函数,ccdirector的子类ccdisplaylinkdirector真正实现了 它:

复制代码 代码如下:

//ccdirector.cpp
void ccdisplaylinkdirector::mainloop(void)
{
    if (m_bpurgedirecotorinnextloop)
    {
        m_bpurgedirecotorinnextloop = false;
        purgedirector();
    }
    else if (! m_binvalid)
     {
         drawscene();

         // release the objects
         ccpoolmanager::sharedpoolmanager()->pop();
     }
}

void ccdirector::drawscene(void)
{
    // calculate "global" dt
    calculatedeltatime();

    //tick before glclear: issue #533
    if (! m_bpaused)
    {
        m_pscheduler->update(m_fdeltatime);
    }

    glclear(gl_color_buffer_bit | gl_depth_buffer_bit);

    /* to avoid flickr, nextscene must be here: after tick and before draw.
     xxx: which bug is this one. it seems that it can't be reproduced with v0.9 */
    if (m_pnextscene)
    {
        setnextscene();
    }

    kmglpushmatrix();

    // draw the scene
    if (m_prunningscene)
    {
        m_prunningscene->visit();
    }

    // draw the notifications node
    if (m_pnotificationnode)
    {
        m_pnotificationnode->visit();
    }

    if (m_bdisplaystats)
    {
        showstats();
    }

    kmglpopmatrix();

    m_utotalframes++;

    // swap buffers
    if (m_pobopenglview)
    {
        m_pobopenglview->swapbuffers();
    }

    if (m_bdisplaystats)
    {
        calculatempf();
    }
}


帧渲染由mainloop调用的drawscene()完成,drawscene方法根据scene下的渲染树,根据node的最新属性逐个渲染 node,并调整各个node的调度定时器数据,细节这里就不详细说明了。

九、ui线程与glthread的交互

用户的屏幕触控动作由ui线程捕捉到,该类事件需要传递给引擎,并由glthread根据各个画面元素的最新状态重新绘制画面。ui线程负责处理用户交互 事件,并将特定的事件通知glthread处理。ui线程通过cocos2dxglsurfaceview的queueevent方法,将事件以及处理方 法传递给glthread执行的。

cocos2dxglsurfaceview的queueevent方法继承自其父类glsurfaceview:

复制代码 代码如下:

    public void queueevent(runnable r) {
        mglthread.queueevent(r);
    }

而glthread的queueevent方法实现如下:
复制代码 代码如下:

public void queueevent(runnable r) {
    if (r == null) {
        throw new illegalargumentexception("r must not be null");
    } 
    synchronized(sglthreadmanager) {
        meventqueue.add(r);
        sglthreadmanager.notifyall();
    } 
}

该方法将event互斥地放入eventqueue,并通知阻塞在queue上的线程取货。

运行着的glthread实例在guardedrun中会从event队列中取出runnable event并run的。

复制代码 代码如下:
 
while (true) {
    synchronized (sglthreadmanager) {
        while (true) {
            if (mshouldexit) {
                return;
            } 

            if (! meventqueue.isempty()) {
                event = meventqueue.remove(0);
                break;
            } 
         …….
        } 
     } 

     … …
     if (event != null) {
        event.run();
        event = null;
        continue;
    } 
    …
}

activity的各种事件pause、resume、stop以及view的各种屏幕触控事件都是通过queueevent传递给glthread执行的,比如:view的onkeydown方法:

复制代码 代码如下:

    //cocos2dxglsurfaceview.java
    @override
    public boolean onkeydown(final int pkeycode, final keyevent pkeyevent) {
        switch (pkeycode) {
            case keyevent.keycode_back:
            case keyevent.keycode_menu:
                this.queueevent(new runnable() {
                    @override
                    public void run() {
                        cocos2dxglsurfaceview.this.mcocos2dxrenderer.handlekeydown(pkeycode);
                    }
                });
                return true;
            default:
                return super.onkeydown(pkeycode, pkeyevent);
        }
    }

十、小结

有了以上的对cocos2d-x引擎的理解后,再编写游戏代码就更加游刃有余了,至少出现问题时,我们知道应该在哪里查找了。就像对汽车的发动机了如指掌 后,一旦发生动力故障,我们基本知道排除的方法。但对发动机了解的再透彻,也不能代表就能设计和生产出好车,游戏也是这样,对引擎了解是一码事,设计和实现出好游戏是另外一码事。学习引擎只是编写游戏的起点而已。