Unityで「積み上げ式棒グラフのリザルト画面」を作る＆いい感じに音をつける

# 積み上げ棒グラフのリザルト画面とは？

対戦ゲームなどで複数ラウンドの試合を繰り返して合計点数を競うタイプのゲームを考えます。1ラウンドごとの得点合計をグラフで積み上げ、基本点数とボーナス点数が横方向に積みあがってく見せ方を「積み上げ棒グラフのリザルト画面」と呼びます。
正式な名称が分からないのですが、たとえば「Ultimate Chicken Horse」というタイトルではそのような見せ方になっています。

Ultimate Chicken Horse - Launch Trailer
https://youtu.be/FYaE_xw4krw?t=25

これをUnityで作る方法を考えてみましょう。完成形の動きは次の通りです。

![baranimation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/c6b0f2b2-beb5-03ab-f423-eeb26570bc7e.gif)

## Layout Groupの活用

Unityの標準コンポーネントのひとつに、「Layout Group」というものがあります。
これは、アタッチされているゲームオブジェクトの子となるUI要素の位置を調整・変更するコンポーネントです。ボタンなどのUI要素を、指定の空間内にバランスよく並べることができます。
親となるコンポーネントにアタッチすると子のRect Transformが従属し自動で変更されます。横方向用は「Horizontal Layout Group」、縦は「Vertical Layout Group」です。

![hori2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/6841b2ac-7834-6a0b-906c-e85e5a18b5cb.png)


このコンポーネントの本来の使い方は、「UIの整列」です。スクリーンサイズが異なる端末へのマルチ展開や、プレイヤーがUIのサイズを自由に変更できるシステムへの対応の時に使います。

![horizontal.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/8a6f7938-ef64-6dcd-fc07-7849340ad329.gif)

このコンポーネントは、ゲームが実行中であってもリアルタイムに反映されます。これを転用して、積み上げ式の棒グラフを楽に作ることができます。

長さ0のImageをあらかじめ用意しておき、順番にWidthを変更していきます。
それぞれのImageが横並びに整頓されるため、棒グラフに使うと、1段目の棒が伸びきった位置が2段目の起点にできます。長さの変更は、DoTweenを使ってなめらかに変更します。

（描画負荷はたぶんよろしくないのですが、リザルト画面では他の描画処理はあんまりいそがしくないだろう、という想定であまり考えません）

## 実装 
まずは、親となるゲームオブジェクトにHorizontal Layout Groupコンポーネントをアタッチします。「左端から伸びる」動きのために、Child AlignmentをMiddle Leftに設定します。また、隙間は作らないのでチェックボタンは全部外して、Padding, Spacingも0に設定します。

![hori3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/6d6d2972-b19a-e557-02ad-736de3b12c7b.png)

一緒に新規のコードを作ってアタッチします。（ここではStacked Bar Resultsという名前のスクリプトファイルにしています）

コードでは、まずこのコンポーネントを親として、高さが一定で幅が０のImageを生成して参照をリストで持ちます。いわゆるオブジェクトプーリングです。

```StackedBarResults.cs
    public int barHeight = 80;
    List<Tuple<RectTransform, Image>> barDataList = new List<Tuple<RectTransform, Image>>();

    private void Start()
    {
        for (var i = 0; i < 20; i++)
        {
            barDataList.Add(GenerateBarParts());
        }
    }

    private Tuple<RectTransform, Image> GenerateBarParts()
    {
        GameObject go = new GameObject("BarParts");
        go.transform.parent = this.transform;

        Image image = go.AddComponent<Image>();
        RectTransform rect = go.GetComponent<RectTransform>();

        rect.sizeDelta = new Vector2(0, barHeight);

        return new Tuple<RectTransform, Image>(rect,image);
    }

```

ゲームを実行状態にしたら、プールしたImageを使って順番にバー伸びアニメを表示します。リストbarDataListにImageとRectの参照セットが入っているので、ひとつずつに色と大きさを指定します。

```StackedBarResults.cs
public int counter = 0;

public void ShowBarParts(Color color, float size)
{
  if(counter >= barCount) return;

  barDataList[counter].Item2.color = color;
  barDataList[counter].Item1.DOSizeDelta(new Vector3 (size, barHeight),1.0f)
.SetEase(Ease.InCubic));
  counter++;
}
```

ShowBarPartsメソッドにランダムな値を与えてみると、次のアニメーションでバーが伸びていきます。

![baranimation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/c6b0f2b2-beb5-03ab-f423-eeb26570bc7e.gif)

これで「積み上げ棒グラフのリザルト画面」を作ることができました。

