Unityでルーレットを作ろう:止まった場所を判定して結果を表示する
プログラミング学習の一環として「回転して止まるルーレット」を作ると、入力処理・回転アニメーション・停止判定・条件分岐といった基本要素を一度に体験できます。今回は Unity の Transform.eulerAngles.z を使って、ルーレットが止まった位置を判定し、結果をデバッグログに出力する仕組みを解説します。
完成コード
using UnityEngine;
public class RouletteController : MonoBehaviour
{
float rotSpeed = 0;
void Start()
{
Application.targetFrameRate = 60;
}
void Update()
{
// クリックでルーレットを回す
if (Input.GetMouseButtonDown(0))
{
rotSpeed = 10;
}
// 回転処理
transform.Rotate(0, 0, rotSpeed);
rotSpeed *= 0.96f; // 徐々に減速
// ↓ 停止直前に結果を判定
if (rotSpeed < 0.1f && rotSpeed > 0f)
{
float angle = transform.eulerAngles.z; // 0〜360°
float a = (angle + 30f) % 360f; // セクション中心に合わせる補正
if (a < 60f) Debug.Log("凶"); // 上(針の直下)
else if (a < 120f) Debug.Log("大吉"); // 右上
else if (a < 180f) Debug.Log("大凶"); // 右下
else if (a < 240f) Debug.Log("小吉"); // 下
else if (a < 300f) Debug.Log("末吉"); // 左下
else Debug.Log("中吉"); // 左上
rotSpeed = 0f; // 判定は一度だけ
}
}
}
1. 一般的に多い書き方
if (rotSpeed < 0.1f && rotSpeed > 0f)
こちらの方が「左から右に読む」自然な流れになり、C# のコードとしてもよく見かけます。教科書やリファレンスでもこの形が標準的に使われることが多いです。
2. 数学的に好まれる書き方
if (0f < rotSpeed && rotSpeed < 0.1f)
こちらは「0 < x < 0.1」のように数学の不等式に近い表現で、範囲指定だと直感的に理解しやすい人もいます。Python などの一部言語はこの形式(チェーン比較)を直接書けるので、数学的な見方に慣れている人にはこちらがスッキリ感じられることもあります。
3. 結論(おすすめ)
- C# の世界では前者(rotSpeed < 0.1f && rotSpeed > 0f)が主流→ 読む人が慣れているので違和感が少ない。
- 数学的に範囲を強調したい場合は後者もアリ→ 授業や説明資料で「0より大きく、0.1未満」と見せたいときに有効。
コードの意味
float a = (angle + 30f) % 360f;
- angle … 現在の回転角度(transform.eulerAngles.z の値、0~360°)。
- + 30f … ルーレットの「針の位置」と「区切り線の位置」をずらす補正。
- % 360f … 360°を超えた角度を「0~360°の範囲」に収めるための剰余演算。
なぜ +30f するのか?
ルーレットは「針の下に止まった位置」で結果を判定したいのですが、
角度の基準(Unityの座標系)と実際の見た目の「区切りの中心」がズレています。
例えば:
- Unityの角度 0° … 上方向
- でも、実際の区切りは「60°ごと」に分かれていて、その境目が 0°にある
このまま判定すると「境界線上で結果がずれる」ので、
区切りの真ん中に合わせるために +30° の補正をしています。
% 360f の役割
Unity の角度は 0°~360°ですが、
計算で 360° を超える値になることがあります。
例:
angle = 350f のとき
angle + 30f = 380f
このままだと判定しづらいので、
% 360 をかけて 0~360°に収める ことで扱いやすくしています。
- 380 % 360 = 20 → 上方向に近い角度、と判定できる。
まとめ
float a = (angle + 30f) % 360f; は
- 30°ずらして「針と区切り」を揃える
- 360°を超えたら 0~360°に戻す
という2つの役割を同時に果たす処理です。
処理の流れ
- クリック入力Input.GetMouseButtonDown(0) で左クリックを検出し、回転速度を rotSpeed = 10 に設定します。
- 回転処理transform.Rotate(0, 0, rotSpeed); で Z 軸を回転。毎フレーム rotSpeed *= 0.96f; で減速させます。
- 停止判定rotSpeed < 0.1f になったら「止まった」と判定し、角度から結果を求めます。
- 角度の正規化transform.eulerAngles.z は常に 0〜360° の値を返します。ただし針の中心に合わせるために +30f の補正を行い、% 360f で 0〜360° に再収めています。
- 結果の条件分岐60°ごとに分割し、判定を if / else if で書き分けることで、結果をログに出力します。
学べるポイント
- 入力処理:Input.GetMouseButtonDown
- 回転アニメーション:transform.Rotate と減速処理
- 角度判定:transform.eulerAngles.z は常に 0〜360° の範囲で返る
- 条件分岐:範囲ごとに結果を割り当てるロジック
これらは Unity でゲームを作る上で非常に基礎的かつ重要な仕組みです。
応用アイデア
- UI 表示Debug.Log ではなく、Text や TMP_Text に結果を表示すれば実用的なルーレットになります。
- ランダムスタート回転開始時に rotSpeed をランダムに設定すれば、毎回違った動きが楽しめます。
- 演出追加SE(効果音)を再生したり、停止時にアニメーションを入れるとゲームらしくなります。
まとめ
- Unity の transform.eulerAngles.z を利用することで、ルーレットの停止角度を簡単に判定できます。
- 判定は オフセット補正 → 60°ごとに分岐 というシンプルな処理で実現できます。
- 入力・回転・停止・判定という一連の流れを作ることで、ゲームの基礎的な仕組みを学べます。
このサンプルを拡張すれば、おみくじアプリ や アイテム抽選ルーレット に発展させられます。
using UnityEngine;
using UnityEngine.Events;
public class RouletteController : MonoBehaviour
{
[Header("回転の設定")]
[SerializeField] float startAngularVel = 360f; // 回し始めの角速度[度/秒]
[SerializeField] float dampingPer60Fps = 0.96f; // 減衰率(60FPS基準)
[SerializeField] float stopThreshold = 5f; // 止まりかけ判定の閾値[度/秒]
[Header("区画の設定")]
[SerializeField] float offsetDeg = 30f; // 針の位置調整オフセット
[SerializeField] string[] labels = // 60°ごとのラベル
{ "凶", "大吉", "大凶", "小吉", "末吉", "中吉" };
[Header("結果イベント")]
public UnityEvent<string> OnResult; // 結果を通知するイベント
float angularVel; // 現在の角速度[度/秒]
bool spinning; // 結果判定を一度だけ行うためのフラグ
void Update()
{
// 入力(マウスクリック or タッチ)で回転開始
if (Input.GetMouseButtonDown(0) || TouchBegan())
{
StartSpin();
}
// 回転処理
if (angularVel > 0f)
{
transform.Rotate(0f, 0f, angularVel * Time.deltaTime);
// 減衰率をΔtに応じて計算
float factor = Mathf.Pow(dampingPer60Fps, Time.deltaTime * 60f);
angularVel *= factor;
}
// 停止直前に一度だけ結果を判定
if (spinning && angularVel > 0f && angularVel <= stopThreshold)
{
string result = ComputeResult();
if (OnResult != null && OnResult.GetPersistentEventCount() > 0)
OnResult.Invoke(result);
else
Debug.Log(result);
spinning = false;
angularVel = 0f; // 完全停止
}
}
void StartSpin()
{
angularVel = startAngularVel;
spinning = true;
}
string ComputeResult()
{
// 回転角度を正規化 + オフセット
float angle = Mathf.Repeat(transform.eulerAngles.z + offsetDeg, 360f);
float sectorWidth = 360f / labels.Length;
int idx = Mathf.FloorToInt(angle / sectorWidth) % labels.Length;
return labels[idx];
}
static bool TouchBegan()
{
if (Input.touchCount <= 0) return false;
var t = Input.GetTouch(0);
return t.phase == TouchPhase.Began;
}
}
ディスカッション
コメント一覧
まだ、コメントがありません