Cocos2d-x入門教程(詳細(xì)的實(shí)例和講解)
智能終端上的游戲目前風(fēng)頭正勁,試問哪個(gè)智能手機(jī)上沒有幾款企鵝公司出品的游戲呢!之前從未涉獵過游戲開發(fā),但知道游戲開發(fā)前要挑選一款合適的游戲引擎,自己從頭開始敲代碼的時(shí)代已經(jīng)out了。在尋覓游戲引擎之前,我需要回答三道擺在我面前的選擇題:
1、2D引擎還是3D引擎?
2、平臺(tái)專用引擎還是跨平臺(tái)引擎?
3、收費(fèi)引擎還是開源引擎?
作為入門級(jí)選手,2D游戲顯然更適合上手一些,另外適合果果這個(gè)年齡段的幼教類的游戲也多以2D游戲居多。3D游戲本身也太難了,不僅要 Programming能力,還要3D建模能力,這些學(xué)習(xí)起來周期就太長了;一直是Ubuntu Fans,手頭沒有Mac Book,這樣開發(fā)iOS程序變成一件糟心的事,在Ubuntu下搭建iOS App開發(fā)環(huán)境繁雜的很,即便是虛擬機(jī)也懶得嘗試。但從游戲體驗(yàn)來看,還是在iPad上玩更好一些,因此最好引擎能跨平臺(tái),以便后續(xù)遷移到iOS上;開源 和用開源慣了,收費(fèi)的引擎目前不在考慮范圍之內(nèi)。綜上,我要尋找的是一款開源的、跨平臺(tái)的Mobile 2D Game Engine。
于是我找到了Cocos2d-x!Cocos2d-x是Cocos2d-iphone的C++跨平臺(tái)分支,由于是國人創(chuàng)立的,在國內(nèi)有著較大的用 戶群,引擎資料也較多,社區(qū)十分活躍。國內(nèi)已經(jīng)出版了多本有關(guān)Cocos2d-x的中文書籍,比如《Cocos2d-x高級(jí)開發(fā)教程:制作自己的 “捕魚達(dá)人”》 、《Cocos2d-x權(quán)威指南》 等都還不錯(cuò)。更重要的是Cocos2d-x自帶了豐富的例子,供初學(xué)者“臨摹學(xué)習(xí)”,其中cocos2d-x-2.2.2/samples/Cpp /TestCpp這個(gè)例子幾乎涵蓋了該引擎的絕大多數(shù)功能。下面就開啟Cocos2d-x的入門之旅(For Android)。
一、引擎安裝
試驗(yàn)環(huán)境:
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官網(wǎng)目前提供2.2.2穩(wěn)定版以及3.0beta2版的下載(當(dāng)然你也可以下載到更老的版本)。由于3.0改變較大,資料不 多,且對(duì)編譯器等版本的要求較高(需要支持C++ 11標(biāo)準(zhǔn)),因此這里依舊以2.2.2版本作為學(xué)習(xí)目標(biāo)。Cocos2d-x-2.2.2下載后解壓到某個(gè)目錄:比如/home1/tonybai/android-dev/cocos2d-x-2.2.2。 如果僅是用Cocos2d-x開發(fā)Android版本游戲,則不需要做什么編譯工作。Android Game Project會(huì)在Project build時(shí)自動(dòng)用NDK的編譯器編譯C++代碼,并與NDK鏈接。如果你想早點(diǎn)看看Cocos2d-x sample中的例子運(yùn)行起來到底是什么樣子的,你可以在Ubuntu下編譯出Linux版本的游戲:在cocos2d-x-2.2.2下執(zhí)行make-all-linux-project.sh即可。編譯需要一段時(shí)間,編譯成 功后,我們可以進(jìn)入到“cocos2d-x-2.2.2/samples/Cpp/HelloCpp/proj.linux/bin/release” 下執(zhí)行“HelloCpp”這個(gè)可執(zhí)行文件,一個(gè)最簡單的Cocos2d-x游戲就會(huì)展現(xiàn)在你的面前了。
Android sample project的構(gòu)建稍微復(fù)雜些:
首先在Eclipse中添加libcocos2dx Library project from existed code(注意:不Copy到workspace,原地建立)。該P(yáng)roject的代碼路徑為cocos2d-x-2.2.2/cocos2dx/platform /android/java。在project.properties和AndroidManifest.xml適當(dāng)修改你所使用的api版本, 以讓編譯通過。我這里用的是 target=android-19。
然后,設(shè)置NDK_ROOT環(huán)境變量(比如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適當(dāng)修改你所使用 的api版本,以讓編譯通過。
如果一切順利的話,你會(huì)在Console窗口看到“**** Build Finished ****”。Problems窗口顯示“0 errors“。 啟動(dòng)Android模擬器,Run Application,同樣的HelloCpp畫面會(huì)呈現(xiàn)在模擬器上。
Cocos2d-x是建構(gòu)在OpenGL技術(shù)之上的。對(duì)于Android平臺(tái)而言,Android SDK已經(jīng)完全封裝了opengl es 1.1/2.0的API(android.opengl.*;javax.microedition.khronos.egl.*;javax.microedition.khronos.opengles.*), 引擎完全可以建立在這個(gè)之上,無需C++代碼。但Cocos2d-x是一個(gè)跨平臺(tái)的2D游戲引擎,核心選擇了用C++代碼實(shí)現(xiàn)(iOS提供的C綁 定,不提供Java綁定;Android則提供了Java和C綁定),因此 在開發(fā)Android平臺(tái)的2D游戲時(shí),引擎部分是SDK與NDK交相互應(yīng),比如GLThread的創(chuàng)建和管理用的是SDK的 GLSurfaceView和GLThread,但真正的Surface繪制部分則是回調(diào)Cocos2d-x用C++編寫的繪制實(shí)現(xiàn)(鏈接NDK 中的庫)。
二、Cocos2d-x Android工程代碼組織結(jié)構(gòu)
以samples/Cpp/HelloApp的Android工程為例,Android版的Cocos2d-x工程與普通android應(yīng)用程序 差別 不大,核心部分只是多了一個(gè)jni目錄和一個(gè)build_native.sh腳本文件。其中jni目錄下存放的是Java和C++調(diào)用轉(zhuǎn)換的“膠 水”代碼;build_native.sh則是用于編譯jni下C++代碼以及 cocos2dx_static library代碼的構(gòu)建腳本。
HelloCpp的構(gòu)建過程摘要如下:
**** 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工程代碼閱讀
單獨(dú)將如何閱讀代碼拿出來,是為了后面分析引擎的驅(qū)動(dòng)流程做準(zhǔn)備工作。學(xué)習(xí)類似Cocos2d-x這樣的游戲引擎,僅僅停留在游戲邏輯層代碼是不 能很好的把握引擎本質(zhì)的,因此適當(dāng)?shù)耐诰蛞鎸?shí)現(xiàn)實(shí)際上對(duì)于理解和使用 引擎都是大有裨益的。
以一個(gè)Cocos2d-x Android工程為例,它的游戲邏輯代碼以及涉及的引擎代碼涵蓋在一下路徑下(還是以HelloCpp的Android工程為例):
項(xiàng)目層:
* cocos2d-x-2.2.2/samples/Cpp/HelloCpp/proj.android/src 主Activity的實(shí)現(xiàn);
* cocos2d-x-2.2.2/samples/Cpp/HelloCpp/proj.android/jni/hellocpp Cocos2dxRenderer類的nativeInit實(shí)現(xiàn),用于引出Application的入口;
* cocos2d-x-2.2.2/samples/Cpp/HelloCpp/Classes 你的游戲邏輯,以C++代碼形式呈現(xiàn);
引擎層:
* cocos2d-x-2.2.2/cocos2dx/platform/android/java/src 引擎層對(duì)Android Activity、GLSurfaceView以及Render的封裝
* cocos2d-x-2.2.2/cocos2dx/platform/android/jni 對(duì)應(yīng)上面封裝的native method實(shí)現(xiàn)
* cocos2d-x-2.2.2/cocos2dx、cocos2d-x-2.2.2/cocos2dx/platform、cocos2d-x- 2.2.2/cocos2dx/platform/android cocos2dx引擎的核心實(shí)現(xiàn)(針對(duì)android平臺(tái))
后續(xù)的代碼分析也將從這兩個(gè)層次、六處位置出發(fā)。
四、從Activity開始
之前多少了解了一些Android App開發(fā)的知識(shí),Android App都是始于Activity的。游戲也是App的一種,因此在Android平臺(tái)上,Cocos2d-x游戲也是從Activity開始的。于是 Activity,確切的說是Cocos2dxActivity是我們這次引擎驅(qū)動(dòng)機(jī)制分析的出發(fā)點(diǎn)。
回顧Android Activity的Lifecycle,Activity啟動(dòng)的順序是:Activity.onCreate -> Activity.onStart() -> Activity.onResume()。接下來我們將按照 這條主線進(jìn)行引擎驅(qū)動(dòng)機(jī)制的分析。
HelloCpp.java中的HelloCpp這個(gè)Activity完全無所作為,僅僅是繼承其父類Cocos2dxActivity的實(shí)現(xiàn)罷 了。
// 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調(diào)用的init方法才是Cocos2dxActivity初始化的核心。在init方法 中,Cocos2dxActivity創(chuàng)建了一個(gè)Framelayout實(shí)例,并將該實(shí)例作為content View賦給了Cocos2dxActivity的實(shí)例。Framelayout實(shí)例也并不孤單,一個(gè)設(shè)置了Cocos2dxRenderer實(shí)例的 GLSurfaceView被Added to it。而Cocos2d-x引擎的初始化已經(jīng)悄悄地在這幾行代碼間完成了,至于初始化的細(xì)節(jié)我們后續(xù)再做分析。
接下來是onResume方法,它的實(shí)現(xiàn)如下:
@Override
protected void onResume() {
super.onResume();
Cocos2dxHelper.onResume();
this.mGLSurfaceView.onResume();
}
onResume調(diào)用了View的onResume()。
// Cocos2dxGLSurfaceView:
@Override
public void onResume() {
super.onResume();
this.queueEvent(new Runnable() {
@Override
public void run() {
Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleOnResume();
}
});
}
Cocos2dxGLSurfaceView將該事件打包放到隊(duì)列里,扔給了另外一個(gè)線程去執(zhí)行(后續(xù)會(huì)詳細(xì)說明這個(gè)線程),對(duì)應(yīng)的方法在 Cocos2dxRenderer class中。
public void handleOnResume() {
Cocos2dxRenderer.nativeOnResume();
}
Render實(shí)際上調(diào)用的是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();
}
這里僅是重新獲得了一下時(shí)間罷了。
五、Render Thread(渲染線程) - GLThread
游戲引擎要兼顧UI事件和屏幕幀刷新。Android的OpenGL應(yīng)用采用了UI線程(Main Thread) + 渲染線程(Render Thread)的模式。Activity活在Main Thread(主線程)中,也叫做UI線程。該線程負(fù)責(zé)捕獲與用戶交互的信息和事件,并與渲染(Render)線程交互。比如當(dāng)用戶接聽電話、切換到其他 程序時(shí),渲染線程必須知道發(fā)生了 這些事件,并作出即時(shí)的處理,而這些事件及處理方式都是由主線程中的Activity以及其裝載的View傳遞給渲染線程的。我們?cè)贑ocos2dx的框 架代碼中看不到渲染線程的誕生過程,這是因?yàn)檫@一過程是在Android SDK層實(shí)現(xiàn)的。
我們回顧一下Cocos2dxActivity.init方法的關(guān)鍵代碼:
// 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編程的人應(yīng)該都清楚GLSurfaceView的重要性。但渲染線程并非是在Cocos2dxGLSurfaceView實(shí)例化時(shí)被創(chuàng)建的,而是在 setRenderer的時(shí)候。
我們來看Cocos2dxGLSurfaceView.setCocos2dxRenderer的實(shí)現(xiàn):
public void setCocos2dxRenderer(final Cocos2dxRenderer renderer) {
this.mCocos2dxRenderer = renderer;
this.setRenderer(this.mCocos2dxRenderer);
}
setRender是Cocos2dxGLSurfaceView父類GLSurfaceView實(shí)現(xiàn)的方法。在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的實(shí)例是在這里被創(chuàng)建并開始執(zhí)行的。至于渲染線程都干了些什么,我們可以通過其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方法并沒有給我們帶來太多有價(jià)值的東西,真正有價(jià)值的信息藏在guardedRun方法中。guardedRun是這個(gè)源文件中規(guī)模最為龐 大的方法,但抽取其核心結(jié)構(gòu)后,我們發(fā)現(xiàn)它大致就是一個(gè)死循環(huán),以下是摘要式的偽代碼:
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的三個(gè)回調(diào)方法onSurfaceCreated、onSurfaceChanged以及 onDrawFrame,后續(xù)我們會(huì)對(duì)這三個(gè)函數(shù)做詳細(xì)分析的。
六、游戲邏輯的入口
在HelloCpp的Classes下有好多C++代碼文件(涉及具體的游戲邏輯),在HelloCpp的android project jni目錄下也有Jni膠水代碼,那么這些代碼是如何和引擎一起互動(dòng)生效的呢?
上面講到過,涉及到畫面的一些渲染都是在GLThread中進(jìn)行的,這涉及到onSurfaceCreated、 onSurfaceChanged以及onDrawFrame三個(gè)方法。我們看看 Cocos2dxRenderer.onSurfaceCreated方法的實(shí)現(xiàn),該方法會(huì)在Surface被首次渲染時(shí)調(diào)用:
public void onSurfaceCreated(final GL10 pGL10, final EGLConfig pEGLConfig) {
Cocos2dxRenderer.nativeInit(this.mScreenWidth, this.mScreenHeight);
this.mLastTickInNanoSeconds = System.nanoTime();
}
該方法繼續(xù)調(diào)用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();
繼續(xù)追蹤C(jī)CApplication::run方法:
int CCApplication::run()
{
// Initialize instance and cocos2d.
if (! applicationDidFinishLaunching())
{
return 0;
}
return -1;
}
applicationDidFinishLaunching,沒錯(cuò)這就是游戲邏輯的入口了。我們得回到Samples代碼目錄中去找到對(duì)應(yīng)方法 的實(shí)現(xiàn)。
//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中我們做了很多引擎參 數(shù)的設(shè)置。接下來大管家CCDirector實(shí)例登場,并運(yùn)行了HelloWorld Scene的實(shí)例。但這依舊是初始化的一部分,雖然方法名讓人聽起來像是某種持續(xù)連貫行為:
//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;
}
兩個(gè)方法均只是初始化了某些數(shù)據(jù)成員變量,并未真正將引擎驅(qū)動(dòng)起來。
七、驅(qū)動(dòng)引擎
之所以游戲畫面是運(yùn)動(dòng)的,那是因?yàn)槠聊灰暂^高的幀數(shù)刷新的緣故,這樣人眼就會(huì)看到連續(xù)的動(dòng)作,就和電影的放映原理是一樣的。在Cocos2d-x 引擎中這些驅(qū)動(dòng)屏幕刷新的代碼在哪里呢?
我們回顧一下之前談到的GLThread線程,我們說過畫面渲染的工作都是由它來完成的。GLThread的核心是guardedRun函數(shù),該 函數(shù)以“死循環(huán)”的方式調(diào)用Cocos2dxRender.onDrawFrame方法對(duì)畫面進(jìn)行持續(xù)渲染。
我們來看看引擎實(shí)現(xiàn)的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;
*/
}
這個(gè)方法實(shí)現(xiàn)得比較奇怪,似乎修改過多次,但最后還是決定只保留了一個(gè)方法調(diào)用: Cocos2dxRenderer.nativeRender()。從注釋掉的代碼來看,似乎是想在這個(gè)方法中通過Thread.sleep來控制 Render Thread渲染的幀率。但由于控制的不理想,索性就不控制了,讓guardedRun真正變成了dead loop。但從HelloCpp Sample運(yùn)行時(shí)的狀態(tài)顯示,畫面始終保持在60幀左右,讓人十分詫異。據(jù)說Cocos2d-x 3.0版本重新設(shè)計(jì)了渲染這塊的機(jī)制。(后記:在Android上雖然沒有幀數(shù)控制,但真正的渲染幀率實(shí)際上還受到"垂直同步"信號(hào) – vertical sync的影響。在游戲中,也許強(qiáng)勁的顯卡迅速的繪制完一屏的圖像,但是沒有垂直同步信號(hào)的到達(dá),顯卡無法繪制下一屏,只有等vsync信號(hào)到達(dá),才可以繪制。這樣fps實(shí)際上要要受到操作系統(tǒng)刷新率值的制約)。
nativeRender從命名來看,這顯然是一個(gè)C++編寫的函數(shù)實(shí)現(xiàn)。我們只能到j(luò)ni目錄下尋找。
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也很簡潔,直接調(diào)用了CCDirector的mainLoop,也就是說每幀渲染過程中真正干活地是 CCDirector::mainLoop。到此我們終于找到了引擎渲染的驅(qū)動(dòng)器:GLThead::guardedRun,以“死循環(huán)”的方式刷新著畫面,讓我們感受到“動(dòng)”的魅力。
八、mainLoop
進(jìn)一步我們來看看mainLoop所做的工作。mainLoop是CCDirector類的一個(gè)純虛函數(shù),CCDirector的子類CCDisplayLinkDirector真正實(shí)現(xiàn)了 它:
//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調(diào)用的drawScene()完成,drawScene方法根據(jù)Scene下的渲染樹,根據(jù)node的最新屬性逐個(gè)渲染 node,并調(diào)整各個(gè)Node的調(diào)度定時(shí)器數(shù)據(jù),細(xì)節(jié)這里就不詳細(xì)說明了。
九、UI線程與GLThread的交互
用戶的屏幕觸控動(dòng)作由UI線程捕捉到,該類事件需要傳遞給引擎,并由GLThread根據(jù)各個(gè)畫面元素的最新狀態(tài)重新繪制畫面。UI線程負(fù)責(zé)處理用戶交互 事件,并將特定的事件通知GLThread處理。UI線程通過Cocos2dxGLSurfaceView的queueEvent方法,將事件以及處理方 法傳遞給GLThread執(zhí)行的。
Cocos2dxGLSurfaceView的queueEvent方法繼承自其父類GLSurfaceView:
public void queueEvent(Runnable r) {
mGLThread.queueEvent(r);
}
而GLThread的queueEvent方法實(shí)現(xiàn)如下:
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上的線程取貨。
運(yùn)行著的GLThread實(shí)例在guardedRun中會(huì)從event隊(duì)列中取出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執(zhí)行的,比如: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);
}
}
十、小結(jié)
有了以上的對(duì)Cocos2d-x引擎的理解后,再編寫游戲代碼就更加游刃有余了,至少出現(xiàn)問題時(shí),我們知道應(yīng)該在哪里查找了。就像對(duì)汽車的發(fā)動(dòng)機(jī)了如指掌 后,一旦發(fā)生動(dòng)力故障,我們基本知道排除的方法。但對(duì)發(fā)動(dòng)機(jī)了解的再透徹,也不能代表就能設(shè)計(jì)和生產(chǎn)出好車,游戲也是這樣,對(duì)引擎了解是一碼事,設(shè)計(jì)和實(shí)現(xiàn)出好游戲是另外一碼事。學(xué)習(xí)引擎只是編寫游戲的起點(diǎn)而已。
- CocosCreator入門教程之網(wǎng)絡(luò)通信
- Cocos2d-x 3.x入門教程(二):Node節(jié)點(diǎn)類
- Cocos2d-x 3.x入門教程(一):基礎(chǔ)概念
- 詳解CocosCreator制作射擊游戲
- 如何在CocosCreator里畫個(gè)炫酷的雷達(dá)圖
- 詳解CocosCreator MVC架構(gòu)
- 詳解CocosCreator消息分發(fā)機(jī)制
- 如何用CocosCreator制作微信小游戲
- 詳解CocosCreator系統(tǒng)事件是怎么產(chǎn)生及觸發(fā)的
- 怎樣在CocosCreator中使用游戲手柄
- 詳解CocosCreator華容道數(shù)字拼盤
- CocosCreator入門教程之用TS制作第一個(gè)游戲
相關(guān)文章
Android自定義View實(shí)現(xiàn)箭頭沿圓轉(zhuǎn)動(dòng)實(shí)例代碼
這篇文章主要介紹了Android自定義View實(shí)現(xiàn)箭頭沿圓轉(zhuǎn)動(dòng)實(shí)例代碼,需要的朋友可以參考下2017-09-09flutter InheritedWidget使用方法總結(jié)
這篇文章主要為大家介紹了flutter InheritedWidget使用方法總結(jié)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11Android線程中設(shè)置控件的值提示報(bào)錯(cuò)的解決方法
這篇文章主要介紹了Android線程中設(shè)置控件的值提示報(bào)錯(cuò)的解決方法,實(shí)例分析了textview報(bào)錯(cuò)的原因以及Handler設(shè)置來解決錯(cuò)誤的實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-06-06Android編程實(shí)現(xiàn)監(jiān)控apk安裝,卸載,替換的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)監(jiān)控apk安裝,卸載,替換的方法,涉及Android基于Intent監(jiān)控apk狀態(tài)的功能實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-01-01Jetpack?Compose?的新型架構(gòu)?MVI使用詳解
這篇文章主要介紹了Jetpack?Compose?的新型架構(gòu)?MVI使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09深入解讀Android開發(fā)中Activity的生命周期
這篇文章主要介紹了Android開發(fā)中Activity的生命周期,包括Activity的停止和銷毀等重要內(nèi)容,非常推薦!需要的朋友可以參考下2015-12-12android 判斷網(wǎng)絡(luò)是否可用與連接的網(wǎng)絡(luò)是否能上網(wǎng)
下面小編就為大家分享一篇android 判斷網(wǎng)絡(luò)是否可用與連接的網(wǎng)絡(luò)是否能上網(wǎng),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-01-01flutter 微信聊天輸入框功能實(shí)現(xiàn)
這篇文章主要介紹了flutter 微信聊天輸入框功能實(shí)現(xiàn),本文通過實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03