cocos2dx简单入门实例

2015年01月14日 10:34 0 点赞 0 评论 更新于 2025-11-21 14:19

这篇文章主要介绍了Cocos2d-x入门实例,包含详细的实例、讲解以及实现过程,有需要的朋友可以参考。

目前,智能终端上的游戏发展势头迅猛,几乎每部智能手机上都有几款企鹅公司出品的游戏。我之前从未涉足过游戏开发,但了解到在进行游戏开发前,需要挑选一款合适的游戏引擎,从头开始编写代码的时代已经过去。在寻觅游戏引擎之前,我需要回答以下三个问题:

  1. 选择2D引擎还是3D引擎?
  2. 选择平台专用引擎还是跨平台引擎?
  3. 选择收费引擎还是开源引擎?

作为入门级开发者,2D游戏显然更容易上手,而且适合低年龄段的幼教类游戏大多也是2D游戏。3D游戏开发难度较大,不仅需要具备编程能力,还需要掌握3D建模技能,学习周期较长。我一直使用Ubuntu系统,没有Mac Book,在Ubuntu下搭建iOS App开发环境十分繁杂,即便使用虚拟机我也不想尝试。但从游戏体验来看,在iPad上玩游戏的效果更好,因此最好选择跨平台的引擎,以便后续将游戏迁移到iOS平台。我习惯使用开源软件,目前不考虑收费引擎。综合以上因素,我要寻找的是一款开源的、跨平台的移动2D游戏引擎。

于是,我找到了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的入门之旅(针对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游戏项目在构建时会自动使用NDK的编译器编译C++代码,并与NDK进行链接。若想提前查看Cocos2d-x示例的运行效果,可以在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示例项目的构建相对复杂,步骤如下:

  1. 添加库项目:在Eclipse中,从现有代码添加libcocos2dx库项目(注意:不要复制到工作区,原地建立)。该项目的代码路径为cocos2d-x-2.2.2/cocos2dx/platform/android/java。在project.propertiesAndroidManifest.xml文件中适当修改所使用的API版本,以确保编译通过,我这里使用的是target=android-19
  2. 设置环境变量:设置NDK_ROOT环境变量,例如export NDK_ROOT='/home1/tonybai/android-dev/adt-bundle-linux-x86_64/android-ndk-r9c',供build_native.sh脚本使用。
  3. 添加游戏项目:在Eclipse中,从现有代码添加HelloCpp项目,位置为cocos2d-x-2.2.2/samples/Cpp/HelloCpp/proj.android(注意:不要复制到工作区,原地建立)。在HelloCppproject.properties文件中添加android.library.reference.1=../../../../cocos2dx/platform/android/java。同样,别忘了在project.propertiesAndroidManifest.xml文件中适当修改所使用的API版本,以确保编译通过。

如果一切顺利,你会在控制台窗口看到“** Build Finished **”,问题窗口显示“0 errors”。启动Android模拟器,运行应用程序,同样的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的GLSurfaceViewGLThread,而真正的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库代码的构建脚本。

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/hellocppCocos2dxRenderer类的nativeInit实现,用于引出应用程序的入口。
  • cocos2d-x-2.2.2/samples/Cpp/HelloCpp/Classes:游戏逻辑,以C++代码形式呈现。

引擎层

  • cocos2d-x-2.2.2/cocos2dx/platform/android/java/src:引擎层对Android Activity、GLSurfaceView以及渲染的封装。
  • cocos2d-x-2.2.2/cocos2dx/platform/android/jni:对应上面封装的本地方法实现。
  • cocos2d-x-2.2.2/cocos2dxcocos2d-x-2.2.2/cocos2dx/platformcocos2d-x-2.2.2/cocos2dx/platform/android:cocos2dx引擎的核心实现(针对Android平台)。

后续的代码分析将从这两个层次、六个位置展开。

四、从Activity开始

我之前对Android App开发有一定了解,Android App都是从Activity开始的。游戏也是一种App,因此在Android平台上,Cocos2d-x游戏同样从Activity开始。所以,Activity,确切地说是Cocos2dxActivity,是我们分析引擎驱动机制的起点。

回顾Android Activity的生命周期,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实例,并将其作为内容视图赋值给Cocos2dxActivity实例。FrameLayout实例中添加了一个设置了Cocos2dxRenderer实例的GLSurfaceView。Cocos2d-x引擎的初始化在这几行代码中悄然完成,初始化的细节我们后续再进行分析。

接下来是onResume方法,其实现如下:

@Override
protected void onResume() {
super.onResume();
Cocos2dxHelper.onResume();
this.mGLSurfaceView.onResume();
}

onResume方法调用了视图的onResume()方法:

// Cocos2dxGLSurfaceView:
@Override
public void onResume() {
super.onResume();
this.queueEvent(new Runnable() {
@Override
public void run() {
Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleOnResume();
}
});
}

Cocos2dxGLSurfaceView将该事件打包放入队列,交给另一个线程处理(后续会详细介绍这个线程),对应的方法在Cocos2dxRenderer类中:

public void handleOnResume() {
Cocos2dxRenderer.nativeOnResume();
}

Render实际上调用的是本地方法:

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线程(主线程) + 渲染线程的模式。Activity运行在主线程中,也称为UI线程。该线程负责捕获用户交互信息和事件,并与渲染线程进行交互。例如,当用户接听电话、切换到其他程序时,渲染线程需要知道这些事件并及时处理,这些事件及处理方式由主线程中的Activity及其装载的视图传递给渲染线程。在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);

