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://www.jb51.cc/article/p-mjmbpfkl-vu.html),而进一步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函数一步步传递过来的。

相关文章

    本文实践自 RayWenderlich、Ali Hafizji 的文章《...
Cocos-code-ide使用入门学习地点:杭州滨江邮箱:appdevzw@1...
第一次開始用手游引擎挺激动!!!进入正题。下载资源1:从C...
    Cocos2d-x是一款强大的基于OpenGLES的跨平台游戏开发...
1.  来源 QuickV3sample项目中的2048样例游戏,以及最近《...
   Cocos2d-x3.x已经支持使用CMake来进行构建了,这里尝试...