モノ創りで国造りを

ハード/ソフト問わず知見をまとめてます

Unityでブラックホールのようなものを作る

Unityの物理演算のコンポーネントは多機能で
使いこなせればスクリプト不要ではと思っていましたが
全ての物理現象を網羅しているはずもなく。

Unityで、ブラックホールのようなものにキャラが引っぱられる挙動(=重力)の実装を試みました。
重力といっても真下への自由落下ではなく、
地球と太陽のように質量を有するものがお互いを引き付けあう万有引力です。
マリオギャラクシーをイメージするとわかりやすいです。
重力は二つの物体間の距離の2乗に反比例し質量の積に比例します。
{\displaystyle F = G(\frac{Mm}{r^{2}})}
Unityの2D Jointコンポの中で2物体間を近づける力を実装できるのは
Target JointとSpring Jointですが
どちらも物体の距離が離れると引力が強くなるもので、
引力とは異なります。
結局Unityのデフォルトの機能からは重力の挙動は出来そうになかったので、
いよいよScriptの出番ですね。

と、その前に実装したい数式を明確にしておきましょう。
重力の公式は上に示した通りですが
Unityに実装する場合はオブジェクトにかかる力をX方向への力とY方向への力に分ける必要があります。
各々の方向にかかる力は以下の通りです。
{\displaystyle F_x = F\frac{\Delta X}{r} =  GMm(\frac{\Delta X}{r^{3}})}
{\displaystyle F_y = F\frac{\Delta Y}{r} =  GMm(\frac{\Delta Y}{r^{3}})}
ここで
{\displaystyle \Delta X}は2物体間の距離のX成分
{\displaystyle \Delta Y}は2物体間の距離のY成分です。

コードは以下の通り。

public class gravity : MonoBehaviour {
    Rigidbody2D rigid2D;
    Vector3 distance;//2物体間の距離
    Vector3 sunPosition;//軌道の中心の座標
    Vector3 forceEarth;//移動する物体にかかる力
    float gravityConst = 100.0f;//定数(=GMm)のパラメータ
    float velocityX = 220.0f;//初速の設定用パラメーター、これがないと単振動になる

    // Use this for initialization
    void Start () {
        this.rigid2D = GetComponent<Rigidbody2D>();
        sunPosition = new Vector3(0.0f, 0.0f, 0.0f);
        //Set initial velocity
        this.rigid2D.AddForce(transform.right * velocityX);
    }
    
    // Update is called once per frame
    void Update () {
        distance = sunPosition - transform.position;
        forceEarth = gravityConst * distance / Mathf.Pow(distance.magnitude, 3);
        this.rigid2D.AddForce(forceEarth, ForceMode2D.Force);

    /* !!!注意!!!
    forceEarthの分母が0にならないように配慮する必要あり。
    例えばdistance.magnitudeが一定値以下でDestroy(gameObject)とする等。
     */
    }
}

オブジェクトのBody TypeをDynamicとし、
Gravity Scaleは0にします。
これでパラメーターによっては、オブジェクトが安定した円運動を行うようになります。
オブジェクトを地球の画像にし、 軌道の中心を太陽にした場合はこのような動きになります。
f:id:yuji2yuji:20171224220742g:plain
めでたしめでたし。

パラメーターの設定がとても大変。ゲームに活用するのは難しいかもしれません。
マリオギャラクシーすごい。さすが世界の任天堂