【VRC】Audio Listenerを利用して四角い空間に沿って音響効果をつける【UdonSharp】

## 環境
- VRCSDK3-WORLD-2021.02.23.11.40_Public
- UdonSharp_v0.19.2
- Unity 2018.4.20f1

## 実現したいこと
- カラオケ喫茶ワールドの中にリバーブがある空間と無音空間を作りたい
    - カラオケで騒ぎつかれた人が静かな空間でチルアウトできるように
- `Audio Reverb Zone` だと境界線が丸くなるので「この四角い部屋の中は無音！」というのが難しい?

## `Audio Listener` って？

VRChatでいうとアバターの「耳」にあたるオブジェクト
ここにフィルタをかけることで外から聞こえる音にエフェクトをかけることができる

### VRChat 特有の仕様（？）
- **ワールド作成時に `Active` な `Audio Listener` はどこに設置していてもアバターと合わせて動く**
    - この仕様を利用して空間を移り変わるごとに `Audio Listener` の有効/無効を切り替えることで音響効果をつける
    - おそらく `Audio Listener` を1つにしてコンポーネントをつけ外しすることでも実現可能

## 大まかな手順
1. 当たり判定を作成し `Is Trigger` を有効にする
2. 当たり判定配下あたりに `Audio Listener` コンポーネントを含む `GameObject` を作成し好きな音響効果をつける
3. 1.で作った当たり判定の出入りで `Audio Listener` の有効/無効を切り替える(UdonSharp)

## 当たり判定を作成し `Is Trigger` を有効にする
- エフェクトを効かせたい空間を覆うように Cube オブジェクトを作成
- **Is Triggerを有効にする（1敗）**
- Mesh Renderer を無効にするかコンポーネントごと削除する（以下の図では有効になっている）

![スクリーンショット 2021-03-03 013926.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169868/b08c9614-8524-02c9-96ce-619f131433b7.jpeg)



## 2. 当たり判定配下あたりに `Audio Listener` コンポーネントを含む `GameObject` を作成し好きな音響効果をつける
- 当たり判定の近傍（出来たら子）に `Create Empty` を行い空オブジェクトを作成
- `Audio Listener` と好きなフィルターを付ける(今回はHPFとLPFを両方つけて無理やり音量を下げている)

## 3. 1.で作った当たり判定の出入りで `Audio Listener` の有効/無効を切り替える(UdonSharp)
- 1.で作成した当たり判定オブジェクトに `Udon Behavior(Script)` コンポーネントを追加
- New Program　ボタンをクリック

![new program.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169868/9ebacce3-d6fd-550f-93d4-96cb607e2280.jpeg)

- Create Script ボタンをクリックし好きな場所にC#ファイルを作成する


![add script.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169868/979376ea-aa77-7c09-0237-443230dc863e.jpeg)

- 以下のように実装

```c#:SilentArea.cs

using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;


// SilentArea

public class SilentArea : UdonSharpBehaviour {

    void Start()
    {

        this._silentAreaListener = this.transform.Find("Listener").gameObject;
        this._silentAreaListener.SetActive(false);
    }


    // UdonSharpBehaviour

    public override void OnPlayerTriggerEnter(VRCPlayerApi player)
    {
        
        // ローカルのプレイヤーの場合のみ切り替え
        if (player.isLocal)
        {

            this._silentAreaListener.SetActive(true);
        }

    }

    public override void OnPlayerTriggerExit(VRCPlayerApi player)
    {

        // ローカルのプレイヤーの場合のみ切り替え
        if (player.isLocal)
        {

            this._silentAreaListener.SetActive(false);
        }
    }
    
    
    // Private

    private GameObject _silentAreaListener;
}

```

- 保存してビルドが通ったら挙動を確認する

### ソースコードの解説

**変数宣言**

- 操作する `Audio Listener` コンポーネントを含む `GameObject` を宣言しておく
- 外部から余計な参照をされないように `private` で宣言

**`void Start()`**

- ワールド読み込み時に呼ばれる関数
- `this._silentAreaListener` の初期化を行う
- `this.transform.find("<GameObjectの相対パス>").gameObject` で操作する `GameObject` にアクセス
    - ちなみにヒエラルキーの親にアクセスする場合は `this.transform.parent`
- ワールド起動時に `AudioListener` を無効化することで `AudioListener` がアバターにくっつく

**`public override void OnPlayerTriggerEnter(VRCPlayerApi player)`　ほか**

- On〇〇系列の操作やタイミングに関連する関数は凡そ `UdonSharpBehaviour` から継承されている模様
- エリアにアバターが出入りするタイミングで `this._silentListener` の有効/無効を切り替えることでエフェクトをかけている
- この関数は **ワールドのどのアバターがエリアに出入りしても発火する**
    - **`player` の `isLocal` が `true` であることを検証しないとほかのアバターの出入りで音の聞こえ方が変化してしまうため注意（1敗）**


## その他注意点
- `Audio Listener` を検知エリアと同じ `GameObject` に設定すると **ゲームオブジェクトを無効にした際に当然ながら二度とエリアが発火しなくなるため注意(1敗)**
- エリアの出入りを行うとノイズが混じることがある
    - エフェクトの切り替わりがぱっつりすぎて波形が切れるためか、`Audio Listener` が一瞬ダブるからか...
    - 「徐々に洞窟に入っていく」といった表現には不向き

## まとめ
- `Audio Listener` と各種フィルターを利用することで四角い空間にエフェクトをかけることができる
- **`UdonSharpBehavior` の `OnPlayer〇〇` 関数を利用するときは必ず `player.isLocal` を検証しましょう**


## わからなかったこと・知りたいこと
- 単純に音量を下げたい場合にはどうすればよかったのか？
    - 今はFPFとLPFをつけて無理やり音量を下げているため音が軋んで聞こえる
- 空間内部のアバター同士は普通に会話を楽しませたい
    - `Audio Listener` を切り離してアバターの座標と音声のやり取り座標を切り離せないか？
        - アバターの発話を担当している (おそらく)`Sound Source` の取得方法がわからなかった

## 参考
- [VRChat Udon ではじめる VR 開発](https://medium.com/eureka-engineering/vrchat-udon-udonsharp-842d7b9b9d0f)
- [VRChat 技術メモ帳 - VRChat tech notesAudio](https://vrcworld.wiki.fc2.com/wiki/Audio)