このバーのアニメーションがうまく再生されない場合は、HorizontalLayoutGroupのメソッド「CalculateLayoutInputHorizontal」や「SetLayoutHorizontal」を読んであげることで正常に反映されるようになるそうです。

# いい感じの音をつける
さて、このバーのアニメーションに音をつけることもやっておきましょう。

バーが伸びているときに「ニニニニニ...」という効果音をつけたいと考えてみます。バーの長さは不定なので伸びきるタイミングは不定です。伸び初め～伸び加速時に音のピッチを変えて演出をつけながら、伸びきった時に停止音を再生したい場合はどのように実装すればよいでしょうか。

完成形は次の通りです。
https://youtu.be/JKEJXy76VCA

Unity AudioSourceで実装しようとしたときは、

* バー伸びスタート：ループ音再生開始
* バー伸び中：音量、ピッチ（音の高さ）を変更
* バー伸びエンド：ループ音再生停止、停止音再生、ピッチ変更が途中だったらそれも停止

といった形で、処理や条件のチェック等が意外と面倒です。そこで本記事では、サウンドミドルウェアの「CRI ADX2」を使ってこのあたりの処理を簡略化します。
ADX2は、サウンドの各種演出を内包するライブラリと、音に演出を埋め込む専用ツールがセットになったSDKです。Unityにおける基本的な使い方については、次の記事をご確認ください。

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

## バー伸び開始～伸びアニメション中の音を作る
バーが伸びているときの音は、ADX2のツール「Atom Craft」で次の設定を行います。
![MeterStart.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/4e345483-7fc7-8eeb-2575-fe1c4a7006d5.png)

この音には、次の演出が埋め込まれています。

* 音をループさせる
* 時系列に沿ってボリュームを上げる（黄色線）
* 時系列に沿ってピッチを上げる（青線）

プログラムからは、この「MeterStart」を再生とやると、短い音をループ再生しつつ、序盤はピッチとボリュームが上昇する演出で音が再生されます。

## バー伸び終了時の音を作る
バーが伸びきった時は、終了時の音の再生と共に、バー伸び中の音を停止する処理が必要です。
この処理のために、ADX2の「アクション機能」を使います。
アクション機能は、音の中に「ほかの音の停止」や「パラメータの変更」などの指定を埋め込むことができるものです。

「アクション機能」を使うことによって、連続した効果音の演出を作る時、1つのトリガーで複数の音の操作ができます。

今回は「ループ音の再生停止」と「バーが伸び終了時の効果音再生」を同時に行います。これによって、プログラム側が「何の音を止めなくてはならないか」を保持しておかなくてよくなります。

アクション機能については、詳しくは以下の記事をご確認ください。

CRI ADX2で「アクション」ベースの実装を行う
https://qiita.com/Takaaki_Ichijo/items/f9c0e67cd64d7e976ae8

![MeterEnd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/6663a5e2-4b37-190c-98e6-80fbf9234c1b.png)

停止音「MeterStop」は、停止したときの音の通常再生と共に、「アクショントラック」と呼ばれるアクションを実行するトラックが追加されています。
この中で操作対象の音を指定し、アクションで何をするかはインスペクターの中で指定します。

![MeterStopのインスペクター.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/2aa414e8-4653-28f4-a072-78ad033f2e95.png)

今回は、0.2秒をかけて音がフェードアウトする処理を「MeterStart」に対して行います。

## コードの修正

音側にピッチ変更やループ、停止命令などがすべて埋め込まれているので、コード側は「MeterStart」「MeterStop」の2つの音を再生するだけになります。

```StackedBarResults.cs

    public AtomSource atomSource;

    public void ShowBarParts(Color color, float size)
    {
        if(counter >= barCount) return;

        barDataList[counter].Item2.color = color;

        atomSource.Play("MeterStart");

        barDataList[counter].Item1.DOSizeDelta (new Vector3 (size, 100),1.0f)
            .SetEase(Ease.InCubic)
            .OnComplete(() =>
            {
                atomSource.Play("MeterStop");
            });

        counter++;
    }

```

ADX2を組み合わせることによって、アニメーションと連動する音の操作がかなり簡潔に記述できます。

# まとめ

Horizontal Layout Groupを使うと、「積み上げ棒グラフのリザルト画面」がすぐ作れます。
また、一緒にADX2を使うことで、棒グラフのアニメーションの音を簡単に作ることができます。ループ再生やピッチ変更、再生停止などの処理をデータ側に持たせることで、実装量を減らしつつ、リッチな音の演出が表現可能です。