【Unity】コマンドルーレットの作り方

#コマンドルーレットとは
こういうのが作りたかったんですよね。(↓画像)
![スクリーンショット (337).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1233990/1aa551b2-4b09-e1af-a486-c211e96593f6.png)

調べてみて、見つからなかったので、作ってみることにしました。

# 1.実行環境について
Unityバージョン：2019.4.18f1
Visual Studio Community 2019バージョン：16.9.1

# 2.UIの配置
## ①Hierarchy(ヒエラルキー)の"＋"からUI→Canvasと進みCanvasを設置
シーン画面で編集しやすいように、Canvasを全て画面に収まるように調整だとか、Render Mode弄ってカメラの範囲にCanvasがおさまるように調整だとか、その辺お好みで。

## ②Canvasの下に、空オブジェクト一つ作成
空オブジェクト（以下、空オブ）のInspector（インスペクター）いって、Rect Transformのコンポーネントでサイズ調整とかは各自。（私は下の画像の通りにしましたが、たぶん皆さんと画面比全然違うので参考にならないんじゃないでしょうか…）

## ③空オブに"Vertical Layout Group(垂直レイアウトグループ)"のコンポーネントを追加
空オブのインスペクター一番下の"Add Component"から、"Vertical Layout Group"を検索、追加。

"Vertical Layout Group"は垂直に均等にオブジェクトを並べてくれるコンポーネント。皆さんが作りたいルーレットのコマンド数が、私と同じ7個とは限らないわけですが、これさえあればその下に何個オブジェクトつけても均等に配置してくれるので、その辺の問題を解決してくれて便利。

写真のように設定して、次は一つ一つのコマンドに当たるUIを作っていきます。

<img width="700" src="https://1.bp.blogspot.com/-oswRTYYyGNg/YFmMrAdQPrI/AAAAAAAAAFQ/X126FX0kCz0XmrFMLAwFgegkegoiPvpcQCNcBGAsYHQ/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588%2B%2528329%2529.png">

## ④空オブの下に、Image、さらにその下にTextのUI作成
このTextにコマンドを記入する感じですね。Imageは特に何もしなくていいですが、TextのRect Transformは下画像みたいに弄って、親オブジェクトとサイズ一致させておいてください。

<img width="700" src="https://1.bp.blogspot.com/-cRrLjYLcx4s/YFrP64uiSEI/AAAAAAAAAKo/4cY3TA_7EsQ1Fo5wT-FLmOQ0QrWX_NfqACNcBGAsYHQ/s899/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588%2B%2528331%2529.png">

Textのコンポーネントも適当に弄って、文字の大きさとか配置とか設定してください。

…さきほど、「Imageは特に何もしなくていい」と書いた直後に恐縮ですが、ここで一工夫、Imageのソース画像に適当なフリー素材を入れると見映えがよくなったり。

<img width="700" src="https://1.bp.blogspot.com/-J5WX8eGTBiE/YFrSjx0yjdI/AAAAAAAAAKw/H8PavcC8kG4ApnKdBcsC9Z3TtLljAL7bgCNcBGAsYHQ/s551/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588%2B%2528332%2529.png">

私のはこんな感じ。
下記のサイトから借りてきました！クレジット代わりにリンクを貼っておきます。
https://kopacurve.blog.fc2.com/blog-entry-557.html

## ⑤Imageを好きなだけ複製
見映えもよくなったコマンドが完成したら、あとは増産するだけ。
Imageにカーソル合わせてマウス右クリック、Duplicate(複製)を、お求めの数だけしてください。
③でも説明しましたが、どれだけ増産しても勝手にサイズ調整されるはず。

これでUIの準備は完了です。

# 3.コマンドルーレット基本のスクリプト作成

ProjectのAssetsで右クリック、create（作成する）からC#scriptへ、というおなじみの流れはもう省略しますが、いよいよスクリプトです。

難しいスクリプトではありませんが、基本的にはこんな感じでしょうか。

```c#:RoulettEasy.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; //UI弄るときに必須

public class RoulettEasy : MonoBehaviour
{
    [SerializeField] Image[] commandlist;
    private float countTime;
    private int lastTime;
    private float speed = 10;
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        countTime += Time.deltaTime * speed;
        if (countTime > commandlist.Length)
        {
            countTime = 0f;
        }
        if (lastTime != (int)countTime)
        {
            foreach (var command in commandlist)
            {
                command.color = new Color(1, 1, 1);
            }
            lastTime = (int)countTime;
            commandlist[(int)countTime].color = new Color(1, 0, 0);
        }
        if (Input.GetKeyDown(KeyCode.Space))
        {
            speed = 0;
        }
    }

}
```

