【Unity】Unityで爆速でルーレットを実装する

今回はUnityで爆速でルーレットを作ったので手順を記述します！

![aaaaaa.gif](https://qiita-image-store.s3.amazonaws.com/0/360619/fd986e21-229e-c2e9-b5a9-3310635cdf32.gif)

##ルーレット作成方針（ざっくり）

・白い円の画像を1枚用意する

・選択肢の数だけ白い円の画像が貼られたImageを生成して、それぞれ色を変える

・ImageのImageTypeをFilledにし、Fill Amountを割合ごとに変える

・それぞれにTextを配置する

ざっくりですがこんな感じで作ろうと思います。他に何か方法あれば教えてください！


##作成手順

まずはルーレットの画像のPrefabを用意します。

1: 背景が透明の白い円の画像をUnityにImportし、TextureTypeをSpriteに変更したら、Imageに貼り付けます。

<img width="400" alt="スクリーンショット 2019-03-04 0.56.35.png" src="https://qiita-image-store.s3.amazonaws.com/0/360619/8abdaaa3-e92f-ce60-3ee4-24342d576235.png">

2: ImageのImage Typeを**Filled**にし、
Fill Methodを**Radial360**に、
Fill Originを**Top**にしておきます。
ついでにRaycastTargetもチェック外しちゃいましょう。

<img width="364" alt="スクリーンショット 2019-03-04 1.01.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/360619/2191b431-213d-3e0e-c10f-3d78d28537b8.png">
↑画像の通りになるようにしてください

3: Imageの子供に、**空のゲームオブジェクト**を配置します。
Textを回転させるためにあとで使います。**名前をRotateText**としておきます。

<img width="934" alt="nvnv.png" src="https://qiita-image-store.s3.amazonaws.com/0/360619/1921ba2d-4f68-3cf0-3baf-fcbc7e94ad41.png">

↑もしTransformのPositionやRotationが0以外になってたら0にしておいてください!

4: **RotateTextの子供に、Textを配置**します。
今回、Textを縦書きで使いたかったので、以下の記事の**RotateText.cs**をお借りしました！

[UnityのuGUIで縦書きテキスト表示](https://scrapbox.io/ConsoleSoup/Unity%E3%81%AEuGUI%E3%81%A7%E7%B8%A6%E6%9B%B8%E3%81%8D%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E8%A1%A8%E7%A4%BA)

Textを縦書きにできたら、中央の上の方に配置してください！

<img width="400" alt="スクリーンショット 2019-03-04 1.22.18.png" src="https://qiita-image-store.s3.amazonaws.com/0/360619/d8fe9b00-3183-3cfe-d384-6de8d552395b.png">
こんな感じです！

5: ここまでできたら、このImageを**Prefab化**します！
<img width="275" alt="スクリーンショット 2019-03-04 1.24.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/360619/1479be99-7e2a-4e81-6752-9f1e8f8808b3.png">
↑こんな感じの階層になってるはずです

###これで準備完了です！

6: Canvasの配下に**Roulette**という名前の**空のゲームオブジェクト**を配置します。
また、ここでも必ずTransformのPositionとRotationを0にしておいてください!

<img width="936" alt="スクリーンショット 2019-03-04 1.39.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/360619/cf28f4e5-3bfc-735c-fd68-c6816e9d5c63.png">

7: 以下のスクリプト（RouletteMaker.cs）を作成して、空のゲームオブジェクトにAddします。

```C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class RouletteMaker : MonoBehaviour {
    [SerializeField] private Transform imageParentTransform;
    public List<string> choices;
    [SerializeField] private List<Color> rouletteColors;
    [SerializeField] private Image rouletteImage;
    private void Start () {
        float ratePerRoulette = 1 / (float) choices.Count;
        float rotatePerRoulette = 360 / (float) (choices.Count);
        for (int i = 0; i < choices.Count; i++) {
            var obj = Instantiate (rouletteImage, imageParentTransform);
            obj.color = rouletteColors[(choices.Count - 1 - i)];
            obj.fillAmount = ratePerRoulette * (choices.Count - i);
            obj.GetComponentInChildren<Text> ().text = choices[(choices.Count - 1 - i)];
            obj.transform.GetChild (0).transform.rotation = Quaternion.Euler (0, 0, ((rotatePerRoulette / 2) + rotatePerRoulette * i));
        }
    }
}
```

その後Inspectorで必要な項目を設定します。

<img width="932" alt="スクリーンショット 2019-03-04 1.56.15.png" src="https://qiita-image-store.s3.amazonaws.com/0/360619/b6a89eea-5f0f-dffc-6d91-6234907df543.png">

画像の通りImageParentTransformとRouletteImageの項目にオブジェクトをアタッチしてあげ、Choicesにルーレットの選択肢、
またRouletteColorsにそれぞれの選択肢の色を指定しましょう。

またこの時、InspectorでColorを指定する時**デフォルトでalpha値が0**になってしまっているかと思いますので、直してあげてください。（alpha値が0だと透明になってしまいます）


<img width="746" alt="スクリーンショット 2019-03-04 2.02.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/360619/0ad82907-d328-d498-b7ef-e2f7946f536a.png">

↑再生したらきちんとルーレットが生成されるかと思います！

##作成したルーレットを回して判定

さて、ルーレットが作れたのであとは回して判定するだけです。

Canvas配下に、3つのボタン

・StartButton
・StopButton
・RetryButton

それと、結果を表示するようのText（ResultText）を作成し、

また矢印の画像も良い感じの位置に配置しちゃいましょう。
<img width="286" alt="スクリーンショット 2019-03-04 22.16.17.png" src="https://qiita-image-store.s3.amazonaws.com/0/360619/3e8aadcd-08bf-d129-1b52-e67a14cd0a36.png">
↑Canvas配下はこんな感じ。

ここまでできたら以下のスクリプト（**RouletteController.cs**）を作成して、空のゲームオブジェクトにAddします。

```C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class RouletteController : MonoBehaviour {
    [HideInInspector] public GameObject roulette;
    [HideInInspector] public float rotatePerRoulette;
    [HideInInspector] public RouletteMaker rMaker;
    private string result;
    private float rouletteSpeed;
    private float slowDownSpeed;
    private int frameCount;
    private bool isPlaying;
    private bool isStop;
    [SerializeField] private Text resultText;
    [SerializeField] private Button startButton;
    [SerializeField] private Button stopButton;
    [SerializeField] private Button retryButton;

    public void SetRoulette () {
        isPlaying = false;
        isStop = false;
        startButton.gameObject.SetActive (true);
        stopButton.gameObject.SetActive (false);
        retryButton.gameObject.SetActive(false);
        startButton.onClick.AddListener (StartOnClick);
        stopButton.onClick.AddListener (StopOnClick);
        retryButton.onClick.AddListener (RetryOnClick);
    }

    private void Update () {
        if (!isPlaying) return;
        roulette.transform.Rotate (0, 0, rouletteSpeed);
        frameCount++;
        if (isStop && frameCount > 3) {
            rouletteSpeed *= slowDownSpeed;
            slowDownSpeed -= 0.25f * Time.deltaTime;
            frameCount = 0;
        }
        if (rouletteSpeed < 0.05f) {
            isPlaying = false;
            ShowResult (roulette.transform.eulerAngles.z);
        }
    }

    private void StartOnClick () {
        rouletteSpeed = 14f;
        startButton.gameObject.SetActive (false);
        Invoke ("ShowStopButton", 1.5f);
        isPlaying = true;
    }

    private void StopOnClick () {
        slowDownSpeed = Random.Range (0.92f, 0.98f);
        isStop = true;
        stopButton.gameObject.SetActive (false);
    }

    private void RetryOnClick(){
        SceneManager.LoadScene(SceneManager.GetActiveScene().name);
    }

    private void ShowStopButton () {
        stopButton.gameObject.SetActive (true);
    }

    private void ShowResult (float x) {
        for (int i = 1; i <= rMaker.choices.Count; i++) {
            if (((rotatePerRoulette * (i - 1) <= x) && x <= (rotatePerRoulette * i)) ||
                (-(360 - ((i - 1) * rotatePerRoulette)) >= x && x >= -(360 - (i * rotatePerRoulette)))) {
                result = rMaker.choices[i - 1];
            }
        }
        resultText.text = result + "\nが当たったよ！";
        retryButton.gameObject.SetActive(true);
    }
}
```



また、先ほど作成した**RouletteMaker.cs**を、
以下のように編集します。



```C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class RouletteMaker : MonoBehaviour {
    [SerializeField] private Transform imageParentTransform;
    public List<string> choices;
    [SerializeField] private List<Color> rouletteColors;
    [SerializeField] private Image rouletteImage;
    [SerializeField] private RouletteController rController;
    private void Start () {
        float ratePerRoulette = 1 / (float) choices.Count;
        float rotatePerRoulette = 360 / (float) (choices.Count);
        for (int i = 0; i < choices.Count; i++) {
            var obj = Instantiate (rouletteImage, imageParentTransform);
            obj.color = rouletteColors[(choices.Count - 1 - i)];
            obj.fillAmount = ratePerRoulette * (choices.Count - i);
            obj.GetComponentInChildren<Text> ().text = choices[(choices.Count - 1 - i)];
            obj.transform.GetChild (0).transform.rotation = Quaternion.Euler (0, 0, ((rotatePerRoulette / 2) + rotatePerRoulette * i));
        }
        rController.SetRoulette();
        rController.rMaker = this;
        rController.rotatePerRoulette = rotatePerRoulette;
        rController.roulette = imageParentTransform.gameObject;
    }
}
```

あとは、Inspectorで必要なオブジェクトをアタッチしてあげてください！

RouletteMakerの**↓ここ**と
<img width="933" alt="スクリーンショット 2019-03-04 22.27.29.png" src="https://qiita-image-store.s3.amazonaws.com/0/360619/76bd8ba3-50ed-f114-4a3b-86ba673a8f29.png">

RouletteControllerの**↓赤枠の部分**です
<img width="934" alt="スクリーンショット 2019-03-04 22.30.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/360619/2449d63f-8b70-4015-52f7-09080122bf5c.png">

できたら再生して確認してください。
止まった場所の文字がResultText内に表示されるかと思います！

![aaaaaa.gif](https://qiita-image-store.s3.amazonaws.com/0/360619/466556c5-318b-5f03-8a20-8eb711fdcbba.gif)


以上です！

##おまけ　露骨なイカサマルーレット

**おまけ**と書いてますが、今回自分はこれが作りたくてルーレットをUnityで作りました笑

![ikasama.gif](https://qiita-image-store.s3.amazonaws.com/0/360619/342e23cd-d787-1876-2b09-448e1d8b1c15.gif)

どこで止めても**絶対に指定した場所まで最後にぶっ飛ぶ**、、というルーレットです笑
指定した場所に自然に止まったらそのままです。
（露骨すぎますが盛り上がれば良し。）

###実装方法
神アセット**DoTween**を拝借して、ルーレットの速度が遅くなったらDoRotateでルーレットをぶん回すという感じです。
もし、**自然な動きのイカサマルーレットが作りたい！**という方がいれば、まだルーレットの速度が速い時にRotateすれば良いのではないでしょうか！！

DoTweenをAssetStoreからProjectにimportして、

**RouletteController.cs**を以下のように書き換えます。

```C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using DG.Tweening;

public class RouletteController : MonoBehaviour {
    [HideInInspector] public GameObject roulette;
    [HideInInspector] public float rotatePerRoulette;
    [HideInInspector] public RouletteMaker rMaker;
    [SerializeField] private bool isIkasama;
    [SerializeField] private int ikasamaID;
    private string result;
    private float rouletteSpeed;
    private float slowDownSpeed;
    private int frameCount;
    private bool isPlaying;
    private bool isStop;
    [SerializeField] private Text resultText;
    [SerializeField] private Button startButton;
    [SerializeField] private Button stopButton;
    [SerializeField] private Button retryButton;

    public void SetRoulette () {
        isPlaying = false;
        isStop = false;
        startButton.gameObject.SetActive (true);
        stopButton.gameObject.SetActive (false);
        retryButton.gameObject.SetActive(false);
        startButton.onClick.AddListener (StartOnClick);
        stopButton.onClick.AddListener (StopOnClick);
        retryButton.onClick.AddListener (RetryOnClick);
    }

    private void Update () {
        if (!isPlaying) return;
        roulette.transform.Rotate (0, 0, rouletteSpeed);
        frameCount++;
        if (isStop && frameCount > 3) {
            rouletteSpeed *= slowDownSpeed;
            slowDownSpeed -= 0.25f * Time.deltaTime;
            frameCount = 0;
        }
        if (rouletteSpeed < 0.05f) {
            isPlaying = false;
            ShowResult (roulette.transform.eulerAngles.z);
        }
    }

    private void StartOnClick () {
        rouletteSpeed = 14f;
        startButton.gameObject.SetActive (false);
        Invoke ("ShowStopButton", 1.5f);
        isPlaying = true;
    }

    private void StopOnClick () {
        slowDownSpeed = Random.Range (0.92f, 0.98f);
        isStop = true;
        stopButton.gameObject.SetActive (false);
    }

    private void RetryOnClick(){
        SceneManager.LoadScene(SceneManager.GetActiveScene().name);
    }

    private void ShowStopButton () {
        stopButton.gameObject.SetActive (true);
    }

    private void ShowResult (float x) {
        for (int i = 1; i <= rMaker.choices.Count; i++) {
            if (((rotatePerRoulette * (i - 1) <= x) && x <= (rotatePerRoulette * i)) ||
                (-(360 - ((i - 1) * rotatePerRoulette)) >= x && x >= -(360 - (i * rotatePerRoulette)))) {
                result = rMaker.choices[i - 1];
            }
        }
        if(isIkasama && result != rMaker.choices[ikasamaID]){
            StartCoroutine(Buttobi());
            return;
        }
        resultText.text = result + "\nが当たったよ！";
        retryButton.gameObject.SetActive(true);
    }

     private IEnumerator Buttobi(){
         yield return roulette.transform.DORotate(
             new Vector3(0,0,rotatePerRoulette*((float)ikasamaID + 0.5f)),
             1.0f,
             RotateMode.FastBeyond360
         ).WaitForCompletion();
        resultText.text = rMaker.choices[ikasamaID] + "\nが当たったよ！";
        retryButton.gameObject.SetActive(true);
     }
}
```

このイカサマが使いたい時だけ、
↓InspectorのisIkasamaにチェックを入れ、選択肢配列の何番目を当たりにするかを指定してあげてください！（IDは0から始まるので注意）
<img width="362" alt="スクリーンショット 2019-03-04 22.49.31.png" src="https://qiita-image-store.s3.amazonaws.com/0/360619/8aeca2cf-b789-66e4-b9f6-2d210f48632d.png">


```C#
private IEnumerator Buttobi(){
         yield return roulette.transform.DORotate(
             new Vector3(0,0,rotatePerRoulette*((float)ikasamaID + 0.5f)),
             1.0f,
             RotateMode.FastBeyond360
         ).WaitForCompletion();
        resultText.text = rMaker.choices[ikasamaID] + "\nが当たったよ！";
        retryButton.gameObject.SetActive(true);
     }
```
↑この部分で回転を実装しています。
第2引数で秒数を指定しているので、もっと早く・遅くしたいなどあれば変えてください。

他にもスクリプト内の数字をいじって自分だけのルーレットにしてください！！

##GitHub

今回のサンプルプロジェクトをあげておきます。

[GitHub](https://github.com/kaku710/Unity_Roulette_Sample)

少しでも参考になれば嬉しいです。以上です！