Cocos2d-x⼊门教程(详细的实例和讲解)
智能终端上的游戏⽬前风头正劲,试问哪个智能⼿机上没有⼏款企鹅公司出品的游戏呢!之前从未涉猎过游戏开发,但知道游戏开发前要挑选⼀款合适的游戏引擎,⾃⼰从头开始敲代码的时代已经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/relea” 下执⾏“HelloCpp”这个可执⾏⽂件,⼀个最简单的Cocos2d-x游戏就会展现在你的⾯前了。
Android sample project的构建稍微复杂些:
⾸先在Eclip中添加libcocos2dx Library project from existed code(注意:不Copy到workspace,原地建⽴)。该Project的代码路径为cocos2d-x-2.2.2/cocos2dx/platform /android/java。在project.properties和l适当修改你所使⽤的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。在Eclip中添加HelloCpp project from existed code,位置cocos2d-x-
2.2.2/samples/Cpp/HelloCpp/proj.android(注意:不Copy到Workspace中,原地建⽴)。在HelloCpp的project.properties中添加“ference.1=../../../../cocos2dx/platform/android /java”。同样别忘了在project.properties和l适当修改你所使⽤的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.l.*;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 中的库)。
以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这样的游戏引擎,仅仅停留在游戏逻辑层代码是不能很好的把握引擎本质的,因此适当的挖掘引擎实现实际上对于理解和使⽤引擎都是⼤有裨益的。
以⼀个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/Class 你的游戏逻辑,以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平台)
后续的代码分析也将从这两个层次、六处位置出发。
之前多少了解了⼀些Android App开发的知识,Android App都是始于Activity的。游戏也是App的⼀种,因此在Android平台上,Cocos2d-x游戏也是从Activity开始的。于是 Activity,确切的说是Cocos2dxActivity是我们这次引擎驱动机制分析的出发点。
回顾Android Activity的Lifecycle,Activity启动的顺序是:Create -> Start() -> Resume()。接下来我们将按照这条主线进⾏引擎驱动机制的分析。
HelloCpp.java中的HelloCpp这个Activity完全⽆所作为,仅仅是继承其⽗类Cocos2dxActivity的实现罢了。
复制代码代码如下:
// HelloCpp.java
public class HelloCpp extends Cocos2dxActivity{
protected void onCreate(Bundle savedInstanceState){
炒菜用什么油好
}
… …
}
我们来看Cocos2dxActivity类。
复制代码代码如下:
// Cocos2dxActivity.java
@Override
protected void onCreate(final Bundle 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.tLayoutParams(framelayout_params);
… …
/
/ Cocos2dxGLSurfaceView
我把魔王变成妹了this.mGLSurfaceView = CreateView();
// …add to FrameLayout
framelayout.addView(this.mGLSurfaceView);
… …
this.mGLSurfaceView.tCocos2dxRenderer(new Cocos2dxRenderer());
… …
// Set framelayout as the content view
tContentView(framelayout);
}
从上⾯代码可以看出,onCreate调⽤的init⽅法才是Cocos2dxActivity初始化的核⼼。在init⽅法中,C
ocos2dxActivity创建了⼀个Framelayout实例,并将该实例作为content View赋给了Cocos2dxActivity的实例。Framelayout实例也并不孤单,⼀个设置了Cocos2dxRenderer实例的 GLSurfaceView被Added to it。⽽Cocos2d-x引擎的初始化已经悄悄地在这⼏⾏代码间完成了,⾄于初始化的细节我们后续再做分析。
接下来是onResume⽅法,它的实现如下:
@Override
protected void onResume() {
Resume();
}
onResume调⽤了View的onResume()。
复制代码代码如下:
// Cocos2dxGLSurfaceView:
@Override
public void 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 u SimpleAudioEngine, it must resume here
// SimpleAudioEngine::sharedEngine()->resumeBackgroundMusic();
}
这⾥仅是重新获得了⼀下时间罢了。
游戏引擎要兼顾UI事件和屏幕帧刷新。Android的OpenGL应⽤采⽤了UI线程(Main Thread) + 渲染线程(Render Thread)的模式。Activity活在Main Thread(主线程)中,也叫做UI线程。该线程负责捕获与⽤户交互的信息和事件,并与渲染(Render)线程交互。⽐如当⽤户接听电话、切换到其他程序时,渲
染线程必须知道发⽣了这些事件,并作出即时的处理,⽽这些事件及处理⽅式都是由主线程中的Activity以及其装载的View传递给渲染线程的。我们在Cocos2dx的框架代码中看不到渲染线程的诞⽣过程,这是因为这⼀过程是在Android SDK层实现的。
我们回顾⼀下Cocos2dxActivity.init⽅法的关键代码:
// Cocos2dxGLSurfaceView
this.mGLSurfaceView = CreateView();
// …add to FrameLayout
framelayout.addView(this.mGLSurfaceView);莫卧尔帝国
this.mGLSurfaceView.tCocos2dxRenderer(new Cocos2dxRenderer());
// Set framelayout as the content view
tContentView(framelayout);
Cocos2dxGLSurfaceView是 android.opengl.GLSurfaceView的⼦类。在android 上做原⽣opengl es 2.
0编程的⼈应该都清楚GLSurfaceView的重要性。但渲染线程并⾮是在Cocos2dxGLSurfaceView实例化时被创建的,⽽是在 tRenderer的时候。我们来看Cocos2dxGLSurfaceView.tCocos2dxRenderer的实现:
复制代码代码如下:
public void tCocos2dxRenderer(final Cocos2dxRenderer renderer) {
this.mCocos2dxRenderer = renderer;
this.tRenderer(this.mCocos2dxRenderer);
}
tRender是Cocos2dxGLSurfaceView⽗类GLSurfaceView实现的⽅法。在Android SDK GLSurfaceView.java⽂件中,我们看到:
复制代码代码如下:
public void tRenderer(Renderer renderer) {
牛肉配送checkRenderThreadState();
if (mEGLConfigChoor == null) {
mEGLConfigChoor = new SimpleEGLConfigChoor(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() {
tName("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);
}
}