はじめに
こちらは @SigRem とのコラボ記事です。「ADX2 for UE4で変化するループBGMを再生する(UE4編)」の内容をUnityに移植したものです。
デモは次の通り。
Unity + #ADX2 で、トラックのミックスをシームレスに変化させる縦の遷移(Vertical Remixing)と、ループ楽曲を良いタイミングで切り替える横の遷移(Horizontal Resequencing)の組合わせ。明日記事公開します。楽曲は @sleepyslowsheep#ゲーム開発 #ゲームサウンド #インタラクティブミュージック pic.twitter.com/tqJq2LYktT
— ichijo (@Takaaki_Ichijo) October 6, 2020
キャラクターを操作して部屋を移動すると、BGMが変わっていきます。
インタラクティブミュージックをゲームに導入しよう
ゲームのBGMは、単一の楽曲データがループするシンプルな表現にとどまらず、ゲーム内の状況に合わせてシームレスに変化する「インタラクティブミュージック」の導入が進んでいます。
「魔族の領域に入った」「戦闘状態になった」などのゲームのステージの変化によって曲が変わったり、「キャラクターが瀕死になった」「必殺技のためのエネルギーがチャージされた」といったキャラのステータスに連動して変化する、といった演出が見られます。
ざっくりといえば、異なるループ曲をクロスフェードして切り替えるのではなく、変化がシームレスに、かつつながった曲に聞こえるように処理してゲームを盛り上げる、それがインタラクティブミュージックです。
インタラクティブミュージックの実装方式は2通りあり、まずは「各楽器の音を別に出力して同期ループ再生させておき、ボリュームをリアルタイムに変化させる」というものです。
これはDAW上で縦に並んだ楽器ごとの音(トラック)に対して処理を行うため、「縦の遷移(Vertical Remixing)」とも呼ばれ、楽曲自体は一緒ですが、楽器が加わったり減ったり、それぞれの音量バランスが変わることで曲が変化していくことを表現します。
もうひとつは別のループ曲に切り替えるが、シームレスにつながるタイミングで切り替えを行ってつながった曲のように聞かせるものです。こちらはDAW上では横方向に別々のループを展開して作っていくため、「横の遷移(Horizontal Resequencing)」とも呼ばれています。
今回はこれら2つのインタラクティブミュージックの手法を両方取り入れたものを、ゲーム向けサウンドミドルウェアである「CRI ADX2」を使って実現します。
CRI ADX2とは
ゲーム向けの統合型サウンドミドルウェアです。プロ用の有償製品と、同人・インディーゲームクリエイター向けの無償版「CRI ADX2 LE」があります。基本的な機能は両バージョンで同一です。
CRI ADX2 LE
https://game.criware.jp/products/adx2-le/
導入することで、大量のサウンドデータをさばいたり、複雑な音の演出をツールで組んでいくことができます。
Unityにおいてはモバイルゲームで多く利用されており、処理負荷を抑えた音声コーデックや、特にAndroid機器上での音の遅延を低減させる機能が支持されています。
そして、ADX2は前述した「インタラクティブミュージック」のための機能を持っています。ゲーム側からパラメータを受けとってトラックのミックスを変化させたり、別のループ音へ拍を合わせてジャンプする、といった難易度の高い処理を実装済みです。
今回はこの「インタラクティブミュージック」について、ゲーム内の状態に応じてループ楽曲の「曲調」と「テンション」を変化させていく見せ方をします。
CRI ADX2を全く触ったことが無い、という場合は下記のチュートリアル記事を先にご覧ください。
Unityのサウンド機能をADX2で強化する
https://qiita.com/Takaaki_Ichijo/items/16e6501fc07f5b3b3377
ツール設定については別記事参照
本記事はUE4向けにかかれた実装紹介をUnityに移植したものです。CRI ADX2のツールである「Atom Craft」はUnityでもUE4でも使い方は同じであるため、Atom Craft側の設定については下記の記事の手順をそのまま行ってください。
ADX2 for UE4で変化するループBGMを再生する(AtomCraft編)
https://qiita.com/SigRem/items/a6ccd57a2881e8a43905
最後にキューシートをビルドする際、「Unity Assets出力」にチェックを入れてビルドします。
今回の楽曲構成
縦の遷移で変化させる各トラックのミックスバランスは、このデモに限り「テンション」と定義します。テンションは「ロー」「ミドル」「ハイ」の3段階を用意し、ゲームからそのパラメータを受け取れるようにします。
「ロー」は一部の楽器のみ、「ミドル」でパッド、ベース、ドラムが追加、「ハイ」でリード、さらにドラムが追加になります。これらの操作はADX2のAISAC機能を用いて操作します。
横の遷移で変化するループの種類については「フレーズ」と呼びます。デモでは4種類を用意しています。フレーズは4種類用意され、それぞれ0から3までの番号が振ってあります。これらの操作はADX2のブロック再生機能を用いて操作します。
これらの3種類のトラック組み合わせ x 4種類のループ音で、12通りの変化をするインタラクティブミュージックを作っていきます。
Unity側の設定
まずは、新しいプロジェクトを作成し、ADX2のプラグインを導入するところまで進めます。
プラグインパッケージ一式をインポートし、シーンにCriWareLibraryInitializer、CriWareErrorHandlerを配置します。
先ほどビルドしたデータをAtom Window経由でインポートし、内容をチェックします。
Audio Managerスクリプトの作成
BGMを再生したり、ADX2にパラメータを渡して曲を変化させるためのマネージャースクリプトを作成します。
まずは必要なフィールド、プロパティを作っていきます。
[RequireComponent(typeof(CriAtomSource))]
public class AudioManager : MonoBehaviour
{
public CriAtomSource atomSource;
[Range(0f, 1f)]
public float currentAisac00 = 0f;
[Range(0f, 1f)]
public float currentAisac01 = 0f;
[Range(0,3)]
public int currentPhrase = 0;
private int currentTension = 0;
private readonly List<Tuple<float, float>> aisacControllPresetts = new List<Tuple<float, float>>()
{
{ new Tuple<float, float> (0f,0f)}, //low
{ new Tuple<float, float> (0f,1f)}, //middle パッド、ベース、ドラムBが追加
{ new Tuple<float, float> (1f,1f)} //high リード、ドラムCが追加
};
private CriAtomExPlayback criAtomExPlayback;
public Text currentParameterText;
/略/
このスクリプトを張り付けるコンポーネントには、BGMを再生するためのAtomSourceを配置します。その参照をインスペクター経由で取得します。
Rangeアトリビュートを使って、aisac値を0から1、ブロックを0から3に制限して定義します。これは、ゲームの実行中にインスペクタ経由から直接操作して変化を試すためです。
ゲームからのパラメータは「テンション」「フレーズ」の2値で来ます。フレーズは0から3までで、これはそのまま当該楽曲のブロック番号とイコールのため、そのままADX2へ渡します。
テンションについては、0,1,2の3つの値を「ロー」「ミドル」「ハイ」と定義してゲーム内で扱います。楽曲に反映するためには。はAISACパラメーター2種類の操作に割り当てています。aisacControlPresettsリストは、この3値において2つのAISACコントロール値がいくつになるのかを定義したテーブルです。
次に定義されたCriAtomExPlaybackメンバーは、キューが再生されたときに取得できるパラメータ操作用です。ブロック再生のブロック指定に使用します。
最後に、現在のテンションとフレーズを表示するテキスト要素を定義します。
次に、2つメソッドを書きます。今回は簡易的に、おなじみStartとUpdateに書いてしまいます。
/略/
void Start()
{
criAtomExPlayback = atomSource.Play();
}
private void Update()
{
atomSource.SetAisacControl("AisacControl_00", currentAisac00);
atomSource.SetAisacControl("AisacControl_01", currentAisac01);
criAtomExPlayback.SetNextBlockIndex(currentPhrase);
currentParameterText.text = "current tension " + currentTension + "\ncurrent phrase " + criAtomExPlayback.GetCurrentBlockIndex();
}
}
まずStartでatomSourceコンポーネントに設定されているキューシート・キューのデータを再生し、戻り値としてCriAtomExPlaybackを受けとります。
Updateでは、atomSourceへ現在のaisac値を二種類渡します。また、criAtomExPlaybackへ現在のフレーズをそのまま次のブロックの番号として渡します。
また、現在のパラメータを表示するテキストエリアに現在のテンションの値、現在のブロックの値を取得して表示します。
AudioManagerのテスト
ここまでできたら、ヒエラルキーにオブジェクトを作ってスクリプトをアタッチします。
自動的にCri Atom Sourceコンポーネントも付きますので、CueSheetとCueNameにそれぞれ再生対象を記入します。
AudioManagerスクリプトのAtom Sourceフィールドに同コンポーネントのCri Atom Sourceの参照を入れます。
また、現在のパラメータを表示するテキストエリアを作ります。ヒエラルキーにuGUI Textを用意し、その参照をCurrent Parameter Textフィールドに参照を入れます。
この段階で一回ゲームを再生してみましょう。楽曲が流れ始めますので、Audio Managerコンポーネントのパラメータを色々変えてみましょう。
曲が変化するはずです。
インタラクティブミュージックの経過報告。いそぎUnity Editorのインスペクター側でミックスの変更、ループのシームレス切り替えはできるようになった。 (曲データ by @sleepyslowsheep 、サウンドエンジンはADX2 ) pic.twitter.com/XuzLrtq2fS
— ichijo (@Takaaki_Ichijo) October 6, 2020
AudioManagerの修正
次に、ゲーム内のオブジェクトにキャラクターが入ったらパラメータが変化する、という仕組みのためにスクリプトを書きます。
現在のAudioManagerはインスペクターからのブロック指定を実現するため、Updateでブロックを指定していますが、これは「フレーズが指定されたとき」に1回指定すればいいだけなので、次の一行を消します。
criAtomExPlayback.SetNextBlockIndex(currentPhrase);
代わりにSetParametersメソッドを作ります。これは外部から呼び出されるメソッドです。
public void SetParameters(int tension, int phrase)
{
criAtomExPlayback.SetNextBlockIndex(phrase);
currentAisac00 = aisacControllPresetts[tension].Item1;
currentAisac01 = aisacControllPresetts[tension].Item2;
currentTension = tension;
}
criAtomExPlaybackに次のブロック番号としてフレーズをそのまま渡します。
テンションについては、最初に作ったaisacControllPrisettsテーブルを使って、テンションの番号と対応する実際のAISACの値を取得。指定します。
テンションの値はテキストで表示しているため、currentTensionメンバに保存します。
この実装ではSetParametersが呼ばれると楽曲のミックスがいきなり変わってしまいますので、aisacの値の変化をTweenライブラリを使ってなだらかにすると良いです。
DOTweenを使った場合は次の書き方になります。
public void SetParameters(int tension, int phrase)
{
criAtomExPlayback.SetNextBlockIndex(phrase);
DOTween.To(
() => currentAisac00,
num => currentAisac00 = num,
aisacControllPresetts[tension].Item1,
2.0f
);
DOTween.To(
() => currentAisac01,
num => currentAisac01 = num,
aisacControllPresetts[tension].Item2,
2.0f
);
currentTension = tension;
}
PhraseChangerインスタンスの作成
プレイヤーが入ってきたらAudioManagerへパラメータを送信するトリガーオブジェクトを作ります。名前を「フレーズチェンジャー」とします。
実装はシンプルで、さきほどのAudioManagerへの参照と、テンション・フレーズのパラメータ保持、OnTriggerEnter経由でパラメータのAudioManagerへの受け渡しのみです。
[RequireComponent(typeof(BoxCollider))]
public class PhraseChanger : MonoBehaviour
{
public AudioManager audioManager;
[Range(0,2)]
public int tension = 0;
[Range(0,3)]
public int phrase = 0;
private void OnTriggerEnter(Collider other)
{
audioManager.SetParameters(tension,phrase);
}
}
以上で楽曲変化に必要なスクリプトがそろいました。PhraseChangerスクリプトを適当なオブジェクトにアタッチし、tension、phraseの値をインスペクターで変えます。
CharacterControllerなどでプレイヤーキャラクターを作り、ridgidBodyを使ってOnTriggerEnterが走るようにし、配置します。
元となった @SigRem のサンプルに従って、3x4の格子状にPhraseChangerを配置し、それぞれのパラメーターの組み合わせを別にしておきます。
(プレイヤーキャラのキー入力による操作、PhraseChangerに設定された値を取得してuGUI Textで重ねて表示させる処理については省略します。RectTransformUtilityを使ったごく一般的な実装です。)
ゲームを実行してみましょう。プレイヤーをフレーズチェンジャー内にキャラクターが入ると、楽曲が変化します。
AISACパラメータにTween処理を加えている場合、その時間かけてミックスが変化します。ブロックについては、ループが終わって次のループに入るタイミングで別のブロックに遷移します。
なお、今回のデモではブロックは毎回1ループしますが、特定の拍タイミングでブロックの移動を可能にする設定もできます。ループごとにしかジャンプできないわけではありません。
まとめ
インタラクティブミュージックは、楽曲の遷移タイミング管理や同期再生、パラメータの複雑な変化などがあり、素のUnityに実装するにはハードルが高いです。
もとよりインタラクティブミュージックの機能を内包するCRI ADX2を導入することにより、その機能開発をスキップして曲や演出の作り込み側を始めることができます。
インタラクティブミュージックは作曲部分から特有の難しさがありますが、これがゲームと気持ちよくハマることで、体験のクオリティが非常に上がります。
ぜひ、チャレンジしてみてもらえればと思います。