欢迎转载:http://blog.csdn.net/fylz1125/article/details/8518737
终于抽时间把这个游戏写完了。由于没有自拍神器,所以把它移植到了Android上,用我的戴妃跑的很欢啊。自此,我算是完成了一个功能比较完善的游戏了。
麻雀虽小,五脏俱全,该有的都有,不该有的估计也有,嘿嘿。这几天把写这个游戏的经历和学习过程整理一下,多写几篇博客,就当做记笔记了。
首先还是就我个人的理解,讲讲游戏引擎的处理流程。
其实游戏逻辑简单化就是一个死循环,如下:
-
- bool game_is_running = true;
-
- while( game_is_running ) {
- update_game();
- display_game();
- }
我们所看到的游戏画面,游戏音乐,以及一些触控,输入等。在逻辑上就是这么一个死循环。这个循环一直在跑,期间会处理一些列的事件,简化之就是上面的两个函数。
cocos2d-x引擎也是如此,所有的逻辑都是在这个主循环下实现的。下面看看cocos2dx在各平台上的主循环实现。
1.Win
看它的main.cpp
- #include "main.h"
- #include "../Classes/AppDelegate.h"
- #include "CCEGLView.h"
-
- USING_NS_CC;
-
- int APIENTRY _tWinMain(HINSTANCE hInstance,
- HINSTANCE hPrevInstance,
- LPTSTR lpCmdLine,
- int nCmdShow)
- {
- UNREFERENCED_PARAMETER(hPrevInstance);
- UNREFERENCED_PARAMETER(lpCmdLine);
-
-
- AppDelegate app;
- CCEGLView* eglView = CCEGLView::sharedOpenGLView();
- eglView->setFrameSize(2048, 1536);
-
-
- eglView->setFrameZoomFactor(0.4f);
- return CCApplication::sharedApplication()->run();
- }
前面都不要关心,只是用来传递OpenGL窗口的,关键是最后一句,CCApplication::sharedApplication()->run()。看这个run函数:
- int CCApplication::run()
- {
- PVRFrameEnableControlWindow(false);
-
-
- MSG msg;
- LARGE_INTEGER nFreq;
- LARGE_INTEGER nLast;
- LARGE_INTEGER nNow;
-
- QueryPerformanceFrequency(&nFreq);
- QueryPerformanceCounter(&nLast);
-
-
- if (!applicationDidFinishLaunching())
- {
- return 0;
- }
-
- CCEGLView* pMainWnd = CCEGLView::sharedOpenGLView();
- pMainWnd->centerWindow();
- ShowWindow(pMainWnd->getHWnd(), SW_SHOW);
-
- while (1)
- {
- if (! PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
- {
-
- QueryPerformanceCounter(&nNow);
-
-
- if (nNow.QuadPart - nLast.QuadPart > m_nAnimationInterval.QuadPart)
- {
- nLast.QuadPart = nNow.QuadPart;
- CCDirector::sharedDirector()->mainLoop();
- }
- else
- {
- Sleep(0);
- }
- continue;
- }
-
- if (WM_QUIT == msg.message)
- {
-
- break;
- }
-
-
- if (! m_hAccelTable || ! TranslateAccelerator(msg.hwnd, m_hAccelTable, &msg))
- {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
- }
-
- return (int) msg.wParam;
- }
不熟悉windows的童鞋估计都知道windows是消息驱动的。这个死循环就是用来处理windows的消息循环的,在其中处理了FPS逻辑,消息分发等。注意看其中红色标标注的
- CCDirector::sharedDirector()->mainLoop();
这是神马东西啊!这个就是cocos2d-x的主循环了,由导演负责维护。从此就进入了cocos2d-x的世界,跟windows没有一毛钱关系了。
2.Android
Android平台的游戏是从一个Activity开始的。(话说好像Android的所有应用都是从Activity开始的吧)。
在引擎源码下有个目录是android的java代码,是模板代码,几乎所有的游戏都用这个,不怎么变。不信可以你可以看
YourCocos2dxDir/cocos2dx/platform/android/java这个目录,就是创建android工程的时候会去这个目录拷贝java代码作为模板。
来看看HelloCpp的代码
- package org.cocos2dx.hellocpp;
-
- import org.cocos2dx.lib.Cocos2dxActivity;
-
- import android.os.Bundle;
-
- public class HelloCpp extends Cocos2dxActivity{
-
- protected void onCreate(Bundle savedInstanceState){
- super.onCreate(savedInstanceState);
- }
-
- static {
- System.loadLibrary("hellocpp");
- }
- }
很简单,对吧。几行代码而已,这里说明了两个问题
1. Cocos2dxActivity才是核心的Activity。
2. 游戏的C++部分包括引擎部分,被编译成了动态链接库hellocpp。这里就是加载了hellocpp动态链接库。
这个动态链接库是在用NDK编译的时候生成的,就是libs/armeabi/libhellocpp.so。(扯远了)
还是来看看Cocos2dxActivity这个Activity。
- public abstract class Cocos2dxActivity extends Activity implements Cocos2dxHelperListener {
-
-
-
-
- private static final String TAG = Cocos2dxActivity.class.getSimpleName();
-
-
-
-
-
- private Cocos2dxGLSurfaceView mGLSurfaceView;
- private Cocos2dxHandler mHandler;
-
-
-
-
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- this.mHandler = new Cocos2dxHandler(this);
-
- this.init();
-
- Cocos2dxHelper.init(this, this);
- }
-
-
-
-
-
-
-
-
-
- @Override
- protected void onResume() {
- super.onResume();
-
- Cocos2dxHelper.onResume();
- this.mGLSurfaceView.onResume();
- }
-
- @Override
- protected void onPause() {
- super.onPause();
-
- Cocos2dxHelper.onPause();
- this.mGLSurfaceView.onPause();
- }
-
- @Override
- public void showDialog(final String pTitle, final String pMessage) {
- Message msg = new Message();
- msg.what = Cocos2dxHandler.HANDLER_SHOW_DIALOG;
- msg.obj = new Cocos2dxHandler.DialogMessage(pTitle, pMessage);
- this.mHandler.sendMessage(msg);
- }
-
- @Override
- public void showEditTextDialog(final String pTitle, final String pContent, final int pInputMode, final int pInputFlag, final int pReturnType, final int pMaxLength) {
- Message msg = new Message();
- msg.what = Cocos2dxHandler.HANDLER_SHOW_EDITBOX_DIALOG;
- msg.obj = new Cocos2dxHandler.EditBoxMessage(pTitle, pContent, pInputMode, pInputFlag, pReturnType, pMaxLength);
- this.mHandler.sendMessage(msg);
- }
-
- @Override
- public void runOnGLThread(final Runnable pRunnable) {
- this.mGLSurfaceView.queueEvent(pRunnable);
- }
-
-
-
-
- public void init() {
-
-
- ViewGroup.LayoutParams framelayout_params =
- new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
- ViewGroup.LayoutParams.FILL_PARENT);
- FrameLayout framelayout = new FrameLayout(this);
- framelayout.setLayoutParams(framelayout_params);
-
-
- ViewGroup.LayoutParams edittext_layout_params =
- new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
- Cocos2dxEditText edittext = new Cocos2dxEditText(this);
- edittext.setLayoutParams(edittext_layout_params);
-
-
- framelayout.addView(edittext);
-
-
- this.mGLSurfaceView = this.onCreateView();
-
-
- framelayout.addView(this.mGLSurfaceView);
-
- this.mGLSurfaceView.setCocos2dxRenderer(new Cocos2dxRenderer());
- this.mGLSurfaceView.setCocos2dxEditText(edittext);
-
-
- setContentView(framelayout);
- }
-
- public Cocos2dxGLSurfaceView onCreateView() {
- return new Cocos2dxGLSurfaceView(this);
- }
-
-
-
-
- }
代码很多,呵呵。其实核心就是那个mGLSurfaceView和它的渲染器new Cocos2dxRenderer()。在Android上,OpenGL的渲染是由一个GLSurfaceView和其渲染器Render组成。GLSurfaceView显示界面,Render渲染更新。这个Render其实是一个渲染线程,不停再跑,由框架层维护。这里不多讲。
来看这个Cocos2dxRenderer
- package org.cocos2dx.lib;
-
- import javax.microedition.khronos.egl.EGLConfig;
- import javax.microedition.khronos.opengles.GL10;
-
- import android.opengl.GLSurfaceView;
-
- public class Cocos2dxRenderer implements GLSurfaceView.Renderer {
-
-
-
-
- private final static long NANOSECONDSPERSECOND = 1000000000L;
- private final static long NANOSECONDSPERMICROSECOND = 1000000;
-
- private static long sAnimationInterval = (long) (1.0 / 60 * Cocos2dxRenderer.NANOSECONDSPERSECOND);
-
-
-
-
-
- private long mLastTickInNanoSeconds;
- private int mScreenWidth;
- private int mScreenHeight;
-
-
-
-
-
-
-
-
-
- public static void setAnimationInterval(final double pAnimationInterval) {
- Cocos2dxRenderer.sAnimationInterval = (long) (pAnimationInterval * Cocos2dxRenderer.NANOSECONDSPERSECOND);
- }
-
- public void setScreenWidthAndHeight(final int pSurfaceWidth, final int pSurfaceHeight) {
- this.mScreenWidth = pSurfaceWidth;
- this.mScreenHeight = pSurfaceHeight;
- }
-
-
-
-
-
- @Override
- public void onSurfaceCreated(final GL10 pGL10, final EGLConfig pEGLConfig) {
- Cocos2dxRenderer.nativeInit(this.mScreenWidth, this.mScreenHeight);
- this.mLastTickInNanoSeconds = System.nanoTime();
- }
-
- @Override
- public void onSurfaceChanged(final GL10 pGL10, final int pWidth, final int pHeight) {
- }
-
- @Override
- public void onDrawFrame(final GL10 gl) {
-
-
-
-
-
-
-
-
-
-
-
-
- Cocos2dxRenderer.nativeRender();
-
-
-
-
-
-
-
-
-
-
-
-
- }
-
-
-
-
-
- private static native void nativeTouchesBegin(final int pID, final float pX, final float pY);
- private static native void nativeTouchesEnd(final int pID, final float pX, final float pY);
- private static native void nativeTouchesMove(final int[] pIDs, final float[] pXs, final float[] pYs);
- private static native void nativeTouchesCancel(final int[] pIDs, final float[] pXs, final float[] pYs);
- private static native boolean nativeKeyDown(final int pKeyCode);
- private static native void nativeRender();
- private static native void nativeInit(final int pWidth, final int pHeight);
- private static native void nativeOnPause();
- private static native void nativeOnResume();
-
- public void handleActionDown(final int pID, final float pX, final float pY) {
- Cocos2dxRenderer.nativeTouchesBegin(pID, pX, pY);
- }
-
- public void handleActionUp(final int pID, final float pX, final float pY) {
- Cocos2dxRenderer.nativeTouchesEnd(pID, pX, pY);
- }
-
- public void handleActionCancel(final int[] pIDs, final float[] pXs, final float[] pYs) {
- Cocos2dxRenderer.nativeTouchesCancel(pIDs, pXs, pYs);
- }
-
- public void handleActionMove(final int[] pIDs, final float[] pXs, final float[] pYs) {
- Cocos2dxRenderer.nativeTouchesMove(pIDs, pXs, pYs);
- }
-
- public void handleKeyDown(final int pKeyCode) {
- Cocos2dxRenderer.nativeKeyDown(pKeyCode);
- }
-
- public void handleOnPause() {
- Cocos2dxRenderer.nativeOnPause();
- }
-
- public void handleOnResume() {
- Cocos2dxRenderer.nativeOnResume();
- }
-
- private static native void nativeInsertText(final String pText);
- private static native void nativeDeleteBackward();
- private static native String nativeGetContentText();
-
- public void handleInsertText(final String pText) {
- Cocos2dxRenderer.nativeInsertText(pText);
- }
-
- public void handleDeleteBackward() {
- Cocos2dxRenderer.nativeDeleteBackward();
- }
-
- public String getContentText() {
- return Cocos2dxRenderer.nativeGetContentText();
- }
-
-
-
-
- }
代码很多,一副貌似很复杂的样子。其实脉络很清晰的,我们只看脉络,不考虑细节哈。我们顺藤摸瓜来...
首先要知道GLSurfaceView的渲染器必须实现GLSurfaceView.Renderer接口。就是上面的三个Override方法。
onSurfaceCreated在窗口建立的时候调用,onSurfaceChanged在窗口建立和大小变化是调用,onDrawFrame这个方法就跟普通View的Ondraw方法一样,窗口建立初始化完成后渲染线程不停的调这个方法。这些都是框架决定的,就是这个样子。下面分析下代码几处标记的地方:
看标记①,窗口建立,这个时候要进行初始化。这个时候它调用了一个native函数,就是标记②。看到这个函数形式是不是能想到什么呢,等下再说。
看标记③,前面说了,这个函数会被渲染线程不停调用(像不像主循环的死循环啊)。然后里面有个很牛擦的函数④。
这又是一个native的函数,呵呵。
native的代码是神马啊,就是C++啊。知之为知之,不知谷歌之。
既然是java调用C++,那就是jni调用了。
我们来看看jni/hellocpp/下的main.cpp
- #include "AppDelegate.h"
- #include "platform/android/jni/JniHelper.h"
- #include <jni.h>
- #include <android/log.h>
-
- #include "HelloWorldScene.h"
-
- #define LOG_TAG "main"
- #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
-
- using namespace cocos2d;
-
- extern "C"
- {
-
- jint JNI_OnLoad(JavaVM *vm, void *reserved)
- {
- JniHelper::setJavaVM(vm);
-
- return JNI_VERSION_1_4;
- }
-
- void Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit(JNIEnv* env, jobject thiz, jint w, jint h)
- {
- if (!CCDirector::sharedDirector()->getOpenGLView())
- {
- CCEGLView *view = CCEGLView::sharedOpenGLView();
- view->setFrameSize(w, h);
- CCLog("with %d,height %d",w,h);
-
- AppDelegate *pAppDelegate = new AppDelegate();
- CCApplication::sharedApplication()->run();
- }
- else
- {
- ccDrawInit();
- ccGLInvalidateStateCache();
-
- CCShaderCache::sharedShaderCache()->reloadDefaultShaders();
- CCTextureCache::reloadAllTextures();
- CCNotificationCenter::sharedNotificationCenter()->postNotification(EVNET_COME_TO_FOREGROUND, NULL);
- CCDirector::sharedDirector()->setGLDefaultValues();
- }
- }
-
- }
根据Jni的命名规则,那个标注②的nativeInit方法就是上面红色一长串(呵呵)。窗口建立起来后调用nativeInit方法,就是调用这个C++的实现。这里做了窗口的初始化处理。
看标注⑤,你以为这个run函数就进入主循环了么,呵呵
看这个run
- <span style="font-size:18px;">int CCApplication::run()
- {
-
- if (! applicationDidFinishLaunching())
- {
- return 0;
- }
-
- return -1;
- }</span>
我们看到了神马!实质上就调了一下applicationDidFinishLaunching,别的什么也没干。所以这里没有进入主循环。
现在再看③和④。这个逻辑貌似就是主循环。
这个nativeRender()函数的实现在Yourcocos2dDir/cocos2dx/platform/android/jni/Java_org_cocos2dx_lib_Cocos2dxRenderer.cpp
- #include "text_input_node/CCIMEDispatcher.h"
- #include "CCDirector.h"
- #include "../CCApplication.h"
- #include "platform/CCFileUtils.h"
- #include "CCEventType.h"
- #include "support/CCNotificationCenter.h"
- #include "JniHelper.h"
- #include <jni.h>
-
- using namespace cocos2d;
-
- extern "C" {
- JNIEXPORT void JNICALL Java_org_cocos2dx_lib_<span style="color:#ff0000;">Cocos2dxRenderer_nativeRender</span>(JNIEnv* env) {
- <span style="color:#ff0000;">cocos2d::CCDirector::sharedDirector()->mainLoop();
- }
-
- JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeOnPause() {
- CCApplication::sharedApplication()->applicationDidEnterBackground();
-
- CCNotificationCenter::sharedNotificationCenter()->postNotification(EVENT_COME_TO_BACKGROUND, NULL);
- }
-
- JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeOnResume() {
- if (CCDirector::sharedDirector()->getOpenGLView()) {
- CCApplication::sharedApplication()->applicationWillEnterForeground();
- }
- }
-
- JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInsertText(JNIEnv* env, jobject thiz, jstring text) {
- const char* pszText = env->GetStringUTFChars(text, NULL);
- cocos2d::CCIMEDispatcher::sharedDispatcher()->dispatchInsertText(pszText, strlen(pszText));
- env->ReleaseStringUTFChars(text, pszText);
- }
-
- JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeDeleteBackward(JNIEnv* env, jobject thiz) {
- cocos2d::CCIMEDispatcher::sharedDispatcher()->dispatchDeleteBackward();
- }
-
- JNIEXPORT jstring JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeGetContentText() {
- JNIEnv * env = 0;
-
- if (JniHelper::getJavaVM()->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK || ! env) {
- return 0;
- }
- const char * pszText = cocos2d::CCIMEDispatcher::sharedDispatcher()->getContentText();
- return env->NewStringUTF(pszText);
- }
- }
看上面标注,找到导演了,导演又开始主循环了。真是众里寻他千百度,那人却在灯火阑珊处啊。
到这里可以发现,Android上的主循环跟win上的不太一样,它不是一个简单的while就完了。它是由java的渲染线程发起的,通过不断调用render来驱动。
3.iOs
ios上面和Android上类似。看AppController.mm
直接看标注,跟进run()
- <span style="font-size:18px;">int CCApplication::run()
- {
- if (applicationDidFinishLaunching())
- {
- <span style="color:#ff0000;">[[CCDirectorCaller sharedDirectorCaller] startMainLoop]</span>;
- }
- return 0;
- }</span>
再跟标注的startMainLoop()
- -(void) startMainLoop
- {
-
- [displayLink invalidate];
- displayLink = nil;
- NSLog(@"run loop !");
- displayLink = [NSClassFromString(@"CADisplayLink") displayLinkWithTarget:self selector:@selector(doCaller:)];
- [displayLink setFrameInterval: self.interval];
- [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
- }
好了,看红色标注。这个貌似循环不起来啊,呵呵。
仔细看他加载的这个类CADisplayLink,就是这个东西循环起来的。这其实就是个定时器,默认每秒运行60次,其有个属性可以设置FPS。看后面有个回调函数doCaller,跟进
- -(void) doCaller: (id) sender
- {
- cocos2d::CCDirector::sharedDirector()->mainLoop();
- }
好了,终于又看到导演了。导演很忙,又开始主循环了。
一旦进入主循环,游戏就开始我们自己设计的游戏逻辑。
打字打的睡着了....不打了