
エディタ拡張を使用してゲーム実行前に設置出来る、カスタマイズ可能な3Dルーレット。
2D版と違い、ベース部分を可変に出来ないので、画像素材や3Dモデルを多めに用意。
2、3、4、5、6、8、10、12ピースに対応。
2D(UI)版はこちら↓
サンプル動画
- 左側: ワールド空間に2DルーレットVer
 - 右側: 3DモデルVer
 
各パターンの設置&動作テスト
ルーレット結果の精度テスト
画像&3Dモデル素材
- ライセンス: CC0
 
画像の設定
- Projectウィンドウの任意のフォルダに、ドラッグ&ドロップしてインポート。
 - 対象画像を選択。
 - インスペクター -> Texture TypeをSprite (2D and UI)に変更。
 - Apply
 
コード
RouletteManager (ワールド空間に2Dのルーレットを設置Ver)
- 要素数を決めて、それに応じたスプライトを紐付けしておく。
 - (要素数が6以上の場合は)色配列と、文字配列を追加で設定。
 - 各種設定を任意に調整。
 - インスペクター最下部のCreateをクリック。
 
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class RouletteManager : MonoBehaviour
{
	[HideInInspector]
	[SerializeField]
	Transform tf;
	[HideInInspector]
	[SerializeField]
	Transform elementsTf;
	[Header("ルーレットの1ピースの画像を紐付け")]
	[SerializeField]
	Sprite elementSprite;
	[Header("ルーレットの針の画像を紐付け")]
	[SerializeField]
	Sprite clapperSprite;
	[Header("ルーレットの要素数")]
	[SerializeField]
	[Range(2, 12)]
	int elementCount = 6;
	[Header("ルーレットの要素の色を指定 (要素数と配列数を同じにしてください)")]
	[SerializeField]
	Color[] elementColors = {
		new Color32(255, 0, 0, 255),
		new Color32(255, 128, 0, 255),
		new Color32(255, 255, 0, 255),
		new Color32(128, 255, 0, 255),
		new Color32(0, 255, 128, 255),
		new Color32(0, 255, 255, 255),
	};
	[Header("ルーレットのサイズ (Scale)")]
	[SerializeField]
	[Range(0.1f, 10.0f)]
	float rouletteSize = 1.0f;
	[Header("ルーレットの針のサイズ (Scale)")]
	[SerializeField]
	[Range(0.03f, 3.0f)]
	float clapperSize = 0.2f;
//(マイナスで逆回転)
	[Header("回転するスピード (秒速)")]
	[SerializeField]
	[Range(-1080, 1080)]
	float spinSpeed = 360;
	[HideInInspector]
	[SerializeField]
	float elementRotationToAdd;
//コルーチン管理用。
	Coroutine spin;
	[Header("結果のインデックス (elementCountが6の場合、0~5)")]
	public int resultElementIndex;
	[Header("(回転終了時の)慣性の長さ (秒)")]
	[SerializeField]
	[Range(0.1f, 10.0f)]
	float inertiaDuration = 0.5f;
	float inertiaElapsedTime;
	[Header("ルーレットを設置するオブジェクトの半分の厚み")]
	[SerializeField]
	[Range(0.1f, 5.0f)]
	float parentHalfDepth = 0.5f;
//要素に入れる文字関係。
//フォントを指定しない場合はデフォルトの物が使われる。
	[Header("文字のフォント")]
	[SerializeField]
	TMP_FontAsset font;
	[Header("要素毎に入れる文字のサイズ (Scale)")]
	[SerializeField]
	[Range(0.01f, 1.0f)]
	float fontSize = 0.1f;
	[Header("文字の色")]
	[SerializeField]
	Color tmpColor = new Color32(255, 255, 255, 255);
	[Header("各要素に表示するテキスト (要素数と配列数を同じにしてください)")]
	[SerializeField]
	string[] tmpTexts = new string[] {
		"0",
		"1",
		"2",
		"3",
		"4",
		"5",
	};
//ルーレット生成時に一時的に使用。
	GameObject elementGo;
	Transform elementTf;
	GameObject elementSrGo;
	Transform elementSrTf;
	SpriteRenderer elementSr;
	GameObject elementTmpGo;
	Transform elementTmpTf;
	TextMeshPro elementTmp;
	Vector2 elementSpriteSize;
	Vector2 clapperSpriteSize;
	void Awake()
	{
		if (tf == null)
			tf = transform;
		if (tf.childCount == 0)
			CreateRoulette();
	}
//ルーレットの回転をスタートしたい時にコレを呼ぶ。
	public void StartSpin()
	{
//一応、多重実行を防止。
		if (spin != null)
			StopCoroutine(spin);
		spin = StartCoroutine(Spin());
	}
	IEnumerator Spin()
	{
		while (true) {
			elementsTf.Rotate(Vector3.forward * spinSpeed * Time.deltaTime);
//画面をタップ(クリック)で、徐々に停止。
			if (Input.GetMouseButtonDown(0)) {
				inertiaElapsedTime = 0;
				while (true) {
					inertiaElapsedTime += Time.deltaTime;
					elementsTf.Rotate(Vector3.forward * Mathf.Lerp(spinSpeed, 0, inertiaElapsedTime / inertiaDuration) * Time.deltaTime);
					if (inertiaDuration <= inertiaElapsedTime) {
						resultElementIndex = Mathf.FloorToInt(elementsTf.eulerAngles.z / elementRotationToAdd % elementCount);
//ここでresultElementIndexに応じた処理を入れる。
//止まった領域の文字はtmpTexts[resultElementIndex]で取得可能。
						spin = null;
						yield break;
					}
					yield return null;
				}
			}
			yield return null;
		}
	}
	public void CreateRoulette()
	{
		if (elementSprite == null || clapperSprite == null) {
			print("インスペクターから各Spriteを紐付けしてください!");
			return;
		}
		if (tf == null)
			tf = transform;
		elementRotationToAdd = 360.0f / elementCount;
		elementSpriteSize = elementSprite.bounds.size;
		clapperSpriteSize = clapperSprite.bounds.size;
		elementGo = new GameObject("RouletteElements");
		elementsTf = elementGo.transform;
		elementsTf.SetParent(tf);
		elementsTf.localPosition = new Vector3(0, 0, -parentHalfDepth - 0.01f);
		for (int i = 0; i < elementCount; i++) {
			elementGo = new GameObject(System.String.Format("RouletteElement{0}", i + 1));
			elementTf = elementGo.transform;
			elementTf.SetParent(elementsTf);
			elementTf.localPosition = Vector3.zero;
			elementSrGo = new GameObject(System.String.Format("Sr{0}", i + 1));
			elementSrTf = elementSrGo.transform;
			elementSrTf.SetParent(elementTf);
			elementSrTf.localScale = new Vector3(rouletteSize / 2 * (1.0f / elementSpriteSize.y), rouletteSize / 2 * (1.0f / elementSpriteSize.y), 1.0f);
			elementSrTf.localPosition = new Vector3(0, rouletteSize / 4, 0);
			elementSr = elementSrGo.AddComponent<SpriteRenderer>();
			elementSr.sprite = elementSprite;
			if (i < elementColors.Length)
				elementSr.color = elementColors[i];
			elementTmpGo = new GameObject(System.String.Format("Tmp{0}", i + 1));
			elementTmpTf = elementTmpGo.transform;
			elementTmpTf.SetParent(elementTf);
			elementTmpTf.localPosition = new Vector3(0, rouletteSize / 4 + fontSize / 2, -0.01f);
			elementTmp = elementTmpGo.AddComponent<TextMeshPro>();
			if (font != null)
				elementTmp.font = font;
//大体フォントサイズ10で、Scale1程度の大きさになった。
			elementTmp.fontSize = fontSize * 10.0f;
			elementTmp.enableWordWrapping = false;
			elementTmp.alignment = TextAlignmentOptions.Center;
//一応、リッチテキストを無効化している(使う場合は戻してください)。
			elementTmp.richText = false;
			elementTmp.sortingOrder = 1;
			if (i < tmpTexts.Length)
				elementTmp.text = tmpTexts[i];
			elementTmp.color = tmpColor;
			elementTf.Rotate(Vector3.back * (elementRotationToAdd / 2 + elementRotationToAdd * i));
		}
		elementGo = new GameObject("Clapper");
		elementTf = elementGo.transform;
		elementTf.SetParent(tf);
		elementTf.localScale = new Vector3(clapperSize * (1.0f / clapperSpriteSize.y), clapperSize * (1.0f / clapperSpriteSize.y), 1.0f);
		elementTf.localPosition = new Vector3(0, rouletteSize / 2, -parentHalfDepth - 0.02f);
		elementSr = elementGo.AddComponent<SpriteRenderer>();
		elementSr.sprite = clapperSprite;
		elementSr.sortingOrder = 2;
	}
}
RouletteManager (3DモデルVer)
- 要素数を決めて、それに応じた3Dモデルを紐付けしておく。
 - (要素数が6以上の場合は)色配列と、文字配列を追加で設定。
 - 各種設定を任意に調整。
 - インスペクター最下部のCreateをクリック。
 