ちょこちょこ説明いれてますが、だいたいこんな感じですね。

あとはこのスクリプトを空オブにアタッチして、インスペクターから[SerializeField]の事項を登録するだけですね。
私の場合、コマンドが7個の設定だったので、こんな感じ。

<img width="700" src="https://1.bp.blogspot.com/-Rogx8lU6OxI/YFrhngwQFkI/AAAAAAAAAK4/PRFzY3W8gRM8VnfGufGO8WbbPsH3d4KhQCNcBGAsYHQ/s690/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588%2B%2528334%2529.png">

Sizeのところに7と記入してEnter、出てくるElementたちにImageをドラッグして登録してはい、完成！
実際に動かしたら、ちゃんとルーレット動くと思います。
Spaceキー押したら止まるはずなので、それも確認してください。

# 4.他スクリプトへつなげやすいように改造版
一応の完成を見たコマンドルーレット。この子は特性上、ルーレットで得た情報が次の行動につながるというようなシチュエーションで使うことが多いのではないでしょうか。
すなわち、他のスクリプトへ情報を送るのが前提での使用が予想されます。ということで、お節介やもしれませんが少しだけ、他スクリプトへアクセスしやすいように付け足しておきます。

```c#:RoulettEasy.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; 

public class RoulettEasy : MonoBehaviour
{
    [SerializeField] Image[] commandlist;
    private float countTime;
    private int lastTime;
    private float speed = 10;
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        countTime += Time.deltaTime * speed;
        if (countTime > commandlist.Length)
        {
            countTime = 0f;
        }
        if (lastTime != (int)countTime)
        {
            foreach (var command in commandlist)
            {
                command.color = new Color(1, 1, 1);
            }
            lastTime = (int)countTime;
            commandlist[(int)countTime].color = new Color(1, 0, 0);
        }
        if (Input.GetKeyDown(KeyCode.Space))
        {
            speed = 0;
            //ここで(int)countTimeを他スクリプトに送信
        }
    }

    public void StartEasyRoulett()
    {
        speed = 10;
        countTime = 0;
    }
}
```

この場合、他スクリプトからコマンドルーレットを開始させる時は、そのスクリプトにこのRoulettEasyスクリプトをアクセスしたうえで（[SerializeField]が楽じゃないかな…）、StartEasyRoulett()を呼び出せばいいし。

他スクリプトに結果を流す場合は、このスクリプトに目的のスクリプトをアクセスしたうえで(int)countTimeを送るプログラムを書けばいいと。

まぁこんな感じですね。

# 4.はらはらドキドキ(?)コマンドルーレット
…名称だけでは意味わかんないと思います…
こういうのをイメージしてて、実際作ってみました！
https://youtu.be/Hd1DixwDXCM?t=294

…私が青春の時間を捧げた思い出深いゲームですが…。
こういう感じで、押したらすぐ止まるのではなく、少々間があって（しかもその間がランダム）止まる、みたいなルーレットを作ってみたいと思います。

```c#:RoulettControll.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class RoulettControll : MonoBehaviour
{
    [SerializeField] Image[] commandlist;
    private float countTime;
    private int lastTime;
    private float fireTime;
    private float speed = 10;
    bool isStop = false;
    private float lottery;
    bool justOnce = true;

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        //タイマーA
        countTime += Time.deltaTime * speed;
        if (countTime > commandlist.Length)
        {
            countTime = 0f;
        }
        //タイマーB
        fireTime += Time.deltaTime;
        if (lastTime != (int)countTime)
        {
            fireTime = 0f;
            foreach (var command in commandlist)
            {
                command.color = new Color(1, 1, 1);
            }
            lastTime = (int)countTime;
            commandlist[(int)countTime].color = new Color(1, 0, 0);
        }
        if (Input.GetKeyDown(KeyCode.Space))
        {
            isStop = true;
            lottery = Random.Range(990, 997) * 0.001f;
        }
        if (isStop)
        {
            speed *= lottery;
        }
        if (fireTime >= 2.5 && justOnce)
        {
            fireTime = 0;
            justOnce = false;
            //(int)countTimeを他スクリプトに送信
        }
    }

    public void startRoulett()
    {
        isStop = false;
        speed = 10;
        justOnce = true;
        countTime = 0;
        fireTime = 0;
    }
}
```

タイマー二つ使った荒技なんですが、もう少しスマートなやり方あってもいいような…。

# 5.おわりに
一年経過して記事を振り返ってみたら、いろいろと言いたいことがあるコードを組んでいるなぁ、と思います。
何かの参考になればいいのですが、皆さんの力で改善されることをおすすめします。
以上です。