版本: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函数一步步传递过来的。