クイックノート

ちょっとした発見・アイデアから知識の発掘を

Unity で万有引力を実装してみる

Unityではデフォルトで重力による運動をシミュレートすることができますが、
あくまで地面に向かって一方向的かつ一定の大きさの力に限定されています。

惑星の動きなどをシミュレーションするには、
惑星の位置関係によって、
重力の働く向きや大きさが変わる状況を、
シミュレーションする必要があります。

万有引力の実装は検索すればいくつもヒットするのですが、
引力が働く物体をスクリプト内で明示的に指定するものばかりでした。
これだと、複数の物体間の万有引力を記述するのが面倒そうです。

Unityではコンポーネントを追加することで物体の動き方を決める
というのが自然だと思うので、
万有引力コンポーネント」を追加すれば、
その物体同士が自然に引き合うような形が理想的にでしょう。

ということで、ここでは、「万有引力コンポーネントを実装して、
引き合いたいもの同士にコンポーネントの追加のみで、
万有引力が働くようにしてみましょう。


万有引力と三体問題

考え方

万有引力は、質量を持った全ての物体が互いに引き合う現象です。

一つの物体に注目すれば、
その物体は他の全ての物体から引っ張られることになります。

ただし、Unityの中のオブジェクトには、
光源やカメラなどもあり、
これらも含めたオブジェクト全てから引っ張られるというのはよろしくないでしょう。

ということで、「万有引力コンポーネント」を持ったオブジェクトのみが、
同じく「万有引力コンポーネント」を持ったオブジェクトから引っ張られる
という動作を実装するのが良さそうです。

この時、万有引力が働くオブジェクトと、
そうでないオブジェクトを識別する必要があります。
ここでは、Unityのタグを使ってこの識別を行うことにします。

準備

タグを使って、万有引力が働く物体を識別するため、
万有引力用のタグを作成しておきます。

Unity上で適当なオブジェクトを選択して、
そのInspectorウィンドウから、
「Tag」→「Add Tag」を選択します。

f:id:u874072e:20191213130759p:plain
タグの追加

続いて、Tagsの「+」ボタンをクリックして、
タグの名前を入力して、「Save」をクリックします。
ここでは「ug_obj」をタグの名前としています。

f:id:u874072e:20191213131434p:plain
ug_objタグを追加

スクリプト

下のように万有引力コンポーネントの中身を作ります。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class universal_gravity : MonoBehaviour
{
    Rigidbody rb;
    float CONST_G = 0.0001f;
    string TAG_NAME = "ug_obj";
    // Start is called before the first frame update
    void Start()
    {
        gameObject.AddComponent<Rigidbody>();
        rb = gameObject.GetComponent<Rigidbody>();
        rb.useGravity = false;

        gameObject.tag = TAG_NAME;
    }

    // Update is called once per frame
    void Update()
    {
        var objs = GameObject.FindGameObjectsWithTag(TAG_NAME);

        var p0 = rb.position;

        foreach(var obj in objs)
        {
            var trb = obj.GetComponent<Rigidbody>();
            var q0 = trb.position;
            var pmq = p0 - q0;

            var F = - CONST_G * rb.mass * trb.mass * pmq * Mathf.Pow(pmq.magnitude, 3);
            rb.AddForce(F, ForceMode.Impulse);
        }
    }
}

以降ではスクリプトの詳細を説明します。

定数

CONST_G

万有引力定数を表します。
万有引力の強さを決める数値ですが、
現実の万有引力はこの定数が小さいため極めて小さな力になります。
必要に応じて調整しましょう。

TAG_NAME

万有引力が働くオブジェクトを識別するためのタグです。
準備で用意したタグの名前を指定します。

Start処理

Start関数の中では、

  • Rigidbody の追加
  • Rigidbodyの重力の無効化
  • オブジェクトへのタグの追加

を行なっています。

質量を持った剛体としての動きをシミュレーションするために、
Rigidbodyを持っていない場合はRigidbodyを追加し、
万有引力を別途実装するので、
Rigidbodyに付随する重力は無効にしています。

Update処理

Update関数では、
タグを目印にオブジェクトをサーチして、
他のオブジェクトから受ける万有引力を、
次の式で計算して加えています。

 \vec{F} = - G \frac{Mm}{r^3}\vec{r}

G万有引力定数、M,mは二つの物体の質量、
\vec{r}は物体の相対的な位置ベクトルを表します。

ベクトル式ですが、
コードの中でも数式と同じように一行で記述できているのが、
気持ちいいですよね。

var F = - CONST_G * rb.mass * trb.mass * pmq * Mathf.Pow(pmq.magnitude, 3);

ただし、オブジェクトが増えて計算が重くなる場合には、
近い距離のオブジェクトだけ万有引力が働くなどの近似を行うなど
工夫が必要そうです。

使い方

準備でタグを生成したら、
万有引力を働かせたいオブジェクトに、
上記の万有引力コンポーネントを追加するだけです。

下の動画は、万有引力コンポーネントを持った二つの物体に、
適当な初速を与えて動きをシミュレーションしています。
また、途中で3つ目の物体を追加しています。


万有引力と三体問題

まとめ

Unity で万有引力コンポーネントを実装して、
コンポーネントを追加するだけで、
オブジェクト同士に引力が働く動作を実装しました。

引力が働きあうオブジェクトを識別するために、
タグを使用しましたが、
タグは1つのオブジェクトに1つしかつけられないので、
他に良さそうな方法があれば、そちらを使いたいですね。

と思ったら、タグではなくて、
クラスのインスタンスリストを保持すれば良いことに気づいたので、
下のスクリプトを使えばタグ不要です。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class universal_gravity : MonoBehaviour
{
    Rigidbody rb;
    float CONST_G = 0.0001f;
//    string TAG_NAME = "ug_obj";
    static List<universal_gravity> ug_obs = new List<universal_gravity>();
    // Start is called before the first frame update
    void Start()
    {
        gameObject.AddComponent<Rigidbody>();
        rb = gameObject.GetComponent<Rigidbody>();
        rb.useGravity = false;

//       gameObject.tag = TAG_NAME;

        ug_obs.Add(this);
    }

    // Update is called once per frame
    void Update()
    {
        //var objs = GameObject.FindGameObjectsWithTag(TAG_NAME);

        var p0 = rb.position;

        foreach(var obj in ug_obs)
        {
            var trb = obj.GetComponent<Rigidbody>();
            var q0 = trb.position;
            var pmq = p0 - q0;

            var F = - CONST_G * rb.mass * trb.mass * pmq * Mathf.Pow(pmq.magnitude, 3);
            rb.AddForce(F, ForceMode.Impulse);
        }
    }
}
プライバシーポリシー