一、OpenGL结构
cocos2dx程序本质上就是一个OpenGL程序,掌握OpenGL基础是掌握好cocos的前提。OpenGL相关知识见OpenGL部分。
下面列出一个简单的OpenGL程序来展示其基本结构:
语雀内容
二、主循环
当Application对象被初始化后,就可以以设定的帧率执行循环,Application::run是整个程序的入口,Director::mainLoop中是一个循环的所有工作。每一帧就是一次循环,每帧发生的事情如下:
- 处理用户输入:如触摸、鼠标、重力感应
- 动画计算:优先级最高的scheduler,Scheduler::PRIORITY_SYSTEM。
- 物理模拟
- 逻辑更新:scheduler,
- UI树遍历
- UI绘制
- 交换缓冲区
- 双缓冲机制,一个默认缓冲,一个离线缓冲。
- 屏幕展示的为默认缓冲内容,正在渲染的数据输出到离线帧缓冲。
- 渲染完成,交换两个缓冲。
下面是win32平台的Application中关于游戏主循环的代码。
int Application::run(){ // 游戏启动
......;
initGLContextAttrs(); // 初始化GL State
......;
applicationDidFinishLaunching(); // Initialize instance and cocos2d
......;
while(!glview->windowShouldClose()) // 主循环
{
......
if (interval >= ...) // 上一帧距离当前时间间隔>=设置的帧间隔
{ // animationInterval,则立即进行循环
......;
director->mainLoop(); // 游戏主循环
glview->pollEvents(); // 轮询事件,使事件发生会触发回调。
}
else // 时间间隔没有超过,主线程强行sleep。
{
......
}
}
// Director should still do a cleanup if the window was closed manually.
if (glview->isOpenGLReady()) { // 主循环终止,进行收尾工作。
director->end();
director->mainLoop();
director = nullptr;
}
}
//**********************************************
void Director::mainLoop(){
......
else if (! _invalid)
{
drawScene(); // 渲染绘制一帧
//每帧结束,清空栈顶(当前)AutoReleasePool,并release一次所有auto release对象
PoolManager::getInstance()->getCurrentPool()->clear();
}
}
//**********************************************
void Director::drawScene() // 循环主逻辑
{
// calculate "global" dt
calculateDeltaTime(); // 计算帧间隔
if (_openGLView)
{
_openGLView->pollEvents(); // 处理队列中的事件(触摸、按键等,会触发注册绑定的回调)
}
//tick before glClear: issue #533
if (! _paused)
{
_eventDispatcher->dispatchEvent(_eventBeforeUpdate);
_scheduler->update(_deltaTime); // 执行schedule
_eventDispatcher->dispatchEvent(_eventAfterUpdate);
}
_renderer->clear(); // 清除颜色缓冲、深度缓冲
experimental::FrameBuffer::clearAllFBOs(); // 清空当前所有帧缓冲的附件(颜色、深度、模板)
_eventDispatcher->dispatchEvent(_eventBeforeDraw); // 绘制前事件
/* to avoid flickr, nextScene MUST be here: after tick and before draw.
* FIXME: Which bug is this one. It seems that it can't be reproduced with v0.9
*/
if (_nextScene)
{
setNextScene(); // 切换到下一个场景
}
pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); // 压栈初始MV矩阵。
if (_runningScene)
{
......
// 清除当前的绘制状态信息
_renderer->clearDrawStats();
// 遍历runningScene为根节点的UI树生成渲染指令加入渲染队列,然后执行渲染。
if(_openGLView)
_openGLView->renderScene(_runningScene, _renderer);
_eventDispatcher->dispatchEvent(_eventAfterVisit); // UI渲染树遍历完成并绘制完成事件
}
// 遍历生成渲染指令,入队列。
if (_notificationNode)
{
_notificationNode->visit(_renderer, Mat4::IDENTITY, 0); // 遍历notifications node生成绘制指令并入绘制队列
}
updateFrameRate(); // 更新帧率
if (_displayStats)
{
#if !CC_STRIP_FPS
showStats(); // show FPS
#endif
}
// 绘制上面的notificationNode
_renderer->render();
_eventDispatcher->dispatchEvent(_eventAfterDraw); // 所有绘制工作完成事件
popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); // MV矩阵弹栈
_totalFrames++; // 记录帧数
// swap buffers
if (_openGLView)
{
_openGLView->swapBuffers(); // 双缓冲机制:调用的glfwSwapBuffers
}
if (_displayStats)
{
#if !CC_STRIP_FPS
calculateMPF();
#endif
}
}
void Director::calculateDeltaTime() // 计算帧间隔
{
// new delta time. Re-fixed issue #1277
if (_nextDeltaTimeZero)
{
_deltaTime = 0;
_nextDeltaTimeZero = false;
_lastUpdate = std::chrono::steady_clock::now();
}
else
{
// delta time may passed by invoke mainLoop(dt)
if (!_deltaTimePassedByCaller)
{
auto now = std::chrono::steady_clock::now();
_deltaTime = std::chrono::duration_cast<std::chrono::microseconds>(now - _lastUpdate).count() / 1000000.0f;
_lastUpdate = now;
}
_deltaTime = MAX(0, _deltaTime);
}
#if COCOS2D_DEBUG
// If we are debugging our code, prevent big delta time
if (_deltaTime > 0.2f)
{
_deltaTime = 1 / 60.0f;
}
#endif
}