cocos2d-js 是一個開源的可用於web、native環境的遊戲引擎,它是HTML5版本的 cocos2d-x。 cocos2d-js 包含兩部分,一部分是用於web game的cocos2d-html5,另外一部分則是用於native game的 cocos2d-x javaScript binding (JSB)。cocos2d-js 提供了 cocos2d-html5 和 cocos2d-x jsb 兼容的 API,使得用cocos2d-js編寫的game能夠無縫、不須要任何修改的就能夠跑在基於cocos2d-x jsb的native環境裏。javascript
基於javaScript技術棧,在開發效率上有很多的優點。衆所周知,web開發是至關高效的,開發過程無需編譯、打包,直接刷新下瀏覽器便可,並且如今的瀏覽器都支持強大的開發工具,好比chrome dev tools,包含了好比單步調試、網絡數據訪問、動態修改、內存/CPU性能分析、交互式控制檯。html
cocos2d-js 正是基於這一點,經過上層提供統一兼容API,快速的迭代,無縫的跨平臺部署,進行遊戲產出。html5
不過,在追求開發效率的道路上還不能僅僅這樣就能夠知足了,因爲是基於javaScript棧開發,就須要考慮好比:java
接下來會針對這些問題,提出探討,並給出相應的解決方案,最後會提供一個 cocos2d-js 的 example 以供參考node
javaScript 依賴管理是一個老生長談的話題,由於javaScript語言自己到目前爲止還沒推出統一的模塊、依賴管理的機制(es6 module還須要很久),cocos2d-js 在這方面使用的是:android
基於全局的依賴管理做爲引擎這樣子使用問題還不大,由於咱們最終使用cocos2d-js的地方都從cc對象獲取便可,可是若是是你本身的應用層代碼呢?ios
很明顯,本身在作應用(遊戲)開發時,要儘可能避免使用全局變量,使用全局變量則必須當心,要考慮全部使用到的地方,不然一步留神就改出了問題,同時使用全局變量,對於一個文件依賴全局變量很難一眼看出這個文件原來依賴了某個全局變量,這樣子尤爲是在項目的後期帶來了很多的麻煩,更不利於多人協做的項目git
腳本加載須要手動配置,也是存在問題的。若是文件之間相互有加載順序的依賴關係呢?(好比a.js依賴於b.js那麼b.js就須要先加載完才行)這樣一來就必須當心的寫好jsList裏面的順序,不然就加載不成功了es6
所以,一個完善的依賴管理機制仍是須要的。解決這個問題的關鍵是要把腳本的加載過程與腳本之間的依賴處理過程分開。github
目前也有很多解決方案,AMD、CommonJS、Dependency Injection,關於這三種方式的比較與討論能夠參考另一篇博文 javaScript 依賴管理
能夠看到基於 Dependency Injection 模式進行管理,能夠極大程度的鬆散耦合,先後端代碼進行復用,而且能夠直接使用基於CommonJS的模塊(npm module)。目前,bearcat 已經對 cocos2d-x jsb 環境進行了支持, 意味着咱們能夠編寫同一套代碼邏輯,而後跑在瀏覽器和jsb環境中,固然也能夠在node.js中進行復用。而 AMD(只適用於瀏覽器)、CommonJS(依賴於文件I/O,瀏覽器環境受到限制) 都不能很好的適用於全部的環境,也包括cocos2d-js的jsb環境
第三方庫其實也是一種依賴,目前最大的javaScript module生態圈是npm,咱們可使用 browserify 直接使用 npm 裏面現有的 module,而非拷貝代碼,而後手動添加,若是要升級第三方庫呢?再拷貝,再添加。明顯不太優雅。使用 browserify 的時候,配置個 package.json,而後添加依賴,build一下便可,因爲依賴並非頻繁更改的,所以這個browerify的build的過程也是能夠接受的
使用全局global變量的狀況下是很難作單元測試的,要作單元測試首先引用的全局變量要是一個拷貝,這樣子能夠防止因爲在這裏的單元測試修改了全局變量,而形成在另外的單元測試裏面失敗。
好比:
var fs = require('fs'); module.exports = function(path, cb) { fs.readFile(path, 'utf-8', function(err, content) { cb(null, JSON.parse(content)); }) }
這裏有幾個依賴?怎麼作單元測試?
(依賴了 fs 和全局的 JSON)
Dependency Injection則沒有這個問題,由於依賴是傳入的,並非本身獲取的,傳入的這個依賴能夠很方便的進行mock進行單元測試。
換成依賴注入則一目瞭然
module.exports = function(fs, JSON) { return function(path, cb) { fs.readFile(path, 'utf-8', function(err, content) { cb(null, JSON.parse(content)); }) } }
對於javaScript,咱們可使用 mocha 做爲測試驅動框架,使用 should 做爲斷言庫
因爲咱們基於javaScript技術棧,代碼共享確定能帶來很多好處,遊戲開發中很多的邏輯是能夠直接複用的,好比model定義、數據的校驗、尋路邏輯等等,這裏的代碼複用不是指的代碼拷貝,真正的複用是同一個文件直接在客戶端和服務端引用
接下來會以改造 cocos2d-js 官方 helloworld 例子爲例,說明如何使用 cocos2d-js + bearcat 來進行開發
直接使用 cocos 命令
cocos new -l js bearcat_cocos2d_js_example
而後在web上跑起來
cd bearcat_cocos2d_js_example/ cocos run -p web
package.json 中,咱們能夠填寫須要依賴的第三方庫,而後利用npm進行庫的維護與管理
這裏咱們依賴庫須要 bearcat,開發庫須要 grunt 以及相關 grunt task
package.json
{ "name": "bearcat-cocos2d-js-example", "version": "0.0.1", "dependencies": { "bearcat": "0.4.x" }, "devDependencies": { "grunt": "~0.4.2", "grunt-bearcat-browser": "0.x", "grunt-browserify": "3.2.x" } }
添加完依賴,咱們執行 npm 命令,安裝依賴
npm install
利用 grunt 咱們能夠創建起自動化的開發構建發佈流程
好比,咱們能夠配置 browserify build task,bearcat 生成 bearcat-bootstrap.js task
使用grunt前,須要全局安裝grunt
npm install -g grunt
gruntfile.js
'use strict'; module.exports = function(grunt) { grunt.loadNpmTasks('grunt-browserify'); grunt.loadNpmTasks('grunt-bearcat-browser'); var src = []; // Project configuration. grunt.initConfig({ // Metadata. pkg: grunt.file.readJSON('package.json'), bearcat_browser: { default: { dest: "bearcat-bootstrap.js", context: "client-context.json" } }, // browserify everything browserify: { standalone: { src: ['client.js'], dest: 'main.js', options: { } } } }); // Default task. grunt.registerTask('default', ['bearcat_browser', 'browserify']); };
這裏加入了 browserify 和 bearcat 的 task,而後咱們執行grunt命令便可運行這些tasks
browserify 裏配置 source 文件 client.js,目標文件爲 main.j。client.js 其實就是以前的 main.js 文件,只不過使用 browserify 提供的 require 加載了第三方庫,main.js 則是已經打包好第三庫的主文件,供cocos2d-js做爲項目入口文件加載
bearcat 裏配置根據 client-context.json 來生成 bearcat-bootstrap.js 以便支持script的加載
grunt
若是還想加入單元測試、代碼覆蓋率的工做流,加入相關mocha、coverage task便可
因爲是javaScript技術棧,考慮先後端代碼共享,那麼咱們能夠把源碼文件夾分紅三個,好比
建立 client 源碼文件夾
mkdir app-client
建立 server 源碼文件夾
mkdir app-server
建立共享源碼文件夾
mkdir app-shared
這裏採用bearcat進行script腳本加載與依賴管理,所以修改 project.json 文件,把 jsList 的配置置爲空數組
project.json
{ "project_type": "javascript", "debugMode": 1, "showFPS": true, "frameRate": 60, "id": "gameCanvas", "renderMode": 0, "engineDir": "frameworks/cocos2d-html5", "modules": ["cocos2d"], "jsList": [] }
這個是 bearcat 在 client 端的配置,配置着須要掃描的源文件路徑,這裏咱們掃描 app-client 與 app-shared 這兩個文件夾,相應的,對於node.js開發,有一個context.json,裏面配置的掃描文件夾則是 app-server 與 app-shared
client-context.json
{ "name": "bearcat-cocos2d-js-example", "description": "client context.json", "scan": ["app-client", "app-shared"], "beans": [] }
client.js 就是以前的main.js,只不過使用了browserify進行第三方庫依賴、使用bearcat進行業務層代碼的管理
client.js
require('./bearcat-bootstrap.js'); var bearcat = require('bearcat'); // 依賴bearcat庫 window.bearcat = bearcat; // using browserify to resolve npm modules cc.game.onStart = function() { cc.view.adjustViewPort(true); cc.view.setDesignResolutionSize(800, 450, cc.ResolutionPolicy.SHOW_ALL); cc.view.resizeWithBrowserSize(true); var self = this; //load resources bearcat.createApp(); bearcat.use(['helloWorldScene']); bearcat.start(function() { var resourceUtil = bearcat.getBean('resourceUtil'); var g_resources = resourceUtil.getResources(); cc.LoaderScene.preload(g_resources, function() { var helloWorldScene = bearcat.getBean('helloWorldScene'); cc.director.runScene(helloWorldScene.get()); }, self); }); }; cc.game.run();
至此客戶端項目環境已經配置完畢,咱們能夠愉快的coding
使用cocos2d-js建立一個layer至關容易
var HelloWorldLayer = cc.Layer.extend({ ctor: function() { } });
咱們繼承cc.Layer,而後在子類裏實現咱們具體的邏輯
可是這個 HelloWorldLayer 放在哪兒呢?全局嗎?固然 say no!
在bearcat裏,你能夠編寫一個管理HelloWorldLayer的factory bean也即工廠類,由這個factory bean維護着這個HelloWorldLayer,須要時,依賴這個factory bean,而後經過factory bean的工廠方法獲取HelloWorldLayer的實例,同時,若是HelloWorldLayer須要依賴其餘的script文件,也在factory bean經過bearcat提供的依賴注入來進行便可
// HelloWorldLayer 工廠bean var HelloWorldLayer = function() { this.$id = "helloWorldLayer"; this.$init = "init"; this.ctor = null; // 若是須要依賴,直接在這裏用bearcat依賴注入 // this.$xxxUtil = null; } HelloWorldLayer.prototype.init = function() { var self = this; // 初始化HelloWorldLayer this.ctor = cc.Layer.extend({ sprite: null, helloLabel: null, ctor: function() { } }); } // 工廠方法 HelloWorldLayer.prototype.get = function() { return new this.ctor(); } bearcat.module(HelloWorldLayer, typeof module !== 'undefined' ? module : {});
在HelloWorldLayer中咱們須要依賴一個resourceUtil來處理資源的管理,那麼咱們能夠簡單的這樣作就行
var HelloWorldLayer = function() { this.$id = "helloWorldLayer"; this.$init = "init"; this.$resourceUtil = null; this.ctor = null; }
使用時,咱們直接用 this.$resourceUtil 便可拿到依賴,調用裏面的方法
在HelloWorldLayer中,咱們可能有這樣的邏輯
HelloWorldLayer.prototype.init = function() { var self = this; this.ctor = cc.Layer.extend({ sprite: null, helloLabel: null, ctor: function() { // 1. super init first this._super(); self.addCloseItem(this); self.addHelloWorldLabel(this); self.addSplashScreen(this); self.runAction(this); return true; } }); }
而後咱們能夠這麼進行抽象與封裝,把每一個邏輯代理到factory bean的無狀態的方法上去,並把this context做爲參數傳入
這樣子實現好處也是明顯的:
在這裏就是ui組件的複用(固然,對於ui,咱們已經有了cocosstudio來進行編輯生成,而不用本身一個個去編寫代碼,可是代碼實現的思路是一致的)
時刻不忘代碼的可維護性、複用性
實現就大膽使用cocos2d-js裏提供的各類方法便可,這裏的self參數則是ctor裏的this context
HelloWorldLayer.prototype.addCloseItem = function(self) { // 獲取 resourceUtil 依賴 var res = this.$resourceUtil.getRes(); // 2. add a menu item with "X" image, which is clicked to quit the program // ask the window size var size = cc.winSize; // add a "close" icon to exit the progress. it's an autorelease object var closeItem = new cc.MenuItemImage( res.CloseNormal_png, res.CloseSelected_png, function() { cc.log("Menu is clicked!"); }, self); closeItem.attr({ x: size.width - 20, y: 20, anchorX: 0.5, anchorY: 0.5 }); var menu = new cc.Menu(closeItem); menu.x = 0; menu.y = 0; self.addChild(menu, 1); }
其它代碼也相似,不一一說明,完整例子在 bearcat-coscos2d-js-example
寫完代碼,執行 grunt 進行構建
grunt
對於web平臺,直接執行cocos命令
cocos run -p web
對於android平臺,則須要修改 cocos2d-js 庫下的 frameworks/runtime-src/proj.android/build-cfg.json 文件
把咱們自定義的源代碼文件添加進打包apk配置中
build-cfg.json
{ "ndk_module_path": [ "../../js-bindings", "../../js-bindings/cocos2d-x", "../../js-bindings/cocos2d-x/cocos", "../../js-bindings/cocos2d-x/external" ], "copy_resources": [{ "from": "../../../app-client", "to": "app-client" }, { "from": "../../../app-shared", "to": "app-shared" }, { "from": "../../../res", "to": "res" }, { "from": "../../../main.js", "to": "" }, { "from": "../../../project.json", "to": "" }, { "from": "../../js-bindings/bindings/script", "to": "script" }] }
這裏咱們添加了 app-client 與 app-shared 文件夾
而後,咱們就能夠直接用cocos命令編譯部署到android設備(模擬器)上
cocos run -p android
ios 平臺,因爲沒有mac,暫時沒法測試,還望讀到這的同窗實際測試下,並給予反饋
cocos2d-js很是不錯,基於javaScript咱們能夠用一份代碼就能夠發佈到web、pc、android、ios等平臺上,大大減小了開發成本。固然cocos2d-js開發並非很是完美,這其中也有javaScript這門語言自己的不足。本文就對這些問題進行了探討,並給出了使用bearcat來編寫cocos2d-js項目的例子,拋磚望引玉,enjoy coding with bearcat