文章撰寫日期︰2013/08/24 01:54
cocos2dx使用版本︰v 2.1.4
cocosBuilder使用版本︰v 3.0 alpha 5
文章來源︰劉松的Github教學
前言
由國人自己研發的開源遊戲引擎Cocos2d-x,在很短的時間內就風靡全球,來自多個國家的開發者均參與貢獻代碼,其優秀毋庸置疑。但是,年輕的
Cocos2d-x正處於快速發展階段,圍繞其周邊的配套工具、文檔、教程等等均尚不完善。我本人在學習的過程中就體會過把Google、
StackOverflow、github以及官方論壇等等翻個底朝天都找不到相關資料的窘境。這也是本篇教程誕生的根本原因。
因我本人亦是剛剛加入Cocos2d陣營學習不久,加上自身能力有限,教程之中若有錯誤,還望不吝指出,我將及時更新修復錯誤。另外,本人熱愛開源,因此本教程亦設定為“
開源教程
”。希望能藉此拋磚引玉,讓更多的朋友參與到這個教程的編寫過程中來,共同學習和完善相關內容,讓更多人後來者受益。
一、Cocos2d-x JavaScript Binding簡單介紹
眾所周知,Cocos2d-x的主要開發語言是C++,而Cocos2d-x JavaScript Binding則是基於Cocos2d-x用JavaScript語法來編寫遊戲。 Cocos2d-x JavaScript Binding又常常被簡稱為JSB,其實現原理可簡單地總結為:Cocos2d-x內置了一套JavaScript的解析引擎SpiderMonkey,通過SpiderMonkey在引擎內部將JavaScript代碼“映射”為C++代碼,從而實現了用JavaScript語法調用Cocos2d-x的API來完成遊戲的邏輯的編寫。這意味著,JSB開發模式在依然保持了C++原生應用同樣的性能這個優點之外,還大大降低了開發語言的門檻。
另外,由於Cocos2d家族的另一支新秀Cocos2d-HTML5也繼承了Cocos2d家族相同的API結構,這就使得我們可以將由JSB編寫的代碼很輕鬆地移植到Cocos2d-HTML5,從而實現了橫跨原生客戶端和瀏覽器的全新的遊戲開發模式。這一特性優勢,也是目前與JSB並行存在的Cocos2d-x Lua Binding所不能比擬的。雖然相比Lua Binding而言JavaScript Binding目前還顯得不太成熟,但我認為後者一定是未來。
在講述Cocos2d-x JavaScript Binding開發實踐之前,我們不得不提到CocosBuilder。相信很多人都至少聽說過這款功能強大的工具,其集成了所見即所得的場景編輯器、動畫編輯器、智能紋理打包以及JavaScript綁定相關配置功能,讓開發工程師不再需要跟任何視覺相關的環節打交道,從而專注於遊戲的邏輯控制部分,使得遊戲開髮變得更加簡單。下面就將結合我的實際使用經歷來講解Cocos2d-x JavaScript Binding結合CocosBuilder的跨平台遊戲開發實踐。
二、開發環境配置
本節內容我大量參考和翻譯了國外的一篇文章《Setup a cross platform project with CocosBuilder and Cocos2d-x》(須翻牆),這篇文章裡所描述的項目目錄結構較為合理,我個人認為非常適合用作跨平台開發和調試的通用結構。我將這篇文章的主要內容摘錄翻譯下來,並結合這篇教程的需要做了一些修改,同時也方便了英文不好的朋友。
1、在Mac終端下載Cocos2d-x最新版本並解壓(此時最新版本為2.1.4)。
我更習慣用命令工具列,參考下面:
$ cd #回到根目录,也可以放在别的目錄
$ wget https://cocos2d-x.googlecode.com/files/cocos2d-x-2.1.4.zip
$ unzip cocos2d-x-2.1.4.zip
提示:以上過程解壓後的目錄應該是cocos2d-x-2.1.4,你可能改下目錄名,也可以保持不變。下面的內容將會把這個目錄名用{cocos2d-x}代替。比如,我的{cocos2d-x}實際指代的是/Users/liusong/cocos2d-x
2、使用cocos2d-x自带的工具创建工程。
$ cd {cocos2d-x}/tools/project-creator
$ ./create_project.py -project MyGame -package com.MyCompany.AwesomeGame -language javascript #創建一個名叫MyGame的跨平台工程,會在{cocos2d-x}/projects/目錄下自動生成MyGame工程代碼
$ cd ../../projects/MyGame #切换到工程目錄
$ rm -rf Resources/ #刪除默認生成的資源目录,後面會用到CocosBuilder生成的資源目錄
提示:這裡創建的工程名稱MyGame會在後續教程中頻繁出現,如果你不是使用這個名稱,那麼請注意在後面自行更改
3、下載、安裝、配置CocosBuilder的最新版(此時最新版為3.0 alpha4)。
在這裡我推薦使用Git來clone源碼的方式來下載CocosBuilder,因為目前CocosBuilder尚在發展中,並不太完善和穩定,下載源代碼的好處是今後遇到問題時可以自己通過追蹤源代碼來解決,也可以及時合併最新的補丁而不用等官方發布新版。
$ cd #回到根目錄,也可以放在别的目錄
$ git clone https://github.com/cocos2d/CocosBuilder.git
$ cd CocosBuilder
$ git submodule update --init --recursive
$ open ./CocosBuilder/CocosBuilder.Xcodeproj #打开CocosBuilder的Xcode工程
在打開的Xcode工程中,確認左上角的scheme是CocosBuilder而不是其他,直接點擊“Run”,經過一段時間的編譯運行後CocosBuilder將會被打開。這時候,你可以在CocosBuilder根目錄下的build文件夾中看到Xcode生成的CocosBuilder.app文件。今後如果有需要啟動CocosBuilder,就可以直接打開這個來啟動,無須再在Xcode中編譯運行,除非是你修改過CocosBuilder的源碼。這個我們後面會提到。
在打開的CocosBuilder界面中新建一個CocosBuilder工程(File->New->New Project),注意這裡的命名一定和之前的項目名稱一樣,並且要保存在工程目錄。如下圖所示:
保存時會提示是否覆蓋,請點擊“Replace”確認覆蓋。如下圖:
提示:如果你操作時沒有出現這個提示,那麼一定是你的名稱或保存目錄位置有錯,請刪除剛才創建的內容重新操作。
以上操作完畢後,你會看到在MyGame目錄下新增了MyGame.ccbproj文件和Resources目錄。 MyGame.ccbproj是CocosBuilder的工程文件,而Resources則是默認的資源存放路徑。
還記得我們之前創建項目工程後刪掉的Resources目錄嗎?這裡又重新補上了,因為我們下來都要用CocosBuilder來組織和生成遊戲資源。注意這個資源路徑設置僅僅是默認的,為了方便教學我們不做修改,如果你需要修改,請打開菜單File->Project Settings進行修改。
打開Resources目錄可以看到,裡面已經有一些文件。這是CocosBuilder在創建工程時默認為我們添加的,我們這個教程將直接使用這些默認的資源來進行演示,所以我們予以保留。如下圖:
接下來,點擊菜單(File->Publish Settings)打卡發佈設置界面,激活所有平台。默認情況下,iOS和HTML5的發佈設置都是選中狀態,我們只需要再勾選上中間的Android。請注意每個選項中以Published-開頭的設置,那代表著你用CocosBuilder製作的遊戲資源或代碼將會被存儲到那些對應的目錄裡。圖示:
最後點擊菜單(File->Publish)進行發布,這個發布行為會自動生成上面提到的三個Published-開頭的文件夾。打開看看,會發現裡面有一些文件,那都是剛才由CocosBuilder自動生成的。仔細觀察會發現,不同平台目錄下的文件會有所差異,這就是CocosBuilder的強大之處,它可以根據你的設置來針對不同平台的特徵自動處理資源兼容和適配問題。另外,原來資源目錄Resources下的ccb文件均被發布成了ccbi的二進製版本,以供程序代碼直接調用。
4、修改工程配置以連接CocosBuilder
修改AppDelegate.cpp
$ cd {cocos2d-x}/projects/MyGame #切换到项目根目錄
$ vim Classes/AppDelegate.cpp
最後,修改applicationDidFinishLaunching的內容如下:
bool AppDelegate::applicationDidFinishLaunching()
{
// initialize director
CCDirector *pDirector = CCDirector::sharedDirector();
pDirector->setOpenGLView(CCEGLView::sharedOpenGLView());
CCSize designSize = CCSizeMake( 480,320);
CCSize resourceSize = CCSizeMake( 960,640);
CCSize screenSize = CCEGLView::sharedOpenGLView()->getFrameSize();
std::vector<std::string> resDirOrders;
TargetPlatform platform = CCApplication::sharedApplication()->getTargetPlatform();
if (platform == kTargetIphone || platform == kTargetIpad)
{
std::vector<std::string> searchPaths = CCFileUtils::sharedFileUtils()->getSearchPaths();
searchPaths.insert(searchPaths.begin(), "Published-iOS");
CCFileUtils::sharedFileUtils()->setSearchPaths(searchPaths);
if (screenSize.height == 1536)//iPad-hd
{
CCLog("resources-ipadhd");
resourceSize = CCSizeMake(2048, 1536);
resDirOrders.push_back("resources-ipadhd");
}
else if (screenSize.height == 768)//iPad
{
CCLog("resources-ipad");
resourceSize = CCSizeMake(1024, 768);
resDirOrders.push_back("resources-ipad");
}
else if (screenSize.height == 640)//iPhone 4/4s/5
{
CCLog("resources-iphonehd");
resourceSize = CCSizeMake( 960,640);
resDirOrders.push_back("resources-iphonehd");
}
else//iPhone 3GS
{
CCLog("resources-iphone");
resourceSize = CCSizeMake(480, 320);
resDirOrders.push_back("resources-iphone");
}
}
else if (platform == kTargetAndroid || platform == kTargetWindows)
{
if (screenSize.height > 720)
{
resourceSize = CCSizeMake( 960,640);
resDirOrders.push_back("resources-large");
}
else if (screenSize.height > 568)
{
resourceSize = CCSizeMake(480, 720);
resDirOrders.push_back("resources-medium");
}
else
{
resourceSize = CCSizeMake(320, 568);
resDirOrders.push_back("resources-small");
}
}
CCFileUtils::sharedFileUtils()->setSearchResolutionsOrder(resDirOrders);
pDirector->setContentScaleFactor(resourceSize.width/designSize.width);
CCEGLView::sharedOpenGLView()->setDesignResolutionSize(designSize.width, designSize.height, kResolutionNoBorder);
// turn on display FPS
pDirector->setDisplayStats(true);
// set FPS. the default value is 1.0/60 if you don't call this
pDirector->setAnimationInterval(1.0 / 60);
ScriptingCore* sc = ScriptingCore::getInstance();
sc->addRegisterCallback(register_all_cocos2dx);
sc->addRegisterCallback(register_all_cocos2dx_extension);
sc->addRegisterCallback(register_all_cocos2dx_extension_manual);
sc->addRegisterCallback(register_cocos2dx_js_extensions);
sc->addRegisterCallback(register_CCBuilderReader);
sc->addRegisterCallback(jsb_register_chipmunk);
sc->addRegisterCallback(jsb_register_system);
sc->addRegisterCallback(JSB_register_opengl);
sc->addRegisterCallback(MinXmlHttpRequest::_js_register);
sc->addRegisterCallback(register_jsb_websocket);
sc->start();
CCScriptEngineProtocol *pEngine = ScriptingCore::getInstance();
CCScriptEngineManager::sharedManager()->setScriptEngine(pEngine);
ScriptingCore::getInstance()->runScript("main.js");
return true;
}
全部修改完成後保存,退出編輯。
仔細對比不難發現,這裡所做的修改,是在應用啟動的時候根據設備類型和屏幕分辨率來設置資源搜索路徑。這個步驟的設置常常會被忽略掉,我本人也曾經在這裡浪費掉很多時間。究其原因,是CocosBuilder給人留下了“自動適配多種分辨率”印象,從而下意識地認為這個適配工作將會由引擎來自動完成,而實際情況卻不是這樣。
CocosBuilder可以以某個分辨率下的資源為準,通過自動縮放來為生成適配其他分辨率的資源,但遊戲中如何加載這些不同的資源卻需要手工設置規則。
修改iOS工程配置
$ open proj.ios/MyGame.Xcodeproj/ #打开iOS工程
打開後,分別做以下三件事:
可以看到Resources分組下的main.js, res, src是顯示紅色的,這些文件或文件夾在此前的步驟裡已經連帶Resources目錄刪除而不存在,但其引用關係依然在,所以我們將其選中並右鍵點選“delete”刪除。
將此前由CocosBuilder自動生成的Published-iOS文件夾拖拽到Xcode中。注意要放在MyGame的根節點下,並且要在彈出的窗口中去掉第一個"Copy items into …"選項前面的勾選狀態,然後選中“Create folder references for any added folders”。這兩個操作非常重要,其含義是在MyGame的Xcode工程中對Published-iOS文件夾建立一個“引用”關係,而不是將此文件夾的內容複製到工程中。這樣的好處是,今後Published-iOS目錄中有任何變動都會及時地反應到MyGame,而無須再次手動添加到工程。
注意Xcode左上角的scheme區域的左側,如果顯示的是cocos2dx,請點擊該按鈕將其手動改為MyGame。如果本就是MyGame則無須修改。另外,右邊的iOS Device也需要重新點選為一個具體的模擬器,如圖,我選擇的是iPhone 6.1 Simulator。
完成以上步驟後,點擊左上角Run按鈕,Xcode將會開始編譯並自動啟動模擬器來啟動和運行MyGame。看看效果,就是此前在CocosBuilder所生成的默認畫面。
修改Android工程配置[待更新,欢迎提交]
配置HTML5环境
HTML5版本中加載資源需要通過HTTP請求來完成,所以必須要有一個WebServer。你可以將Published-HTML5中的所有文件都上傳到你已有的服務器,也可以在本地搭建一個。本教程將不會涉及WebServer的內容,所以採用本地環境來搭建。 Python中有個快速搭建的命令:
$ cd {cocos2d-x}/projects/MyGame/Published-HTML5
$ python -m SimpleHTTPServer &
提示:python -m SimpleHTTPServer會在本機啟動一個WebServer,監聽8000端口,其默認目錄即是執行命令所在的當前目錄。因此註意要切換到你的Published-HTML5目錄下執行。
當屏幕出現類似如下提示時表示WebServer啟動成功:
Serving HTTP on 0.0.0.0 port 8000 ...
這時候打開瀏覽器,輸入http://localhost:8000即可在瀏覽器中啟動由CocosBuilder為我們自動生成的遊戲。
至此,我們的開發環境已經全部搭建完成,從此,我們就可以開始一種全新的、完全在CocosBuilder中進行遊戲開發的開發體驗了。接下來,我們會以CocosBuilder為MyGame工程默認創建的資源為基礎,通過示例講解如何在CocosBuilder中進行JSB的開發。
三、在CocosBuilder中進行遊戲開發
打開MyGame的CocosBuilder工程(也可以用圖形界面的菜單選項打開File->Open):
$ open {cocos2d-x}/projects/MyGame/MyGame.ccbproj
其界面大致介紹如下圖:
在左側的資源列表區,我們可以看到兩個以MainScene開頭的文件,一個是ccb,一個是js。
ccb文件類似PhotoShop的PSD文件,我們開發遊戲過程中的場景、精靈、動畫等都可以在ccb文件中進行編輯。另一個js文件無須多講,這就是遊戲的邏輯代碼,以JavaScript來編寫。
MainScene.ccb是CocosBuilder的默認入口,即遊戲啟動後第一個加載的場景。如果你想用其他文件作為默認入口,只需打開工程設置(File->Project settings)修改“Start ccb-file name”即可。在工程設置窗口,還可以看到其他選項,第一個“Resources paths”前面已經講過,設置資源路徑,第二個默認勾選的“JavaScript based project”表示是否使用JSB開發模式,如果你的遊戲不採用JSB,就可以去掉這個勾選。第三個默認入口已經講過,最後一個選項是設置遊戲支持的屏幕旋轉方向,默認是橫屏。
我們來到CocosBuilder界面中下面的編輯區,“Default Timeline”表示這是當前ccb的默認時間軸,點擊這裡會彈出新建和編輯timeline等選項,每個ccb文件都可以創建多個timeline,並且可以將多個timeline連接起來,具體可見點擊界面底部的“No chained timeline”的彈出菜單。
點擊選中CCLayer節點,右側會顯示這個層的各種屬性設置選項。我們注意到最頂部的“Code Connections”中“JS Controller”,這就是ccb文件和JavaScript自動連接的重要選項,它為當前這個層定義了一個JavaScript的控制器類的名稱,這樣我們就可以在程序代碼中來直接操作這個層裡面的資源或節點。
再往下,點擊選中CCLabelTTF節點,右側的“Code Connections”中“JS Controller”被禁用,第二個選項則定義了這個文本控件的名稱helloLabel。類似地,我們再選中最後一個CCMenuItemImage節點,這是一個按鈕控件,右側下面的CCMenuItem區域也已經為這個按鈕定義好了其點擊時觸發的方法名稱onPressButton,這樣我們也只需在JavaScript代碼中定義此方法即可。
在左側資源列表區雙擊打開MainScene.js,這裡的內容就可以看到MainScene的類定義、按鈕點擊時調用的onPressButton的方法定義及成員變量helloLabel的用法。到這裡,我們應該就基本明白了在CocosBuilder中進行遊戲開發的大致流程,我簡單總結如下:
1)
在ccb中組織美術資源,創建動畫、特效
2)
根據需要設置相關節點的屬性以備代碼連接
3)
編寫JavaScript程序實現遊戲邏輯
4)
發布,然後分別在Xcode、Eclipse和瀏覽器中調試結果
提示:以上第4步的調試工作也可以在CocosPlayer中進行,但CocosPlayer僅支持iOS和Android模擬器,考慮到HTML5平台調試的問題,本教程將暫不涉及CocosPlayer。
通常情況下,一個遊戲開發過程中有關場景、動畫和特效等視覺上的工作都是由團隊中的美術來負責完成的,因此ccb這些文件理論上來說應該由美術來完成編輯再交由開發工程師來調用,但我更推薦工程師也要學會使用CocosBuilder中場景、動畫和特效編輯等功能的用法,或者索性由二者共同完成。因為CocosBuilder目前尚不完善,美術在學習和使用過程中可能會遇到一些問題,另外資源的組織方式也是會對遊戲的性能產生影響的,這時候就需要工程師能夠協助把關,共同實現最優的解決方案。
看完了默認的示例,我們接下來將基於這個例子的資源和代碼逐步深入講解一些遊戲開發中常見問題的實現。
1、響應觸屏事件
觸屏事件是遊戲開發中必不可少的,是玩家和遊戲產生交互的基礎。我們將以MainScene為例來講解如何響應玩家的觸屏事件,目標是實現玩家點擊屏幕任意區域後在helloLabel這個文本區域內顯示其點擊位置的坐標。
需要特別強調下,一個已知的問題是Cocos2d-x和CocosBuilder中對於事件定義的標準存在差異,這導致了我們必須通過修改Cocos2d-x中CCBReader的源代碼才能正確地響應各種事件。具體修改方式如下:
$ vim {cocos2d-x}/extensions/CCBReader/CCLayerLoader.cpp
將
#define PROPERTY_TOUCH_ENABLED "isTouchEnabled"
#define PROPERTY_ACCELEROMETER_ENABLED "isAccelerometerEnabled"
#define PROPERTY_MOUSE_ENABLED "isMouseEnabled"
#define PROPERTY_KEYBOARD_ENABLED "isKeyboardEnabled"
替換為:
#define PROPERTY_TOUCH_ENABLED "touchEnabled"
#define PROPERTY_ACCELEROMETER_ENABLED "accelerometerEnabled"
#define PROPERTY_MOUSE_ENABLED "mouseEnabled"
#define PROPERTY_KEYBOARD_ENABLED "keyboardEnabled"
這個問題已經存在了較長的時間,但至今最新版本依然沒有得以解決。
做完以上修改後,我們回到CocosBuilder。雙擊打開MainScene.ccb,選中根節點(CCLayer),我們在右側最下面的CCLayer屬性設置中可以看到四個選項,其各自的含義解釋如下圖:
我們預期的是想讓MainScene響應觸屏事件,因此默認選項不用更改即可,其他三項鼠標事件、重力感應事件和鍵盤事件沒有用到則暫且不管。
現在來到代碼中,雙擊打開MainScene.js,在文件最後一行下面增加如下代碼:
MainScene.prototype.onDidLoadFromCCB = function()
{
cc.log('MainScene ccb file has been loaded!');
this.rootNode.onTouchesBegan = function( touches, event) {
// 将触屏开始事件转发给控制器 (this)
this.controller.onTouchesBegan(touches, event);
return true;
};
this.rootNode.onTouchesMoved = function( touches, event) {
// 将触屏移动事件转发给控制器 (this)
this.controller.onTouchesMoved(touches, event);
return true;
};
this.rootNode.onTouchesEnded = function( touches, event) {
// 将触屏结束事件转发给控制器 (this)
this.controller.onTouchesEnded(touches, event);
return true;
};
};
MainScene.prototype.onTouchesBegan = function(touches, event)
{
// 修改文本内容
this.helloLabel.setString("TOUCH START: "+parseInt(touches[0].getLocation().x)+", "+parseInt(touches[0].getLocation().y));
};
MainScene.prototype.onTouchesMoved = function(touches, event)
{
// do some staff here
};
MainScene.prototype.onTouchesEnded = function(touches, event)
{
// do some staff here
};
以上代碼中,onDidLoadFromCCB方法會在ccb文件加載完成後自動調用,類似iOS中的viewDidLoad。我們在onDidLoadFromCCB中分別將三種觸屏事件轉發給控制器自定義的方法去處理。在觸屏開始事件的處理方法onTouchesBegan中,我們將觸屏位置信息轉換成文本顯示在屏幕上,實現了我們的目標。另外兩個onTouchesMoved和onTouchesEnded暫時沒有用上,但我們應該能就此理解它們的用法了。
2、HTTP網路請求
為了更好地演示XMLHttpRequest的用法,我們利用前面配置HTML5環境時開啟的WebServer來提供一個HTTP服務接口,該接口直接返回一段JSON格式的字符串,JavaScript將在接收到接口返回的數據時進一步解析處理。
$ cd {cocos2d-x}/projects/MyGame/Published-HTML5
$ echo '{"data":"I am from the remote server"}' > test.json
以上操作在WebServer的根目錄創建了一個test.json文件,內容是一段JSON格式的文本。我們的目標是玩家點擊屏幕上的按鈕時向http://localhost:8000/test.json發起一個HTTP請求,並將返回數據中data字段的內容打印在屏幕上。
同樣,雙擊打開MainScene.js,將MainScene.prototype.onPressButton的方法修改如下:
// Create callback for button
MainScene.prototype.onPressButton = function()
{
// Rotate the label when the button is pressed
//this.helloLabel.runAction(cc.RotateBy.create(1,360));
//新增以下代碼
var theHelloLabel = this.helloLabel;
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState==4) {// 4 = "loaded"
if (xhr.status==200) {// 200 = "OK"
var response = JSON.parse(xhr.responseText);
cc.log(response);
theHelloLabel.setString(response.data);
} else {
cc.log("Problem retrieving JSON data:" + xhr.statusText);
}
}
};
//发起一个GET请求
xhr.open("GET", "http://localhost:8000/test.json");
xhr.send(null);
};
3、多分辨率適配方案[待更新,歡迎提交]
4、WebSocket[待更新,歡迎提交]
5、Plugin-X整合綁定[待更新,歡迎提交]
6、自定義第三方庫的綁定[待更新,歡迎提交]
7、在線資源更新解決方案[待更新,歡迎提交]