首页 > 代码库 > Cocos2d-x 动手实现游戏主循环

Cocos2d-x 动手实现游戏主循环

由于Cocos2d-x封装的很好,所以对于很多新手,他们只知道先new一个场景,在场景上添加布景或精灵,然后用Director的runWithScene便可以运行游戏了。如果给一个精灵加个动作,精灵就会动,如果给布景层添加个定时器,游戏会定时执行。你知道为什么会这样吗?

作为一个游戏开发者,我觉得进入游戏这一行业之前,一定要先搞清楚“游戏主循环”这个东东,可惜我到现在才来研究这个东东。或许网上关于Cocos2d-x游戏主循环的讲解一大把,但是这篇文章,我会教你怎么来实现游戏主循环。

一、了解Cocos2d-x游戏的入口

windows应用程序入口一般都在main(),我们Cocos2d-x找到main.cpp文件,里面的代码很简单:

int APIENTRY _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();
}

好了,我们知道了Application类里面的run函数就是入口了。找到Application.cpp文件,我截取run函数最重要的那部分代码:

while(!glview->windowShouldClose())
    {
        QueryPerformanceCounter(&nNow);
        if (nNow.QuadPart - nLast.QuadPart > _animationInterval.QuadPart)
        {
            nLast.QuadPart = nNow.QuadPart;
            
            director->mainLoop();
            glview->pollEvents();
        }
        else
        {
            Sleep(0);
        }
    }

看到没,这里有一个死循环,粗略地看,也就是这样的:_animationInterval是我们设定的循环间隔,每次前后两个时间的差大于这个间隔,就执行一次主循环director->mainLoop(),小于这个间隔呢,就一直获取当前的时间,直到大于这个间隔。

大概就是这样,为什么程序会一直执行呢?接下来我们来自己动手实现以下这个循环。




二、Windows性能计数器(Performance Counter)

在run函数中,我们发现每次时间差大于设定的间隔,就会执行游戏主循环,那么这个时间差是哪里来的呢?就是这个东东:QueryPerformanceCounter,选中QueryPerformanceCounter按F12,看看是何方神圣:

//
// Performance counter API's
//

WINBASEAPI
BOOL
WINAPI
QueryPerformanceCounter(
    _Out_ LARGE_INTEGER * lpPerformanceCount
    );


WINBASEAPI
BOOL
WINAPI
QueryPerformanceFrequency(
    _Out_ LARGE_INTEGER * lpFrequency
    );

也就是说,这两个函数其实是Windows API来的。

QueryPerformanceCounter():返回高精度性能计数器的值,QueryPerformanceCounter确切的精确计时的最小单位是与系统有关的,所以必须查询系统以得到QueryPerformanceCounter返回的滴答声的频率。

QueryPerformanceFrequency()则提供了这个频率值,返回每秒滴答声的个数。

计算两个时间的间隔,就是用QueryPerformanceCounter()先后获取两个时间,这两个时间的间隔再除以时钟频率,就是真正的时间差了。


再看类型LAGER_INTEGER:

#if defined(MIDL_PASS)
typedef struct _LARGE_INTEGER {
#else // MIDL_PASS
typedef union _LARGE_INTEGER {
    struct {
        DWORD LowPart;
        LONG HighPart;
    } DUMMYSTRUCTNAME;
    struct {
        DWORD LowPart;
        LONG HighPart;
    } u;
#endif //MIDL_PASS
    LONGLONG QuadPart;
} LARGE_INTEGER;
LARGE_INTEGER既可以是一个8字节长的整型数(LONGLONG),也可以是两个4字节长的整型数的联合结构,具体用法根据编译器是否支持64位而定。




三、具体实现主循环

往事具备,只欠东风。有了上面那些知识,我们就可以自己动手实现了。

用VS2012新建一个win32控制台工程,打开main.cpp文件开始敲代码了。

先创建一个MainLoop类:

class MainLoop
{
public:
	MainLoop():timeCount(0){}
	void setAnimationInterval(double interval);
	void run();

private:
	LARGE_INTEGER _animationInteval;
	unsigned timeCount;
};

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

void MainLoop::run()
{
	LARGE_INTEGER nFreq;
	LARGE_INTEGER nBeginTime;
	LARGE_INTEGER nEndTime;

	QueryPerformanceFrequency(&nFreq);		// 获取机器内部计时器的时钟频率
	QueryPerformanceCounter(&nBeginTime);	// 获得第一次计数	

	while (1)	// 死循环
	{
		QueryPerformanceCounter(&nEndTime);	// 获得第二次计数
		if (nEndTime.QuadPart - nBeginTime.QuadPart > _animationInteval.QuadPart)	// 两次计数差大于指定间隔则执行
		{
			timeCount++;
			printf("%d\n", timeCount);
			nBeginTime = nEndTime;
		}
	}
}

这个类可以设置时间间隔,当前后两次时间差大于该间隔的时候,就输出一次timeCount,timeCount就是计数器。


int _tmain(int argc, _TCHAR* argv[])
{
	MainLoop loop;
	loop.setAnimationInterval(1);
	loop.run();

	system("pause");
	return 0;
}
在main函数中,我们先设置时间间隔为1秒,开始run,这时可以看到,每隔1秒钟,控制台就会输出计数器:



这就是我们要的循环了,想一下,时间间隔越小,计数器就加的越快,当时间间隔为1/60时,就跟Cocos2d-x的一样了。

回去看看Application的代码,是不是觉得很像很像。只不过在Application中执行的不是计数器叠加,而是director->mainLoop(),也就是所谓的游戏主循环。


游戏主循环的分析就到这里了,网上充斥着大量一样的文章,ctrl+c和ctrl+v太简单了,希望我这一篇有别于其他Cocos2d-x游戏主循环的文章能帮到你。

Cocos2d-x 动手实现游戏主循环