Why?
做什么事情都要问下自己为什么,带着目的去做,才好抓住重点。
为什么要了解Cocos的运行流程呢?没有深入的理解cocos就去开发,这个是能接受的,因为人生不是炒菜,不能等什么都准备好了才下锅。但是在开发了一段时间以后是不是想对cocos的运行机制有一定了解呢。我建议大家要适度的刨根问底,什么叫适度呢?比如现在计算机发展那么多年了,你想了解程序是怎么运行的,你要去学习操作系统、计算机组成原理、逻辑门电路。如果没有基础的人学这些至少要个几年时间,这个就是不适度。但是我们经常用的cocos,我们要去刨根问底要了解什么呢,比如启动流程、绘制流程、结束流程、以及和各个平台的交互等,底层的OpenGL ES我们都可以不用去了解,除非要在绘图方面有深入的研究。
游戏基本运行流程
本人刚进公司做的是嵌入式平台的游戏开发,用C语言开发,我以前也没做过游戏开发,刚开始就是添加了图片或者帧动画到场景中显示,当时我很困惑,是怎么显示出来的呢。后来我就去研究了底层的代码,终于了解了游戏的基本运行原理。一图胜千文,先上图:
粗略代码如下:
int main()
{
initGame();
while(bRun)
{
processLogic(); // 处理逻辑,该函数中会处理游戏中元素的各种属性变化
drawScene(); // 根据游戏中的元素的各种属性进行绘图,比如位置 透明度 缩放等
sleep(1000/8); // 睡眠一段时间 此处为1/8秒,即一秒8帧, 此处是比较粗略的处理方法,没有考虑到逻辑处理和绘图的用时
}
return 0;
}
可以看出,游戏的运行流程就是一个死循环,一直不停的处理逻辑并且绘制界面。直到满足某个条件退出这个死循环则游戏结束。
对照这个流程,我们要去看一下cocos中的这个流程是怎么走的。
游戏引擎版本:3.15
Windows平台
入口:project/proj.win32/main.cpp
int WINAPI _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函数中查看:
int Application::run()
{
...
...
// Initialize instance and cocos2d.
if (!applicationDidFinishLaunching()) // AppDelegate中回调
{
return 1;
}
while(!glview->windowShouldClose()) // 死循环
{
QueryPerformanceCounter(&nNow);
interval = nNow.QuadPart - nLast.QuadPart;
if (interval >= _animationInterval.QuadPart)
{
nLast.QuadPart = nNow.QuadPart;
director->mainLoop(); // 逻辑处理和绘图
glview->pollEvents();
}
else
{
waitMS = (_animationInterval.QuadPart - interval) * 1000LL / freq.QuadPart - 1L;
if (waitMS > 1L)
Sleep(waitMS); // 等待下一帧
}
}
...
...
return 0;
}
是不是和上方的流程一致?(这里只贴了关键代码)。
我们再进入Director的mainLoop函数中查看详细信息。
void Director::mainLoop()
{
if (_purgeDirectorInNextLoop)
{
_purgeDirectorInNextLoop = false;
purgeDirector();
}
else if (_restartDirectorInNextLoop)
{
_restartDirectorInNextLoop = false;
restartDirector();
}
else if (! _invalid)
{
drawScene(); // 处理逻辑和绘图
// release the objects
PoolManager::getInstance()->getCurrentPool()->clear(); // 自动释放池 后面会讲
}
}
再看看drawScene中的逻辑
void Director::drawScene()
{
// 此处会调用各个节点中的update,就是我们经常处理逻辑的地方
if (! _paused)
{
_eventDispatcher->dispatchEvent(_eventBeforeUpdate);
_scheduler->update(_deltaTime);
_eventDispatcher->dispatchEvent(_eventAfterUpdate);
}
...
...
if (_runningScene)
{
#if (CC_USE_PHYSICS || (CC_USE_3D_PHYSICS && CC_ENABLE_BULLET_INTEGRATION) || CC_USE_NAVMESH)
_runningScene->stepPhysicsAndNavigation(_deltaTime);
#endif
//clear draw stats
_renderer->clearDrawStats();
//render the scene
_openGLView->renderScene(_runningScene, _renderer); // 绘图 绘制过程后面说
_eventDispatcher->dispatchEvent(_eventAfterVisit);
}
...
...
}
里面先处理了逻辑,然后绘制的界面。与基本的游戏流程一致。
android平台
看完Windows端,我们来看android端。
1:加载so库
cocos2d\cocos\platform\android\java\src\org\cocos2dx\lib\Cocos2dxActivity.java中在onCreate中调用的onLoadNativeLibraries方法
protected void onLoadNativeLibraries() {
try {
ApplicationInfo ai = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);
Bundle bundle = ai.metaData;
String libName = bundle.getString("android.app.lib_name");
System.loadLibrary(libName);
} catch (Exception e) {
e.printStackTrace();
}
}
现在的so的名称要在Manifest中配置,了解了这个设定后我们后面可以更改这些配置。
2:初始化SurfaceView
surfaceView是安卓中的一种view,是开线程进行绘制的,不会影响主线程的绘制。所以本质上就是一个死循环在跑。
3:添加renderer
this.mGLSurfaceView.setCocos2dxRenderer(new Cocos2dxRenderer());
4:renderer的初始化
@Override
public void onSurfaceCreated(final GL10 GL10, final EGLConfig EGLConfig) {
Cocos2dxRenderer.nativeInit(this.mScreenWidth, this.mScreenHeight);
this.mLastTickInNanoSeconds = System.nanoTime();
mNativeInitCompleted = true;
}
调用了一个nativeInit,我们来找找这个调用了哪个Jni
在cocos2d\cocos\platform\android\javaactivity-android.cpp中我们找到另外Jni调用的地方(关于jni的调用后面会说)
JNIEXPORT void Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit(JNIEnv* env, jobject thiz, jint w, jint h)
{
auto director = cocos2d::Director::getInstance();
auto glview = director->getOpenGLView();
if (!glview)
{
glview = cocos2d::GLViewImpl::create("Android app");
glview->setFrameSize(w, h);
director->setOpenGLView(glview);
cocos2d::Application::getInstance()->run();
}
else
{
cocos2d::GL::invalidateStateCache();
cocos2d::GLProgramCache::getInstance()->reloadDefaultGLPrograms();
cocos2d::DrawPrimitives::init();
cocos2d::VolatileTextureMgr::reloadAllTextures();
cocos2d::EventCustom recreatedEvent(EVENT_RENDERER_RECREATED);
director->getEventDispatcher()->dispatchEvent(&recreatedEvent);
director->setGLDefaultValues();
}
cocos2d::network::_preloadJavaDownloaderClass();
}
看到了没有,里面又有Application中的run函数调用,跟进到run函数中查看
大家注意,每个平台的Application文件是不一样的,这个后面会说到
int Application::run()
{
// Initialize instance and cocos2d.
if (! applicationDidFinishLaunching())
{
return 0;
}
return -1;
}
发现这个run跟windows下面的run是不一样的,那么在哪里绘图呢?我们回头再看看那个renderer
5:renderer绘制
@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 (Cocos2dxRenderer.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();
}
}
在这个绘制函数中我们看到了Cocos2dxRenderer.nativeRender()这个函数的调用,找找调用的哪个jni
cocos2d\cocos\platform\android\jni\Java_org_cocos2dx_lib_Cocos2dxRenderer.cpp
JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeRender(JNIEnv* env) {
cocos2d::Director::getInstance()->mainLoop();
}
是不是看到了熟悉的mainLoop函数。
至此,wnidows和android平台cocos的运行流程已经理清楚了。ios平台不熟悉,不做表述。
总结
万变不离其宗,了解了基本流程以后,就可以迅速找到重点,而不会纠结每一行代码是干什么的。