お初にお目にかかります。
自宅のエスプレッソマシンの調子が悪く、オーバーホールに出そうか悩み中のカフェマスターです。
舞鶴鎮守府の兼業提督でもあります。
コードの修正を反映するたびに、アプリ再ビルド、転送、起動の待ち時間がかかりますがとっても無駄ですよね?
書いたコードが即反映されれば、集中力も切れないし、生産性向上は間違いなし。
ということで、今回は
iOS環境でシミュレータを起動したまま、コードをガシガシ書いていくためのプロジェクト雛形作成方法を
ご紹介したいと思います。
もくじ
環境構築
前提条件として、MacにHomebrewとcocos2d-x 2系をインストール済みの環境を想定しています。
Luaパッケージマネージャセットアップ
Homebrewを使ってLua関連パッケージをインストールします。
luarocksはrubyのgemに相当するパッケージマネージャです。
brew install lua luajit luarocks
MoonScriptセットアップ
Luaはクラスの概念がなかったり初めて扱うには取っ付きづらいのでここではCofeeScript風の文法で記述でき、
LuaにコンパイルできるMoonScriptを使うことにします。
文法はこちらの公式サイトで
luarocks install moonscript
開発
プロジェクトセットアップ
サンプルプロジェクト作成
cd <path_to>/tools/project-creator ./create_project.py -project sample -package com.example -language lua
C++側でスクリプトを動的リロードする仕組みを作成
ここから実際のコーディングを行っていきます。
まずは、C++側でスクリプトを動的にリロードしてシーンを再スタートさせる仕組みを作ります。
スクリプトリロード・シーン管理用クラスを作成
SceneManager.h
class SceneManager { private: static void loadScripts(); static void initGameScene(); public: static void startGameScene(); static void restartGameScene(); };
SceneManager.cpp
#include "SceneManager.h" #include "CCLuaEngine.h" #include "cocos2d.h" using namespace std; using namespace cocos2d; static string scriptFiles[] = { "lua/App.lua", "lua/TitleScene.lua" }; void SceneManager::loadScripts() { CCLuaEngine* pEngine = CCLuaEngine::defaultEngine(); string path; for (int i = 0; i < sizeof(scriptFiles) / sizeof(scriptFiles[0]); i++) { path = CCFileUtils::sharedFileUtils()->fullPathForFilename(scriptFiles[i].c_str()); pEngine->executeScriptFile(path.c_str()); } } void SceneManager::initGameScene() { SceneManager::loadScripts(); CCLuaStack*stack = CCLuaEngine::defaultEngine()->getLuaStack(); lua_State* L = stack->getLuaState(); lua_pcall(L, 1, 1, 0); } void SceneManager::startGameScene() { CCScene* scene = CCScene::create(); CCDirector::sharedDirector()->runWithScene(scene); SceneManager::initGameScene(); } void SceneManager::restartGameScene() { CCDirector::sharedDirector()->popToRootScene(); SceneManager::initGameScene(); }
スクリプトからC++を呼ぶためのグルーコード生成
先ほど作成したSceneManagerをスクリプト側から呼び出せるようにします。
tolua++を使うための定義ファイルを作成します。
Classes/tolua_glue/bind.pkg
$#include "SceneManager.h" class SceneManager { static void startGameScene(); static void restartGameScene(); };
定義ファイルを元にtolua++でグルーコードを作成します。 tolua++はCocos2d-xのtoolsディレクトリ下のものをそのまま利用します。
unzip <path_to>/tools/tolua++/tolua++.Mac.zip cd <path_to>/projects/sample ../../tools/tolua++/tolua++ -n bridge -o Classes/tolua_glue/Bridge.cpp -H Classes/tolua_glue/Bridge.h Classes/tolua_glue/bind.pkg
AppDelegateでアプリのエントリポイントを切り替えます。
AppDelegate.cpp
- std::string path = CCFileUtils::sharedFileUtils()->fullPathForFilename("hello.lua"); - pEngine->executeScriptFile(path.c_str()); + SceneManager::startGameScene();
グルーコードについての詳細はこちらのサイトでご覧ください。
SceneManager.cppで読み込んでいた "App.lua" の元ファイルを作成します。
App.moon
export App export start SCENE_STATE = { TITLE: 0 GAME: 1 } class App @sceneState = nil startTitleScene: => @sceneState = SCENE_STATE.TITLE CCDirector\sharedDirector()\pushScene TitleScene\create() changeGameScene: => @sceneState = SCENE_STATE.GAME CCDirector\sharedDirector()\replaceScene GameScene\create() restartGame: => // SceneManager.cppを呼び出し SceneManager\restartGameScene() start = -> App\startTitleScene()
restartGameのSceneManager\restartGameScene()でスクリプトをリロードし、 App\startTitleScene()が再度呼ばれるようになります。
TitleScene.moon
export TitleScene class TitleScene @layer = nil create: => scene = CCScene\create() @layer = @initLayer() scene\addChild @layer initLayer: => layer = CCLayer\create() bg = CCSprite\create “images/title.png” size = CCDirector\sharedDirector()\getWinSize() bg\setPosition size.width/2, size.height/2 layer\addChild bg reloadButton= CCMenuItemImage\create “images/reload.png", “images/reload.png” reloadButton\setPosition 0, 0 // スクリプト再読み込みボタン reloadButton\registerScriptTapHandler -> App\restartGame() startButton= CCMenuItemImage\create "images/start.png", "images/start.png" startButton\setPosition size.width/2, size.height/2 + 100 // ゲーム開始ボタン startButton\registerScriptTapHandler -> App\changeGameScene() buttons = CCArray\createWithObject startButton buttons\addObject reloadButton menu = CCMenu\createWithArray buttons layer\addChild menu layer
最後にMoonScriptをLuaにコンパイルして完成です。
moonc -w -t /Users/<username>/Library/Application\ Support/iPhone\ Simulator/6.1/Applications/<app_uuid>/Documents/lua .
moonc -w でMoonScriptの更新を検知して自動でコンパイルしてくれます。
Luaの出力先としてiOSシミュレータのアプリケーションのDocuments下に直接書きだします。
こうすることでScenemanager.cppの動的リロード時に最新のスクリプトファイルを読み込むことになります。
実機で動的リロードする仕組みはHTTP serverをアプリ内で動かして
postで差分を投げつける方法で対応可能です。応用編として試してみてください。
まとめ
さて、気になるのはどうれくらい効率化されるか?ですが、
当方が担当したプロジェクトでは直前の素のcocos2d-xで作ったプロダクト(両方エンジニアは私1名)に比べて
1.5倍~2倍の成果が出せていました。
今回は触れていませんが、コールバックが簡潔に書けるため、イベントハンドラなどを記述しだすとさらに威力が発揮されます。
設定ファイルをyaml形式でさくっと記述し、即リロードしてゲームに反映することでレベルデザインも捗りますよ。
さらにチーム開発を加速するためにディレクターが設定値を編集出来るように仕組み化していくと面白いですね。