下面这张图(Google图片第一张)清晰的描绘了Cocos2d-x的总体架构,一共可以跨6个平台,非常强大。再等过一个多月我就要离职去做手游项目了,当然手游主要关注Android和iOS平台,在写游戏之前这段空闲时间,正好可以梳理一下框架逻辑。
如果你刚从Android或者iOS开发转向Cocos2d-x,尤其是从Api如shit一般的Android平台解脱的同学,一定不要太激动,虽然看几个demo就可以写一些小游戏了,但还是要回过头来扎扎实实的全面学习一遍。毫无疑问最好办法就是阅读源代码,强烈建议从平台适配层代码开始入手,剥离层层代码封装,一步步进入Cocos的世界,学习框架代码如何做到与各平台代码的完美衔接,同时欣赏下这些跨平台代码设计的优雅和美妙之处,哈哈。接下来我要分析的就是衔接Android和iOS平台的Platform Layer及Adaptor部分源码,如图:
总所周知,不同移动平台客户端框架(这里不讨论桌面操作系统)中的功能模块,特性,机制非常相似,甚至接口参数基本上都存在一一映射关系,只要做过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 Scene
void 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().
@Override
protected 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 view
setContentView(framelayout);
}
3、在Cocos2dxRender的重载方法onSurfaceCreated中调用了nativeInit()方法。
@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);
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().
@Override
public 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派发机制,至于其它的模块,生命周期方法调用等方式类似,以后在分析吧。