[Unity] Unity×音についてざっくりまとめ

社内勉強会でUnityの音にまつわる話をしたので資料をざっくり公開する

<blockquote class="twitter-video" data-lang="ja"><p lang="ja" dir="ltr">ざっくりこんな感じのデモシーン作りましたー <a href="https://t.co/KwkZ158rVz">pic.twitter.com/KwkZ158rVz</a></p>&mdash; 青木とと(ˊ꒳ˋ*) (@lycoris102) <a href="https://twitter.com/lycoris102/status/804340995659735040">2016年12月1日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

## Unityでの音の取り扱い
### 概要
- **AudioSourceコンポーネント** で音 ( **AudioClip** ) をゲーム世界内で鳴らす
- **AudioListenerコンポーネント** でゲーム世界内の音を聞き、実機で再生する

### AudioClip

![audioclip.png](https://qiita-image-store.s3.amazonaws.com/0/102664/dd7f17c9-f348-6a14-9d28-1af4a08a181b.png)

- https://docs.unity3d.com/jp/current/Manual/class-AudioClip.html
- https://docs.unity3d.com/jp/current/ScriptReference/AudioClip.html
- 音源のこと
- 以下の拡張子がAudioClipとして取り扱える
  - .aif / .wav / .mp3 / .ogg / .xm / .mod / .it /.s3m
- AudioSource経由で再生される
- 主にパフォーマンス絡みで設定を変更することが多い
  - モバイルゲームではモノラルを使用する
  - iOSではMP3の圧縮を有効にする
  - AndroidではVorbisの圧縮を有効にする
  - ([【Unity】Unite 2016「モバイル端末向けのUnityアプリケーションの最適化実践テクニック」翻訳](http://qiita.com/baba_s/items/5ee3079ef2797abdcf99#%E3%82%AA%E3%83%BC%E3%83%87%E3%82%A3%E3%82%AA%E3%81%AE%E6%B1%8E%E7%94%A8%E3%83%AB%E3%83%BC%E3%83%AB) より)

### AudioSourceコンポーネント

![image](https://qiita-image-store.s3.amazonaws.com/0/102664/3f08fc41-b997-306a-ed74-184765110e4e.png)

- https://docs.unity3d.com/jp/current/Manual/class-AudioSource.html
- https://docs.unity3d.com/jp/current/ScriptReference/AudioSource.html
- 音源を再生する君
- GameObjectにAudioSourceコンポーネントをアタッチして使用する
- 主にScript経由で任意のタイミングで再生するパターンが多い
- 1つのAudioSourceからは1つの音源のみ再生出来る
  - 動的にAudioClipを差し替えることは可能だが、同時に再生できない
  - 複数再生したい場合は複数のAudioSourceをアタッチする手法も取れる

## AudioListenerコンポーネント

![audiolistener.png](https://qiita-image-store.s3.amazonaws.com/0/102664/703a21f7-fef7-07a3-09a6-5cbdad1704df.png)

- https://docs.unity3d.com/jp/current/Manual/class-AudioListener.html
- MainCameraにデフォルトでアタッチされた状態なため気付きにくい
- 各シーンで使用できるAudioListenerは1つだけ

## AudioSourceコンポーネントで音を制御する
### ゲーム開始時(Scene遷移時)に音を再生する
- Inspector上でAudioSourceの PlayOnAwake を有効にする

![playonawake.png](https://qiita-image-store.s3.amazonaws.com/0/102664/da52937f-e396-d307-ae2b-36b589204ae3.png)

### BGM用に音をループさせる
- Inspector上でAudioSourceの Loop を有効にする

![loop.png](https://qiita-image-store.s3.amazonaws.com/0/102664/9c669ccd-1777-9956-b209-9ec16eb0087c.png)

### Script経由で音を再生する
- https://docs.unity3d.com/jp/current/ScriptReference/AudioSource.Play.html
- `AudioSource.Play()`
- e.g: ボタンを押したタイミングでBGMを再生する

```csharp
using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(Button))]
public class AudioPlayButton : MonoBehaviour
{
    public AudioSource AudioSource;

    void Start()
    {
        this.GetComponent<Button>().onClick.AddListener(() => this.AudioSource.Play());
    }
}
```

### Script経由で音を停止する
- https://docs.unity3d.com/jp/current/ScriptReference/AudioSource.Stop.html
- `AudioSource.Stop()`
- e.g: ボタンを押したタイミングでBGMを停止する

```csharp
using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(Button))]
public class AudioStopButton : MonoBehaviour
{
    public AudioSource AudioSource;

    void Start()
    {
        this.GetComponent<Button>().onClick.AddListener(() => this.AudioSource.Stop());
    }
}
```

### Script経由で音を一時停止/解除する
- https://docs.unity3d.com/jp/current/ScriptReference/AudioSource.Pause.html
- https://docs.unity3d.com/jp/current/ScriptReference/AudioSource.UnPause.html
- `AudioSource.Pause()` / `AudioSource.UnPause()`
- e.g) ボタンを押した段階で一時停止 / 離した段階で解除する

```csharp
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using UnityEngine.EventSystems;

public class AudioPauseButton : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
    public AudioSource AudioSource;
    public Text Text;

    public void Start()
    {
        this.Text.text = "Pause";
    }

    public void OnPointerDown(PointerEventData eventData)
    {
        this.AudioSource.Pause();
        this.Text.text = "UnPause";
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        this.AudioSource.UnPause();
        this.Text.text = "Pause";
    }
}
```

### Script経由でSEを再生する
- https://docs.unity3d.com/jp/current/ScriptReference/AudioSource.PlayOneShot.html
- `AudioSource.PlayOneShot(AudioClip)`
- 引数にAudioClipを持つ
  - 多分同一の音源が複数のアクションによって違う音を鳴らす設計を想定していそう
- 同じ音を重ねて再生出来る
- e.g) ボタンを押した段階でSEを再生する

```csharp
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;

[RequireComponent(typeof(AudioSource))]
public class SEPlayButton : MonoBehaviour, IPointerDownHandler
{
    public AudioClip SE;

    public void OnPointerDown(PointerEventData eventData)
    {
        this.GetComponent<AudioSource>().PlayOneShot(SE);
    }
}
```

### Script経由で音量を変更する
- https://docs.unity3d.com/jp/current/ScriptReference/AudioSource-volume.html
- `AudioSource.volume`
- e.g) スライダーを動かして音量を変更する

```csharp
using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(Slider))]
public class AudioVolumeSlider : MonoBehaviour
{
    public AudioSource AudioSource;

    void Start()
    {
        this.GetComponent<Slider>().onValueChanged.AddListener(value => this.AudioSource.volume = value);
    }
}
```

### Script経由でミュートする
- https://docs.unity3d.com/jp/current/ScriptReference/AudioSource-mute.html
- `AudioSource.mute`
- 元の音量を保持出来る面にて、直接音量を0にするよりアドバンテージがある
- e.g) ボタンを押した段階でミュート / 離した段階で解除する

```csharp
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using UnityEngine.EventSystems;

public class AudioMuteButton : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
    public AudioSource AudioSource;
    public Text Text;

    void Start()
    {
        this.Text.text = "Mute";
    }

    public void OnPointerDown(PointerEventData eventData)
    {
        this.AudioSource.mute = true;
        this.Text.text = "UnMute";
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        this.AudioSource.mute = false;
        this.Text.text = "Mute";
    }
}

```

## Unity内で音を管理する
### AudioManagerを作って再生管理
- 音が鳴る物にAudioSouce付けて、全シーン毎にAudioListenerを用意して、オーディオファイルを追加する度にAudioClip付けるのが面倒
- 全シーン、どのスクリプトからもBGMとSEを操作出来るようにする
- 以下のようなパターンが多い
  - Resources以下に音源ファイルを設置
  - シングルトン化してどのシーンからも読み込めるようにする
  - AddComponentでBGM/SE用にAudioSourceを用意する
    - SEは同時発音用に複数 (同時再生数 or SE個数分) AddComponentすることがある
- 参考
  - [(:3[kanのメモ帳] - ぼくがかんがえたさいきょうのAudioManager【Unity】](http://kan-kikuchi.hatenablog.com/entry/AudioManager)
  - [(:3[kanのメモ帳] - ぼくがかんがえたさいきょうのAudioManager2【Unity】](http://kan-kikuchi.hatenablog.com/entry/AudioManager2)
  - [しゅみぷろ - Unityのサウンド管理(SoundManager)](http://esprog.hatenablog.com/entry/2016/02/28/122938)

### AudioMixerを使ってミックス/マスタリング
- https://docs.unity3d.com/ja/current/Manual/AudioMixer.html
- 複数の音源を再生する際のシチュエーションを整えてくれる君
- AudioMixerを使うと以下のようなことができる
  - ミックス (同時発音)
  - エフェクト (音に対する効果付け)
  - マスタリング (音同士の音量/音圧の調整)
- AudioManagerとも併用可能
  - AudioManagerにてAudioGroupも管理してあげる

## Unity内で音を加工する
### フェードイン/フェードアウト
- AudioSource.volume をScriptで制御する
  - Coroutineで処理するなり、Tween系のアセットで処理させるなり

```csharp
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using DG.Tweening;

// DOTweenをセットアップしている前提
public class AudioFadeButton : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
    public AudioSource AudioSource;
    public Text Text;
    public float FadeTime = 3.0f;

    private float volume;

    void Start()
    {
        this.Text.text = "FadeOut";
    }

    public void OnPointerDown(PointerEventData eventData)
    {
        if (!this.AudioSource.isPlaying)
        {
            return;
        }

        this.volume = this.AudioSource.volume;
        this.AudioSource.DOFade(0.0f, FadeTime).SetEase(Ease.InCubic);
        this.Text.text = "FadeIn";
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        this.AudioSource.DOFade(volume, FadeTime).SetEase(Ease.InCubic);
        this.Text.text = "FadeOut";
    }
}
```

### ピッチを変更する
- AudioSource.pitch もしくは　AudioMixer.Pitch で変更する
- Pitchが0以下になると逆再生される

### エフェクト (フィルター) を掛ける
- AudioMixerを作成する
- 任意のGroupを作成し、AudioSource
- Groupに対して、下部のAddから任意のFilterを掛ける

## Unity内で音と同期する( Music.cs )
- https://github.com/geekdrums/MusicEngine
- http://www.slideshare.net/geekdrums/about-musicengine-46632468
- [@geekdrums](https://twitter.com/geekdrums?lang=ja) さんの作ったライブラリ

### BGMに合わせて要素の大きさを動かす

```csharp
using UnityEngine;
using DG.Tweening;

public class ChangeScale : MonoBehaviour
{
    public float Rate = 1.1f;
    public float Time = 0.2f;

    private Vector3 baseScale;

    void Start()
    {
        this.baseScale = this.gameObject.transform.localScale;
    }

    void Update()
    {
        if (Music.IsPlaying && Music.IsJustChangedBeat())
        {
            this.gameObject.transform.DOScale(this.baseScale * this.Rate, 0.0f);
            this.gameObject.transform.DOScale(this.baseScale, this.Time);
        }
    }
}
```

### SEをクオンタイズでリズムに合わせて再生する
- リズムに合わせて再生タイミングを自動補正する

```csharp
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;

[RequireComponent(typeof(AudioSource))]
public class SEPlayQuantizeButton : MonoBehaviour, IPointerDownHandler
{
    public void OnPointerDown(PointerEventData eventData)
    {
        Music.QuantizePlay(this.GetComponent<AudioSource>());
    }
}
```

## Unity内でイントロループを実装する
- [[Unity]誰でも簡単にイントロ付きループBGMを再生するスクリプト](http://qiita.com/tatmos/items/d06ce8af00ad3a0d0ac1)
- [【Unity】誰でも簡単にイントロ付きループBGMを再生するスクリプト(2015-11-01繋ぎ改善版)](http://qiita.com/tatmos/items/4c78c127291a0c3b74ed)
- AudioSource.PlayScheduled を使っている
  - 任意のタイミングでBGMを再生する君
  - BPMが同じであれば、アレンジした曲に差し替える用法でも使用できる

## 3D空間で音を取り扱う
- https://docs.unity3d.com/jp/current/Manual/class-AudioSource.html
- 2Dの場合はListnerとの距離等は考慮されない
- Spatial Blend を3Dに振ることで3DSoundSettingsが有効になる
  - 3DSoundSettingsではAudioListenerとの距離と音量の相関やドップラー効果の効き具合が調整出来る

![3dsound.png](https://qiita-image-store.s3.amazonaws.com/0/102664/a66a23f0-5fcb-994c-3b68-453cf669321b.png)

## 同時再生する上での注意点
- https://docs.unity3d.com/jp/current/Manual/class-AudioSource.html
- `AudioSource.Priority` を適切に設定しないとBGMがSEにかき消される等の懸念がある

## 参考
- [任天堂サウンドが大事にしていること](https://www.nintendo.co.jp/jobs/introduction/sound/policy.html)
  - インタラクティブミュージックをやる上でのポイントや事例が分かりやすく書いてある