由于Android的应用层是从Activity开始的,也就是创建完一个Cocos2dx后src文件夹下的Java文件。其中主要看Activity创建时的操作:
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);
}
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;
执行完上面的步骤后开始初始化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方法,不知表达什么含义
bool CCArray::init()
{
return initWithCapacity(1);
}
bool CCArray::initWithCapacity(unsigned int capacity)
{
ccArrayFree(data);
data = ccArrayNew(capacity);
return true;
}
在初始化这个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,以下是百度百科的一句话:
最后把当前的场景存到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);
}
}
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();
}
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的运行始末便结束了。