Unityではデフォルトで重力による運動をシミュレートすることができますが、
あくまで地面に向かって一方向的かつ一定の大きさの力に限定されています。
惑星の動きなどをシミュレーションするには、
惑星の位置関係によって、
重力の働く向きや大きさが変わる状況を、
シミュレーションする必要があります。
万有引力の実装は検索すればいくつもヒットするのですが、
引力が働く物体をスクリプト内で明示的に指定するものばかりでした。
これだと、複数の物体間の万有引力を記述するのが面倒そうです。
Unityではコンポーネントを追加することで物体の動き方を決める
というのが自然だと思うので、
「万有引力コンポーネント」を追加すれば、
その物体同士が自然に引き合うような形が理想的にでしょう。
ということで、ここでは、「万有引力」コンポーネントを実装して、
引き合いたいもの同士にコンポーネントの追加のみで、
万有引力が働くようにしてみましょう。
考え方
万有引力は、質量を持った全ての物体が互いに引き合う現象です。
一つの物体に注目すれば、
その物体は他の全ての物体から引っ張られることになります。
ただし、Unityの中のオブジェクトには、
光源やカメラなどもあり、
これらも含めたオブジェクト全てから引っ張られるというのはよろしくないでしょう。
ということで、「万有引力コンポーネント」を持ったオブジェクトのみが、
同じく「万有引力コンポーネント」を持ったオブジェクトから引っ張られる
という動作を実装するのが良さそうです。
この時、万有引力が働くオブジェクトと、
そうでないオブジェクトを識別する必要があります。
ここでは、Unityのタグを使ってこの識別を行うことにします。
準備
タグを使って、万有引力が働く物体を識別するため、
万有引力用のタグを作成しておきます。
Unity上で適当なオブジェクトを選択して、
そのInspectorウィンドウから、
「Tag」→「Add Tag」を選択します。
続いて、Tagsの「+」ボタンをクリックして、
タグの名前を入力して、「Save」をクリックします。
ここでは「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関数では、
タグを目印にオブジェクトをサーチして、
他のオブジェクトから受ける万有引力を、
次の式で計算して加えています。
は万有引力定数、は二つの物体の質量、
は物体の相対的な位置ベクトルを表します。
ベクトル式ですが、
コードの中でも数式と同じように一行で記述できているのが、
気持ちいいですよね。
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); } } }