こんにちは@giginetです。Cocos2d-x Advent Calendar 2014の16日目を担当させて頂きます。よろしくお願いします。
cocos2d-xでインタラクティブミュージックに挑戦
前回から間が空いてしまいましたが、今回は、ADX2に搭載されている「ブロック再生」の機能を使って、動的にメロディが変化するゲームを実装してみましょう。
ブロック再生機能を使うことで、シームレスな音楽の切り替えを簡単に行うことができます。
この記事は、cocos2d-x + ADX2でインタラクティブミュージックに挑戦する(前編) - Qiitaの続きとなっています。
ADX2 LEについての説明や、環境構築についてはこちらの記事を読んでください。
ブロック再生について知る
ADX2に搭載されている「ブロック再生」とはどのような機能なのでしょうか。
以下の図のように、メロディの任意の部分を「ブロック」と呼ばれる塊として扱います。
「ブロックA」は通常、常にループ再生し続けますが、「ブロックA」の再生中の任意のタイミングで「ブロックB」への遷移リクエストをプログラム側から送ります。
すると、ADX2は自動的に「ブロックA」の再生終了を待ち、今度は自動的に「ブロックB」のループ再生を開始します。
これを上手く使うことで、簡単に動的な音楽の変化(インタラクティブミュージック)を実装することができます。
今回作るもの
今回は、僕が1年半ほど前にリリースした、『VOXCHRONICLE』というゲームの素材を使い、動的に遷移するBGMを実現してみます。
VOXCHRONICLE - オクスクロニクル - on the App Store on iTunes
『VOXCHRONICLE』では、一つの音楽の1小節が1ターンになっている、ターン制のコマンドRPGで、打撃や魔法など、キャラクターのコマンドを入力すると、次のメロディが動的に遷移していくというシステムになっています。
今回は簡単に「待機」と「攻撃」のコマンドを選択したときに、BGMを動的に切り替えるデモを作ってみましょう。
CRI Atom Craftでブロックを定義する
早速、CRI Atom Craftを使ってブロックを定義してみましょう。
CRI Atom Craftの基本的な使い方は、CRI・ミドルウェアの@Takaaki_Ichijoさんが解説記事を執筆してくださいました。詳細はこちらをご覧ください。
Unity - CRI Atom Craftで音声データを作成する - Qiita
今回は『VOXCHRONICLE』で使われている楽曲のうち以下の6小節を使用してみます。
待機用の2小節、voxwait0
, 1
と、攻撃用の4小節、voxattack0 ~ 3
です。
これら6つのマテリアルを新規作成したキュー上に並べます。
その後、右クリック > 新規オブジェクト > ブロックの作成
からvoxwait
とvoxattack
の2つのブロックを作成しています。
上手く設定すると、以下の図のようになるはずです。
また、それぞれのブロックのプロパティを設定しましょう。
例えば、これがvoxwait
ブロックの設定例です。
ブロックループ回数
に無限ループである-1を設定しましょう。これで、ブロック遷移命令がない限り、このブロックがループ再生されつづけます。
ブロック分割数
を2に設定してください。これは1つのブロックを指定した等分に分割し、それらも遷移タイミングに設定できる機能です。今回は1ブロックに2小節分のマテリアルが含まれているため、2に設定しています。
最後に、ブロック遷移タイミング
を「指定分割で」に設定します。これは、ブロック遷移命令があったとき、ブロックの再生終了ではなく、指定分割部分、すなわち今回の場合小節が終わったタイミングで遷移する設定です。
voxattack
についても、同様に分割数を4に設定してください。
ここまでで設定は完了です。ビルドしてcocos2d-xで読み込める形で配置してください。
CRI Atom Craftで作成したブロックを制御する
ADX2の初期セットアップなどは前回の記事を見てください。
ここではHelloWorld
のinit
メソッド内の実装例をご紹介します。
#include "CueSheet.h"
USING_NS_CC;
bool HelloWorld::init()
{
if ( !Layer::init() )
{
return false;
}
// キューシートの初期化
auto cueSheet = ADX2::CueSheet::create("InteractiveMusic.acf", "Main.acb");
this->setCueSheet(cueSheet);
// CriAtomExPlayerHnの取得
CriAtomExPlayerHn player = ADX2::Manager::getInstance()->getDefaultPlayer();
// ブロックが遷移した直後に呼ばれるコールバックの定義
criAtomExPlayer_SetBlockTransitionCallback(player, [] (void * obj,
CriAtomExPlaybackId playbackId,
CriAtomExBlockIndex index){
log("changed");
}, nullptr);
// 波形が切り替わった直後に呼ばれるコールバックの定義
criAtomExPlayer_SetDataRequestCallback(player, [](void *obj,
CriAtomExPlaybackId playbackId,
CriAtomPlayerHn player) {
log("loaded");
}, nullptr);
// CRI_MAIN_VOXキューを再生
auto playbackId = _cueSheet->playCueByID(CRI_MAIN_VOX);
// ボタンの定義
auto waitButton = MenuItemLabel::create(LabelTTF::create("Wait", "Helvetica", 64),
[playbackId](Ref *ref) {
// 現在のブロックを取り出して
auto currentBlock = criAtomExPlayback_GetCurrentBlockIndex(playbackId);
// これから切り替える物と違ったら
if (currentBlock != CRI_MAIN_BLOCK_VOX_VOXWAIT) {
// 次に遷移するブロックを決定する
criAtomExPlayback_SetNextBlockIndex(playbackId, CRI_MAIN_BLOCK_VOX_VOXWAIT);
}
});
auto attackButton = MenuItemLabel::create(LabelTTF::create("Attack", "Helvetica", 64),
[playbackId](Ref *ref) {
auto currentBlock = criAtomExPlayback_GetCurrentBlockIndex(playbackId);
if (currentBlock != CRI_MAIN_BLOCK_VOX_VOXATTACK) {
criAtomExPlayback_SetNextBlockIndex(playbackId, CRI_MAIN_BLOCK_VOX_VOXATTACK);
}
});
// メニューの作成
auto menu = Menu::create(waitButton, attackButton, nullptr);
menu->setPosition(Vec2(200, 200));
menu->alignItemsHorizontally();
this->addChild(menu);
return true;
}
ミソはcriAtomExPlayback_SetNextBlockIndex
関数で、この第1引数に渡した再生を第2引数に渡したブロックに遷移します。
第1引数に渡すplaybackId
は、ある再生コンテキストを表すユニークなIDで、キューの再生開始時に返されるのでそれを保存しておきます。
第2引数に渡すblockIndex
は遷移先のブロックのインデックスです。定数はCRI Atom Craftから生成したヘッダファイルにそれっぽい名前の定数が自動的に定義されます。
わずかこれだけで、ブロックの遷移を行うことができました。
また、ブロックが切り替わったことや、波形が切り替わったときにコールバックを受け取ることができます。
ここではcriAtomExPlayer_SetBlockTransitionCallback
やcriAtomExPlayer_SetDataRequestCallback
を使っています。
// ブロックが遷移した直後に呼ばれるコールバックの定義
criAtomExPlayer_SetBlockTransitionCallback(player, [] (void * obj,
CriAtomExPlaybackId playbackId,
CriAtomExBlockIndex index){
log("changed");
}, nullptr);
本来は関数ポインタを渡すのですが、ここではC++11のlambdaを渡しています。
ラムダを使うことで簡潔に書くことができますね。
ただし、この場合はキャプチャを行うことができないので注意しましょう。
任意のオブジェクトを渡したい場合、第3引数にポインタを渡すことで、コールバックの第1引数で受け取ることができるため、これを上手く使ってください。
その他、ブロック再生の制御には以下のような関数が使用できます。詳しくはドキュメントを参照してください。
関数名 | 概要 |
---|---|
criAtomExPlayback_GetCurrentBlockIndex(playbackId) | 現在再生中のブロックのインデックスを取得できます |
criAtomExPlayer_SetFirstBlockIndex(player, blockIndex) | 一番最初に再生するブロックのインデックスを指定できます。再生開始前に設定しないと効果がないので注意してください |
完成
今回作った物は以下のようになります。
任意のタイミングで「Wait」、「Attack」のボタンを入力すると、BGMが途切れなく、小節の区切れのタイミングで遷移しているのがおわかり頂けるでしょうか。
実際の『VOXCHRONICLE』ではさらに複雑なのですが、今回はシンプルな例を実装して紹介してみました。
『妖怪ウォッチ2』でのインタラクティブミュージックの例
完全に蛇足ですが、身近なゲームで今回のような実装が使われている例を紹介。
世間では『妖怪ウォッチ』がめちゃくちゃ流行していますが、『妖怪ウォッチ2』の戦闘BGMでは、実は今回紹介したブロック再生的な仕組みが使われていました。
戦闘に突入したときに、最初の「たたかう」コマンドを入力するまで、動画中の4~8秒付近のイントロBGMが延々とループし、「たたかう」コマンドを入力するとそのあとのブロックに遷移し、メインメロディが再生開始されます。
わかりやすい動画がなかったので上手く紹介できませんでしたが、『妖怪ウォッチ2』をお持ちの方は試してみてください。
詳しく知りたい方は
この記事では、ADX2 LE向けのGUIツールである『CRI Atom Craft』の詳しい使い方や、Androidへの組み込み方などを割愛しています。
もし、さらに詳しい情報が知りたい場合は、12月25日頃発売予定の拙著『cocos2d-xではじめるスマートフォンゲーム開発 [cocos2d-x Ver.3対応] for iOS/Android』をご覧ください。
Amazon.co.jp: cocos2d-xではじめるスマートフォンゲーム開発 [cocos2d-x Ver.3対応] for iOS/Android: 三木 康暉: 本
こちらの書籍では、開発元のCRI・ミドルウェアさんご協力の下、『ADX2 LE for cocos2d-x』の組み込み方と利用例を紹介しています。