Unityでルーレットの針を物理で揺らす|Rigidbody2DとHingeJoint2Dを使ってみよう
ルーレットが回ると、針(ニードル)が「カタカタ」と弾かれて揺れます。
あの動きは、針の角度を毎フレーム計算しているわけではありません。
Unityの物理エンジンに任せている
だけです。
今回は、その仕組みを2D物理で作ってみます。
今回作るのは、
ルーレットを回すと、ピンに弾かれて針が揺れる
という処理です。
使用環境はUnity 6(2D / URP)で、入力にはInput Systemを使います。

今回の完成イメージ
ルーレットの外側にはピンが並んでいて、その上に針が下がっています。
針の根本(軸)
●
│
│ 針
▼
○ ○ ○ ○ ← ピン(ルーレットの縁)
ルーレットを回すと、ピンが針に当たります。
クリック
↓
ルーレットが回る
↓
ピンが針に当たる
↓
針が弾かれて揺れる
↓
重力で下に戻る
という動きにします。
まずはルーレットを回してみる
最初は、クリックで回って、だんだん止まるルーレットを作ります。
using UnityEngine;
using UnityEngine.InputSystem;
public class RouletteController : MonoBehaviour
{
float rotSpeed = 0;
void Start()
{
Application.targetFrameRate = 60;
}
void Update()
{
if (Mouse.current.leftButton.wasPressedThisFrame)
{
this.rotSpeed = 3;
}
transform.Rotate(0, 0, this.rotSpeed);
this.rotSpeed *= 0.96f;
}
}
やっていることは3つです。
クリック
↓
rotSpeedを3にする
毎フレーム
↓
rotSpeed度ずつ回す
rotSpeedに0.96を掛ける
↓
少しずつ遅くなる
見た目だけなら、これで完成です。
なお、0.96を毎フレーム掛ける減速は、フレームレートによって止まるまでの時間が変わります。今回は60fpsに固定しているので、このまま使います。
でも針は揺れない
このルーレットに針を置いても、針は揺れません。
ルーレットが回っているのに、針はただの飾りのままです。
なぜでしょう。
物理で動かすには何が必要?
Unityの物理エンジンに動きを任せるには、2つの部品が必要です。
Collider2D
↓
物の形(当たり判定)
Rigidbody2D
↓
物理エンジンの管理下に置く
たとえるなら、
Collider2D
↓
体の大きさ
Rigidbody2D
↓
物理の世界に参加するための参加証
です。
ここで大事なのは、
Collider2Dだけの物体は、動かない壁のように扱われる
ということです。
重力で落ちたり、ぶつかって動いたりさせたい物体には、Rigidbody2Dが必要です。
Body Typeは3種類ある
Rigidbody2Dには Body Type という設定があります。
| Body Type | 動き |
|---|---|
| Dynamic | 重力や衝突で動く |
| Kinematic | スクリプトで決めた動きだけをする。ぶつかっても押し返されない |
| Static | 動かない |
今回の役割分担は次のとおりです。
ルーレット
↓
Kinematic
↓
自分で決めた通りに回り続ける
針
↓
Dynamic
↓
ぶつかられて揺れて、重力で戻る
でもtransform.Rotateでは安定して弾かれない
ルーレットのピンに、小さな Circle Collider 2D を付けたとします。
それでも、先ほどのスクリプトでは、針がうまく弾かれません。
原因は、
transform.Rotate(0, 0, this.rotSpeed);
です。
transform.Rotate は、向きを直接書き換えます。Transformの変更は、物理シミュレーションの直前に物理エンジンへ同期されます。ただし、これは回転速度を持った動きではなく、瞬間的な向きの書き換えです。
transform.Rotate
↓
向きだけが瞬間的に書き換わる
↓
物理的な速度を持たない
↓
衝突の反応が不安定になる
物理エンジンから見ると、ピンが瞬間移動を繰り返しているようなものです。そのため、針に伝わる衝突の反応が不安定になります。
そこで、ルーレットにRigidbody2Dを付けて、
Rigidbody2Dの回転速度で回す
ようにします。
ルーレットとピンの構成を決める
今回は、次のような構成にします。
Wheel
├ Rigidbody2D(Kinematic)
├ RouletteController
├ Pin(Circle Collider 2D)
├ Pin(Circle Collider 2D)
└ …
Needle
├ Rigidbody2D(Dynamic)
├ Circle Collider 2D
└ Hinge Joint 2D
ポイントは3つです。
各Pinには、別のRigidbody2Dを付けない
↓
子のCollider2Dは、親(Wheel)のRigidbody2Dに属する
Needleは、Wheelの子にしない
↓
ルーレットの回転に巻き込まれてしまうため
こうすると、WheelのRigidbody2Dが回るのに合わせて、すべてのピンが一緒に動きます。
ルーレットをKinematicにして回す
ルーレットに Rigidbody2D を付けて、Body Type を Kinematic にします。
スクリプトは次のようになります。
using UnityEngine;
using UnityEngine.InputSystem;
public class RouletteController : MonoBehaviour
{
float rotSpeed = 0;
Rigidbody2D rb;
void Awake()
{
rb = GetComponent<Rigidbody2D>();
if (rb == null)
{
rb = gameObject.AddComponent<Rigidbody2D>();
}
rb.bodyType = RigidbodyType2D.Kinematic;
rb.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
}
void Start()
{
Application.targetFrameRate = 60;
}
void Update()
{
if (Mouse.current.leftButton.wasPressedThisFrame)
{
this.rotSpeed = 3;
}
this.rotSpeed *= 0.96f;
}
void FixedUpdate()
{
// 1フレーム(60fps)あたり rotSpeed 度 → 1秒あたりの角度に換算
rb.angularVelocity = this.rotSpeed * 60f;
}
}
コードを順番に追ってみよう
変わったところは3つです。
回し方を変えた
transform.Rotate(0, 0, this.rotSpeed);
をやめて、
rb.angularVelocity = this.rotSpeed * 60f;
にしました。
angularVelocity は、
1秒あたりに回る角度
です。
もともとの rotSpeed は「1フレームあたり何度回すか」でした。60fpsなら1秒に60フレームなので、60を掛けて1秒あたりの角度に直しています。
rotSpeed
↓
1フレームあたりの角度
rotSpeed × 60
↓
1秒あたりの角度
物理の処理はFixedUpdateに書く
回転を設定する処理は、Update ではなく FixedUpdate に書いています。
Update
↓
フレームごとに呼ばれる
FixedUpdate
↓
物理エンジンと同じ間隔で呼ばれる
物理に関わる動きは、物理エンジンと同じタイミングで決めるのが基本です。
なお、クリックの判定と rotSpeed の減速は、これまでどおり Update に残しています。
衝突判定をContinuousにした
rb.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
ルーレットが速く回ると、ピンが一瞬で動いてしまい、針をすり抜けることがあります。
Continuous にすると、動いている途中の衝突も調べるので、すり抜けにくくなります。
針を用意する
次は針です。針には次のコンポーネントを付けます。
Sprite Renderer
↓
見た目
Circle Collider 2D
↓
ピンに当たる部分
Offsetで、根本(軸)より下の先端側にずらす
Is Triggerはオフ
Rigidbody2D
↓
Body Typeは Dynamic
Gravity Scaleは 1
Simulatedはオン
Freeze Rotation Zはオフ
ここで大事なのは、コライダーの位置を、根本より下にずらしておくことです。
コライダーの中心が根本(軸)と同じ位置にあると、重力で下に戻る回転力が生まれません。針が揺れたまま戻ってこないときは、Offsetを確認してください。
位置を固定すれば軸になる?
ここで、こう考えたくなります。
針が飛んでいかないように、位置を固定しよう
そこで、Rigidbody2D の Constraints で、
Freeze Position X
Freeze Position Y
にチェックを入れてみます。
でも先端を中心にくるくる回る
実際に動かすと、針は先端を中心にくるくる回ってしまいます。
なぜでしょう。
Rigidbody2Dは、
重心を中心に回る
からです。
今回のように、円形のコライダーが1つだけの場合、重心はコライダーの中心になります。
今回のコライダーは針の先端側にあります。
針の根本
●
│
│
▼
[○] ← コライダー(ここが重心)
位置を固定すると、この重心が動かない点になります。
位置を固定
↓
重心(先端側)が動かない
↓
先端を中心に回る
つまり、
位置の固定では、根本を軸にすることはできません。
HingeJoint2Dで軸を決める
そこで使うのが Hinge Joint 2D です。
ヒンジとは蝶番のことです。ドアが蝶番を軸にして開閉するように、
ここを軸にして回ってね
と決める部品です。
Freeze Position のチェックは外して、針に Hinge Joint 2D を追加します。
| 設定 | 値 | 意味 |
|---|---|---|
| Connected Rigid Body | 空 | ワールドに固定される |
| Anchor | (0, 0) | 針のTransformの位置が軸になる |
| Auto Configure Connected Anchor | オン | 軸の位置を自動で合わせる |

