Cocos2dx如何实现每一帧的触发

版本:cocos2dx-3.3

Cocos2dx通过调用Director的类mainLoop函数实现整个游戏调度。

源码:CCDirector.cpp
void  DisplayLinkDirector  ::mainLoop()
{
     if  (_purgeDirectorInNextLoop)
    {
        _purgeDirectorInNextLoop =  false ;
        purgeDirector();
    }
     else  if  (! _invalid)
    {
        drawScene();
    
         // release the objects
         PoolManager::getInstance()->getCurrentPool()->clear();
    }
}

上述代码中,drawScene函数主要实现游戏中的Scheduler调度和展示对象渲染;PoolManager ::getInstance()->getCurrentPool()->clear()一句实现的便是Cocos2dx自动释放池对象的释放。
跟游戏开发中关系比较密切的drawScene方法在后续的文章中再解析,在这里主要先讲一下Cocos2dx如何触发每一次的mainLoop。


mainLoop的调用,在不同系统平台下实现的方式有所不同。


Windows版本

源码:CCApplication-win32.cpp
int  Application  ::run()
{
    ... ... 
     LARGE_INTEGER  nLast;
     LARGE_INTEGER  nNow;
    QueryPerformanceFrequency(&nFreq);
    QueryPerformanceCounter(&nLast);
     ... ... 
     auto  glview = director->getOpenGLView();
     ... ... 
     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);
        }
    }
    .... ....
}

void  Application  ::setAnimationInterval( double  interval )
{
     LARGE_INTEGER  nFreq;
    QueryPerformanceFrequency(&nFreq);
    _animationInterval.QuadPart = (  LONGLONG )( interval  * nFreq.QuadPart);
}

上述代码中,Application::run函数由main.cpp入口函数触发;Application::setAnimationInterval函数则由Director::setAnimationInterval函数触发,所以当我们更改游戏帧频时,Application中 _animationInterval成员的值也会被改变。

在windows系统平台下, QueryPerformanceFrequency作用是获取系统高精度计数器的频率,即系统每秒钟触发的高精度计数器计数的次数;而 QueryPerformanceCounter这两个方法分别用来获取系统高精度计时器的当前计数。关于这两个接口的更多介绍可百度查询。

由此可知, _animationInterval.QuadPart中存储的值为游戏单帧时间对应的高精度计时器计数。 因此做语句nNow.QuadPart - nLast.QuadPart > _animationInterval.QuadPart的判断,目的在于如果前后两次调用的间隔时间超过了游戏设置的帧频时间,则进入执行游戏流程,否则,进入Sleep(1)操作,将控制权交给其他程序。


Android版本
Windows版本的mainLoop函数是在Application::run()中调用,但Android版本则不同,请看代码。

源代码:Cocos2dxRenderer.java (位于PROJECT_DIR\cocos2d\cocos\platform\android\java\src\org\cocos2dx\lib目录下,其中PROJECT_DIR为项目路径)
    @Override
    public void onDrawFrame(final GL10 gl) {
        /*
         * No need to use algorithm in default(60 FPS) situation,
         * since onDrawFrame() was called by system 60 times per second by default.
         */
        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) {
                }
            }
            /*
             * Render time MUST be counted in, or the FPS will slower than appointed.
            */
            this.mLastTickInNanoSeconds = System.nanoTime();
            Cocos2dxRenderer.nativeRender();
        }
    }
     ... ... 
      private static native void nativeRender();
     ... ... 
     public static void setAnimationInterval(final double animationInterval) {
         Cocos2dxRenderer.sAnimationInterval = (long) (animationInterval * Cocos2dxRenderer.NANOSECONDSPERSECOND);
    }


源代码:Java_org_cocos2dx_lib_Cocos2dxRenderer.cpp(位于PROJECT_DIR\cocos2d\cocos\platform\android\jni目录下)
    JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeRender(JNIEnv* env) {
        cocos2d::Director::getInstance()->mainLoop();
    }