using System.Collections;
using UnityEngine;
using TMPro;
public class RouletteManager : MonoBehaviour
{
	[HideInInspector]
	[SerializeField]
	Transform tf;
	[HideInInspector]
	[SerializeField]
	Transform elementsTf;
	[Header("ルーレットの3Dモデルを紐付け")]
	[SerializeField]
	GameObject rouletteModelGo;
	[Header("ルーレットの針の3Dモデルを紐付け")]
	[SerializeField]
	GameObject clapperModelGo;
	[Header("ルーレットの要素数")]
	[SerializeField]
	[Range(2, 12)]
	int elementCount = 6;
	[Header("ルーレットの要素の色を指定 (要素数と配列数を同じにしてください)")]
	[SerializeField]
	Color[] elementColors = {
		new Color32(255, 0, 0, 255),
		new Color32(255, 128, 0, 255),
		new Color32(255, 255, 0, 255),
		new Color32(128, 255, 0, 255),
		new Color32(0, 255, 128, 255),
		new Color32(0, 255, 255, 255),
	};
	[Header("ルーレットのサイズ (Scale)")]
	[SerializeField]
	[Range(0.1f, 10.0f)]
	float rouletteSize = 1.0f;
	[Header("回転するスピード (秒速)")]
	[SerializeField]
	[Range(-1080, 1080)]
	float spinSpeed = 360;
	[HideInInspector]
	[SerializeField]
	float elementRotationToAdd;
//コルーチン管理用。
	Coroutine spin;
	[Header("結果のインデックス (elementCountが6の場合、0~5)")]
	public int resultElementIndex;
	[Header("(回転終了時の)慣性の長さ (秒)")]
	[SerializeField]
	[Range(0.1f, 10.0f)]
	float inertiaDuration = 0.5f;
	float inertiaElapsedTime;
	[Header("ルーレットを設置するオブジェクトの半分の厚み")]
	[SerializeField]
	[Range(0.1f, 5.0f)]
	float parentHalfDepth = 0.5f;
//要素に入れる文字関係。
//フォントを指定しない場合はデフォルトの物が使われる。
	[Header("文字のフォント")]
	[SerializeField]
	TMP_FontAsset font;
	[Header("要素毎に入れる文字のサイズ (Scale)")]
	[SerializeField]
	[Range(0.01f, 1.0f)]
	float fontSize = 0.1f;
	[Header("文字の色")]
	[SerializeField]
	Color tmpColor = new Color32(255, 255, 255, 255);
	[Header("各要素に表示するテキスト (要素数と配列数を同じにしてください)")]
	[SerializeField]
	string[] tmpTexts = new string[] {
		"0",
		"1",
		"2",
		"3",
		"4",
		"5",
	};
//ルーレットの向き(インスペクターからは指定し辛いので、コードで指定)。
	Quaternion rouletteRotation = Quaternion.Euler(-90, 0, 0);
//ルーレット生成時に一時的に使用。
	GameObject elementGo;
	Transform elementTf;
	GameObject elementTmpGo;
	Transform elementTmpTf;
	TextMeshPro elementTmp;
	MeshRenderer[] elementMrs;
	Material[] elementMaterials;
	void Awake()
	{
		if (tf == null)
			tf = transform;
		if (tf.childCount == 0)
			CreateRoulette();
	}
//ルーレットの回転をスタートしたい時にコレを呼ぶ。
	public void StartSpin()
	{
//一応、多重実行を防止。
		if (spin != null)
			StopCoroutine(spin);
		spin = StartCoroutine(Spin());
	}
	IEnumerator Spin()
	{
		while (true) {
			elementsTf.Rotate(Vector3.forward * spinSpeed * Time.deltaTime);
//画面をタップ(クリック)で、徐々に停止。
			if (Input.GetMouseButtonDown(0)) {
				inertiaElapsedTime = 0;
				while (true) {
					inertiaElapsedTime += Time.deltaTime;
					elementsTf.Rotate(Vector3.forward * Mathf.Lerp(spinSpeed, 0, inertiaElapsedTime / inertiaDuration) * Time.deltaTime);
					if (inertiaDuration <= inertiaElapsedTime) {
						resultElementIndex = Mathf.FloorToInt(elementsTf.eulerAngles.z / elementRotationToAdd % elementCount);
//ここでresultElementIndexに応じた処理を入れる。
//止まった領域の文字はtmpTexts[resultElementIndex]で取得可能。
						spin = null;
						yield break;
					}
					yield return null;
				}
			}
			yield return null;
		}
	}
	public void CreateRoulette()
	{
		if (rouletteModelGo == null || clapperModelGo == null) {
			print("インスペクターから各3Dモデルを紐付けしてください!");
			return;
		}
		if (tf == null)
			tf = transform;
		elementRotationToAdd = 360.0f / elementCount;
		elementGo = new GameObject("RouletteElements");
		elementsTf = elementGo.transform;
		elementsTf.SetParent(tf);
		elementsTf.localPosition = new Vector3(0, 0, -parentHalfDepth);
		elementGo = Instantiate(rouletteModelGo, elementsTf);
		elementTf = elementGo.transform;
		elementTf.rotation = rouletteRotation;
		elementTf.localScale = Vector3.one * rouletteSize;
		elementTf.localPosition = Vector3.zero;
		elementMrs = elementsTf.GetComponentsInChildren<MeshRenderer>();
		elementMaterials = new Material[elementCount];
		for (int i = 0; i < elementCount; i++) {
			if (i < elementColors.Length) {
				elementMaterials[i] = new Material(Shader.Find("Standard"));
				elementMaterials[i].color = elementColors[i];
				elementMrs[i].material = elementMaterials[i];
			}
			elementTmpGo = new GameObject(System.String.Format("Tmp{0}", i + 1));
			elementTmpTf = elementTmpGo.transform;
			elementTmpTf.SetParent(elementsTf);
			elementTmpTf.localPosition = new Vector3(0, rouletteSize / 4 + fontSize / 2, -0.06f * rouletteSize);
//平面Verと違って、文字をピースとまとめて回転出来ないので、文字のみをRotateAroundでズラす。
			elementTmpTf.RotateAround(elementsTf.position, Vector3.back, elementRotationToAdd / 2 + elementRotationToAdd * i);
			elementTmp = elementTmpGo.AddComponent<TextMeshPro>();
			if (font != null)
				elementTmp.font = font;
//大体フォントサイズ10で、Scale1程度の大きさになった。
			elementTmp.fontSize = fontSize * 10.0f;
			elementTmp.enableWordWrapping = false;
			elementTmp.alignment = TextAlignmentOptions.Center;
//一応、リッチテキストを無効化している(使う場合は戻してください)。
			elementTmp.richText = false;
			elementTmp.sortingOrder = 1;
			if (i < tmpTexts.Length)
				elementTmp.text = tmpTexts[i];
			elementTmp.color = tmpColor;
		}
		elementGo = Instantiate(clapperModelGo, tf);
		elementTf = elementGo.transform;
		elementTf.rotation = rouletteRotation;
		elementTf.localScale = Vector3.one * rouletteSize;
		elementTf.localPosition = new Vector3(0, rouletteSize / 2, -parentHalfDepth);
	}
}
RouletteManagerEditor (インスペクターに生成ボタンを出す為のエディタ拡張)
- 両パターン共通で必須。
 - Editorフォルダ下に入れておく。
(無い場合は新規作成) 
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(RouletteManager))]
public class RouletteManagerEditor : Editor
{
	RouletteManager rouletteManager;
	public override void OnInspectorGUI()
	{
		base.OnInspectorGUI();
		if (rouletteManager == null)
			rouletteManager = target as RouletteManager;
		if (GUILayout.Button("Create")){
			rouletteManager.CreateRoulette();
		}
	}
}
使用手順
- (2D版、2.5D版、3D版で共通)
 
- GameManager等で、RouletteManagerへの参照を保持しておく。
 - 回転をスタートさせたい時に、RouletteManagerのStartSpinを呼ぶ。
 - 画面タップで徐々に回転が停止。
(Spinコルーチンの終了部分へ、結果に応じた処理を追加しておく) 
影が汚い場合
影をなくすパターン
- ルーレットの3Dモデルを選択。
 - Mesh Rendererコンポーネント -> shadowCastingModeをOffに設定。
 
グラフィックの設定を変更するパターン
- Project Settings -> Quality -> Shadow Distanceを低めに調整。
 