Cocos2dxGLSurfaceViewandroid.opengl.GLSurfaceView的子类。在Android上进行原生OpenGL ES 2.0编程的开发者应该清楚GLSurfaceView的重要性。但渲染线程并非在Cocos2dxGLSurfaceView实例化时创建,而是在设置渲染器时创建。

下面看Cocos2dxGLSurfaceView.setCocos2dxRenderer的实现:

public void setCocos2dxRenderer(final Cocos2dxRenderer renderer) {
this.mCocos2dxRenderer = renderer;
this.setRenderer(this.mCocos2dxRenderer);
}

setRendererCocos2dxGLSurfaceView父类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、渲染器的三个回调方法onSurfaceCreatedonSurfaceChanged以及onDrawFrame,后续我们将详细分析这三个函数。

六、游戏逻辑的入口

HelloCppClasses目录下有许多C++代码文件(涉及具体的游戏逻辑),在HelloCpp的Android项目jni目录下也有JNI胶水代码,那么这些代码是如何与引擎协同工作的呢?

前面提到,画面的渲染工作在GLThread中进行,涉及onSurfaceCreatedonSurfaceChanged以及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就是游戏逻辑的入口。我们需要回到示例代码目录中找到该方法的实现:

// 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场景的实例。但这仍然是初始化的一部分,尽管方法名听起来像是一个持续的操作:

// 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方法控制渲染线程的帧率,但由于控制效果不理想,最终放弃了帧率控制,让guardedRun方法变成了一个死循环。然而,从HelloCpp示例的运行状态来看,画面始终保持在60帧左右,这让人感到惊讶。据说Cocos2d-x 3.0版本重新设计了渲染机制。(后记:在Android上,虽然没有进行帧率控制,但实际的渲染帧率会受到“垂直同步”信号(vertical sync)的影响。在游戏中,即使显卡迅速绘制完一屏图像,如果没有垂直同步信号,显卡也无法绘制下一屏,只有等到vsync信号到达才能继续绘制。因此,帧率实际上受到操作系统刷新率的限制。)

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方法很简洁,直接调用了CCDirectormainLoop方法,也就是说,每帧渲染的实际工作由CCDirector::mainLoop完成。至此,我们找到了引擎渲染的驱动核心:GLThread::guardedRun,它以“死循环”的方式刷新画面,让我们看到了动态的效果。

八、mainLoop

接下来,我们进一步分析mainLoop方法的工作内容。mainLoopCCDirector类的一个纯虚函数,由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方法根据场景下的渲染树,按照节点的最新属性逐个渲染节点,并调整各个节点的调度定时器数据,具体细节这里不再详细说明。

九、UI线程与GLThread的交互

用户的屏幕触控动作由UI线程捕获,这些事件需要传递给引擎,并由GLThread根据各个画面元素的最新状态重新绘制画面。UI线程负责处理用户交互事件,并将特定事件通知GLThread处理。UI线程通过Cocos2dxGLSurfaceViewqueueEvent方法,将事件和处理方法传递给GLThread执行。

Cocos2dxGLSurfaceViewqueueEvent方法继承自其父类GLSurfaceView

public void queueEvent(Runnable r) {
mGLThread.queueEvent(r);
}

GLThreadqueueEvent方法实现如下:

public void queueEvent(Runnable r) {
if (r == null) {
throw new IllegalArgumentException("r must not be null");
}
synchronized(sGLThreadManager) {
mEventQueue.add(r);
sGLThreadManager.notifyAll();
}
}

该方法将事件互斥地放入事件队列,并通知阻塞在队列上的线程处理。

运行中的GLThread实例在guardedRun方法中会从事件队列中取出可运行的事件并执行:

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;
}
…
}