【Unity x ADX】グローバルAisacを使用して、ゲーム内で動的にAisacを適用する

# はじめに

ADXアンバサダーのこはとです！

CRIADXでは、Aisacを使用することで様々な演出を音声に付与することができますが、Cue毎に一つ一つ演出を作るのは手間でもあります。

できれば、共通の演出を1つだけ作って、それを**適宜必要なタイミングで音声に付与する**、みたいな使い方ができると嬉しいですよね。

この記事では、UnityでグローバルAisacを活用し、1つの演出を複数の音声に**動的に**適用できる方法をご紹介します。

<iframe width="1280" height="721" src="https://www.youtube.com/embed/aNIuoRypK30" title="【Unity x ADX】グローバルAisacの動的適用のサンプル" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

この記事は、CRIWAREが提供するサウンドミドルウェア「CRI ADX」の機能についての紹介をします。
ある程度ADXを使用していて、基本的な操作や実装方法を知っている方向けの記事となります。
CRI ADXについてはこちら

https://game.criware.jp/products/adx-le/

# 忙しい人向け
```C#:Sample
void Attach()
{
    //グローバルAisacのアタッチ
    atomSource.player.AttachAisac("AtomCraftで設定したグローバルAisacの名前");
}

void Update()
{
    //グローバルAisacの操作（通常と同様）
    atomSource.player.SetAisacControl("グローバルAisacに設定した「AisacControl」の名前", float);
    //Aisac値の適用
    atomSource.player.Update(exPlayback);
}

void Detach()
{
    //グローバルAisacのデタッチ
    atomSource.player.DetachAisac("AtomCraftで設定したグローバルAisacの名前");
}
```
基本的にはこれでランタイムでもAisacをCueに適用、操作、解除することができます。
アタッチ後の操作は通常のAISACコントロール値の操作と同様です。
**AisacControl名を指定する点に注意**が必要なほか、グローバルAisacの適用、解除で混乱が発生しないよう、状態を管理できる仕組みがあるとよりベターです。

以下で詳しく解説します。

# Aisacとは？

CRI ADXには、Aisacという機能が実装されています。
簡単に言うと、AISACコントロール値と呼ばれる0f~1f間で動く値に、様々なパラメーターを追従させられる機能です。
例えば、AISACコントロール値に敵（音源）とプレイヤーの角度を紐づけて、0の時に音が左に、1の時に音が右に振るように設定すれば、簡易的にASMR的な3D音源表現ができます。

![0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450329/f2d86bc9-f26a-4c31-0187-a2e3f89f9d24.png)


こんな感じ。
図は、AISACコントロール値が0で音が-90°度に、1で音が90°になるよう設定した例です。

これ以外にも、**複数のパラメータを一つの値に追従させる**ことで、Unity標準のサウンド機能より、自由な音の演出が可能になる機能です。



# 音声と演出を分離させたい
今回は例として、Aisacでスローモーションの演出を作成してみましょう。
ジャスト回避のようなタイミングで入れる演出を想定します。

実際にAtomCraftで設定してみましょう。
任意のCueを作成、波形ファイルを登録したら、そのCueにAisacを作成します。

![0_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450329/acdcf2fb-19cc-1515-7314-5872a285291a.png)


例では、LowPass＆HighPassフィルター＋ピッチを下げることでなんとなく音声がスローモーションになったような表現を入れています。

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450329/6bcabb60-5a4a-feda-3267-d5cc3fa8796f.png)


↑内容は自由に作っていただいてOKです。

これでCueに効果を適用できるようになりました。

しかし、実際の開発ではこれ以外にも**様々な音声が存在**します。

この演出は、できれば再生中のSEやVoiceにも同じものが適用されてほしいところですが、だからといって全てのCueにこれと同じAisacを作成するのは非常に手間です。
こういった「**同一のAisacを様々なCueに適用したい！**」という問題の解決に、グローバルAisacが役立ちます。

# グローバルAisacとは？
ひとことで表すと、AtomCraftプロジェクト内で共有できるAisacです。

前項で作ったような**通常のAisac**は、各Cue毎に新規作成するAisacであり、**他のCueがこれを参照することはできず**、同時にこのAisacが他のCueに影響を及ぼすことも（基本的には）ありません。

一方**グローバルAisac**は、Cueではなく、プロジェクトに作成するAisacです。
通常のAisacと違う点は、**どのCueからでも参照ができる**Aisacであるという点です。

普通、AisacはCueに作成しますが、グローバルAisacはプロジェクトに作成し、同じプロジェクトに存在するCueが適宜このAisacを参照して自分のAisacとすることができます。
図で表すとこんな感じです。

![1_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450329/b6f9fc37-c9ca-489d-962b-592a150f4a43.png)

図のように、グローバルAisacの主な活用方法には、

+ **予め**、ツール側でキューにグローバルAisac参照をつけておく方法
+ **後から**、プログラムで動的にグローバルAisacを取り付ける方法

の２種類の方法が存在します。
「大量のCueに全く同じ演出を適用したい！」といった場合や「予め演出を作っておいて、ゲーム中で適用したい！」と言った場合に有用です。

