cocos2d-x在Android的运行流程始末
2014-05-16 20:34
267 查看
由于Android的应用层是从Activity开始的,也就是创建完一个Cocos2dx后src文件夹下的Java文件。其中主要看Activity创建时的操作:
这个方法很简单,就是调用父类的onCreate方法,也就是,这个自定义的Activity不做什么,把所有的动作都交给了它的父类Cocos2dxActivity去操作
首先在init中先看this.mGLSurfaceView = this.onCreateView(); this.mGLSurfaceView是一个Cocos2dxGLSurfaceView类。在进入到Cocos2dxGLSurfaceView这个类中可以看到时继承于GLSurfaceView(可以把GLSurfaceView看成一个视图,里面有个方法设置了这个视图的渲染器,然后通过这个渲染器来进行画面的渲染)。
在Android中,GLSurfaceView是一个支持OpenGL的渲染视图,通过继承SurfaceView中的surface来渲染OpenGL
并提供了以下特性(来自于网上)
1> 管理一个surface,这个surface就是一块特殊的内存,能直接排版到android的视图view上。
2> 管理一个EGL display,它能让opengl把内容渲染到上述的surface上。
3> 用户自定义渲染器(render)。
4> 让渲染器在独立的线程里运作,和UI线程分离。
5> 支持按需渲染(on-demand)和连续渲染(continuous)。
6> 一些可选工具,如调试。
重点是自定义的渲染器,也就是Cocos2dx引擎封装的渲染器。进入Cocos2dxRenderer类,看到他是继承GLSurfaceView.Renderer接口,这个接口定义了三个方法:
onSurfaceCreated: 创建GLSurfaceView时被调用,只调用一次,做初始化工作
onSurfaceChanged: 当GLSurfaceView的几何体被改变时被调用
onDrawFrame: 绘制渲染GLSurfaceView
1. onSurfaceCreated:
接下来的两句是是设置窗口大小。而AppDelegate才是入口的重点。
执行完上面的步骤后开始初始化App然后初始化AppDelegate,AppDelegate的初始化过程也很简单。就实现了一个获取AppDelegate的单例。
AppDelegate的构造方法是个空方法
创建完AppDelegate后开始run
在CCDirector的init方法中可以找到m_pobScenesStack的创建。
由于调用了CCArray::init方法,所以只初始了存放一个场景的CCArray类
这里看下CCArray的实现过程
在初始化这个CCArray时,先会清空这个CCArray的所有对象,然后创建ccArray这个结构体。
(
其实CCArray中的数据便是这个ccArray结构体中的一个指向CCObject*的数组
)
calloc在动态分配完内存后,自动初始化该内存空间为零,而malloc不初始化,里边数据是随机的垃圾数据(注意:由于CCArray存放是CCObject指针,在调用calloc时会连续分配长度为sizeof(CCObject*)的内存大小)
最后把当前的场景存到m_pobScenesStack中,最后让m_pNextScene指向该场景
由此可以看到切换场景时不推荐使用pushScene,因为pushScene会不断的把新的场景加到m_pobScenesStack中,而不释放上一个场景的内存空间,导致内存会急剧增长,如果创建的场景很多的话。所以如果要使用pushScene一般的做法便是在创建完一个场景后插入到m_pobScenesStack的头部,然后调用popScene回收上一个场景的内存。(这样的做法会破坏掉栈的先进后出的原则,是不推荐的做法,因为popScene本来就是一个模拟出栈的方法)
再看下replaceScene
先不扯那么多,执行完场景压栈后会调用startAnimation,这个从这个方法开始便会开始又切换到Java应用中
这样Cocos2dx的GLSurfaceView的创建边执行完毕了。(在创建过程并没有执行Cocos2dx的主循环)
2. onSurfaceChanged:
第二个方法是空实现,略过
3. onDrawFrame:
第三个方法是绘制方法。
这里借鉴上网络上的一段关于Android的渲染逻辑。
游戏引擎要兼顾UI事件和屏幕帧刷新。Android的OpenGL应用采用了UI线程(Main Thread) + 渲染线程(Render Thread)的模式。Activity活在Main Thread(主线程)中,也叫做UI线程。该线程负责捕获与用户交互的信息和事件,并与渲染(Render)线程交互。比如当用户接听电话、切换到其他 程序时,渲染线程必须知道发生了 这些事件,并作出即时的处理,而这些事件及处理方式都是由主线程中的Activity以及其装载的View传递给渲染线程的。
上面的那段话说明Android的渲染便是不断执行Cocos2dx的主函数,因为屏幕每时每刻都会在渲染,每渲染一次调用一次Cocos2dx主函数来绘制游戏的画面。
接下俩看下游戏的结束过程。
Cocos2dx的结束控制是放在主循环中判断:
接下来便是purgeDirector方法:
其中主要看m_pobOpenGLView->end()的实现。这个方法每个平台都有自己不同的实现方式,这里选择Android平台。
至此,整个Cocos2dx游戏在Android的运行始末便结束了。
protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); }
这个方法很简单,就是调用父类的onCreate方法,也就是,这个自定义的Activity不做什么,把所有的动作都交给了它的父类Cocos2dxActivity去操作
@Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); sContext = this; this.mHandler = new Cocos2dxHandler(this); this.init(); Cocos2dxHelper.init(this, this); }在这个方法中主要看Cocos2dxActivity的初始化方法init,Cocos2dxHandler是工具辅助类,不是重点。
public void init() { /* Adnroid窗口布局参数的作用(从网上获取) 1)fill_parent 设置一个构件的布局为fill_parent将强制性地使构件扩展,以填充布局单元内尽可能多的空间。这跟Windows控件的dockstyle属性大体一致。设置一个顶部布局或控件为fill_parent将强制性让它布满整个屏幕。 2) wrap_content 设置一个视图的尺寸为wrap_content将强制性地使视图扩展以显示全部内容。以TextView和ImageView控件为例,设置为wrap_content将完整显示其内部的文本和图像。布局元素将根据内容更改大小。设置一个视图的尺寸为wrap_content大体等同于设置Windows控件的Autosize属性为True。 3)match_parent Android2.2中match_parent和fill_parent是一个意思 .两个参数意思一样,match_parent更贴切,于是从2.2开始两个词都可以用。那么如果考虑低版本的使用情况你就需要用fill_parent了 */ //初始化窗口布局 ViewGroup.LayoutParams framelayout_params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT); FrameLayout framelayout = new FrameLayout(this); framelayout.setLayoutParams(framelayout_params); //初始化Cocos2dx的文本编辑布局 ViewGroup.LayoutParams edittext_layout_params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); Cocos2dxEditText edittext = new Cocos2dxEditText(this); edittext.setLayoutParams(edittext_layout_params); framelayout.addView(edittext); //初始化Cocos2dx视图 this.mGLSurfaceView = this.onCreateView(); //把Cocos2dxGLSurfaceView加入到当前的窗口布局中 framelayout.addView(this.mGLSurfaceView); // Switch to supported OpenGL (ARGB888) mode on emulator //在模拟器中切换支持OpenGL模式的渲染(ARGB888) if (isAndroidEmulator()) this.mGLSurfaceView.setEGLConfigChooser(8 , 8, 8, 8, 16, 0); //设置Cocos2dx的渲染器 this.mGLSurfaceView.setCocos2dxRenderer(new Cocos2dxRenderer()); //设置Cocos2dx的文本编辑 this.mGLSurfaceView.setCocos2dxEditText(edittext); //把显示布局(即Cocos2dx的视图)绑定到Activity上,建立显示窗口 setContentView(framelayout); }
首先在init中先看this.mGLSurfaceView = this.onCreateView(); this.mGLSurfaceView是一个Cocos2dxGLSurfaceView类。在进入到Cocos2dxGLSurfaceView这个类中可以看到时继承于GLSurfaceView(可以把GLSurfaceView看成一个视图,里面有个方法设置了这个视图的渲染器,然后通过这个渲染器来进行画面的渲染)。
在Android中,GLSurfaceView是一个支持OpenGL的渲染视图,通过继承SurfaceView中的surface来渲染OpenGL
并提供了以下特性(来自于网上)
1> 管理一个surface,这个surface就是一块特殊的内存,能直接排版到android的视图view上。
2> 管理一个EGL display,它能让opengl把内容渲染到上述的surface上。
3> 用户自定义渲染器(render)。
4> 让渲染器在独立的线程里运作,和UI线程分离。
5> 支持按需渲染(on-demand)和连续渲染(continuous)。
6> 一些可选工具,如调试。
重点是自定义的渲染器,也就是Cocos2dx引擎封装的渲染器。进入Cocos2dxRenderer类,看到他是继承GLSurfaceView.Renderer接口,这个接口定义了三个方法:
onSurfaceCreated: 创建GLSurfaceView时被调用,只调用一次,做初始化工作
onSurfaceChanged: 当GLSurfaceView的几何体被改变时被调用
onDrawFrame: 绘制渲染GLSurfaceView
1. onSurfaceCreated:
@Override public void onSurfaceCreated(final GL10 pGL10, final EGLConfig pEGLConfig) { Cocos2dxRenderer.nativeInit(this.mScreenWidth, this.mScreenHeight); this.mLastTickInNanoSeconds = System.nanoTime(); }
private static native void nativeInit(final int pWidth, final int pHeight);这是一个用JNI调用了C++中的方法(这个C++函数在jni/hellocpp/main.cpp中),如下
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(); } }首先这个方法会判断OpenGLView是否初始化
inline CCEGLView* getOpenGLView(void) { return m_pobOpenGLView; }进入到CCDirector的构造函数中和init方法中。可以在init方法中找到m_pobOpenGLView = NULL;
接下来的两句是是设置窗口大小。而AppDelegate才是入口的重点。
执行完上面的步骤后开始初始化App然后初始化AppDelegate,AppDelegate的初始化过程也很简单。就实现了一个获取AppDelegate的单例。
AppDelegate的构造方法是个空方法
AppDelegate::AppDelegate() { }而AppDelegate的父类是CCApplication类。
class AppDelegate : private cocos2d::CCApplication进入到CCApplication的构造函数中(由于不同的平台CCApplication有着不同的实现类,这里要选中Android的实现类)
// sharedApplication pointer CCApplication * CCApplication::sm_pSharedApplication = 0; CCApplication::CCApplication() { CCAssert(! sm_pSharedApplication, ""); sm_pSharedApplication = this; }可以看到是CCApplication是一个单例,里面也没做多少动作。
创建完AppDelegate后开始run
int CCApplication::run() { // Initialize instance and cocos2d. if (! applicationDidFinishLaunching()) { return 0; } return -1; }可以看到里面调用了applicationDidFinishLaunching方法,这个方法被AppDelegate重写了,也就是在这里通过多态调用了AppDelegate的applicationDidFinishLaunching方法,然后就可以看到是cocos2dx的开发者很熟悉的Cocos2dx游戏的入口函数了:
bool AppDelegate::applicationDidFinishLaunching() { // initialize director CCDirector* pDirector = CCDirector::sharedDirector(); CCEGLView* pEGLView = CCEGLView::sharedOpenGLView(); pDirector->setOpenGLView(pEGLView); // 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; }第一步是在导演类中是在OpenGLView,这样在便能使CCDirector::sharedDirector()->getOpenGLView()返回true。然后又设置了是否显示FPS,帧率,并创建了一个场景。并把这个场景当初是游戏的初始场景,压入栈中(由一个CCArray来维护场景),最后运行该场景,整个游戏便开始由这个场景开始启动。
void CCDirector::runWithScene(CCScene *pScene) { CCAssert(pScene != NULL, "This command can only be used to start the CCDirector. There is already a scene present."); CCAssert(m_pRunningScene == NULL, "m_pRunningScene should be null"); pushScene(pScene); startAnimation(); }由这个方法可以看下场景是如何运行的,进入pushScene方法中
void CCDirector::pushScene(CCScene *pScene) { CCAssert(pScene, "the scene should not null"); m_bSendCleanupToScene = false; m_pobScenesStack->addObject(pScene); m_pNextScene = pScene; }这里的关键是场景栈的实现也就是m_pobSceneStack变量的维护。
在CCDirector的init方法中可以找到m_pobScenesStack的创建。
m_pobScenesStack = new CCArray(); m_pobScenesStack->init();这里发现一个很奇怪的写法,首先在new CCArray时肯定是会调用不带参数的构造函数,结果发现里面已经调用了init方法
CCArray::CCArray() : data(NULL) { init(); }而这里在初始化完CCArray后又调用了init方法,不知表达什么含义
由于调用了CCArray::init方法,所以只初始了存放一个场景的CCArray类
bool CCArray::init() { return initWithCapacity(1); }
bool CCArray::initWithCapacity(unsigned int capacity) { ccArrayFree(data); data = ccArrayNew(capacity); return true; }
这里看下CCArray的实现过程
在初始化这个CCArray时,先会清空这个CCArray的所有对象,然后创建ccArray这个结构体。
(
其实CCArray中的数据便是这个ccArray结构体中的一个指向CCObject*的数组
typedef struct _ccArray { unsigned int num, max; CCObject** arr; } ccArray;
)
ccArray* ccArrayNew(unsigned int capacity) { if (capacity == 0) capacity = 1; ccArray *arr = (ccArray*)malloc( sizeof(ccArray) ); arr->num = 0; arr->arr = (CCObject**)calloc(capacity, sizeof(CCObject*)); arr->max = capacity; return arr; }可以看到ccArrayNew的执行是一个C语言的写法,先动态申请一块内存,然后初始化内存空间为0,以下是百度百科的一句话:
calloc在动态分配完内存后,自动初始化该内存空间为零,而malloc不初始化,里边数据是随机的垃圾数据(注意:由于CCArray存放是CCObject指针,在调用calloc时会连续分配长度为sizeof(CCObject*)的内存大小)
最后把当前的场景存到m_pobScenesStack中,最后让m_pNextScene指向该场景
由此可以看到切换场景时不推荐使用pushScene,因为pushScene会不断的把新的场景加到m_pobScenesStack中,而不释放上一个场景的内存空间,导致内存会急剧增长,如果创建的场景很多的话。所以如果要使用pushScene一般的做法便是在创建完一个场景后插入到m_pobScenesStack的头部,然后调用popScene回收上一个场景的内存。(这样的做法会破坏掉栈的先进后出的原则,是不推荐的做法,因为popScene本来就是一个模拟出栈的方法)
void CCDirector::popScene(void) { CCAssert(m_pRunningScene != NULL, "running scene should not null"); m_pobScenesStack->removeLastObject(); unsigned int c = m_pobScenesStack->count(); if (c == 0) { end();//没有场景,游戏结束(设置一个参数,使Cocos2dx退出主循环来达到目的) } else { m_bSendCleanupToScene = true; m_pNextScene = (CCScene*)m_pobScenesStack->objectAtIndex(c - 1); } }
再看下replaceScene
void CCDirector::replaceScene(CCScene *pScene) { CCAssert(m_pRunningScene, "Use runWithScene: instead to start the director"); CCAssert(pScene != NULL, "the scene should not be null"); unsigned int index = m_pobScenesStack->count(); m_bSendCleanupToScene = true; m_pobScenesStack->replaceObjectAtIndex(index - 1, pScene); m_pNextScene = pScene; } void CCArray::replaceObjectAtIndex(unsigned int index, CCObject* pObject, bool bReleaseObject/* = true*/) { ccArrayInsertObjectAtIndex(data, pObject, index); ccArrayRemoveObjectAtIndex(data, index+1); }replaceScene的做法也很简单,就是用当前的场景替换掉上一个场景,这样便能把上一个场景的内存清掉。替换的做法也跟上面的类似,先插入新场景,再删除新场景。
先不扯那么多,执行完场景压栈后会调用startAnimation,这个从这个方法开始便会开始又切换到Java应用中
void CCDisplayLinkDirector::startAnimation(void) { if (CCTime::gettimeofdayCocos2d(m_pLastUpdate, NULL) != 0) { CCLOG("cocos2d: DisplayLinkDirector: Error on gettimeofday"); } m_bInvalid = false; #ifndef EMSCRIPTEN CCApplication::sharedApplication()->setAnimationInterval(m_dAnimationInterval); #endif // EMSCRIPTEN } void CCApplication::setAnimationInterval(double interval) { JniMethodInfo methodInfo; if (! JniHelper::getStaticMethodInfo(methodInfo, "org/cocos2dx/lib/Cocos2dxRenderer", "setAnimationInterval", "(D)V")) { CCLOG("%s %d: error to get methodInfo", __FILE__, __LINE__); } else { methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, interval); } }可以看到从setAnimationInterval方法开始回调java中的org/cocos2dx/lib/Cocos2dxRenderer.setAnimationInterval方法,并传入一个帧率的大小,返回值为void
public static void setAnimationInterval(final double pAnimationInterval) { Cocos2dxRenderer.sAnimationInterval = (long) (pAnimationInterval * Cocos2dxRenderer.NANOSECONDSPERSECOND); }这里只做了初始化渲染的帧率。
这样Cocos2dx的GLSurfaceView的创建边执行完毕了。(在创建过程并没有执行Cocos2dx的主循环)
2. onSurfaceChanged:
第二个方法是空实现,略过
@Override public void onSurfaceChanged(final GL10 pGL10, final int pWidth, final int pHeight) { }
3. onDrawFrame:
第三个方法是绘制方法。
@Override public void onDrawFrame(final GL10 gl) { Cocos2dxRenderer.nativeRender(); } private static native void nativeRender();这也是一个调用C++的方法(这个方法在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(); }终于到了这个主循环了!!!可以看到这个渲染器的绘制方法不干别的,就是调用Cocos2dx的主循环。这里的主循环跟win32平台不太一样,是通过Render来渲染的,是一种间接的调用方式来实现。
这里借鉴上网络上的一段关于Android的渲染逻辑。
游戏引擎要兼顾UI事件和屏幕帧刷新。Android的OpenGL应用采用了UI线程(Main Thread) + 渲染线程(Render Thread)的模式。Activity活在Main Thread(主线程)中,也叫做UI线程。该线程负责捕获与用户交互的信息和事件,并与渲染(Render)线程交互。比如当用户接听电话、切换到其他 程序时,渲染线程必须知道发生了 这些事件,并作出即时的处理,而这些事件及处理方式都是由主线程中的Activity以及其装载的View传递给渲染线程的。
上面的那段话说明Android的渲染便是不断执行Cocos2dx的主函数,因为屏幕每时每刻都会在渲染,每渲染一次调用一次Cocos2dx主函数来绘制游戏的画面。
接下俩看下游戏的结束过程。
Cocos2dx的结束控制是放在主循环中判断:
void CCDisplayLinkDirector::mainLoop(void) { if (m_bPurgeDirecotorInNextLoop)//退出主循环的标志 { m_bPurgeDirecotorInNextLoop = false; purgeDirector();//清空导演类 } else if (! m_bInvalid) { drawScene();//渲染节点树 // release the objects CCPoolManager::sharedPoolManager()->pop();//内存管理 } }很明显,想让游戏结束,就要让m_bPurgeDirecotorInNextLoop变量为true。而当调用CCDirector::end()方法时便会触发此过程:
void CCDirector::end() { m_bPurgeDirecotorInNextLoop = true; }(也上面说过当没有导演类中的场景栈中没有场景时便会调用end方法。)
接下来便是purgeDirector方法:
void CCDirector::purgeDirector() { //清空定时器 getScheduler()->unscheduleAll(); // don't release the event handlers // They are needed in case the director is run again //清空触摸分发器 m_pTouchDispatcher->removeAllDelegates(); if (m_pRunningScene)//如果还有场景在运行,清空此场景 { m_pRunningScene->onExitTransitionDidStart(); m_pRunningScene->onExit(); m_pRunningScene->cleanup(); m_pRunningScene->release(); } //防止指向其他地址 m_pRunningScene = NULL; m_pNextScene = NULL; //清空场景栈 m_pobScenesStack->removeAllObjects(); //停止继续渲染 stopAnimation(); //清空显示左下角的显示文本 CC_SAFE_RELEASE_NULL(m_pFPSLabel); CC_SAFE_RELEASE_NULL(m_pSPFLabel); CC_SAFE_RELEASE_NULL(m_pDrawsLabel); //清空CCLabelBMFont文本类的缓存数据 CCLabelBMFont::purgeCachedData(); //清空所有缓存 ccDrawFree(); CCAnimationCache::purgeSharedAnimationCache(); CCSpriteFrameCache::purgeSharedSpriteFrameCache(); CCTextureCache::purgeSharedTextureCache(); CCShaderCache::purgeSharedShaderCache(); CCFileUtils::purgeFileUtils(); CCConfiguration::purgeConfiguration(); //清空CCUserDefault和CCNotificationCenter CCUserDefault::purgeSharedUserDefault(); CCNotificationCenter::purgeNotificationCenter(); ccGLInvalidateStateCache(); CHECK_GL_ERROR_DEBUG(); //清空视图 m_pobOpenGLView->end(); m_pobOpenGLView = NULL; //删除导演 release(); }
其中主要看m_pobOpenGLView->end()的实现。这个方法每个平台都有自己不同的实现方式,这里选择Android平台。
void CCEGLView::end() { //终止进程 terminateProcessJNI(); }
#define CLASS_NAME "org/cocos2dx/lib/Cocos2dxHelper" void terminateProcessJNI() { JniMethodInfo t; if (JniHelper::getStaticMethodInfo(t, CLASS_NAME, "terminateProcess", "()V")) { t.env->CallStaticVoidMethod(t.classID, t.methodID); t.env->DeleteLocalRef(t.classID); } }通过JNI调用org.cocos2dx.lib.Cocos2dxHelper类中的terminateProcess方法
public static void terminateProcess() { android.os.Process.killProcess(android.os.Process.myPid()); }可以看到,Android的结束方式便是Android系统自身杀死运行中的Cocos2dx游戏进程。
至此,整个Cocos2dx游戏在Android的运行始末便结束了。
相关文章推荐
- window+eclipse+ndkr7b下cocos2d-x test.android运行
- 【iOS-cocos2d-X 游戏开发之七】整合Cocos2dX的Android项目到Xcode项目中,Xcode编写&编译代码,Android导入打包运行即可!
- cocos2d-x 在android上运行 TestCPP会出错,提示 class not found
- eclipse下运行Android版cocos2d-x
- 玩转Android cocos2d-x(一)创建、生成、运行HelloWorld
- Android设备运行流程
- 关于Cocos2d-x android的环境搭建、Demo运行的注意事项。
- 分析Cocos2d Android 项目的生成和运行
- android源码settings中显示所有正在运行进程流程分析
- 在EeePC上运行Android!(转)(也是代码下载配置编译的流程!)
- 【iOS-cocos2d-X 游戏开发之七】整合Cocos2dX的Android项目到Xcode项目中,Android导入打包运行即可!
- cocos2d-x 2.0.1版本的使用 在android 上运行 初学篇(2)
- cocos2d-x 2.0.1版本的使用 vs2008 android 上运行 初学篇(1)
- iOS-cocos2d-X 游戏开发之七】整合Cocos2dX的Android项目到Xcode项目中,Xcode编写&编译代码,Android导入打包运行即可!
- 如何把win32下写好的cocos2d-x 转到android上运行
- android应用程序运行流程
- 【iOS-cocos2d-X 游戏开发之七】整合Cocos2dX的Android项目到Xcode项目中,Android导入打包运行即可!
- 【Cocos2d-X(2.x) 游戏开发系列之二】cocos2dx最新2.0.1版本跨平台整合NDK+Xcode,Xcode编写&编译代码,Android导入打包运行即可!
- eclipse下运行Android版cocos2d-x
- 在android真机上运行cocos2d-html5-2.2自带的HelloHTML5World和其它demo