読者です 読者をやめる 読者になる 読者になる

WonderPlanet DEVELOPER BLOG

ワンダープラネットの開発者ブログです。モバイルゲーム開発情報を発信。

Hello Cocos2d-x in Lua!

こんにちは、エンジニアの成田です。

Cocos2d-xはiOSAndroidWindows PhoneなどクロスプラットフォームのゲームをC++で開発できることが大きな特徴ですが、C++以外にもJavascriptバインディングLuaバインディングがあることはご存知でしょうか?
今回はC++からLuaを呼び出し、Cocos2d-xのAPIを操作してみます。

1.なぜスクリプト言語を用いるのか

実装に入る前に、Luaの生い立ちなど細かいことはWikipediaに譲るとしまして、JavascriptLuaスクリプト言語です。なぜゲームでスクリプト言語を用いるのでしょうか。

第一は、ゲームから変数やゲームデータ(e.g.シナリオ、ステージ毎のオブジェクト配置)を外部に分離することができます。ゲーム本体側からはXMLJSONを読み込む際に必要になるパーサを書く必要はなく、スクリプト言語を呼び出すだけでOKです。
第二に生産性の向上があります。開発が進んでいくとゲームは通常巨大化していき、ビルドに時間を要するようになっていきます。少しパラメータを変更しただけなのに毎度ビルドを行わなければいけないのは大変な苦痛ですが、パラメータがスクリプトという形で外部化されていれば再ビルドの必要はなくなります。
第三には、書いたり理解するのが比較的簡単です。C++のような汎用的な低級言語と比べ、スクリプト言語は自由で理解しやすく、また別段コンパイラIDEも必要ないため、デザイナー・プロデューサーなど非プログラマが調整を行いたい時に自分で触ることもできます。

これらのメリットのため、スクリプト言語コンシューマゲームで利用実績が多数あります。
逆にデメリットとしては、C/C++のようなネイティブコードと比較して実行速度が遅い、スクリプト部分がアプリケーション中にテキストファイルとして含まれるため、ハックされやすい等が挙げられます。

2.実装

それではCocos2d-xのLuaバインディングを試してみましょう。
今回は、まずC++ベースのCocos2d-xプロジェクトを作成してから、そこへライブラリを追加することでLuaプログラムを動せるようにします。
これにより、C++でのCocos2d-x環境からLuaを動かせるようになるまでの要件が明白になるようにしたいと思います。

ではC++ベースのCocos2d-xプロジェクトを作成します。「cocos2dx」テンプレートを選択してください。
下に「cocos2dx_lua」というのが見えますが今回は選択しません。
image1

C++Hello worldアプリケーションが作成されたと思います。
Luaプログラムを呼び出すためには、まずCocos2d-xのディレクトリからLuaヘッダ群を持ってくる必要があります。

以下ではCocos2d-xのインストールディレクトリを[cocos2dx]、プロジェクトのルートディレクトリを[project-root]とします。

  1. [project-root]/libs 下に、新たにluaディレクトリを作成します。
  2. ヘッダ群をコピーします。
  3. [cocos2dx]/scripting/lua/cocos2dx_support を [project-root]/libs/lua/cocos2dx_support へコピー [cocos2dx]/scripting/lua/luajit/include を [project-root]/libs/lua/luajit/include へコピー [cocos2dx]/scripting/lua/luajit/ios を [project-root]/libs/lua/luajit/ios へコピー [cocos2dx]/scripting/lua/tolua を [project-root]/libs/lua/tolua へコピー
  4. これらのディレクトリをプロジェクトに追加します。ただし、cocos2dx_supportディレクトリに関しては、次のファイルだけを追加するようにします。
  5. CCLuaBridge.cpp CCLuaBridge.h CCLuaEngine.cpp CCLuaEngine.h CCLuaStack.cpp CCLuaStack.h CCLuaValue.cpp CCLuaValue.h Cocos2dxLuaLoader.cpp Cocos2dxLuaLoader.h LuaCocos2d.cpp LuaCocos2d.h platform/ios/CCLuaObjcBridge.h platform/ios/CCLuaObjcBridge.mm tolua_fix.c tolua_fix.h

これで準備ができました。
最終的にこのようなディレクトリ構造になっているかと思います。
image2

それではLuaプログラムを書いてみましょう。まずは簡単な足し算を行ってみます。
[project-root]/Resources に hello.lua ファイルを追加し、次のように記述します。

function add(x, y)  
    return x + y  
end  

引数xとyを加算して返すだけの単純な関数addです。

HelloWorldScene.cpp に#include文を追記し、init()に関数addの呼び出しを追加します。