今回は後者の「**後から付ける方法**」を解説します。



# 実装してみる
「音声と演出を分離させたい」の章で作成したAisacを、そのままグローバルAisacとして使用してみようと思います。

作成したAisacを右クリックして「グローバルAISAC化」を押すと、Cueに作成したAisacをグローバルAisacとして新規作成することができます。

![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450329/42ba81a6-1e21-2e72-7f3c-8dc5fa254db0.png)

ちなみにこの操作を実行すると元のCueのAisacは、今作成したグローバルAisacを参照する形に置き換わります。
ですが、今回はプログラムでAisacをアタッチするので、AtomCraft側では**Cueに付与されているAisacは外しておきましょう**。

![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450329/e92dc99b-6109-c28c-fe5a-489872009dd4.png)
![4_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450329/bac14bf2-c8bb-4eb6-af85-54f361f89869.png)


次に、管理用にグローバルAisacへ任意の名前をつけます。
サンプルでは「Aisac_Global」としていますが、**識別しやすい名前**で登録しておきましょう。

![4_0_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450329/5c78f11b-fd4f-de20-7627-ac1d8d4f3b3c.png)
![4_0_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450329/e26c48ab-ec0b-417e-8196-5e11826a5682.png)



名前をつけたら準備完了です。ちなみに、今回は複数のCueを用意してみました。
どれも**Cue自体にAisacは設定しておりません**。

![4_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450329/f094b7fc-88aa-2327-319c-6c45d24140a8.png)


完成したら、UnityProjectに書き出して、早速ゲームに実装していきましょう。

今回は「ボタンを押したとき、セットされた音声にAisacを適用し、一定時間演出を付与する」といった機能を実装してみようと思います。

まず、新規スクリプトを作成しましょう。
以下がサンプルのコードです。
（便宜上CriAssetsプラグインを使用していますが、通常のCriAtomSourceでも同様です）

https://game.criware.jp/manual/unity_plugin/latest/contents/addon4u_assetsupport_assets_overview.html

```C#:GlobalAisacSample.cs
using UnityEngine;
using System.Collections;
using TMPro;
using CriWare;
using CriWare.Assets;

public class GlobalAisacSample : MonoBehaviour
{
    //効果を適用するプレイヤー。通常のCriAtomSourceでも代替可。
    [SerializeField]
    CriAtomSourceForAsset atomSource;
    //演出の時間
    [SerializeField]
    float fxDuration = 1;
    //多重再生回避用フラグ
    bool isEffectPlaying = false;
    //Aisacの操作用
    CriAtomExPlayback exPlayback;



    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        //Aisacの操作用にexPlayBackの取得
        exPlayback = atomSource.Play();
    }

    // Update is called once per frame
    void Update()
    {
    }
    
    //これをGUIButtonから呼び出して演出開始
    public void StartFXCoroutine()
    {
        StartCoroutine("PlayFX");
    }

    /// <summary>
    /// 現在適用されているatomSourceに演出を適用
    /// </summary>
    /// <returns></returns>
   IEnumerator PlayFX()
   {
        if (!isEffectPlaying)
        {
            //演出開始→falseになるまでボタン連打の多重適用回避
            isEffectPlaying = true;

            //現在再生しているSourceにAtomCraftで設定したグローバルAisacを適用
            atomSource.player.AttachAisac("Aisac_Global");
            
            //演出適用開始
            float timer = 0;
            float dur = fxDuration / 2;
            //AisacControl : 0→1
            while (timer < dur)
            {
                //グローバルAisacの操作。
                //※※ここは通常のAisacと同様、AisacControlの名前を指定します。
                //AisacControlの名前は、AtomCraftで設定したAisacControlの名前を入力してください。
                atomSource.player.SetAisacControl("AisacControl_00",timer/dur);
                //更新
                atomSource.player.Update(exPlayback);
                //タイマー加算
                timer += Time.deltaTime;
                //1フレーム待つ
                yield return new WaitForEndOfFrame();
            }
            
            //AisacControl : 1→0
            while (timer >= 0)
            {
                //グローバルAisacの操作。
                //※※ここは通常のAisacと同様、AisacControlの名前を指定します。
                //AisacControlの名前は、AtomCraftで設定したAisacControlの名前を入力してください。
                atomSource.player.SetAisacControl("AisacControl_00", timer / dur);
                //更新
                atomSource.player.Update(exPlayback);
                //タイマー減算
                timer -= Time.deltaTime;
                //1フレーム待つ
                yield return new WaitForEndOfFrame();
            }
            //AisacControlを解除
            atomSource.player.DetachAisac("Aisac_Global");
            //演出終了
            isEffectPlaying =false;
        }
   }
}
```
今回は、なるべく一回の処理でAttach～Detachが完結してほしかったので、コルーチンでAISACコントロール値を0→1→0へ動かす仕様にしてみました。

特に注目すべきは
**player.AttachAisac()** と、**player.SetAisacControl()** です。

### AttachAisac() ＆ DetachAisac()

