Cocos2d-x3.0源码分析之跨平台适配层

来源:互联网 发布:西华师范大学知乎 编辑:程序博客网 时间:2024/05/15 22:30

下面这张图(Google图片第一张)清晰的描绘了Cocos2d-x的总体架构,一共可以跨6个平台,非常强大。再等过一个多月我就要离职去做手游项目了,当然手游主要关注Android和iOS平台,在写游戏之前这段空闲时间,正好可以梳理一下框架逻辑。

如果你刚从Android或者iOS开发转向Cocos2d-x,尤其是从Api如shit一般的Android平台解脱的同学,一定不要太激动,虽然看几个demo就可以写一些小游戏了,但还是要回过头来扎扎实实的全面学习一遍。毫无疑问最好办法就是阅读源代码,强烈建议从平台适配层代码开始入手,剥离层层代码封装,一步步进入Cocos的世界,学习框架代码如何做到与各平台代码的完美衔接,同时欣赏下这些跨平台代码设计的优雅和美妙之处,哈哈。接下来我要分析的就是衔接Android和iOS平台的Platform LayerAdaptor部分源码,如图:




总所周知,不同移动平台客户端框架(这里不讨论桌面操作系统)中的功能模块,特性,机制非常相似,甚至接口参数基本上都存在一一映射关系,只要做过2个以上平台开发必定深有体会。

以Android和iOS为例,需要适配的有Event模块(Touch,Keyboard,Mouse,Acceleration...),窗口渲染载体(OpenGLView,Render),消息循环机制(逐帧回调),Application生命周期(Launch,Pause,Resume...),存储模块(文件,资源),声音模块等等。

适配层只占所有框架代码的一小部分,剩下的大部分由C++一套代码实现,不需要适配,这些基础模块有网络库,xml/Json解析库,cocos2d核心功能代码(引用计数内存管理机制,Ref与AutoReleasePool体系,OC风格容器,Node体系等等,cocos2d-iphone带过来的,很有特色很有味道哈哈!),标准std库,物理引擎等等。

适配层的代码由各种语言组成,分别属于各个平台,且分散在不同文件夹下面,为了清晰直观的表明所有这些类之间的关系,我画了一个类图将他们放到了一起:




可以根据不同的语言将该图分为3个部分,也可以代表3个平台,但不完全代表不同平台编译时所需要的代码,如iOS平台编译时需要左上部分Objective-C代码外加下面的C++代码,但要除去适配Android的C++代码。

新建Cocos2d-x的iOS工程中,/ios文件夹下面包含了自动生成的iOS适配代码,AppControl和RootViewControl,而Android工程中对应的则是自动生成的AppActivity,开发者无需理会任何适配(除非有特殊的调用需求)即可以开始直接编写Cocos代码了!(一般在XCode里面开发好后,再用脚本编译出Android工程,基本也不用做任何修改即可完美运行,Cocos的确是神一般的跨平台体验!)

接下来整理这些适配代码之间的调用关系,学习之后就会发现,Oh,原来如此!


对于iOS,App启动和普通应用没什么区别,

1、入口从UIApplicationDelegate中的【application: didFinishLaunchingWithOptions】开始,这里首先创建了一个GLView(iOS版本,wrap了一个CCEAGLView),然后直接调用cocos2d::Application(iOS版)的run方法。

2、run()方法首先调用了cocos2d::Application的applicationDidFinishLaunching方法,然后是CCDirectorCaller->startMainLoop()。

int Application::run(){    if (applicationDidFinishLaunching())     {        [[CCDirectorCaller sharedDirectorCaller] startMainLoop];    }    return 0;}

3、startMainLoop()向CADisplayLink注册了一个每帧回调的selector(doCaller:),doCaller里面则调用了cocos2d::Director的mainLoop()方法。