#include "script_support/CCScriptSupport.h"  
#include "CCLuaEngine.h"  
// on "init" you need to initialize your instance  
bool HelloWorld::init()  
{  
    //////////////////////////////  
    // 1. super init first  
    if ( !CCLayer::init() )  
    {  
        return false;  
    }  
  
    /////////////////////////////  
    // 2. add a menu item with "X" image, which is clicked to quit the program  
    //    you may modify it.  
  
    // add a "close" icon to exit the progress. it's an autorelease object  
    CCMenuItemImage *pCloseItem = CCMenuItemImage::create(  
                                        "CloseNormal.png",  
                                        "CloseSelected.png",  
                                        this,  
                                        menu_selector(HelloWorld::menuCloseCallback) );  
    pCloseItem->setPosition( ccp(CCDirector::sharedDirector()->getWinSize().width - 20, 20) );  
  
    // create menu, it's an autorelease object  
    CCMenu* pMenu = CCMenu::create(pCloseItem, NULL);  
    pMenu->setPosition( CCPointZero );  
    this->addChild(pMenu, 1);  
  
    /////////////////////////////  
    // 3. add your codes below...  
  
    // add a label shows "Hello World"  
    // create and initialize a label  
    CCLabelTTF* pLabel = CCLabelTTF::create("Hello World", "Thonburi", 34);  
  
    // ask director the window size  
    CCSize size = CCDirector::sharedDirector()->getWinSize();  
  
    // position the label on the center of the screen  
    pLabel->setPosition( ccp(size.width / 2, size.height - 20) );  
  
    // add the label as a child to this layer  
    this->addChild(pLabel, 1);  
  
    // add "HelloWorld" splash screen"  
    CCSprite* pSprite = CCSprite::create("HelloWorld.png");  
  
    // position the sprite on the center of the screen  
    pSprite->setPosition( ccp(size.width/2, size.height/2) );  
  
    // add the sprite as a child to this layer  
    this->addChild(pSprite, 0);  
      
    //////// ここまでサンプルコードのまま ////////  
      
    // Luaスクリプトの呼び出し  
    CCLuaEngine* pEngine = CCLuaEngine::defaultEngine();  
    CCScriptEngineManager::sharedManager()->setScriptEngine(pEngine);  
    std::string path = CCFileUtils::sharedFileUtils()->fullPathForFilename("hello.lua");  
    pEngine->executeScriptFile(path.c_str());  
      
    lua_State* L = pEngine->getLuaStack()->getLuaState();  
    lua_getglobal(L, "add");  // グローバル関数addをgetしてpush  
    lua_pushinteger(L, 1); // 1をpush  
    lua_pushinteger(L, 2); // 2をpush  
    if (lua_pcall(L, 2, 1, 0)) {    // 戻り値は関数呼び出しが成功するとき0、エラーは0以外  
        CCLOG("error=%s", lua_tostring(L, lua_gettop(L))); // エラーメッセージをget  
    } else {  
        CCLOG("return=%d", lua_tointeger(L, lua_gettop(L))); // 戻り値をget  
    }  
      
    return true;  
}  

実行してみましょう。問題がなければコンソールに次のような出力が得られます。

Cocos2d: return=3  

これでC++からLuaへ引数を渡し、Luaからの戻り値をC++で取得できることが確認できました。

ですがまだLuaスクリプトではプリミティブ型の計算しか行っていません。今度こそCocos2d-xのLuaバインディングを使用してみましょう。

hello.lua ファイルに次のような関数を追加します。

function helloLua(layer)  
    -- CCLabelTTFを生成して…  
    local label = CCLabelTTF:create("Hello Lua!", "Thonburi", 34)  
    -- CCLayerの中央へ配置  
    label:setPosition(layer:getContentSize().width/2, layer:getContentSize().height/2)  
    layer:addChild(label)  
end  

HelloWorldScene.cpp も変更します。

#include "script_support/CCScriptSupport.h"  
#include "CCLuaEngine.h"  
#include <tolua++.h>    // tolua_pushusertypeに必要  
// サンプルコード部分は省略  
    //////// ここまでサンプルコードのまま ////////  
      
    // Luaスクリプトの呼び出し  
    CCLuaEngine* pEngine = CCLuaEngine::defaultEngine();  
    CCScriptEngineManager::sharedManager()->setScriptEngine(pEngine);  
    std::string path = CCFileUtils::sharedFileUtils()->fullPathForFilename("hello.lua");  
    pEngine->executeScriptFile(path.c_str());  
      
    lua_State* L = pEngine->getLuaStack()->getLuaState();  
    lua_getglobal(L, "helloLua");  // グローバル関数helloLuaをgetしてpush  
    tolua_pushusertype(L, this, "CCLayer"); // CCLayerもpushできる!  
    if (lua_pcall(L, 1, 1, 0)) {    // 引数は1つ、戻り値も1つ、エラーハンドラはなし(0)  
        CCLOG("error=%s", lua_tostring(L, lua_gettop(L)));  
    }  
      
    return true;  
}  

正常に実行できれば次のような画面になります。

image3

シーンはC++側で生成し、画面中央のテキストラベルをLua側で生成できました。
このようにC++Luaで密接に連携することもできます。

いかがでしたでしょうか。
ゲームの生産性や拡張性向上に寄与するスクリプト言語。みなさんも積極的に使ってみてください。