一、cocos2d-x跨平台
cocos2d-x到底是怎样实现跨平台的呢?这里以Win32和Android为例。
1. 跨平台项目目录结构
先看一下一个项目创建后的目录结构吧!这还是以HelloCpp为例。
从左边目录可以看到,Classes和Resource已经平台无关了,而Classes中包含了AppDelegate类,因此我们可以认为AppDelegate是与平台最接近的类,在它以上就要区分平台了。
2. Win32下的实现
在前一篇就介绍了Win32怎么开始cocos2dx,Win32平台下main.cpp就是程序入口:
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); // create the application instance AppDelegate app;//创建应用实例 CCEGLView* eglView = CCEGLView::sharedOpenGLView(); eglView->setViewName("HelloCpp"); eglView->setFrameSize(2048, 1536); eglView->setFrameZoomFactor(0.4f); return CCApplication::sharedApplication()->run();//运行程序 }
Win32下的实现比较简单,就是正常的创建实例,运行就可以了。
3.Android下的实现
3.1.cocos2d-x程序入口
我们先看一下Android下cocos2d-x程序入口点在哪,我们知道Android是采用Java编写的,而cocos2d-x是c++编写的,所以如果要在Java中调用c++代码,那就需要采用JNI技术,看起来好像高端大气上档次,其实程序就是函数调用,也就是输入→处理→输出,所以JNI实际上简单抽象出来就这么回事:
java输入→Jni→c++输入→c++处理(API实现)→c++输出→Jni→java输出
在proj.androidjnihellocpp文件夹下可以找到main.cpp,这就是cocos2d-x的入口:
jint JNI_OnLoad(JavaVM *vm, void *reserved) { JniHelper::setJavaVM(vm); return JNI_VERSION_1_4; } 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(); } }
里面包含了2个函数,JNI_OnLoad和Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit。我们看一下功能而先不管它在哪里被调用。
(1)JNI_OnLoad,这个函数主要是用来告诉Android VM当前使用的是什么版本是Jni,如果不提供此函数,则默认使用Jni1.1版本。
(2)Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit,这个函数很明显就是运行一个cocos2d-x的应用实例了,这和Win32是一样的,当然它多了一个openGlView的检测。一旦调用了它那么cocos2d-x游戏启动。
接下来再看看它们是在哪里被调用的。
在Android.mk文件内容如下:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := hellocpp_shared LOCAL_MODULE_FILENAME := libhellocpp LOCAL_SRC_FILES := hellocpp/main.cpp ../../Classes/AppDelegate.cpp ../../Classes/HelloWorldScene.cpp LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../Classes LOCAL_WHOLE_STATIC_LIBRARIES := cocos2dx_static include $(BUILD_SHARED_LIBRARY) $(call import-module,cocos2dx)
3.2 JNI_OnLoad的调用
在proj.androidsrcorgcocos2dxhellocpp目录下,可以看到Android的入口Activity,也就是HelloCpp,它继承自Cocos2dxActivity。
public class HelloCpp extends Cocos2dxActivity{ protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); } static { System.loadLibrary("hellocpp"); } }
很简单的代码,因为功能都被封装到Cocos2dxActivity中了,所以OnCreate中调用了父类的OnCreate就把功能都实现了,而system.LoadLibrary就是载入编译出来的.so文件,此时就会执行JNI_OnLoad。
3.3 Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit的调用
那最重要的Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit是在哪调用呢?这就比较麻烦了,先大致了解一下Cocos2dxActivity做了一些什么事。
直接进入Cocos2dxActivity的OnCreate函数,它调用了一个init初始化函数:
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); // 设置Cocos2dxEditText布局,这一个跟GLSurfaceView兼容的edittext 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); // 添加到framelaout
framelayout.addView(edittext);
// 创建Cocos2dxGLSurfaceView this.mGLSurfaceView = this.onCreateView(); // 添加到framelaout framelayout.addView(this.mGLSurfaceView);
// Switch to supported OpenGL (ARGB888) mode on emulator if (isAndroidEmulator()) this.mGLSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0); //设置Cocos2dxRenderer和Cocos2dxEditText this.mGLSurfaceView.setCocos2dxRenderer(new Cocos2dxRenderer()); this.mGLSurfaceView.setCocos2dxEditText(edittext); // 设置framelayout作为内容视图 setContentView(framelayout); }
在这里Cocos2dxActivity做的就是创建Cocos2dxGLSurfaceView,并设置了Cocos2dxRenderer和Cocos2dxEditText,然后添加到FramLayout。具体的各部分实现这里就不贴代码了,画了个图:
二、 AppDelegate 析造函数没有被调用
怎么样使用 Cocos2d-x 快速开发游戏,方法很简单,你可以看看其自带的例程,或者从网上搜索教程,运行起第一个 Scene HelloWorldScene,然后在HelloWorldScene 里面写相关逻辑代码,添加我们的层、精灵等 ~ 我们并不一定需要知道 Cocos2d-x 是如何运行或者在各种平台之上运行,也不用知道 Cocos2d-x 的游戏是如何运行起来的,它又是如何渲染界面的 ~~~
我们只用知道 Cocos2d-x 的程序是由 AppDelegate 的方法 applicationDidFinishLaunching 开始,在其中做些必要的初始化,并创建运行第一个 CCScene 即可,正如我们第一次使用各种编程语言写 Hello World! 的程序一样,如 Python 打印:
print(‘Hello World!’)
我们可以不用关心其是怎么实现的,我们只要知道这样就能打印一句话就够了,这就是封装所带来的好处 。Cocos2d-x 自带的例程已经足够丰富,但是有些问题并不是看看例子,调用其方法就能明白的事情,在这里遇到了如下问题:
// AppDelegate.cpp 文件 AppDelegate::AppDelegate() { CCLog("AppDelegate()"); // AppDelegate 构造函数打印 } AppDelegate::~AppDelegate() { CCLog("AppDelegate().~()"); // AppDelegate 析构函数打印 } // 程序入口 bool AppDelegate::applicationDidFinishLaunching() { // initialize director CCDirector *pDirector = CCDirector::sharedDirector(); pDirector->setOpenGLView(CCEGLView::sharedOpenGLView()); // 初始化,资源适配,屏幕适配,运行第一个场景等代码 ... ... ... return true; } void AppDelegate::applicationDidEnterBackground() { CCDirector::sharedDirector()->pause(); } void AppDelegate::applicationWillEnterForeground() { CCDirector::sharedDirector()->resume(); }
此时我并不知道程序运行时,何时调用 AppDelegate 的构造函数,析构函数和程序入口函数,我们只要知道,程序在这里调用了其构造函数,然后进入入口函数执行其过程,最后再调用其析构函数即可。然而事与愿违,在实际执行的过程中,发现程序只调用其构造函数和入口函数,而直到程序结束运行,都没有调用其析构函数。要验证此说法很简单,只要如上在析构函数中调用打印日志便可验证。
发生这样的情况,让我在构造函数创建[资源],并且在析构函数中释放[资源] 的想法不能完成!!! 我们知道它是从哪里开始运行,但却不知道它在哪里结束!疑问,唯有疑问!
两个入口
程序入口的概念是相对的,AppDelegate 作为跨平台程序入口,在这之上做了另一层的封装,封装了不同平台的不同实现,比如我们通常认为一个程序是由 main 函数开始运行,那我们就去找寻,我们看到了在 proj.linux 目录下存在 main.cpp 文件,这就是我们要看的内容,如下:
#include "main.h" #include "../Classes/AppDelegate.h" #include "cocos2d.h" #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <string> USING_NS_CC; // 500 is enough? #define MAXPATHLEN 500 int main(int argc, char **argv) { // get application path int length; char fullpath[MAXPATHLEN]; length = readlink("/proc/self/exe", fullpath, sizeof(fullpath)); fullpath[length] = '