源代码:CCApplication-android.cpp(位于PROJECT_DIR\cocos2d\cocos\platform\android目录下
void Application::setAnimationInterval(double interval)
{
  JniMethodInfo methodInfo;
  if (!  JniHelper::getStaticMethodInfo(methodInfo, "org/cocos2dx/lib/Cocos2dxRenderer", "setAnimationInterval", "(D)V"))
  {
    CCLOG("%s %d: error to get methodInfo", __FILE__, __LINE__);
  }
  else
  {
    methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, interval);
  }
}

显然,mainLoop函数是由Cocos2dxRenderer类中的JNI接口Cocos2dxRender.nativeRender方法触发(若不清楚JNI接口为何物,请移步 http://blog.csdn.net/skywalker256/article/details/4677644),而进一步Cocos2dxRender.nativeRender在Cocos2dxRenderer类中的onDrawFrame中被调用。
因此mainLoop函数的触发就受Cocos2dxRenderer.onDrawFrame方法所控制。GLSurfaceView.Renderer接口中的onDrawFrame方法会在系统每一次重画 GLSurfaceView时调用,默认频率为每秒60次。Cocos2dxRenderer类实现了GLSurfaceView.Renderer接口,并作为GLSurfaceView的渲染对象(具体参阅同目录下的Cocos2dxActivity.java文件中init方法的代码),系统自然就会以每秒60次的频率调用该onDrawFrame方法。
尽管如此, Cocos2dx自己对调用的频率也做了限制,具体如何控制,可以参考上面贴出代码。
需要说明的是Cocos2dxRenderer类中sAnimationInterval是在我们调用Director::setAnimationInterval函数时,在Application::setAnimationInterval函数内,通过调用JNIHelper方法调用了Cocos2dxRender.setAnimationInterval方法设置进来,并被换算成以纳秒为单位的数值(1秒=1000000000纳秒)。


iOS版本
思路大同小异,iOS版本调用的源代码先后顺序如下,

源代码:AppController.mm(位于PROJECT_DIR\proj.ios_mac\ios目录下)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {   

    cocos2d::Application *app = cocos2d::Application::getInstance();

    ... ...

    app->run();

    return YES;
}
该函数为iOS平台应用的执行入口,调用了Application::run函数。

源代码:CCApplication-ios.mm(位于PROJECT_DIR\cocos2d\cocos\platform\ios目录下)
int Application::run()
{
    if (applicationDidFinishLaunching()) 
    {
        [[CCDirectorCaller sharedDirectorCaller] startMainLoop];
    }
    return 0;
}


void Application::setAnimationInterval(double interval)
{
    [[CCDirectorCaller sharedDirectorCaller] setAnimationInterval: interval ];
}

void Application::setAnimationInterval(double interval)
{
    [[CCDirectorCaller sharedDirectorCaller] setAnimationInterval: interval ];
}

源代码:CCDirectorCaller-ios.mm(位于PROJECT_DIR\cocos2d\cocos\platform\ios目录下)
-(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];

}

-(void) stopMainLoop
{
    [displayLink invalidate];
    displayLink = nil;
}

-(void) setAnimationInterval:(double)intervalNew
{
    // Director::setAnimationInterval() is called, we should invalidate it first
    [self stopMainLoop];
        
    self.interval = 60.0 * intervalNew;
        
    displayLink = [NSClassFromString(@"CADisplayLink") displayLinkWithTarget:self selector:@selector(doCaller:)];
    [displayLink setFrameInterval: self.interval];
    [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

}

   
其中,CADisplayLink允许我们能定时刷新展示对象的绘制。
官方给出的解释为"A CADisplayLink object is a timer object that allows your application to synchronize its drawing to the refresh rate of the display.
Your application creates a new display link, providing a target object and a selector to be called when the screen is updated. Next, your application adds the display link to a run loop."
指定执行目标对象和回调方法,iOS会在屏幕的每一帧刷新时调用,如上述代码则为对应CCDirectorCaller对象的doCaller方法,而控制doCaller的刷新频率,则由CADisplayLink的setFrameInterval的方法控制了,带入的参数仍是由Director::setAnimationInterval函数一步步传递过来的。

你可能感兴趣的:(Cocos2dx)