cocos2d-x: 死磕"HelloWorld"(1)——入口函數及其帶來的疑問

麻雀雖小,五臟俱全。HelloWorld就是一隻小麻雀,但是涵蓋了遊戲製作的基本流程,以及基本的渲染框架。我們的目標就是要通過解剖這隻小麻雀,一窺cocos2d-x遊戲製作全貌。

由於篇幅較長,我們將整個學習過程分爲七篇博文,一篇一個主題。第一個主題就是入口函數,通過觀察入口函數來獲取一點初步的感受,並提出一些問題,以便帶着問題進一步學習。第二個主題是遊戲應用實例的創建,以及它和遊戲運行主函數run()之間的關係。第三個主題是詳解遊戲運行主函數run(),着重分析其中的應用初始化函數applicationDidFinishLaunching()和渲染主循環函數mainLoop(),但是爲了掌握結構,避免混亂,我們會將這兩個函數中所引用的更爲細節的函數放在後續章節中講解,這些細節函數包括HelloWorld場景的創建,渲染的準備和具體實施。因此,第四個主題就是HelloWorld場景的創建,它通過applicationDidFinishLaunching()中調用的HelloWorld::scene()來實現,所以我們將在該篇中詳解HelloWorld::scene()函數。有了場景之後,下一步就是渲染該場景,因此第五個主題是渲染的準備工作,它通過applicationDidFinishLaunching()中調用的runWithScene()實現,功能是把場景入棧,等待渲染。第六個主題就是渲染的具體實施,它通過mainLoop()裏的drawScene()實現,其中涉及一些“底層”的openGL函數調用。最後在第七篇中作一總結。對於一個單純的遊戲製作者而言,只需瞭解前面四篇內容,而後面的渲染部分可以留給引擎開發者們去操心。

本文針對Cocos2d-x 2.2.3版而寫。希望能夠做到兩點:一是結構清晰,二是細節詳盡。但是由於水平有限,必有許多謬誤,歡迎批評指正。

閒話少敘,進入正題。HelloWorld的C++版項目名爲HelloCPP,其結構如下圖所示:

一個入口函數,main.cpp/h, 兩個類,AppDelegate.cpp/h和HelloWorld.cpp/h, 還有一個定義宏的頭文件,AppMacros.h。看起來so easy,有木有。其實還有很多其他的類,比如application類,導演類,場景類等,都沒有顯示出來。之所以只顯示兩個類其實是有深意的。因爲對於單純的遊戲製作者(而非引擎開發者)而言,只需顯示出來的這兩個類,而那些隱藏的類是封裝好的,無需改動。我們將在後面逐漸認識到這一點。

我們現在就來看一下入口函數的頭文件:

main.h

#ifndef __MAIN_H__
#define __MAIN_H__

#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
//定義了一個空宏WIN32_LEAN_AND_MEAN,可以做爲宏開關使用。例如在後面就可以調用#ifdef WIN32_LEAN_AND_MEAN,進行選擇性編譯。從原有的英文註釋中可以看出,該宏開關是用於剔除不必要的Windows頭文件,這樣可以加速生成解決方案。
// Windows Header Files:
#include <windows.h>
//和windows編程相關頭文件。
#include <tchar.h>
//和字符串編碼有關頭文件,方便處理中文。

// C RunTime Header Files
#include "CCStdC.h"
//和運行時間有關頭文件。
#endif    // __MAIN_H__

該頭文件並無特別之處,無非就是包含了三個相關頭文件。至於這幾個頭文件裏的具體內容,已經超越本文主題,暫且擱下。下面就來看看入口函數的源文件:

main.cpp

#include "CCEGLView.h"

USING_NS_CC;//宏開關,用於選擇使用或者不使用cocos2d名空間。默認狀態是使用。它的具體定義在下一代碼片段中展示。

int APIENTRY _tWinMain(HINSTANCE hInstance,
                       HINSTANCE hPrevInstance,
                       LPTSTR    lpCmdLine,
                       int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);//從該宏定義來看,和直接放置hPrevInstance等效。目的是說明該參數未在程序中其他地方引用,只在此處象徵性引用一下,避免編譯警告。
    UNREFERENCED_PARAMETER(lpCmdLine);//同上。
    //以上三行代碼,如果試過在VS裏創建win32項目的童鞋會發現它們是自動生成的,和cocos2d-x沒啥關係。基本上意思就是建立一個帶四個參數的入口函數,但是第二個和第三個參數並未在程序裏引用。這裏就不再贅述了。

    // create the application instance
    AppDelegate app;//聲明瞭一個AppDelegate實例對象app。AppDelegate是應用代理類,一個應用有且僅有一個代理,它繼承了CCApplication類。其具體內容將在第二篇中分解。
    CCEGLView* eglView = CCEGLView::sharedOpenGLView();// 創建了一個CCEGLView類的指針,eglview,顧名思義,跟視窗有關。並且用一靜態成員函數(必須是靜態的,否則無法直接調用)來初始化該指針。
    eglView->setViewName("HelloCpp");//給窗口命名。
    eglView->setFrameSize(2048, 1536);//設置窗口大小。
    // The resolution of ipad3 is very large. In general, PC's resolution is smaller than it.
    // So we need to invoke 'setFrameZoomFactor'(only valid on desktop(win32, mac, linux)) to make the window smaller.
    eglView->setFrameZoomFactor(0.4f);//調整分辨率。
    return CCApplication::sharedApplication()->run();//整個遊戲的運行就靠這一句。它的具體內容將在第三篇中詳解。在此,我們能看出的是sharedApplication()必須是CCApplication的一個靜態成員函數,否則無法直接調用,而且返回值是一指針,因爲它後面跟了一個指針御用箭頭操作符,而run()就是該指針所指對象的成員函數。

}

最前面的宏開關的定義如下

CCPlatformMacros.h(顧名思義,定義了各種平臺相關的宏)

// namespace cocos2d {}
#ifdef __cplusplus
    #define NS_CC_BEGIN                     namespace cocos2d {
    #define NS_CC_END                       }
    #define USING_NS_CC                     using namespace cocos2d
#else
    #define NS_CC_BEGIN 
    #define NS_CC_END 
    #define USING_NS_CC 
#endif

可見,如果__cplusplus已經被定義,則用cocos2d爲名空間,否則不用。


至此,我們囫圇吞棗將入口函數過了一遍。在閱讀代碼過程中,我們注意到對標識符的命名規則的掌握可以幫助理解。這裏先小結兩處:

經驗小結1,一般全大寫代碼,不是宏名,就是類型別名。

經驗小結2,以shared開頭的函數都是靜態的實例獲取函數,常用於單例模式(在下一篇還會遇到)。

面對如此簡練的入口函數,相信童鞋們心裏一定會有很多疑問,比如, Q1, app對象建立之後去哪了?Q2, 最後是什麼對象在run?Q3, run的內容是什麼?Q3, HelloWorld類去哪了?咋沒露個臉呢?Q4, HelloWorld圖片到底是從哪裏進來的呢?老師經常教導我們要帶着疑問來學習,所以有疑問是件好事。在接下來的篇章中我們將一步步解答這些問題。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章