ゲームのUIでボタン音を操作やカーソル位置に応じて変える

# 単調なボタン音をメニューの状況に合わせて変える

ゲームにおいて、「ボタンを選択した」ときに音を鳴らすことを考えます。最近のゲームでは、選択したボタンの位置によって音を微妙に変え、「終端」や「右端」「左端」を感覚的にわかりやすくしている事例も多くあります。

本投稿ではゲーム開発において、どの位置のボタンを選択しているかによって音を変える演出を作ります。
音の制御には、CRI ADX2を使用します。また、実装例はUnityで紹介しますが、UE4+ADX2でもブループリントを使って同じことが可能です。

## 前提となる知識
この記事は統合型サウンドミドルウェア「CRI ADX2」及び付属のサウンドオーサリングツールAtom Craftの触り方をある程度理解している方向けの解説です。
初めてADX2を使う、という方は各エンジンへの導入の記事を先に参照ください。

Unity: Unityのサウンド機能をADX2で強化する
https://qiita.com/Takaaki_Ichijo/items/16e6501fc07f5b3b3377

UE4：　ADX2 for UE4の導入で、一歩上のサウンド表現を（導入編）
https://qiita.com/SigRem/items/4250925f6d66a4fd287a

# ボタンのセレクト音が変わるシチュエーション

言葉で説明するのは冗長なので、こちらの動画をご覧ください。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">UIのサウンド実装テスト（再）　端のボタン選択してる時だけピッチがちょい上がる <a href="https://t.co/sv0DoMhfQX">pic.twitter.com/sv0DoMhfQX</a></p>&mdash; スーパーミソダンゴ (@Takaaki_Ichijo) <a href="https://twitter.com/Takaaki_Ichijo/status/1268090750186606592?ref_src=twsrc%5Etfw">June 3, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

このように、ボタンを選択したときに音を鳴らす場合、「どのボタンを選択しているか」で音の鳴り方を変えます。