ポイントは、
針のTransformの位置を、針の根本に置いておく
ことです。
Anchor が (0, 0) なら、そのTransformの位置がそのまま軸になります。
これで、
根本を軸にして
↓
重力で下に戻りながら
↓
ピンに弾かれて揺れる
動きになります。
揺れ方を調整する
動くようになったら、数値を変えて好みの動きに近づけます。
| 項目 | 場所 | 効果 |
|---|---|---|
| Angular Damping | Rigidbody2D | 大きいほど、揺れがすぐ収まる |
| Use Limits | HingeJoint2D | 振れる角度の範囲を制限できる(基本はオフ。調整するときだけオン) |
| Lower Angle / Upper Angle | HingeJoint2D | 例: -40 〜 40 度 |
目安は次のとおりです。
軽く回りすぎる
↓
Angular Dampingを3〜5に上げる
Use Limitsで角度を制限する
動きが鈍い
↓
Angular Dampingを1〜2に下げる
正解の数値はありません。
再生しながら少しずつ値を変えて、ちょうどよい動きを探してみてください。
速く回すと軸がぶれる
ルーレットを激しく回すと、針の軸がぶれることがあります。
ピンの動きが速すぎて、物理の計算が追いつかなくなるためです。
そんなときは、物理の精度を上げます。
| 設定 | 場所 | 例 |
|---|---|---|
| Velocity Iterations | Physics 2D | 8 → 16 |
| Position Iterations | Physics 2D | 3 → 10 |
| Fixed Timestep | Time | 0.02 → 0.01 |
Iterationsを増やす
↓
衝突や関節のズレの補正が丁寧になる
Fixed Timestepを小さくする
↓
物理の計算回数が増える
↓
すり抜けにくくなる
その分、処理は少し重くなります。設定を変えたあとは、実行して負荷を確認してください。
それでもぶれる場合は、
クリック時のrotSpeedを下げる
針のコライダー半径を少し大きくする
を試してみてください。
なお、Physics 2Dの設定項目の場所や名前は、Unityのバージョンによって変わることがあります。見つからない場合は、Project Settingsの検索欄で「Iterations」と入力すると探せます。
ここまでで学んだものを整理する
この小さなルーレットには、2D物理の基本がまとまっています。
Collider2D
↓
物の形(当たり判定)
Rigidbody2D
↓
物理エンジンの管理下に置く
Body Type: Kinematic
↓
決めた通りに動く側(ルーレット)
Body Type: Dynamic
↓
動かされる側(針)
FixedUpdate
↓
物理の処理を書く場所
angularVelocity
↓
1秒あたりの回転角度
HingeJoint2D
↓
回転の軸を決める
Angular Damping / Mass / Use Limits
↓
揺れ方の調整
まとめ
今回の重要な部分は、
void FixedUpdate()
{
rb.angularVelocity = this.rotSpeed * 60f;
}
と、針に付けた
Hinge Joint 2D
です。
ルーレットは transform.Rotate ではなく、
KinematicのRigidbody2Dに回転速度を設定して回す
ことで、針に安定して衝突を伝えています。
そして針は、位置を固定するのではなく、
HingeJoint2Dで軸を決める
ことで、根本を中心に揺れるようになります。
一言で整理すると、
ルーレット
↓
Kinematic + angularVelocity
↓
物理の世界で回す
針
↓
Dynamic + HingeJoint2D
↓
軸を決めて、あとは物理に任せる
です。
この考え方は、ルーレットだけでなく、ドア、振り子、シーソーなど、
軸を中心に動くもの
にそのまま使えます。
関連記事:Unityでキャラクターが動く床に乗ると一緒に動く仕組みガイドでは、Kinematic のRigidbody2Dで床を動かす方法と、FixedJoint2Dによる物理的な接続を解説しています。
関連記事:Unity 6 ゲーム制作 補足解説シリーズまとめ ― 6つのサンプルゲームで学ぶ全10本(PC版)では、ルーレットを含むサンプルゲームの補足解説をまとめています。









ディスカッション
コメント一覧
まだ、コメントがありません