ADX for Unityで、BGMのテンポに合わせた演出を実装する

# はじめに
ゲームのBGMに同期させて画面にエフェクトをかけたり、音楽に合わせてテンポよく操作するゲームを作りたいと思ったことはありませんか？しかしUnityには音源のBPMを取得したり、そのタイミングを取得する機能はありません。
CRI ADXにはその機能が備わっており、簡単に使用することが出来ます。
本記事ではUnityとサウンドミドルウェア「ADX for Unity」を連携させ、BGMのビートに合わせて玉を発射する処理を実装してみます。

この記事は「ADX2 for UE4で、BGMのテンポに合わせた演出を実装する（改訂版）」のUnity版です。元記事作者のSigさん ( https://qiita.com/SigRem )の許可を得て作成しています。

[https://qiita.com/SigRem/items/f0b633513eafd7a2e61d](https://qiita.com/SigRem/items/f0b633513eafd7a2e61d)

### 動作確認環境

Windows 10 Home 20H2
Unity 2020.3.27f1  + ADX LE Unity SDK 3.06.03
CRI ADX LE Tools for Windows 3.46.02

### 前提記事

**CRI ADX導入方法や基本的な操作については以下の記事を参照して下さい。**

Unityのサウンド機能をADXで強化する

[https://qiita.com/Takaaki_Ichijo/items/16e6501fc07f5b3b3377](https://qiita.com/Takaaki_Ichijo/items/16e6501fc07f5b3b3377)

# 実装～AtomCraft～

## AtomCraftでマテリアルを準備

まずはAtomCraftでBGMとなるマテリアルを用意しキューを新規に作成します。ワークユニットツリーのキューシート上で右クリックし、「新規オブジェクト」→「キュー『ポリフォニック』の作成」を選択します。キューに名前をつけ、音源をトラック上にドラッグアンドドロップして配置します。
![キューの設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/f9342942-e5f8-3487-3fc5-c675696740dc.png)

## ビート同期情報の設定

次に曲のBPMを確認します。マテリアルツリーで目的の音源を選択して右クリックし、「BPM解析」を選択します。するとログにBPM情報が出力されるので、メモをしておきます。
もしログウィンドウが見当たらない場合、ツールバーの「表示」→「メインビュー」→「ログ」を選択することで再表示が可能です。
![BPM解析の仕方](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/f13898fd-feda-8c38-795f-6dce405259e5.png)

トラックリストの空欄で右クリックし、「新規オブジェクト」→「ビート同期情報の作成」をクリックします。「BeatSync」と書かれたマーカーが追加されます。
![ビート同期情報の作成](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/062bd9d6-2f8a-8210-771c-62db4dd8f377.png)

**「BeatSync」マーカーが選択された状態で**、インスペクターの「BPM」に先ほどメモしたBPMを入力します。
![BPMの設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/4a9622a2-e872-8c90-05b9-20d2f6cd3a0b.png)

## キューシートのビルド

ここまで完了したらキューシートをビルドします。「UnityAssets出力」にチェックを入れるのを忘れないようにして下さい。

# 実装～Unity～

## Unityでセットアップ

AtomBrowserを使いUnityにキューシートをインポートします。
![インポート画面の開き方](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/9aa13494-da17-626c-465e-5149a61e3610.png)
![インポートの仕方](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/717eaaa6-cdc2-788c-6137-ddbc1e6022b1.png)

## 確認用シーンの作成

ビートタイミングに行われる処理を確認するためのシーンを組み立てます。
![2022-03-22 13.36.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/f75c84d6-355e-a6ca-3f17-2ceac7f26b04.png)

- CriWareLibraryInitializer、CriWareErrorHandler
    - 基本コンポーネント、GameObject＞CRIWAREから作成
- CRIWARE
    - 自動生成
- Battle(CriAtomSource)
    - 前項でインポートした音源
- Plane
    - デフォルトでOK
- BeatAction
    - 空オブジェクト、Sphereの生成地点
- Sphere
    - ビートに合わせて生成するオブジェクト
    - Rgidbodyをつけ、Prefab化しておく

## スクリプトでタイミングを取得

以下のC#スクリプトを作成し、「BeatAction」オブジェクトにアタッチします。詳細は後述します。

```BeatAction.cs
using System.Collections;
using UnityEngine;

public class BeatAction : MonoBehaviour
{
    public string CueSheetName;
    public string AcbFilePath;
    private CriAtomExAcb acb;
    private CriAtomExPlayer atomExPlayer;
    public string BgmCueName;
    public GameObject projectile;
    public float velocity;

    private IEnumerator Start()
    {
        atomExPlayer = new CriAtomExPlayer();

        var cueSheet = CriAtom.AddCueSheet(CueSheetName, AcbFilePath, "");
        while (cueSheet.IsLoading)
        {
            yield return null;
        }

        acb = cueSheet.acb;
        PlayBgm(BgmCueName);
    }

    public void PlayBgm(string bgmCueName)
    {
        if (!acb.Exists(bgmCueName)) return;

        atomExPlayer.SetCue(acb, bgmCueName);
        atomExPlayer.Start();

        CriAtomExBeatSync.OnCallback += CriAtomExBeatSync_OnCallback;
    }

    private void CriAtomExBeatSync_OnCallback(ref CriAtomExBeatSync.Info info)
    {
        GameObject instance = Instantiate(projectile, transform.position, Quaternion.identity);
        instance.GetComponent<Rigidbody>().AddForce(new Vector3(velocity, 0, 0), ForceMode.Impulse);
    }
}
```

参考

Unity連携編.04 / CriAtomSourceを使わずに音を鳴らす

[https://criware.info/adx2forunity-use-criatomexplayer/](https://criware.info/adx2forunity-use-criatomexplayer/)

BeatActionの各項目を設定をします。
![2022-03-22 13.53.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/d6b1a35e-6346-4529-90b4-110b6f73b257.png)

- CueSheetName
    - 再生するキューシートの名称
- AcbFilePath
    - acbファイルのパス（Assets/StreamingAssetsからの相対パス）
- BgmCueName
    - 再生するキューの名称
- projectile
    - 前項で作ったSphereのPrefabを設定
- velocity
    - Sphereを打ち出す速度

## テストプレイ

ここまで出来たらシーンを保存し実行してみます。ビートに合わせてsphereが生成されるのが確認できると思います。
<iframe width="560" height="315" src="https://www.youtube.com/embed/UGjag4Uc1ic" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

## 応用例
応用例としては、元記事の作例が分かりやすいですが、DJブースのようなビートに合わせて光るエフェクトを発生させるというのがあります。

ADX2 for UE4で、BGMのテンポに合わせた演出を実装する（改訂版）
https://qiita.com/SigRem/items/f0b633513eafd7a2e61d

ゲームそのものに組み込む場合は「リズム天国」や「クリプト・オブ・ネクロダンサー」のようなゲームが上げられます。
今回取得したのはビートのタイミングだけでしたが、CriAtomExBeatSync.info.beatProgressによって今現在ビート間のどのタイミングかというのを0~1の値で取得できます。
つまり、ビートに対して入力のタイミングがどれくらいズレているかと言うのが簡単に判定できます。
この場合、ボタン入力→beatProgress取得→閾値を比較しゲームへ反映、といった処理の流れになると思います。

# 補足

## BeatAction.csの内容について

重要なのはCriAtomExBeatSync.OnCallbackになります。ここに登録されたコールバックは、CriAtomExPlayerで再生中のキューに設定されたBPM情報に基づき、ビートのタイミングで呼び出されます。
今回の例では、そのタイミングで玉を生成し発射するようにしています。
なおCriAtomSourceは簡易的な実装には向いていますが上記の関数がないため、代わりにCriAtomExPlayerを使用します。Start関数ではその初期化と再生処理を行っています。

## BPM解析について

AtomCraftのBPM解析機能ですが、正確ではない可能性があります。動作タイミングがおかしい場合は、作曲者から直接聞いたり音源の時間と拍数から計算するなどして設定して下さい。