音素材の生成には、Tsugi社の[DSP-Sci-Fi]
(http://tsugi-studio.com/web/jp/products-dspscifi.html)を使用しています。

## Atom Craftでの設定
まずは、「通常状態」と「ピッチが上がった状態」をキューに設定する必要があります。
今回は、ADX2の「AISAC」機能を使って実装を行います。

Atom Craftを立ち上げ、まずはプロジェクトツリーのAISACコントロールで、今回使うAISAC値を操作するコントロールIDを用意します。
例では、ButtonPitchという名前にしました。

![aisac名の設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/3e246ce8-4862-e7eb-7fce-e4f07b3713e7.png)

次に、ボタン音のキューを作成したあと、キューにAISACを追加します。

![AISACの作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/c7165c57-7f74-33ca-fa2c-652d301dfd5b.png)

ダイアログでは、先ほど作成したAISACコントロールIDと、グラフタイプに「ピッチ」を指定します。

![AISACの追加.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/8b05bccd-cc8d-0a63-7bde-23ea6b08f30a.png)

AISACは、ADX2で音を鳴らすときに「0からの1のパラメータ値」をゲームから渡し、値によって音を変化させる機能です。
パラメータの値によってどのくらい音が変化するかは、Atom Craft内のグラフで指定します。

今回はピッチを変化させています。

![ピッチのAISACグラフ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/5da5661f-cde4-d44f-ce2d-71a846afbe67.png)

このスクリーンショットの下に描かれている青い線がASIACグラフです。
キュー「Select」にはAisac_0という名前のAISAC設定がり、その中身はピッチの変更設定で、ゲームから渡される0から1までの値に応じてピッチをどう変化させるかを設定しています。

AISACビューの白い縦線が、ゲームから渡される値を示しています。ButtonPitchの値が0.501fだったとき、を次の図は示しています。

![ピッチのAISACグラフ2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/a89ae1ea-bbf5-d17e-42e7-429bf2e9e985.png)

グラフが凹の形になっているのは、0方向を左端、1方向を右端としてとらえ、それぞれのAISAC値だったときにピッチを上げる、という仕組みにしているためです。
右と左で音の変化が全く同じ場合は、たとえば0.500以下が無加工の音で、0.501以上のときにピッチを上げる、という２値の設定でも構いません。

AISACビューの隣に表示している「ポイントリスト」は、AISAC内の点の位置を数値で調整できるウィンドウです。

![ポイントリスト.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/528ee2db-ca1d-ac4d-2358-d2baeb57d8b9.png)

左から順番にAISACIDには数字が降ってあり、その位置を表形式で確認できます。
マウスで適当に点を打ったあと、こまかい調整はポイントリストで数値での調整がお勧めです。

## ボタンが端に来たことを感知する

次にUnity側の準備です。ボタンが端に来たことを検知して、AISACに適切な値を渡します。
~~今回は非常に単純な実装として、enumでボタン位置を管理しておく仕組みで実装します。
uGUI Buttonのコンポーネントと同じゲームオブジェクトに、次のスクリプトを配置します。~~

ボタンがキーナビゲーションにおいて端に来ているかどうかは、Buttonクラスの基底クラスSelectableのFindSelectableOnLeft、FindSelectableOnRightメソッドでチェックできます。
隣に選択可能なSelectableがあるか調べて、なければnullを返します。

```UIButtonPlaySe.cs

using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class ButtonPlaySe : MonoBehaviour, ISelectHandler
{
    public CriAtomSource seAtomSource;

    private Selectable buttonSelectable;

    private void Awake()
    {
        buttonSelectable = GetComponent<Selectable>();
    }

    public void OnSelect(BaseEventData eventData)
    {
        float aisacControl = 0.5f;
        
        if (buttonSelectable.FindSelectableOnLeft() == null)
        {
            aisacControl = 0.05f;
        }
        else if (buttonSelectable.FindSelectableOnRight() == null)
        {
            aisacControl = 0.95f;
        }

        seAtomSource.SetAisacControl("ButtonPitch", aisacControl);
        seAtomSource.Play();
    }
}

```
seAtomSourceフィールドには、シーンにあるSE再生用のCriAtomSourceの参照をインスペクターで指定します。

~~ButtonPositionの列挙体によって、並べてあるボタンが右端・中・左端の３種類から指定できます。~~

スクリプトはISelectHandlerインターフェースを実装しており、キーボードやパッド入力で当該のボタンが選択されると、OnSelectコールバックが呼ばれます。そのタイミングでAISAC値を渡ししています。

右端の場合は0.05f, 左端は0.95f,それ以外は中央といった具合です。

~~この方法では、シーンの上であらかじめボタンに「端かそうでないか」の指定が必要です。~~

~~動的にボタンが増えたり減ったりする場合は、こうした固定での指定ができませんので、なんらかの検出の仕組みが必要です。~~

~~たとえばX方向におけるボタンの位置を画面解像度から取得し、ある程度画面の端に近いボタンであれば端にいると判定する、などの方法がとれます。~~

~~おそらくEventSystems.BaseInputModule.DetermineMoveDirectionを使えばボタンのナビゲーションで次に遷移できるかできないか検出できると思うのですが、手間が多そうなので簡単な方法をとりました。~~

Selectable.FindSelectableOnLeftメソッドでボタンがどの位置にあるか自動的に判定し、音を変化させることができます。FindSelectableOnUp、FindSelectableOnDownもありますので、上下での変化にも対応できます。

## 応用編
AISACはピッチ以外にも操作が可能ですので、端のボタンを選択したときにだけエコーなどのエフェクトがかかる、といった演出も作れます。
また、音をパラメータで加工するのではなく、完全に別素材の音を鳴らしたい場合は、ADX2の「セレクタ」や「ゲーム変数」機能で実装できます。

#### セレクタラベル
ゲームの状況に合わせてサウンドエフェクトのかかり具合を変える
https://qiita.com/Takaaki_Ichijo/items/957dd09112b9939ff083

#### ゲーム変数
ゲームの状況に合わせて音のエフェクトの掛かり具合を変える　その2
https://qiita.com/Takaaki_Ichijo/items/4777ba953e7992a872e4