2013年9月11日水曜日

[tips : cocos2d-x 2.1.5] luaを気軽に使いたい(1)

■前置き
 cocos2d-xではcocos2dx_luaテンプレートを使うことでluaベースのプログラムを無手から始めることができます。

 現在のスマートデバイスはプロセッサの速度も速く実数演算も実用レベルでこなしますからluaのみの実装で充分なケースも少なくはありません。

 とはいえc++から呼び出しすことができればシーンの初期化やイベント制御、またオブジェクトの制御をプログラムから切り離すことができ、生産性を飛躍的に向上させることが可能です。

 ではどのように実現すればいいか、今回はまずcocos2dx_luaテンプレートで目についたCCLuaEngineとその拡張元であるCCLuaStackについてかなりザックリと記しておきたいと思います。

■CCLuaEngine
 CCLuaStackにイベントやスケジューラーまわりの機能を拡張したクラスで、luaメインで組む場合に使用します。

 CCLuaEngine固有の機能はc++から呼び出すにあたり無駄でしかありませんので素直にCCLuaStackを使った方が良さそうです。

■CCLuaStack
 luaのスレッド(実質VM)であるlua_Stateをcocos2d-x向けに拡張したものですが、CCLuaEngineの基礎として設計されているため欲しい機能が全てある、という状態ではありません。

 ただし、cocos2d-xの多くのクラスがそうであるように継承による調整は容易です。

 なお、継承する際は、luaからcocos2d-xの機能を利用するために用意された68,000行に迫るグルーコードの存在を意識しておきましょう。

※グルーコードをザックリ説明すると、スクリプト言語(lua)から呼び出されるネイティブ言語(c++)の実行関数と登録コードのことです

※グルーコードに興味がある方は"$(PROJECT_NAME)/libs/lua/cocos2dx_support/LuaCocos2d.cpp"を開いてみましょう

 グルーコードはtolua++というツールで自動生成され、非常に多くの恩恵を授けてくれますが、その反面コードサイズが増加し、CCLuaStackの生成にも相応の処理時間が必要となります。

 これは多くの場合取るに足らない程度のものですが、例えば、古いデバイスで、弾ごとにスクリプトを持たせ、弾幕を生成する、といった場合、処理落ちを覚悟する必要はあるでしょう。

 そうなってからでは遅いのであらかじめテストしておいた方が無難、という程度の話ではあります。

 次回は実際にluaスクリプトを動かしてみたいと思います。

2013年9月5日木曜日

[tips : cocos2d-x 2.1.5] マルチタッチ対応

 cocos2d-xで2点以上のタッチを制御したいな、ということでさっそく実装。とりあえずiOSのみ。

■環境
  • cocos2d-x 2.1.5
  • iOS6.0.1(iPod touch 5G) 
■実装
  • AppController.mmに以下を追加。
    __glView.multipleTouchEnabled = true; // __glView定義直後でOK
  • CCLayer派生クラスで以下のメソッドを定義
    virtual void ccTouchesBegan(cocos2d::CCSet *touches, cocos2d::CCEvent *event);
    virtual void ccTouchesMoved(cocos2d::CCSet *touches, cocos2d::CCEvent *event);
    virtual void ccTouchesEnded(cocos2d::CCSet *touches, cocos2d::CCEvent *event);
  • CCLayer派生クラスのinitに以下の処理を追加
    setTouchEnabled(true);
    setTouchMode(kCCTouchesAllAtOnce);
  • CCLayer派生クラスに定義したメソッドを実装
// 実装の一例

/*!
 *  @brief  タッチ開始デリゲート
 *  @param  [in]  touches ある瞬間にタッチされた情報(allTouchesではない)
 *  @param  [in]  event   未使用
 */
