今回はUnityで爆速でルーレットを作ったので手順を記述します!
##ルーレット作成方針(ざっくり)
・白い円の画像を1枚用意する
・選択肢の数だけ白い円の画像が貼られたImageを生成して、それぞれ色を変える
・ImageのImageTypeをFilledにし、Fill Amountを割合ごとに変える
・それぞれにTextを配置する
ざっくりですがこんな感じで作ろうと思います。他に何か方法あれば教えてください!
##作成手順
まずはルーレットの画像のPrefabを用意します。
1: 背景が透明の白い円の画像をUnityにImportし、TextureTypeをSpriteに変更したら、Imageに貼り付けます。
2: ImageのImage TypeをFilledにし、
Fill MethodをRadial360に、
Fill OriginをTopにしておきます。
ついでにRaycastTargetもチェック外しちゃいましょう。
3: Imageの子供に、空のゲームオブジェクトを配置します。
Textを回転させるためにあとで使います。名前をRotateTextとしておきます。
↑もしTransformのPositionやRotationが0以外になってたら0にしておいてください!
4: RotateTextの子供に、Textを配置します。
今回、Textを縦書きで使いたかったので、以下の記事のRotateText.csをお借りしました!
Textを縦書きにできたら、中央の上の方に配置してください!
こんな感じです!5: ここまでできたら、このImageをPrefab化します!
↑こんな感じの階層になってるはずです
###これで準備完了です!
6: Canvasの配下にRouletteという名前の空のゲームオブジェクトを配置します。
また、ここでも必ずTransformのPositionとRotationを0にしておいてください!
7: 以下のスクリプト(RouletteMaker.cs)を作成して、空のゲームオブジェクトにAddします。
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で必要な項目を設定します。
画像の通りImageParentTransformとRouletteImageの項目にオブジェクトをアタッチしてあげ、Choicesにルーレットの選択肢、
またRouletteColorsにそれぞれの選択肢の色を指定しましょう。
またこの時、InspectorでColorを指定する時デフォルトでalpha値が0になってしまっているかと思いますので、直してあげてください。(alpha値が0だと透明になってしまいます)
↑再生したらきちんとルーレットが生成されるかと思います!
##作成したルーレットを回して判定
さて、ルーレットが作れたのであとは回して判定するだけです。
Canvas配下に、3つのボタン
・StartButton
・StopButton
・RetryButton
それと、結果を表示するようのText(ResultText)を作成し、
また矢印の画像も良い感じの位置に配置しちゃいましょう。
↑Canvas配下はこんな感じ。
ここまでできたら以下のスクリプト(RouletteController.cs)を作成して、空のゲームオブジェクトにAddします。
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を、
以下のように編集します。
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で必要なオブジェクトをアタッチしてあげてください!
できたら再生して確認してください。
止まった場所の文字がResultText内に表示されるかと思います!
以上です!
##おまけ 露骨なイカサマルーレット
おまけと書いてますが、今回自分はこれが作りたくてルーレットをUnityで作りました笑
どこで止めても絶対に指定した場所まで最後にぶっ飛ぶ、、というルーレットです笑
指定した場所に自然に止まったらそのままです。
(露骨すぎますが盛り上がれば良し。)
###実装方法
神アセットDoTweenを拝借して、ルーレットの速度が遅くなったらDoRotateでルーレットをぶん回すという感じです。
もし、**自然な動きのイカサマルーレットが作りたい!**という方がいれば、まだルーレットの速度が速い時にRotateすれば良いのではないでしょうか!!
DoTweenをAssetStoreからProjectにimportして、
RouletteController.csを以下のように書き換えます。
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から始まるので注意)
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
今回のサンプルプロジェクトをあげておきます。
少しでも参考になれば嬉しいです。以上です!