-(void) startMainLoop{        // Director::setAnimationInterval() is called, we should invalidate it first        [displayLink invalidate];        displayLink = nil;                displayLink = [NSClassFromString(@"CADisplayLink") displayLinkWithTarget:self selector:@selector(doCaller:)];        [displayLink setFrameInterval: self.interval];        [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];}                      -(void) doCaller: (id) sender{    cocos2d::Director* director = cocos2d::Director::getInstance();    [EAGLContext setCurrentContext: [(CCEAGLView*)director->getOpenGLView()->getEAGLView() context]];    director->mainLoop();}

4、从mainLoop()方法开始,正式进入Cocos的世界,这里做了2件事情,首先调用drawScene(),然后执行PoolManager::getInstance()->getCurrentPool()->clear(); 清理自动管理内存池。

void DisplayLinkDirector::mainLoop(){    if (_purgeDirectorInNextLoop)    {        _purgeDirectorInNextLoop = false;        purgeDirector();    }    else if (! _invalid)    {        drawScene();             // release the objects        PoolManager::getInstance()->getCurrentPool()->clear();    }}
5、drawScene()里面依次执行下列逻辑(代码实在太清晰了),计算每帧时间,_scheduler->update一次(scheduler里面包括了Action,Selector,Timer等),EventDispatcher派发beforeVisit事件,绘制当前帧,EventDispatcher派发afterVisit事件,绘制NotificationNode,执行Render指令栈(3.0优化),EventDispatcher派发afterDraw事件,交换OpenGLView双缓冲(Android里面啥也没干...晕)。

// Draw the Scenevoid Director::drawScene(){    // calculate "global" dt    calculateDeltaTime();    if (_openGLView)    {        _openGLView->pollInputEvents();    }    //tick before glClear: issue #533    if (! _paused)    {        _scheduler->update(_deltaTime);        _eventDispatcher->dispatchEvent(_eventAfterUpdate);    }    ...    // draw the scene    if (_runningScene)    {        _runningScene->visit(_renderer, identity, false);        _eventDispatcher->dispatchEvent(_eventAfterVisit);    }    // draw the notifications node    if (_notificationNode)    {        _notificationNode->visit(_renderer, identity, false);    }    if (_displayStats)    {        showStats();    }    _renderer->render();    _eventDispatcher->dispatchEvent(_eventAfterDraw);    kmGLPopMatrix();    _totalFrames++;    // swap buffers    if (_openGLView)    {        _openGLView->swapBuffers();    }    if (_displayStats)    {        calculateMPF();    }}

注意这里的Event派发只是Cocos体系内的CustomEvent,系统的Event派发并没有在Cocos的逐帧回调中执行(除非是没有提供消息回调的平台,如按需实现GLViewProtocol的pollInputEvents()方法,Android和iOS并不需要),而使用平台系统的消息循环队列,可以理解为系统Event派发与逐帧回调的执行是排队分先后的。看到这里,Cocos2d框架的运行逻辑已经非常清晰,程序启动后就可以循环绘制起来了,事件派发也搞定!

不得不提的是Cocos的Notification实现通过Callback同步调用,和iOS里面发送同步消息的NSNotificationCenter(Each process has a default notification center that you access with the NSNotificationCenter +defaultCenter )类似,而iOS里面发送异步消息的NSNotificationQueue(Every thread has a default notification queue, which is associated with the default notification center for the process.)则与Android中的Handler&MessageLoop机制类似。

Cocos编程主要集中在主线程,编程模型其实非常简单,此外框架还提供了很多的单例类,访问非常方便,而为了效率大多实现都没考虑线程安全。


对于Android,

1、启动后进入AppActivity的初始化,在基类Cocos2dxActivity的onCreate里面调用init().

@Overrideprotected void onCreate(final Bundle savedInstanceState) {super.onCreate(savedInstanceState);...    this.init();}

2、init()中创建一个Cocos2dxGLSurfaceView,同时创建Coco2dxRender。

public void init() {    // FrameLayout        ViewGroup.LayoutParams framelayout_params =            new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,                                       ViewGroup.LayoutParams.MATCH_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());        this.mGLSurfaceView.setCocos2dxEditText(edittext);        // Set framelayout as the content viewsetContentView(framelayout);}

3、在Cocos2dxRender的重载方法onSurfaceCreated中调用了nativeInit()方法。

@Overridepublic 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);

4、nativeInit()方法中创建了GLView(Android版本),然后调用cocos2d::Application(Android版)的run()方法。
void Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit(JNIEnv*  env, jobject thiz, jint w, jint h){    auto director = cocos2d::Director::getInstance();    auto glview = director->getOpenGLView();    if (!glview)    {        glview = cocos2d::GLView::create("Android app");        glview->setFrameSize(w, h);        director->setOpenGLView(glview);        cocos_android_app_init(env, thiz);        cocos2d::Application::getInstance()->run();    }    else    {       ...    }}
5、run()方法直接调用了cocos2d::Application的applicationDidFinishLaunching方法。

int Application::run(){    // Initialize instance and cocos2d.    if (! applicationDidFinishLaunching())    {        return 0;    }        return -1;}

6、上面才刚刚初始化完成,而Android的逐帧回调和iOS不一样,逐帧回调从Coco2dxRender的onDrawFrame(系统自己的逐帧回调,因此也没法精确控制帧率)开始, 调用nativeRender().

@Overridepublic void onDrawFrame(final GL10 gl) {...// should render a frame when onDrawFrame() is called or there is a// "ghost"Cocos2dxRenderer.nativeRender();...}

7、nativeRender()里面调用cocos2d::Director->mainLoop(),等同于上面的第4步,进入Cocos的世界,之后的逻辑几乎完全一样,不过Android版GLView基本是个空壳,双缓冲神马的更没有,不知道SurfaceView里面有没有实现了。

JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeRender(JNIEnv* env) {        cocos2d::Director::getInstance()->mainLoop();    }

上面分析包含了App的初始化回调,渲染载体,逐帧回调方法,系统Event派发机制,至于其它的模块,生命周期方法调用等方式类似,以后在分析吧。


1 0
原创粉丝点击