咱们知道Cocos2dx是一款开源的跨平台游戏引擎,而学习开源项目一个较实用的办法就是读源码。所谓,“源码以前,了无秘密”。而笔者从事的也是游戏开发工做,所以,经过梳理下源码的脉络,来加深对Cocos2dx游戏引擎的理解。html
既然,Cocos2dx是跨平台的,那么,就有针对不一样平台运行的入口以及维持引擎运转的“死循环”。下面,就分别从Windows、Android、iOS三个平台说明下Cocos2dx从启动到进入主循环的过程。java
以引擎下的cpp-empty-test
项目工程为例:
Windows工程的入口函数为cpp-empty-test/win32/main.cpp中的_tWinMain
函数。android
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); // create the application instance AppDelegate app; return Application::getInstance()->run(); }
这里,定义了一个AppDelegate类型的栈对象app。而AppDelegate继承自Application,因此这里会先初始化父类Application。再看下Application的实现,注意是进到CCApplication-win32.h和CCApplication-win32.cpp里。固然,Application还继续继承自ApplicationProtocol(经过预处理宏来针对不一样的平台执行不一样的代码)。这里,并无作什么特别的处理,只是做了下相应的初始化的工做。ios
而在CCApplication-win32.h和CCApplication-win32.cpp代码中都有这样的宏判断:windows
#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
继续追踪下去,能够发现CC_PLATFORM_WIN32
在定义了WIN32宏时定义。架构
接下来,便执行Application::getInstance()->run()
代码,这里的Application为单例的实现,这也是Cocos2dx单例经常使用的实现方式,在2.x版本的引擎中,单例的实现为sharedApplication,这是仿照Objective-C的写法。继续看CCApplication-win32中的run
方法:app
int Application::run() { PVRFrameEnableControlWindow(false); // Main message loop: LARGE_INTEGER nLast; LARGE_INTEGER nNow; QueryPerformanceCounter(&nLast); initGLContextAttrs(); // Initialize instance and cocos2d. if (!applicationDidFinishLaunching()) { return 1; } auto director = Director::getInstance(); auto glview = director->getOpenGLView(); // Retain glview to avoid glview being released in the while loop glview->retain(); while(!glview->windowShouldClose()) { QueryPerformanceCounter(&nNow); if (nNow.QuadPart - nLast.QuadPart > _animationInterval.QuadPart) { nLast.QuadPart = nNow.QuadPart - (nNow.QuadPart % _animationInterval.QuadPart); director->mainLoop(); glview->pollEvents(); } else { Sleep(1); } } // Director should still do a cleanup if the window was closed manually. if (glview->isOpenGLReady()) { director->end(); director->mainLoop(); director = nullptr; } glview->release(); return 0; }
这里主要先看下applicationDidFinishLaunching()的调用,applicationDidFinishLaunching是虚函数,这里会调到子类AppDelegate中的applicationDidFinishLaunching的实现:ide
bool AppDelegate::applicationDidFinishLaunching() { auto director = Director::getInstance(); auto glview = director->getOpenGLView(); if(!glview) { glview = GLViewImpl::create("Cpp Empty Test"); director->setOpenGLView(glview); } director->setOpenGLView(glview); glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::NO_BORDER); director->setAnimationInterval(1.0f / 60); auto scene = HelloWorld::scene(); director->runWithScene(scene); return true; }
这里对代码作了适当的删减。能够看到在AppDelegate的applicationDidFinishLaunching主要作了些跟游戏初始化相关的处理。例如,初始化导演类,设置OpenGL视图,设置适配方式,设置帧率以及初始化场景和运行该场景等。基本这个方法,也能够看成咱们游戏代码初始化的入口。函数
再回到CCApplication的run方法,继续往下看。这里,有个while循环,至此,就找到了引擎的“死循环”了。在这个循环中,调用了导演类的mainLoop主循环方法,而在mainLoop中,主要控制渲染,定时器,动画,事件循环等处理。后续会分析这相关的部分,这里就不过多介绍了。至此,就是Cocos2dx在Windows平台从启动到主循环,代码执行的流程,简单的梳理,能够知道引擎代码是如何架构的。oop
在Android平台的应用,通常由多个Activity组成,一个Activity表明一个“窗口”,Activity根据应用先后台切换有对应的声明周期状态。在配置清单文件中声明了
<action android:name="android.intent.action.MAIN" />
即表明该Acitivity为应用的入口Activity。而入口Activity也通常称为闪屏页(Splash)或启动页,用来呈现公司的或运营的合做伙伴Logo,以后再切换到主Activity。在Cocos2dx游戏中,主Activity通常是继承Cocos2dx引擎封装的Cocos2dxActivity类。先看onCreate()方法:
protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); onLoadNativeLibraries(); sContext = this; Cocos2dxHelper.init(this); this.init(); }
对onCreate里的代码作了精简,只列举了比较重要的几个方法。首先onLoadNativeLibraries
方法:
protected void onLoadNativeLibraries() { try { ApplicationInfo ai = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA); Bundle bundle = ai.metaData; String libName = bundle.getString("android.app.lib_name"); System.loadLibrary(libName); } catch (Exception e) { e.printStackTrace(); } }
该方法会读取配置在Manifest里中的meta-data
标签的字段为android.app.lib_name
的值,来加载动态库。即为:
<meta-data android:name="android.app.lib_name" android:value="cpp_empty_test" />
一样,以cpp_empty_test的项目为例,可知这里要加载名字为libcpp_empty_test.so
动态库。因为Cocos2dx引擎核心部分是C++实现,在Android平台经过jni的方式来调用和启动引擎。
再回到Cocos2dxActivity中的onCreate,继续往下进行。能够看到:
sContext = this;
sContext是Cocos2dxActivity的实例,被声明为静态的,经过这种实现了单例的效果。在须要Context实例的地方以及须要调用Cocos2dxActivity方法的地方,能够直接用该实例。
Cocos2dxHelper.init(this);
Cocos2dxHelper的init中主要是一些对象的初始化,例如:声音,音效,重力感应,Asset管理等。
接下来,调用了Cocos2dxActivity的init方法里:
public void init() { ViewGroup.LayoutParams framelayout_params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); mFrameLayout = new ResizeLayout(this); mFrameLayout.setLayoutParams(framelayout_params); ViewGroup.LayoutParams edittext_layout_params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); Cocos2dxEditBox edittext = new Cocos2dxEditBox(this); edittext.setLayoutParams(edittext_layout_params); mFrameLayout.addView(edittext); this.mGLSurfaceView = this.onCreateView(); mFrameLayout.addView(this.mGLSurfaceView); // Switch to supported OpenGL (ARGB888) mode on emulator if (isAndroidEmulator()) this.mGLSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0); this.mGLSurfaceView.setCocos2dxRenderer(new Cocos2dxRenderer()); this.mGLSurfaceView.setCocos2dxEditText(edittext); setContentView(mFrameLayout); }
该方法主要设置要显示的视图界面,即mFrameLayout。重点关注这几行代码:
this.mGLSurfaceView = this.onCreateView(); mFrameLayout.addView(this.mGLSurfaceView); this.mGLSurfaceView.setCocos2dxRenderer(new Cocos2dxRenderer());
在onCreateView方法中,返回了一个Cocos2dxGLSurfaceView对象,并将该对象添加到了帧布局的容器对象(mFrameLayout)中。首先,了解下Cocos2dxGLSurfaceView类的实现:
public class Cocos2dxGLSurfaceView extends GLSurfaceView { private Cocos2dxRenderer mCocos2dxRenderer; public void setCocos2dxRenderer(final Cocos2dxRenderer renderer) { this.mCocos2dxRenderer = renderer; this.setRenderer(this.mCocos2dxRenderer); } public void onResume() { super.onResume(); this.setRenderMode(RENDERMODE_CONTINUOUSLY); this.queueEvent(new Runnable() { public void run() { Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleOnResume(); } }); } public void onPause() { this.queueEvent(new Runnable() { public void run() { Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleOnPause(); } }); this.setRenderMode(RENDERMODE_WHEN_DIRTY); //super.onPause(); } }
(上述代码有作删减,只保留须要说明的地方)
Cocos2dxGLSurfaceView继承自GLSurfaceView,经过阅读GLSurfaceView文档可知,GLSurfaceView又继承自SurfaceView,而SurfaceView又进一步继承自View。GLSurfaceView封装了OpenGL ES所需的运行环境,同时能让OpenGL ES渲染的内容直接生成在Android的View视图上。绘制渲染时,用户能够自定义渲染器(GLSurfaceView.Renderer),该渲染器运行在单独的线程里,独立于UI线程。GLSurfaceView还能适应于Activity的声明周期的变化作相应的处理(例如:onPause、onResume等)。
GLSurfaceView的初始化过程当中,须要设置渲染器。即调用setRenderer方法。
Cocos2dxGLSurfaceView类中的onResume和onPause方法,这两个方法受Activity的相应的声明周期的方法影响, Activity窗口暂停(pause)或恢复(resume)时,GLSurfaceView都会收到通知,此时它的onPause方法和 onResume方法应该被调用。这样GLSurfaceView就会暂停或恢复它的渲染线程,以便它及时释放或重建OpenGL的资源。其中都分别调用了queueEvent
的方法。这里,须要注意的是,Android的UI运行在主线程,而OpenGL的GLSurfaceView运行在一个单独的线程中,所以,须要调用queueEvent
来给OpenGL线程分发调用,来达到两个线程间通讯。最后都交给Cocos2dxRenderer处理。
最后,再重点看下渲染器类Cocos2dxRenderer的实现:
public class Cocos2dxRenderer implements GLSurfaceView.Renderer { public void onSurfaceCreated(final GL10 GL10, final EGLConfig EGLConfig) { Cocos2dxRenderer.nativeInit(this.mScreenWidth, this.mScreenHeight); this.mLastTickInNanoSeconds = System.nanoTime(); mNativeInitCompleted = true; } public void onSurfaceChanged(final GL10 GL10, final int width, final int height) { Cocos2dxRenderer.nativeOnSurfaceChanged(width, height); } public void onDrawFrame(final GL10 gl) { if (sAnimationInterval <= 1.0 / 60 * Cocos2dxRenderer.NANOSECONDSPERSECOND) { Cocos2dxRenderer.nativeRender(); } else { final long now = System.nanoTime(); final long interval = now - this.mLastTickInNanoSeconds; if (interval < Cocos2dxRenderer.sAnimationInterval) { try { Thread.sleep((Cocos2dxRenderer.sAnimationInterval - interval) / Cocos2dxRenderer.NANOSECONDSPERMICROSECOND); } catch (final Exception e) { } } this.mLastTickInNanoSeconds = System.nanoTime(); Cocos2dxRenderer.nativeRender(); } } }
首先,Cocos2dxRenderer继承了渲染器类GLSurfaceView.Renderer,并重写了如下上个方法:
onSurfaceCreated:
该方法是当Surface被建立的时候,会调用,即应用程序第一次运行的时候。当设备被唤醒或用户从其它Activity切换回来的时候,该方法也可能被调用。所以,该方法可能会被屡次调用。通常会在该方法中,完成一些OpenGL ES的初始化工做。
onSurfaceChanged:
该方法是在Surface被建立之后,每次Surface尺寸发生变化时(例如:横竖屏切换),该方法会被调用。
onDrawFrame:
绘制的每一帧,该方法都会被调用。
其实,看到onDrawFrame中的代码,能够知道Cocos2dx引擎在Android平台的“死循环”在该方法中。最后,经过jni的方式调用nativeRender
来启动导演类的主循环。
熟悉jni调用的能够知道,nativeRender是声明为native的方法,Cocos2dxRenderer.nativeRender最终会调到Java_org_cocos2dx_lib_Cocos2dxRenderer.cpp类中:
JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeRender(JNIEnv* env) { cocos2d::Director::getInstance()->mainLoop(); }
能够看到,跟Windows平台的同样,最终调用到导演类的mainLoop方法,异曲同工。以上即是,Android平台Cocos2dx引擎从启动到进入死循环的过程。
一样,以引擎下的cpp-empty-test
项目工程为例:
iOS工程的入口函数为cpp-empty-test/proj.ios/main.cpp中的main
函数。
int main(int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; int retVal = UIApplicationMain(argc, argv, nil, @"AppController"); [pool release]; return retVal; }
在iOS应用中,都必须在函数main中调用UIApplicationMain
方法来启动应用和设置相应的事件循环。UIApplicationMain函数原型以下:
UIKIT_EXTERN int UIApplicationMain(int argc, char *argv[], NSString * __nullable principalClassName, NSString * __nullable delegateClassName);
其中,argc是参数的个数,argv是可变的参数列表,principalClassName表明的是一个继承自UIApplication类的类名,delegateClassName是应用程序的代理类名称。在跟踪AppController类代码以前,有必要先了解下iOS应用的运行状态以及相应的生命周期方法:
生命周期方法有:
application:didFinishLaunchingWithOptions::
应用程序启动并进行初始化时会调用该方法。
applicationDidBecomeActive:
应用程序进入前台并处于活动状态时调用该方法。
applicationWillResignActive:
应用程序从活动状态进入非活动状态时调用该方法。
applicationDidEnterBackground:
应用程序进入后台时调用该方法。
applicationWillEnterForeground:
应用程序进入前台,但尚未处于活动状态时调用该方法。
applicationWillTerminate:
应用程序被终止时调用该方法。
进入AppController类,AppController实现了UIApplicationDelegate,并重写了相应的生命周期的方法。那么,重点看application:didFinishLaunchingWithOptions:
方法:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { cocos2d::Application *app = cocos2d::Application::getInstance(); app->initGLContextAttrs(); cocos2d::GLViewImpl::convertAttrs(); // Override point for customization after application launch. // Add the view controller's view to the window and display. window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]]; CCEAGLView *eaglView = [CCEAGLView viewWithFrame: [window bounds] pixelFormat: (NSString*)cocos2d::GLViewImpl::_pixelFormat depthFormat: cocos2d::GLViewImpl::_depthFormat preserveBackbuffer: NO sharegroup: nil multiSampling: NO numberOfSamples: 0]; // Use RootViewController manage CCEAGLView viewController = [[RootViewController alloc] initWithNibName:nil bundle:nil]; viewController.wantsFullScreenLayout = YES; viewController.view = eaglView; // Set RootViewController to window if ( [[UIDevice currentDevice].systemVersion floatValue] < 6.0) { // warning: addSubView doesn't work on iOS6 [window addSubview: viewController.view]; } else { // use this method on ios6 [window setRootViewController:viewController]; } [window makeKeyAndVisible]; [[UIApplication sharedApplication] setStatusBarHidden: YES]; // IMPORTANT: Setting the GLView should be done after creating the RootViewController cocos2d::GLViewImpl *glview = cocos2d::GLViewImpl::createWithEAGLView(eaglView); cocos2d::Director::getInstance()->setOpenGLView(glview); app->run(); return YES; }
这里主要是实例化一个UIWindow对象,每个UIWindow对象上面都有一个根视图,它所对应的控制器为根视图控制器(ViewController),最后把根视图控制器放到UIWindow上。最后,app->run()
会调用到CCApplication-ios.mm(这个也是根据项目中的预编译宏实现)中的run方法:
int Application::run() { if (applicationDidFinishLaunching()) { [[CCDirectorCaller sharedDirectorCaller] startMainLoop]; } return 0; }
这里有个跟生命周期方法相似的名字applicationDidFinishLaunching,这个会调到ApAppDelegate的applicationDidFinishLaunching方法,这点跟Windows平台相似,通常是在这个方法作跟游戏内容相关的初始化。run方法接下来,就是调startMainLoop方法,看这个名字,知道跟要找的目标很接近了,再继续跟下去。这里会调到CCDirectorCaller-ios.mm中的startMainLoop方法:
-(void) startMainLoop { // Director::setAnimationInterval() is called, we should invalidate it first [self stopMainLoop]; displayLink = [NSClassFromString(@"CADisplayLink") displayLinkWithTarget:self selector:@selector(doCaller:)]; [displayLink setFrameInterval: self.interval]; [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; }
首先是经过NSClassFromString动态加载CADisplayLink类,而后调用了该类的displayLinkWithTarget方法,该方法相似定时器的功能,周期的调用该selector包装的方法(即:doCaller:方法):
-(void) doCaller: (id) sender { if (isAppActive) { cocos2d::Director* director = cocos2d::Director::getInstance(); [EAGLContext setCurrentContext: [(CCEAGLView*)director->getOpenGLView()->getEAGLView() context]]; director->mainLoop(); } }
至此,咱们就找到了导演类的mainLoop方法,开启了引擎的主循环。以上,即是Cocos2dx引擎在iOS平台从启动到进入主循环的过程。
经过以上简单的分析,咱们知道,Cocos2dx引擎利用了相应的平台循环方式来调用导演类的主循环来进入引擎的内部工做。下一篇继续经过代码的方式来梳理下Cocos2dx的渲染过程。若是在本篇中,有你以为不对的地方,也欢迎来和我讨论。
技术交流QQ群:528655025
做者:AlphaGL
出处:http://www.cnblogs.com/alphagl/ 版权全部,欢迎保留原文连接进行转载 :)