グローバルAisacの名前を指定して、ゲーム中でAisacをExPlayerにアタッチ/解除します。

https://game.criware.jp/manual/unity_plugin/latest/contents/classCriWare_1_1CriAtomExPlayer.html#a749b70757db2f02a0ccf558f32242b3a:~:text=inline-,%E3%83%97%E3%83%AC%E3%83%BC%E3%83%A4%E3%81%ABAISAC%E3%82%92%E4%BB%98%E4%B8%8E,-%E5%BC%95%E6%95%B0

ここで指定する値は、AtomCraftで設定した**グローバルAisacの名前**である点に注意しましょう。
グローバルAisacの名前は、AtomCraftの「AISAC」タブで確認できます。
![6_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450329/0ed5388c-ff84-4348-9673-8e6131ca340b.png)

```C#
public void Sample()
    {
        //グローバルAisacのアタッチ
        atomSource.player.AttachAisac("グローバルAisacの名前");

        //グローバルAisacのデタッチ
        atomSource.player.DetachAisac("グローバルAisacの名前");
    }
```

なお、グローバルAisacはAcfデータに記録されており、AtomCraftで編集したグローバルAisacは、UnityプロジェクトのAcfデータを上書きすることで更新可能です。

### SetAisacControl()

通常のAisacに使用するものと同様のメソッドです。
こちらで注意すべきなのは、AttachAisac()とは異なりグローバルAisacに登録した**AISACコントロール値の名前**を入力する必要がある点です。

![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450329/e4d82248-111b-6a3b-be64-2712c0223349.png)

```C#
public void Sample()
    {
        //グローバルAisacのアタッチ
        atomSource.player.AttachAisac("グローバルAisacの名前");
        //グローバルAisacの操作
        atomSource.player.SetAisacControl("グローバルAisacに設定した「AisacControl」の名前", float);
    }
```



## Unityに実装する

Unity側では、必要なCri用コンポーネントを作成の他、再生したいCueのAtomSourceとGUIButtonを作成しておきましょう。

![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450329/dba1cc8f-0625-a2a7-1613-a27e9f537b56.png)


作成したら、適当なオブジェクトに先ほど作成したスクリプトをアタッチしましょう。
例では「SoundManager」にアタッチしています。
アタッチしたら、SoundManagerに効果を適用したいAtomSourceをセットしましょう。

![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450329/f779c81e-16ec-decf-e0a0-ef94ba3f8cee.png)

設定が完了したら、あとはGUIButtonでStartFXCoroutine()をOnClick()で呼び出せるようにしてセット完了です。

![8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450329/aefdf42f-de98-c731-4a71-62376bfeb2a3.png)


実際に実行してみましょう！



Buttonを押した際に、効果が適用されているのが確認できましたか？

他にもSoundManagerの「atomSource」の値にSEやVoiceなど、別の音声をセットして実行してみましょう。

# 注意すること
### グローバルAisacの解除に注意

アタッチされたグローバルAisacは、デタッチ（又はDispose）されない限りCueに残り続けます。
今回は効果が確実に解除されるよう、コルーチンで囲んで実行する形式を採用しました。

不用意にグローバルAisacを多様すると、存在しないControl値を操作してエラーの原因になったり、複数のAisacが重複適用されて演出が意図しない内容になるなど、思わぬ不具合を招きかねません。
**player.GetAttachedAisacInfo()** などを活用して、グローバルAisacを管理できるシステムがあるとより安全になります。

https://game.criware.jp/manual/unity_plugin/latest/contents/classCriWare_1_1CriAtomExPlayer.html#a749b70757db2f02a0ccf558f32242b3a:~:text=bool%20GetAttachedAisacInfo

```C#
public void Sample()
{
    //該当のAisacが存在する場合のみ入力を実施
    if(CheckAttachedAisacByName(atomSource.player, "Aisac_Global"))
    {
         //AisacControl処理など…
    }
}

  /// <summary>
  /// プレーヤーに適用されているAisacを検索し、もし存在するならTrueを返す。
  /// </summary>
  /// <param name="player"></param>
  /// <param name="aisacName"></param>
  /// <returns></returns>
  public bool CheckAttachedAisacByName(CriAtomExPlayer player,string aisacName)
  {
      bool chk = false;
      if(aisacName != "")
      {
          string name = aisacName;
          //Playerに適用できるAisacは最大8個のため、全て検索する。
          for (int i = 0; i < 8; i++)
          {
              //Index指定でAisacInfoを検索。無ければfalseが返す
              player.GetAttachedAisacInfo(i, out var info);
              //出力されたInfoを指定のAisacNameが一致すればTrueを返す
              if(info.name == name)
              {
                  Debug.Log("AisacName " + name + " found at " + player.guid + " index of " + i);
                  chk = true;
                  break;
              }
          }
      }

      return chk;
  }
```


紹介したスクリプトを拡張して、再生中の音声全てに効果を適用したり、メソッド呼び出し時に適用するAisacを指定する、などの機能を作成することもできます。
より柔軟に音声に演出を適用できるようにしてみましょう！