こんにちは!
ADXアンバサダーのこはとです。
本記事は、インディーゲーム開発サークル「ArrayCats(@ArrayCats)」様が配布されている、「タテヨコ冒険譚」のサウンドデータを利用した、インタラクティブミュージックの実践方法について解説する記事です。
はじめに
本記事は、実際にインタラクティブミュージックとして使用されている楽曲素材を、ADXプラグインを活用した場合の実装方法として解説する、Unityユーザー向けの記事となります。
CRI Atom Craftなど関連ツールの使用方法については記事内でも紹介しますが、やや複雑なため、ある程度操作に習熟した方向けの記事となります。
インストールはこちらから。
「タテヨコ冒険譚」とは?
ゲーム開発サークル「ArrayCats」様が開発したパズルゲームで、UnityRoomにて無料で公開されています。
https://unityroom.com/games/v_and_h_adventure
ゲーム内では、タイトル、ゲーム本編での進行に応じて、BGMの曲調が変化するようになっています。
本記事では、ArrayCats様がSNSで公開されている「タテヨコ冒険譚」の音楽素材を活用し、ADXを用いたインタラクティブミュージックの実装方法として解説します。
今回は主に以下の内容で説明します。
0:CRI ADXを導入すると、何が起こるのか
1:実装内容について確認する
2:CRI Atom CraftでUnity用音声ファイルを作成する
3:Unityでスクリプトから音声をコントロールする
0:CRI ADXを導入すると、何が起こるのか
「タテヨコ冒険譚」では、Unity標準のAudioSourceを使用して実装されています。
本記事では、これらをCRI ADXのシステムで実装し、同等の結果を得られるようにします。
では、標準AudioでできることをわざわざCRI ADXに置き換える利点とは、何でしょうか?
・インタラクティブミュージックのオーサリングを、AtomCraft上で作成できる
「ゲームの進行に合わせて曲調が変化する」「行動に合わせてアレンジが変わる」などのインタラクティブミュージック機能を、CRI ADXの持つオーサリングソフト「AtomCraft」内で行うことができます。
これにより 作曲者は、Unityのプロジェクトに直接触れずとも、DAWに近い操作感で、インタラクティブミュージックの具体的な挙動を製作することができます。
・Unityではカバーできない、サンプルの詳細な制御、音声ファイルの管理が可能になる
Unity上では、音声サンプルの詳細な編集まではできませんが、上述の「AtomCraft」を使用することで、音声サンプルに対し、デュレーションの調整、カットやループ、ローパスなどの編集が可能になります。
また、AtomCraftで編集した音声ファイルは、「.Acb(.Awb)ファイル」という形式のデータにまとめて書き出すことで、肥大化しがちなインタラクティブミュージックの音声ファイルを一元管理することも可能になります。
・ビルド後も、HCA圧縮形式で軽量かつ多重再生に強い
CRI ADXでは、音声再生に特化した独自の圧縮形式「HCA形式」を開発、採用しており、音声ファイルの最大1/16のサイズ圧縮、高品質、低負荷の再生を可能にします。
標準のUnityAudioでは再生遅延の発生するAndroidビルドでも、CRI ADXでは遅延なく再生できるという優位性があります。
なお、本記事で使用する無料版の「ADX LE」では、web GLビルドはサポート外である点にご注意ください。
本記事の内容ではあくまでも一部分の実装なため、その恩恵は小さいですが、よりゲームの規模が大きくなればなるほど、これらの恩恵は大きくなります。
本記事の内容で、少しでもこれらの良さに触れていただけたらと思います。
1:実装内容について確認する
それではまず、今回使用する音声データで期待される動きを確認します。
使用データは、ArrayCats様が配信されているこちらのURLのページ下部「GameAsset」の「ダウンロード」から取得してください。
本記事では、ダウンロードされたデータの中の「ゲーム用素材>音楽素材」に入っている楽曲のうち「1_」「3_」で始まるファイルを対象とします。
該当の音声の実際の動作については、上記ポスト先の動画のほか、実際にUnityRoomで「タテヨコ冒険譚」をプレイして確認していただければと思います。
動作について、ざっくりまとめるとこんな感じです。
今回は「1_」の名前で始まる音声群を「BGM_1」
「3_」で始まる音声群を「BGM_2」と呼称します。
BGM_1では、いわゆる縦、横の遷移を両方含む遷移の実装を行います。
インタラクティブミュージック用語での縦、横の遷移とは、再生される音楽の「動き方」を示す用語です。
本記事では
縦=曲のアレンジの変化
横=曲の展開の変化
という定義で説明していきます。
BGM_1の内容では、
縦の遷移:Introでの通常←→Harpとのアレンジ変化&ゲーム本編での各アレンジ変化
横の遷移:IntroでのBGMループ→Introつなぎ→ゲーム本編BGMループへの、曲の進行の変化
といった機能を「ブロック再生」という機能を使い、ひとつながりの曲として実現します。
また、BGM_2では、ADXに搭載される「AISAC」機能を使い、全ての音声を重ねて再生させたまま、音声の重ね合わせのバランスを調整して演出を行う方法を解説します。
2:CRI Atom CraftでUnity用音声ファイルを作成する
まずはダウンロードされたファイルをCRI Atom Craft向けにwavファイルへ変更します。
変換はAudacityなど、任意のソフトウェアを使用して頂いて構いません。
変換したら、CRI Atom Craftを開き、新規プロジェクトを作成。
マテリアルルートフォルダに作成したwavファイルをドラッグアンドドロップでロードします。
今回使用するファイルは上記のとおりです。
ファイルの登録が完了したら、実際にCueの作成に入ります
CueSheet_0に「BGM_1」「BGM_2」のCueを作成し、内容を編集していきます。
BGM_1の作成
BGM_1では「ブロック再生」機能を利用します。
概要は以下をご覧ください。
BGM_1のCueを選択。「Intro_Loop」をBGM_1のCueにドラッグアンドドロップで登録した後「BGM_1」の空白部分を右クリックし、「新規オブジェクト>ブロックの作成」を選択します。
上部にブロックが作成されたと思います。
クリックしてインスペクタ画面から名前を「Intro_loop」など、任意の名前にしましょう。
この名前はCRI Atom Craft上でユーザーが認識するために使用され、ゲームの実装に関係はありません。
ゲーム実装の際は、ブロックごとのID(左から0,1...)を使用します。
適用できたら、続けてBGM_1で使用するファイル数分、以上と同様に設定します。
タイムラインのトラックの隣に音声ファイルをドラッグアンドドロップすると、自動でブロックも追加されます。
全て適用し終わった状態がこちら(ブロック名は任意)。
ブロックの境界はそれぞれの音声毎に始点、終点を合わせることが重要です。
全て適用し終えたら、次にブロックの設定を行っていきます。
今回設定する内容は、冒頭で確認した動作の内容に沿って行っていきます。
内容は主に以下の4点です。
・Intro_Loop,Intro_loop_Harp,Strings,Wind,Harpは、入力のない限り、同じブロック内をループし続ける。
・Introは、再生した後はStrings,Wind,Harpのブロックへと遷移する。
・Intro_loop,Intro_loop_Harpは、遷移の処理が入力された後、ブロックの終端に達したら遷移する。
・Strings,Wind,Harpは、遷移の処理が入力後、曲の小節に合ったタイミングで遷移する。
これらの設定は全て、ブロックのインスペクタ設定から行うことができます。
任意のブロックをクリックし、インスペクタ画面を確認しましょう。
この中の「ブロック」の内容を設定することで、任意の処理を作成することができます。
ブロックの設定項目について
各項の設定内容は以下のとおりです。
・ブロックの終端ミリ秒:ブロックの終わり時間を設定します。
・ブロック遷移先
【なし】:そのブロックの終端で再生が停止します。
【次のブロック】:ブロックの終端に達したら、一つ右のブロックへに遷移します。
【ブロック遷移先指定】:右へ遷移する代わりに、任意のブロックへと遷移します。遷移先は同じCue内のブロックのみです。複数の任意のブロックの中からランダムに遷移させることも可能です。
・ブロックのループ回数:ループ回数を指定します。-1を設定することで、ブロックは無限ループします。
無限ループの際、ゲーム内から遷移先が指定されない限り遷移することは無いため、前項の「ブロックの遷移先」の設定は無視されます。
・ブロックの分割数:任意の数にブロックを分割します。次項の「ブロック遷移タイミング」で「指定分割で」を設定した場合、ゲームからの遷移入力があった際は、ここで指定した分割のタイミングでブロックが遷移します。
・ブロックの遷移タイミング:ゲーム側から遷移の入力を受けた際の遷移タイミングを設定します。なお、ゲームからの入力がない場合は、ループするか、「ブロック遷移先」に従って遷移します。
【ブロックエンド時】:ブロックが終了したタイミングに遷移します。
【指定分割で】:「ブロックの分割数」で分割したタイミングで遷移します。例えば、音楽の小節毎に分割すれば、曲の進行に合わせて遷移させることができます。
【即時】:信号が送られた時点で、即座に遷移します。チャージショットなど、即時性のある演出効果などに。
・ブロックの遷移モード:前項で「指定分割で」を設定した場合のみ有効。
【ブロックの先頭に遷移】:分割タイミングで遷移した後、遷移先のブロックの先頭から再生します。
【分割位置を維持して遷移】:分割タイミングで遷移した後、遷移先のブロックにおける、同じ分割タイミングから再生します。曲の進行を変えず、アレンジだけを変える場合はこれを使用。
【分割位置を維持して遷移】をする際、遷移元と遷移先の分割数が異なる場合は、遷移したカウントの分割タイミングに遷移します。
(例:分割数[4]と分割数[8]の場合→2/4時に遷移すると2/8から再生されます)
曲の進行を同期させたい場合は、必ず遷移元と遷移先の分割数が一致していることを確認しましょう。
・ブロック遷移の振る舞い
【ブロック停止】:遷移した後、ブロックは再生を停止します。
【なし】:遷移しても、遷移元の再生が残り、再生が重なります。
・明示的な遷移禁止ブロック
誤った遷移入力などにより音声の再生に齟齬が発生しないよう、CRI Atom Craft側で明示的に遷移させない場所を指定します。
上記を踏まえ、以下の設定を行います。
Intro_loop、Intro_loop_Harpブロック
ループ回数:-1
明示的な遷移禁止:Strings,Wind,Harp
Introブロック
明示的な遷移禁止:Intro_loop,Intro_Harp,Intro
Strings、Wind、Harpブロック
ループ回数:-1
分割数:57(※)
遷移タイミング:指定分割で
ブロック遷移モード:分割位置を維持
明示的な遷移禁止:Intro_loop,Intro_Harp,Intro
※分割数について
音楽に合わせて遷移する場合、分割数は音楽の小節数によって算出する必要があります。
小節数は [(曲の長さ*(BPM/60))/分割したい小節の拍数] で算出できます。
Strings,Wind,Harpは、128.304秒、BPM160、1小節6拍子となっております。
したがって、分割数は、(128.304*(160/60))/6= 342.144/6 = 57.024≒57となります。
なお、BPMは、トラックを右クリックして「BPM解析」を選択すると表示できます。
設定できたら、F5キーで再生し、Intro_loopから再生を確認してみましょう。
再生中にブロックをクリックすることで、ブロック遷移が行なえます。
タイミングがズレたり、空白ができる場合は、トラック位置やブロックの始点、終点の調整を行ってみてください。
BGM_2の作成
BGM_2では、AISACという機能を使います。
AISACとは、AISAC値一つに音声の様々なパラメーターを紐づけ、複合的な音声制御を可能にします。
今回は簡易的に、音量操作の機能としてAISACを使用します。
まずはCRI Atom Craft上で作成した「BGM_2」に、該当の音声ファイルをドラッグアンドドロップで適用します。
BGM_2では4つの音声を使用します。
今回はトラックは全て無限ループにしたままでOKなので、Cue自体の設定をループ設定にします。
BGM_2のトラックを選択した状態で、インスペクタ画面から「キュー>再生モード」を「PlayList」に設定します。
設定したら、早速AISACを設定していきます。
今回は、一個目Accompを鳴らしたまま「Percuss」「Wind」「Harp」を上に重ねる形にしようと思います。
したがって「Accomp」にはAISACを設定せず、他3つに設定しようと思います。
まず、Percussのトラックの空白で右クリック。
「新規オブジェクト>AISACの作成」を選択します。
選択したら出てきたWindowで内容を入力。
こちらの名前も、AtomCraft内だけで使用する名前のため、任意で構いません。
AISACコントロールは「AisacControl_00」に、
AISACグラフタイプ(操作したいパラメーター)は「ボリューム」に設定します。
設定したら、「AISAC」タブを開き、ボリュームのグラフを以下のように左の終端を0の値にまでドラッグしておろします。
以上の内容を、同様に「Wind」「Harp」にも適用します。
なお、それぞれAISACコントロールは「AisacControl_01」「AisacControl_02」に変更するよう注意してください。
設定が終わったら、再生の確認をします。
AISACを用いた確認は、画面左上タブ「表示>セッションウィンドウ」から確認できます。
BGM_2のCueをセッションウィンドウにドラッグし、再生ボタンを押すことで確認が行なえます。
ウインドウ中部にあるAISAC値にチェックマークを入れ、スライダーを調整し、音量が変化することを確認しましょう。
ここまで作成できたら、Unityの実装に入りましょう!
3:Unityでスクリプトから音声をコントロールする
最後に、Unityで作成した内容をUnityへ実装させていきます。
まず、Unityの新規プロジェクトを作成し、作成したプロジェクトにADXプラグインを導入します。
導入方法については、下記の記事を参考ください。
導入が完了したら、一度CRI Atom Craftへ戻り、CueSheet_0をビルド。作成したプロジェクトのStreamingAssetフォルダへ保存します。
保存が確認できたら、UnityのHierarchyにオブジェクトを追加します。
今回はOnGUIのみで動作の確認をするため、特にUIや3Dオブジェクトは作成しません。
最初にADXの必須Componentである「CriWareLibraryInitializer」「CriWareErrorHandler」を作成。
次に空のGameObjectを作成し、名前を「BGMController」とします。
BGMControllerには予め「CriAtom」「CriAtomSource」をアタッチしておきましょう。
作成したら、新規スクリプトを作成します。
スクリプト名は「BGMController」とし、内容は下記となります。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using CriWare;
using System;
public class BGMController : MonoBehaviour
{
//ADX Component===========================================
//再生するプレイヤー
CriAtomSource atomSource;
//再生するAcbファイル
CriAtomExAcb acb;
//ブロック再生コントロール用プレイバック
CriAtomExPlayback exPlayback;
//General Variable========================================
//現在再生しているBGM番号
int nowBGM = 1;
//BGM_1 Variable==========================================
//ブロックIDをブロック名と紐づけ
public enum BGM1_Block
{
Title_Strings = 0,
Title_Harp,
Title_Span,
InGame_Strings,
InGame_Wind,
InGame_Harp,
}
//外部から参照できるよう、現在再生されているブロックを代入
public BGM1_Block nowBGM1_Block
{
get;
private set;
}
//BGM_2 Variable==========================================
//スライダーから入力したAISAC値(0~1)を代入
float AISACVal_0;
float AISACVal_1;
float AISACVal_2;
private IEnumerator Start()
{
//CriAtomSourceComponent取得
atomSource = gameObject.GetComponent<CriAtomSource>();
//AtomCraftデータ取得
while (CriAtom.CueSheetsAreLoading)
{
yield return null;
}
acb = CriAtom.GetAcb("CueSheet_0");
//再生
atomSource.player.SetCue(CriAtom.GetAcb("CueSheet_0"), "BGM_1");
//ブロック再生の操作にはCriAtomExPlaybackクラスを必要とします。
//CriAtomSource.player.Start()はCriAtomExPlaybackクラスを返すため、再生と同時に代入します。
exPlayback = atomSource.player.Start();
}
//Update is called once per frame
void Update()
{
//現在再生されているブロックを取得
nowBGM1_Block = (BGM1_Block)Enum.ToObject(typeof(BGM1_Block), exPlayback.GetCurrentBlockIndex());
}
private void OnGUI()
{
//BGMを1と2で切り替え。
if (GUI.Button(new Rect(0, 0, 100, 30), $"BGM_{nowBGM}"))
{
//クリック時の応答・処理
if (nowBGM == 1)
{
//BGM1から2へ
nowBGM = 2;
//初期化
AISACVal_0 = 0;
AISACVal_1 = 0;
AISACVal_2 = 0;
//再生
atomSource.Stop();
atomSource.player.SetCue(CriAtom.GetAcb("CueSheet_0"), "BGM_2");
exPlayback = atomSource.player.Start();
}
else
{
//BGM2から1へ
nowBGM = 1;
//初期化
isInGame = false;
//ChangeBGM1_Block_Title(BGM1_Title_Block.Strings);
//ChangeBGM1_Block_InGame(BGM1_InGame_Block.Strings);
ChangeBGM1_Block(BGM1_Block.Title_Strings);
//再生
atomSource.Stop();
atomSource.player.SetCue(CriAtom.GetAcb("CueSheet_0"), "BGM_1");
exPlayback = atomSource.player.Start();
}
Debug("ChangeState");
}
//BGM_1
if (nowBGM == 1 )
{
//現在再生されているブロックを参照し、対応するEnumの値をラベルとして表示。
GUI.Label(new Rect(120, 40, 100, 30), nowBGM1_Block.ToString());
//タイトル画面
if (!isInGame)
{
//Intro_loopへ
if (GUI.Button(new Rect(0, 40, 100, 30), $"Intro_Loop"))
{
ChangeBGM1_Block(BGM1_Block.Title_Strings);
}
//Harpへ
if (GUI.Button(new Rect(0, 80, 100, 30), $"Intro_Harp"))
{
ChangeBGM1_Block(BGM1_Block.Title_Harp);
}
//Span→ゲーム本編へ
if (GUI.Button(new Rect(0, 120, 100, 30), $"Intro_Span"))
{
ChangeBGM1_Block(BGM1_Block.Title_Span);
}
//Stringsへ
if (GUI.Button(new Rect(0, 160, 150, 30), $"BGMState_Strings"))
{
ChangeBGM1_Block(BGM1_Block.InGame_Strings);
}
//Windへ
if (GUI.Button(new Rect(0, 200, 150, 30), $"BGMState_Wind"))
{
ChangeBGM1_Block(BGM1_Block.InGame_Wind);
}
//Harpへ
if (GUI.Button(new Rect(0, 240, 150, 30), $"BGMState_Harp"))
{
ChangeBGM1_Block(BGM1_Block.InGame_Harp);
}
}
//BGM_2
}else if(nowBGM == 2)
{
//ラベル表示
GUI.Label(new Rect(0, 40, 100, 30), "Percuss");
GUI.Label(new Rect(0, 80, 100, 30), "Strings");
GUI.Label(new Rect(0, 120, 100, 30), "Wind");
//スライダーから値を取得
AISACVal_0 = GUI.HorizontalSlider(new Rect(120, 40, 100, 30), AISACVal_0, 0.0F, 1.0F);
AISACVal_1 = GUI.HorizontalSlider(new Rect(120, 80, 100, 30), AISACVal_1, 0.0F, 1.0F);
AISACVal_2 = GUI.HorizontalSlider(new Rect(120, 120, 100, 30), AISACVal_2, 0.0F, 1.0F);
//取得した値を反映
ChangeBGM2_SetAISAC(0, AISACVal_0);
ChangeBGM2_SetAISAC(1, AISACVal_1);
ChangeBGM2_SetAISAC(2, AISACVal_2);
}
}
/// <summary>
/// タイトル画面におけるBGMのブロック変更
/// </summary>
/// <param name="status"></param>
public void ChangeBGM1_Block(BGM1_Block tgtBlock)
{
//該当EnumのIDを入力
exPlayback.SetNextBlockIndex((int)tgtBlock);
nowBGM1_Block = tgtBlock;
print($"ChangeState : {(int)tgtBlock}");
}
/// <summary>
/// AISACのIDを指定し、そのIDに値を入力する。
/// </summary>
/// <param name="id"></param>
/// <param name="value"></param>
public void ChangeBGM2_SetAISAC(uint AISACid, float value)
{
// 念のため、入力値はメソッド内で0~1に強制的に変換
value = Mathf.Clamp01(value);
atomSource.SetAisacControl(AISACid, value);
}
}
上記のスクリプトで重要なのは以下の3つの関数です。
・CriAtomExPlayback.SetNextBlockIndex(int)
遷移先のブロックを指定します。入力後、再生中のブロックは、CRI Atom Craftで設定した「ブロック遷移タイミング」に応じて、指定のブロックへ遷移します。後入力優先。
・CriAtomExPlayback.GetCurrentBlockIndex(int)
現在再生されているブロックのIDを返します。ブロック遷移には、遷移したタイミングを取得するイベントコールバックは実装されていないため、この関数を使用し、ブロック変化を監視する等が必要となります。
・CriAtomExPlayer.SetAisacControl(uint, int)
第2引数でAISACコントロール値を反映します。第1引数は対象のAisacControlを指定するIDであり、”AisacControl_00”のようにString型でも指定することが可能です。
スクリプトが完成したら、先程作成した「BGMController」オブジェクトにアタッチし、CRIAtomの必要情報を入力します。
アタッチが完了したら、再生して挙動を確認してみましょう!
一番上のボタンでBGM_1とBGM_2を切り替えます。
BGM_1では、各項目のボタンを押すことで、遷移先を設定→特定のタイミングで遷移します。
BGM_2では、スライダーを操作することで各パートがAccompの音声に重なります。
特にBGM_1について、今回作成したスクリプトでは、敢えてどの状態でも全ての遷移を選択できるようにしました。
しかし、CRI Atom Craftで予め「明示的な遷移禁止ブロック」を指定したため、仕様にない遷移はされないようになっているはずです。
BGM_1
BGM_2
「遷移選択後に別の遷移を選択する」「Span中にWindを選択する」などを行い、どのような挙動をするのかを確認してみましょう!
まとめ
お疲れ様でした!
今回は、実際にインタラクティブミュージックとして使用されているBGM素材を、ADXを用いたケースとして実装方法を紹介しました。
本記事で解説した機能、ブロック再生とAISACによるパートの重ねがけは、それぞれ縦、横の遷移の機能としてインタラクティブミュージックでも広く使われる手法となります。
一つのCueで両方を掛け合わせることで、更に広がりのあるBGM表現を行うこともできるので、このプロジェクトを基に、様々な改造を行ってみてください!
また、今回素材を使用させていただいた、ArrayCats様のゲーム「タテヨコ冒険譚」はこちらからプレイできます!
最後まで読んでいただきありがとうございました!
参考: