Unityでルーレットの針を物理で揺らす|Rigidbody2DとHingeJoint2Dを使ってみよう

広告

ルーレットが回ると、針(ニードル)が「カタカタ」と弾かれて揺れます。

あの動きは、針の角度を毎フレーム計算しているわけではありません。

Unityの物理エンジンに任せている

だけです。

今回は、その仕組みを2D物理で作ってみます。

今回作るのは、

ルーレットを回すと、ピンに弾かれて針が揺れる

という処理です。

使用環境はUnity 6(2D / URP)で、入力にはInput Systemを使います。

[画像: 完成イメージ(ルーレットと針が揺れているGIFまたはスクリーンショット)]

今回の完成イメージ

ルーレットの外側にはピンが並んでいて、その上に針が下がっています。

      針の根本(軸)
          ●
          │
          │ 針
          ▼
   ○   ○   ○   ○   ← ピン(ルーレットの縁)

ルーレットを回すと、ピンが針に当たります。

クリック
↓
ルーレットが回る
↓
ピンが針に当たる
↓
針が弾かれて揺れる
↓
重力で下に戻る

という動きにします。

まずはルーレットを回してみる

最初は、クリックで回って、だんだん止まるルーレットを作ります。

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を掛ける
↓
少しずつ遅くなる

見た目だけなら、これで完成です。

でも針は揺れない

このルーレットに針を置いても、針は揺れません。

ルーレットが回っているのに、針はただの飾りのままです。

なぜでしょう。

物理で動かすには何が必要?

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.Rotate
↓
向きだけ書き換わる
↓
物理エンジンは「ピンがぶつかってきた」と気づけない

物理エンジンから見ると、ピンが何も言わずにワープしているようなものです。

そこで、ルーレットに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
↓
ピンに当たる部分(針の先端側にずらしておく)

Rigidbody2D
↓
Body Typeは Dynamic

[画像: 針のInspector(Sprite Renderer、Circle Collider 2D、Rigidbody2D)]

位置を固定すれば軸になる?

ここで、こう考えたくなります。

針が飛んでいかないように、位置を固定しよう

そこで、Rigidbody2D の Constraints で、

Freeze Position X
Freeze Position Y

にチェックを入れてみます。

でも先端を中心にくるくる回る

実際に動かすと、針は先端を中心にくるくる回ってしまいます。

なぜでしょう。

Rigidbody2Dは、

重心(コライダーの位置)を中心に回る

からです。

今回のコライダーは針の先端側にあります。

針の根本
  ●
  │
  │
  ▼
 [○] ← コライダー(ここが重心)

位置を固定すると、この重心が動かない点になります。

位置を固定
↓
重心(先端側)が動かない
↓
先端を中心に回る

つまり、

位置の固定では、根本を軸にすることはできません。

HingeJoint2Dで軸を決める

そこで使うのが Hinge Joint 2D です。

ヒンジとは蝶番のことです。ドアが蝶番を軸にして開閉するように、

ここを軸にして回ってね

と決める部品です。

Freeze Position のチェックは外して、針に Hinge Joint 2D を追加します。

設定値意味
Connected Rigid Body空ワールドに固定される
Anchor(0, 0)針のTransformの位置が軸になる
Auto Configure Connected Anchorオン軸の位置を自動で合わせる

[画像: HingeJoint2DのInspector]

ポイントは、

針のTransformの位置を、針の根本に置いておく

ことです。

Anchor が (0, 0) なら、そのTransformの位置がそのまま軸になります。

これで、

根本を軸にして
↓
重力で下に戻りながら
↓
ピンに弾かれて揺れる

動きになります。

揺れ方を調整する

動くようになったら、数値を変えて好みの動きに近づけます。

項目場所効果
Angular DampingRigidbody2D大きいほど、揺れがすぐ収まる
MassRigidbody2D大きいほど、弾かれにくくなる
Use LimitsHingeJoint2D振れる角度の範囲を制限できる
Lower Angle / Upper AngleHingeJoint2D例: -40 〜 40 度

目安は次のとおりです。

軽く回りすぎる
↓
Angular Dampingを3〜5に上げる
Use Limitsで角度を制限する

動きが鈍い
↓
Angular Dampingを1〜2に下げる

正解の数値はありません。

再生しながら少しずつ値を変えて、ちょうどよい動きを探してみてください。

速く回すと軸がぶれる

ルーレットを激しく回すと、針の軸がぶれることがあります。

ピンの動きが速すぎて、物理の計算が追いつかなくなるためです。

そんなときは、物理の精度を上げます。

設定場所例
Velocity IterationsPhysics 2D8 → 16
Position IterationsPhysics 2D3 → 10
Fixed TimestepTime0.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のまま、物理エンジン経由で回す

ことで、針に衝突を伝えています。

そして針は、位置を固定するのではなく、

HingeJoint2Dで軸を決める

ことで、根本を中心に揺れるようになります。

一言で整理すると、

ルーレット
↓
Kinematic + angularVelocity
↓
物理の世界で回す

針
↓
Dynamic + HingeJoint2D
↓
軸を決めて、あとは物理に任せる

です。

この考え方は、ルーレットだけでなく、ドア、振り子、シーソーなど、

軸を中心に動くもの

にそのまま使えます。

関連記事:Unityでキャラクターが動く床に乗ると一緒に動く仕組みガイドでは、Kinematic のRigidbody2Dで床を動かす方法と、FixedJoint2Dによる物理的な接続を解説しています。

関連記事:Unity 6 ゲーム制作 補足解説シリーズまとめ ― 6つのサンプルゲームで学ぶ全10本(PC版)では、ルーレットを含むサンプルゲームの補足解説をまとめています。

訪問数 1 回, 今日の訪問数 1回

広告

Physics,Unity

Posted by hidepon