ADX for Unityでインタラクティブミュージック（フィルインの実装）

# **はじめに**

ゲーム中の展開に応じてBGMがシームレスに変化していくのを「インタラクティブミュージック」といいます。これにより、ゲームプレイと音楽を連動させ盛り上がりなどの演出をより強化することができます。

過去の記事ではイントロやサビを1つの塊として再生順序を制御する、ブロック再生の実装方法を紹介しました。

ADX for Unityでインタラクティブミュージック（ブロック再生編）

本記事では、ブロック再生でフレーズが移り変わる際により滑らかな遷移にするため、またアクセントをつけてより盛り上がるようにするためにドラムなどの特殊なフレーズを追加する「フィルイン」の紹介します。

![フィルイン.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/0f3747f4-0571-31f9-63c3-c18139f45ce6.png)

フレーズをループさせている間は通常の演奏ですが、ループを脱してサビに遷移する際などに専用のフレーズを再生させます。

この記事は「ADX2 for UE4でインタラクティブミュージック（フィルインの実装）」のUnity版です。元記事作者のSigさん ( [https://qiita.com/SigRem](https://qiita.com/SigRem) )の許可を得て作成しています。

https://qiita.com/SigRem/items/b116ca83234dc94a5052

### 動作確認環境

Windows 10 Home 21H2

Unity 2021.3.15f1

ADX LE Unity SDK　3.07.02

CRI ADX LE Tools for Windows　3.47.01

### 前提記事

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

https://qiita.com/Takaaki_Ichijo/items/16e6501fc07f5b3b3377

# **やること**

- AtomCraftでフィルインとクラッシュ（＝遷移時専用のフレーズ）を追加する
- Unityで遷移を制御する実装をする
- 遷移時フレーズを確認する

# **実装**

## **AtomCraftでフィルインを追加する**

まずは、再生する音データの作成を行います。ADXにおいては、再生用のデータはUnityとは別のツール「AtomCraft」を使います。その手順については、元の記事（UE4向け記事）の同名の項と同じになりますので省略しています。

https://qiita.com/SigRem/items/b116ca83234dc94a5052#%E5%AE%9F%E8%A3%85

本記事の手順を試す場合は、まず元の記事の該当項目を進めてから本記事を進めてください。

UE4版との違いとして1点、「Atomキューシートバイナリのビルド」画面で、一番左下のオプション「UnityAssets出力」にチェックを入れるのを忘れないようにして下さい。

またUnityへのインポート手順は以下の記事、「Unity側のセットアップ」の項を参照してください。

https://qiita.com/Takaaki_Ichijo/items/16e6501fc07f5b3b3377#unity%E5%81%B4%E3%81%AE%E3%82%BB%E3%83%83%E3%83%88%E3%82%A2%E3%83%83%E3%83%97

## **Unityで遷移を制御する**

Unityで遷移を制御するための実装は、前記事「ADX for Unityでインタラクティブミュージック（ブロック再生編）」と同一です。

https://qiita.com/nimushiki/items/0d8ae6a7c54c34097d00

### レベルを作成する

![Unityでのテスト再生用実装例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/902be343-1872-ddfe-d4d0-2609a6fb2f52.png)

まずはPlayerタグのついたプレイヤーキャラクターを用意します。

インタラクティブミュージックを管理するためのオブジェクト（Collider）を用意し、管理用のコンポーネント（IMBlockController.cs）をアタッチします。

```IMBlockController.cs
public class IMBlockController : MonoBehaviour
{
    [SerializeField] private bool OnPlayerStay;
    [SerializeField] private CriAtomSource bgmCriAtomSource;
    [SerializeField] private IMBlockName iMBlockName;
    [SerializeField] private int blockIndex = 0;
    private CriAtomExPlayback playback;

    private void Update()
    {
        switch (iMBlockName)
        {
            case IMBlockName.Intro:
                if (playback.GetCurrentBlockIndex() == 1)
                {
                    blockIndex = 2;
                }
                else
                {
                    blockIndex = 1;
                }
                break;
            case IMBlockName.Sabi:
                if (playback.GetCurrentBlockIndex() == 3)
                {
                    blockIndex = 4;
                }
                else
                {
                    blockIndex = 3;
                }
                break;
            case IMBlockName.Outro:
                blockIndex = 5;
                break;
            default:
                break;
        }
        playback.SetNextBlockIndex(blockIndex);

    }

    public void SetBlock(IMBlockName _IMBlockName)
    {
        iMBlockName = _IMBlockName;
        switch (_IMBlockName)
        {
            case IMBlockName.Intro:
                if (bgmCriAtomSource.status == CriAtomSourceBase.Status.Playing) return;
                playback = bgmCriAtomSource.Play();
                blockIndex = 1;
                break;
            case IMBlockName.Sabi:
                if (bgmCriAtomSource.status == CriAtomSourceBase.Status.Playing)
                {
                    blockIndex = 3;
                }
                break;
            case IMBlockName.Outro:
                if (bgmCriAtomSource.status == CriAtomSourceBase.Status.Playing)
                {
                    blockIndex = 5;
                }
                break;
            default:
                break;
        }
        playback.SetNextBlockIndex(blockIndex);
    }
}

public enum IMBlockName
{
    Intro,
    Sabi,
    Outro,
}
```

その子に、前述の管理コンポーネントへプレイヤーとの接触判定を通知するオブジェクト（Trigger_Apart、Trigger_Bpart、Trigger_Cpart）を作成します。それぞれ通知用コンポーネント（IMBlockTrigger.cs）とコライダーをアタッチします。

```IMBlockTrigger.cs
public class IMBlockTrigger : MonoBehaviour
{
    private IMBlockController iMBlockController;
    [SerializeField] private IMBlockName _IMBlockName;

    void Awake()
    {
        iMBlockController = GetComponentInParent<IMBlockController>();
    }

    private void OnTriggerStay(Collider other)
    {
        if (other.CompareTag("Player")) iMBlockController.SetBlock(_IMBlockName);
    }
}
```

機能上必要ありませんが、再生中でも判定が見やすいように壁オブジェクトなどを追加するとよいでしょう。

### 制御の確認

テスト再生をしてプレイヤーをIntroのエリアに移動させると音楽が再生されます。

そのままの位置に居続けると、AメロBメロをループ再生します。

Introエリア、Outroエリアにプレイヤーを移動させると、それぞれサビへ遷移してループ再生、アウトロへ遷移して再生します。
この時、今回の実装であるクラッシュとフィルインが再生されているのを確認します。

## 注意点

SetNextBlockIndexメソッドで遷移を制御した場合、同じブロックへと遷移した際も「遷移時のみ再生」にしたトラックも再生されることに注意してください。

つまり、ループ毎にフィルインとクラッシュが再生されます。

もし再生させたくない場合、セレクタなどを使用してトラック自体の再生を制御する、もしくは同じブロックを2つ作成して片方だけフィルインとクラッシュを設定する、といった方法を取る必要があります。