void MyLayer::ccTouchesBegan(CCSet *touches, CCEvent *event)
{
    CC_UNUSED_PARAM(event);

    for (CCSetIterator it = touches->begin(); it != touches->end(); ++it)
    {
        CCTouch *touch  = (CCTouch *)(*it);
        int      id     = touch->getID(); // 0 to (CC_MAX_TOUCHES - 1)
        m_touchFlag[id] = true;                 // タッチ状態に遷移
        m_touchAt  [id] = touch->getLocation(); // 位置を保存
        // 以下、必要な情報を取得し、update内で処理してやると良い
    }
}

/*!
 *  @brief  タッチ移動デリゲート
 *  @param  [in]  touches ある瞬間に移動した情報
 *  @param  [in]  event   未使用
 */
void MyLayer::ccTouchesMoved(CCSet *touches, CCEvent *event)
{
    CC_UNUSED_PARAM(event);

    for (CCSetIterator it = touches->begin(); it != touches->end(); ++it)
    {
        CCTouch *touch = (CCTouch *)(*it);
        int      id    = touch->getID(); // 0 to (CC_MAX_TOUCHES - 1)
        m_touchAt[id]  = touch->getLocation(); // 位置を更新
        m_delta[id]    = touch->getDelta();    // 移動値を取得
        // 以下、必要な情報を取得し、update内で処理してやると良い
    }
}

/*!
 *  @brief  タッチ終了デリゲート
 *  @param  [in]  touches ある瞬間に離された情報
 *  @param  [in]  event   未使用
 */
void MyLayer::ccTouchesEnded(CCSet *touches, CCEvent *event)
{
    CC_UNUSED_PARAM(event);

    for (CCSetIterator it = touches->begin(); it != touches->end(); ++it)
    {
        CCTouch *touch  = (CCTouch *)(*it);
        int      id     = touch->getID(); // 0 to (CC_MAX_TOUCHES - 1)
        m_touchFlag[id] = false;         // タッチ状態を終了
        // 以下、必要があれば情報を初期化する
    }
}
  • ビルドして動作確認
  • あとは実装次第

2013年9月1日日曜日

[tips : cocos2d-x 2.1.5] 停止したBGMがバックグラウンドタスクからの復帰で再生される

■現象
  • 停止したはずのBGMが鳴り始める

■環境
  • cocos2d-x 2.1.5
  • SimpleAudioEngine
  • iOS5.1.1(iPod touch 3G)、iOS5.1(iPod touch 4G)、iOS6.0.1(iPod touch 5G)
※.iOS4.3.3(for iPad)では未解決

■手順
  • SimpleAudioEngine::playBackgroundMusicでBGMを再生する
  • SimpleAudioEngine::stopBackgroundMusicでBGMを停止する
  • ホームボタンを押しタスクをバックグラウンドに移行する
  • タスクを選択し再度フォアグラウンドに移行する

■原因
  AppDelegate::applicationDidEnterBackgroundで呼び出されるSimpleAudioEngine::pauseBackgroundMusicおよびAppDelegate::applicationWillEnterForegroundで呼び出されるSimpleAudioEngine::resumeBackgroundMusicの挙動が原因。

 SimpleAudioEngine::resume最終的にAVAudioPlayer::playするだけであるためSimpleAudioEngine::stopBackgroundMusicで停止したはずのBGMが再生される。

■対応
 サウンドヘルパークラスで吸収。主な処理は以下の通り。

  • BGM再生フラグを持たせ、playでtrue、stopでfalseにする
  • resumeされた場合、BGM再生フラグをみてSimpleAudioEngine::resumeを呼び出すかを決定する

 SimpleAudioEngine.mm/hを直接変更する場合は以下の通り。

 SimpleAudioEngine::stopBackgroundMusicの代わりに無音のBGMを再生する、もしくはミュートするという強引な解決法も存在するが少なくともライブラリとして実装することはオススメできない。

■その他
 ゲームの一時停止機能でpause / resumeを利用したいとして、AppDelegate::applicationWillEnterForegroundresumeBackgroundMusicが強制的に呼び出されてしまう件に関してはまた別の機会にでも。