cocos2dx 的jni封装

源地址:http://www.lugw.net/?p=171003


补充:

android:Cocos2dxActivity.onCreate

sContext = this; //
    this.mHandler = new Cocos2dxHandler(this);//创建handler,在新线程中处理与其他线程的通信
    this.init();//创建Cocos2dxGLSurfaceView,并设置Cocos2dxRenderer,在Cocos2dxRenderer中有一些函数将在c++中实现

Cocos2dxHelper.init(this, this); //创建一些java功能类如Cocos2dxMusic,Cocos2dxSound等


Cocos2dxRenderer一些C++调用的实现过程是,DemoActivity System.loadLibrary("DemoActivity");

libDemoActivity中包含有main.cpp中的Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit实现,以及import的库文件,如cocos2dx_static

而cocos2dx_static中有cocos2dx\platform\android\jni文件夹下面的一些C++实现,如Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeTouchesBegin等等。

到此,当android应用有触摸,输入等回调时候,将会调用C++实现函数。


下面是一些C++实现的函数列表

private static native void nativeTouchesBegin(final int pID, final float pX, final float pY);
private static native void nativeTouchesEnd(final int pID, final float pX, final float pY);
private static native void nativeTouchesMove(final int[] pIDs, final float[] pXs, final float[] pYs);
private static native void nativeTouchesCancel(final int[] pIDs, final float[] pXs, final float[] pYs);
private static native boolean nativeKeyDown(final int pKeyCode);
private static native void nativeRender();
private static native void nativeInit(final int pWidth, final int pHeight);
private static native void nativeOnPause();
private static native void nativeOnResume();

而c++调用java函数在下面的文章中有提及。



JNI交互

C++接口封装完毕后,我们就开始看一下Java代码,了解一下最终实现的流程和效果,Java代码如下:

Java层的框架也很简单,这里并没有多Accelerometer和Music、Sound等进行分析,只是对涉及到显示相关的进行分析。Java层面流程如下:

如上,如果熟悉Android界面开发,可以从基类了解到Java层面是通过Activity、GLSuffaceView来进行的显示。这里不详细介绍,如果有兴趣,可以看一下《剖析游戏开发用view还是surfaceView》,View类似传统的二维静态界面,数据驱动显示,而SurfaceView则类似三维机制,实时渲染。因为Cocos2d是OpenGL的,这也好解释。

对于整个框架其实要说的也很多,不过我对Java还不太了解,所以有些东西看的不一定透,也难免有一些问题。

Renderer

Renderer类负责每一帧的渲染驱动,调用步骤如图里面的1和2,在2中调用jni里面的nativeRender实现一帧的渲染,而GLSurfaceView则负责UI交互的监听。

这种机制的好处是在Java中Renderer渲染器是独立线程调用,因此和UI之间没有交互性,这样既保证了用户体验(用户的事件通过GLSurfaceView监听,最终通过Renderer传递至C++层面来响应),也保证了渲染过程的抗干扰,依旧通过C++层面进行渲染。,整个显示过程用到的jni封装主要如下:

   1: private static native void nativeTouchesBegin(int id, float x, float y);

   2: private static native void nativeTouchesEnd(int id, float x, float y);

   3: private static native void nativeTouchesMove(int[] id, float[] x, float[] y);

   4: private static native void nativeTouchesCancel(int[] id, float[] x, float[] y);

   5: private static native boolean nativeKeyDown(int keyCode);

   6: private static native void nativeRender();

   7: private static native void nativeInit(int w, int h);

   8: private static native void nativeOnPause();

   9: private static native void nativeOnResume();

jni封装

jni的封装主要有两部分,一个是cocos2d自己的JNI封装,这部分封装主要是为了在Java中调用cocos2d的jni接口,一个是HelloWorld中自己的jni接口封装。这一块本来是我比较感兴趣的地方,因为jni封装还是挺繁琐的一件事情,最后发现cocos2d在本质上也没有什么区别,麻烦的还是得封装。第二点,cocos2d主要是游戏引擎,所以基本所有功能都是由C++层面来实现,一帧的渲染,事件的处理,而Java层主要负责逻辑处理,最终通过jni调用C++接口来实现。第三点来说,cocos2d本身封装的还是很简洁的,这点我觉得做的还是很优雅的,在设计这块,是以Java的逻辑为依据来进行划分,我觉得这个很可取,虽然cocos2d是C++做起来的,但是并没有为了保证各个平台的一致性而强迫接口的一致,而是在jni层按照SDK在具体平台的应用特点来进行封装,这样减低了实现难度,提高了代码的易用度,牺牲就是应用平台接口的局部不一致性。jni层面主要是事件传递和窗口渲染部分的接口封装,针对游戏开发者而言,最核心的部分都可以在Windows平台下完成,然后在Android部分完成特有事件的传递,渲染部分直接采用cocos2d给出的标准范例实现即可,大大简化了开发者自己封装jni的工作。

窗口绑定

