Cocos2dx源码赏析(1)之启动流程及主循环

Cocos2dx源码赏析(1)之启动流程和主循环

我们清楚Cocos2dx是如出一辙暂缓开源之跨平台游戏引擎,而上学开源项目一个于实用的方法就是是读源码。所谓,“源码之前,了无暧昧”。而笔者从的也罢是耍支付工作,因此,通过梳理下源码的条,来深化对Cocos2dx游戏引擎的懂得。

既然如此,Cocos2dx是过平台的,那么,就产生对不同平台运行的入口以及保障引擎运转的“死循环”。下面,就各自于Windows、Android、iOS三独阳台说明下Cocos2dx自启动至跻身主循环的过程。

1、Windows

以引起擎下之cpp-empty-test路工程也例:
Windows工程的入口函数为cpp-empty-test/win32/main.cpp中之_tWinMain函数。

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(通过先行处理宏来针对不同之平台实施不一的代码)。这里,并没有召开呀特别的拍卖,只是发了生相应的初始化的行事。

C++ 1

如若当CCApplication-win32.h以及CCApplication-win32.cpp代码中都有诸如此类的宏判断:

#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32

连续追踪下去,可以发现CC_PLATFORM_WIN32于概念了WIN32宏时定义。

接下来,便执行Application::getInstance()->run()代码,这里的Application为单例的落实,这也是Cocos2dx单例常用之兑现方式,在2.x本的引擎中,单例的实现吗sharedApplication,这是仿照Objective-C的写法。继续看CCApplication-win32遭遇之run方法:

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的兑现:

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平台从起步暨主循环,代码执行的流程,简单的梳理,可以知晓引擎代码是怎样架构的。

2、Android

在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引擎从起步至进入死循环的历程。

3、iOS

同样,以引起擎下之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应用的运转状态及对应的生命周期方法:

  • Not Running(非运行状态):应用尚未运行或吃系统已。
  • Inactive(前台非活动状态):应用在上前台状态,但是还免能够承受事件处理。
  • Active(前台活动状态):应用上前台,能领事件处理。
  • Background(后台状态):应用上后台后,依然会实施代码。如果产生可实行之代码,就会见履,如果无可实行的代码或可实施之代码执行完毕,应用会这进挂于状态。
  • Suspended(挂于状态):处于挂于底以上同一栽“冷冻”状态,不能够实施代码。如果系统内存不够,应用会受停。

生命周期方法发生:
application:didFinishLaunchingWithOptions:
应用程序启动并展开初始化时会调用该办法。

applicationDidBecomeActive:
应用程序进入前台并处于活动状态时调用该措施。

applicationWillResignActive:
应用程序从活动状态进入未活动状态时调用该办法。

applicationDidEnterBackground:
应用程序进入后台C++时调用该法。

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/
版权所有,欢迎保留原文链接进行转载 🙂