窗口绑定我理解的并不太透彻,首先,我认为CCEGLView_Android只是一个虚的窗口,并没有实质功能,只是为了便于架构理解。

   1: void Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit(JNIEnv*  env, jobject thiz, jint w, jint h)

   2: {

   3:     if (!cocos2d::CCDirector::sharedDirector()->getOpenGLView())

   4:     {

   5:         cocos2d::CCEGLView *view = &cocos2d::CCEGLView::sharedOpenGLView();

   6:         view->setFrameWidthAndHeight(w, h);

   7:         // if you want to run in WVGA with HVGA resource, set it

   8:         // view->create(480, 320);  Please change it to (320, 480) if you're in portrait mode.

   9:         cocos2d::CCDirector::sharedDirector()->setOpenGLView(view);

  10:  

  11:         AppDelegate *pAppDelegate = new AppDelegate();

  12:         cocos2d::CCApplication::sharedApplication().run();

  13:     }

  14: }

  15:  

  16: void Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeRender(JNIEnv* env)

  17: {

  18:     cocos2d::CCDirector::sharedDirector()->mainLoop();

  19: }

 

函数一是Java层调用onSurfaceCreated时调用函数,用来获取GLView窗口,用来下一步的渲染,而这个View窗口并没有类似Windows下的handle绑定,而接下来函数二是Java中onDrawFrame渲染每一帧时进行调用,最终调用底层的Director渲染,完成一帧绘制(详细内容可参考《cocos2d-x之HelloWorld范例分析(一)》)。

怎么来理解这种窗口绑定方式,保证我现在调用的gl函数,就能够绘制在窗口呢,通篇没有一个类似的handle从Java传递给JNI,通篇C++层面的View也只是一个只有Width和Height属性的结构体,所以我理解的是GLSurfaceView.Renderer默认在自己的线程中进行了封装,已经自己完成了和OpenGL的绑定。这个我觉得应该是靠谱的吧,而且自己来实时的每一帧渲染,下面的就不管里,你自己愿意调Java的接口也行,自己调gl的渲染也可以。这样也挺好的,都不用我顾虑这个事情了,只要给我高度宽度知道位置信息,我直接渲染。

文字

其他图形图像的绘制,都是和系统无关的。整个的渲染过程,也是跨平台的,一个平台的整合,主要是环境搭建、不同语言之间的消息传递、View的映射这些,前面也都阐述了,只是文字有一定的特殊,在Windows下使用CDC,在Linux是Freetype,在Android下如何实现?我觉得cocos2d实现思路也是不错的:C++通过JNI在Java层绘制,生成一张BitMap给C++,然后贴图完成。这个优点是简单,缺点就是如果文字太多的话,效率损失还是有的,其实我觉得如果有机会,还是用Freetype来画应该也可以尝试一下。

当然,也新学了一招,C++调用Java的方式,在jni里面也提供了,呵呵,代码在下面贴一下:

   1: bool getBitmapFromJava(const char *text, int nWidth, int nHeight, CCImage::ETextAlign eAlignMask, const char * pFontName, float fontSize)

   2: {

   3:     JniMethodInfo methodInfo;

   4:     if (! JniHelper::getStaticMethodInfo(methodInfo, "org/cocos2dx/lib/Cocos2dxBitmap", "createTextBitmap", 

   5:         "(Ljava/lang/String;Ljava/lang/String;IIII)V"))

   6:     {

   7:         CCLOG("%s %d: error to get methodInfo", __FILE__, __LINE__);

   8:         return false;

   9:     }

  10:  

  11:     /**create bitmap

  12:      * this method call Cococs2dx.createBitmap()(java code) to create the bitmap, the java code

  13:      * will call Java_org_cocos2dx_lib_Cocos2dxBitmap_nativeInitBitmapDC() to init the width, height

  14:      * and data.

  15:      * use this appoach to decrease the jni call number

  16:     */

  17:     jstring jstrText = methodInfo.env->NewStringUTF(text);

  18:     jstring jstrFont = methodInfo.env->NewStringUTF(pFontName);

  19:  

  20:     methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, jstrText, 

  21:         jstrFont, (int)fontSize, eAlignMask, nWidth, nHeight);

  22:  

  23:     methodInfo.env->DeleteLocalRef(jstrText);

  24:     methodInfo.env->DeleteLocalRef(jstrFont);

  25:     methodInfo.env->DeleteLocalRef(methodInfo.classID);

  26:  

  27:     return true;

  28: }

  29: static bool getStaticMethodInfo_(cocos2d::JniMethodInfo &methodinfo, const char *className, const char *methodName, const char *paramCode)

  30: {

  31:     jmethodID methodID = 0;

  32:     JNIEnv *pEnv = 0;

  33:     if (! getEnv(&pEnv))

  34:     {

  35:         break;

  36:     }

  37:  

  38:     jclass classID = getClassID_(className, pEnv);

  39:  

  40:     methodID = pEnv->GetStaticMethodID(classID, methodName, paramCode);

  41: }

 

参照里面的注释,C++驱动Java实现绘制,Java完成绘制后,调用Java_org_cocos2dx_lib_Cocos2dxBitmap_nativeInitBitmapDC接口,实现内存的拷贝,而s_BmpDC中的m_pData用来保存,进行下一步的纹理贴图,完成整改流程的传递.

总结

介绍完毕,整个过程中,cocos2d使用的技术并不神秘,主要是一个熟悉的过程.最值得称赞的是JNI封装的比较使用,本身做游戏开发,基本所有功能都会在C++中封闭实现,只需要提供一个规范的Java外壳就可以,既跨平台有高效.另外,就是cocos2d对各个平台的语言取舍,哪些用Java方便,哪些用C++ 保持平台一致,都做的还是很合理的.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值