【Unity】万有引力を実装して人工衛星を飛ばそう！

・この記事ではUnityで万有引力を実装し、人工衛星を飛ばして遊びます。

・アセットは使用せず、初めからUnityに搭載されている機能のみを使って実装します。

・動けば良い！の精神でおねがいします。

# はじめに
みなさん宇宙って不思議で神秘的だと思いませんか？

「人類は地球を冒険するのには遅すぎ、宇宙を冒険するのには早すぎた。」と言われているぐらいです。

今回は宇宙を語る上では欠かせない『重力』について考え、実装し、遊び倒しましょう！

# 今回のゴール
![Qiita_2_img_1_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1522500/75f58ffd-c506-3724-3db5-1fb948ce0501.png)
1：重力源を複数配置し、人工衛星を周回させる。
2：人工衛星の周回軌道を描画させる。

# 万有引力について

万有引力とは、すべての物体は互いに引かれあっているということだ。（超ザックリ説明）

その力 F の大きさは以下の式で表すことができる。

```math
F = G\frac{Mm}{r^2}
```

Gは万有引力定数を、mとMは物体の質量（kg）を、ｒは物体間の距離（m）を、Ｆは万有引力の大きさ（kg）を表している。

いやぁ～なんとも簡潔で分かりやすい式ですね。

万有引力の発見やより詳しい説明はこちらを参考にしてください。
> 
> Wikipedia　万有引力
> https://ja.wikipedia.org/wiki/%E4%B8%87%E6%9C%89%E5%BC%95%E5%8A%9B
> 

# 万有引力とベクトル

さて、万有引力の概要が分かったのだが、この万有引力FはUnityで実装する際に少々都合が悪い。

というのも、このFは力の大きさであり、方向（ベクトル）の情報が抜け落ちているのだ。

Unityの3Dでは三次元のふるまいを考える必要があるのでこの式では不十分である。

では万有引力の式に方向（ベクトル）の概念を付け加えよう。

重力の中心の座標を位置ベクトル( x )として考えると、その重力の中心への方向は位置ベクトル( x )を正規化する必要がある。

じっくり考えたい方は以下のリンクからどうぞ,,,

> 
> EMANの物理学　万有引力をベクトルで表す
> https://eman-physics.net/dynamics/gravity_vector.html
> 

# 実装
1：UnityもしくはUnity Hubを起動し、新規3Dプロジェクトを作る。

2：重力源の惑星（球体）を作る。``[Hierarchy +タブ]`` -> ``[3D Object]`` -> ``[Sphere]``

3：Sphere の名前を Planet に変更する。

4：Planetオブジェクトを選択し、``[Inspector]`` -> ``[Add Component]`` -> ``[Physics]`` -> ``[Rigidbody]``でコンポーネントを追加する。
※Rigidbody の Use Gravity のチェックを外しておく。
※Rigidbody の Is Kinematic のチェックを付けておく。

5：人工衛星（立方体）を作る。``[Hierarchy +タブ]`` -> ``[3D Object]`` -> ``[Cube]``

6：Cube の名前を Player に変更する。
※Planet と Player　の座標はお好みで(Planetが画面の真ん中でPlayerが右上にすると分かりやすい)

7：Playerオブジェクトを選択し、``[Inspector]`` -> ``[Add Component]`` -> ``[Physics]`` -> ``[Rigidbody]``でコンポーネントを追加する。
※Rigidbody の Use Gravity のチェックを外しておく。

8：重力を発生させるスクリプトを作成する。``[Project +タブ]`` -> ``[C# Script]``
※ファイル名は「Planet_Gravity」で作成する。

9：以下のコードをPlanet_Gravityにコピペする。（解説はのちほど）

```C#:Planet_Gravity.C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Planet_Gravity : MonoBehaviour
{
    public GameObject Player = null;//プレイヤー
    public GameObject Planet = null;//惑星

    private Rigidbody PlayerRig = null;//プレイヤーのRigidbody
    private Rigidbody PlanetRig = null;//惑星のRigidbody

    private Vector3 f;    //万有引力(ベクトル)
    private float m;    //プレイヤーの質量
    private float M;    //惑星の質量
    private float r;    //プレイヤーと惑星の距離

    public float G = 6.67430f * Mathf.Pow(10, -11);//万有引力定数

    // Use this for initialization
    void Start()
    {

        PlayerRig = Player.GetComponent<Rigidbody>();//プレイヤーのRigidbodyを取得
        PlanetRig = Planet.GetComponent<Rigidbody>();//惑星のRigidbodyを取得

        //プレイヤーの質量を取得しておく
        m = Player.GetComponent<Rigidbody>().mass;

        M = 5.972f * Mathf.Pow(10, 10);//質量をミニ地球レベルに変更
    }

    private void FixedUpdate()
    {
        //惑星に対するプレイヤーのベクトル（方向）を計算
        Vector3 direction = (Planet.transform.position - Player.transform.position).normalized;

        //惑星とプレイヤーの距離を計算
        r = Vector3.Distance(Player.transform.position, Planet.transform.position);

        //万有引力(ベクトル)を計算
        f = (G * m * M * direction) / (r * r);

        //万有引力(ベクトル)を与える
        PlayerRig.AddForce(f, ForceMode.Impulse);
    }
}
```

10：Planet_GravityスクリプトをPlanetオブジェクトにドラッグアンドドロップでアタッチする。
11：Planet_Gravityスクリプトのインスペクターから Planet と Player のオブジェクトを参照する。

ここで一度再生ボタンを押してみて動作を確認しましょう。Player（Cube）がPlanet（Sphere）に吸い寄せられていれば成功です。

#　Planet_Gravityスクリプトの解説
・宣言部分

```C#:Planet_Gravity.C#
    public GameObject Player = null;//プレイヤー
    public GameObject Planet = null;//惑星

    private Rigidbody PlayerRig = null;//プレイヤーのRigidbody
    private Rigidbody PlanetRig = null;//惑星のRigidbody

    private Vector3 f;    //万有引力(ベクトル)
    private float m;    //プレイヤーの質量
    private float M;    //惑星の質量
    private float r;    //プレイヤーと惑星の距離

    public float G = 6.67430f * Mathf.Pow(10, -11);//万有引力定数

```

万有引力定数をMathf.Pow関数を用いて書いています。
Mathf関数は数学的な入出力を計算してくれる便利な機能です。
Mathf関数について詳しく知りたい方は こちら↓ をどうぞ。

> 
> Unity DOCUMENTION Mathf
> https://docs.unity3d.com/ja/current/ScriptReference/Mathf.html
> 


・Start関数部分

```C#:Planet_Gravity.C#
    void Start()
    {
        PlayerRig = Player.GetComponent<Rigidbody>();//プレイヤーのRigidbodyを取得
        PlanetRig = Planet.GetComponent<Rigidbody>();//惑星のRigidbodyを取得

        //プレイヤーの質量を取得しておく
        m = Player.GetComponent<Rigidbody>().mass;

        M = 5.972f * Mathf.Pow(10, 10);//質量をミニ地球レベルに変更
    }
```

PlayerRig と PlanetRig にそれぞれの Rigidbodyを 代入しています。
また、private変数の m に Player の質量を代入しています。
Rigidbody には質量（mass）の情報を入れることができる。これを GetComponent を通じて取得している。
Mの値を強引に指定しているが、これは、惑星の質量を極端に大きくしないと万有引力の影響が小さくなってしまうからだ。
※試しにM=300.0kg で実行してみると Player がほとんど動かなくなる。

・FixedUpdate関数部分

```C#:Planet_Gravity.C#
    private void FixedUpdate()
    {
        //惑星に対するプレイヤーのベクトル（方向）を計算
        Vector3 direction = (Planet.transform.position - Player.transform.position).normalized;

        //惑星とプレイヤーの距離を計算
        r = Vector3.Distance(Player.transform.position, Planet.transform.position);

        //万有引力(ベクトル)を計算
        f = (G * m * M * direction) / (r * r);

        //万有引力(ベクトル)を与える
        PlayerRig.AddForce(f, ForceMode.Impulse);
    }
```

このスクリプトの本体部分といっても過言ではない部分です。

```C#
        //惑星に対するプレイヤーのベクトル（方向）を計算
        Vector3 direction = (Planet.transform.position - Player.transform.position).normalized;
```
Vector3 型の direction変数 にPlayerからみたPlanetの方向を代入しています。
ポイントは末尾の .normalized で正規化を行っているところで、正規化をすることで正しい方向ベクトルを得ることができる。
※正規化をしないとベクトル方向のほかにベクトルの大きさも含まれてしまうため

```C#
    //惑星とプレイヤーの距離を計算
    r = Vector3.Distance(Player.transform.position, Planet.transform.position);
```

ここではVector3.Distanceを用いて Player と Planet 間の距離を取得しています。
これは第一引数と第二引数が入れ替わっても特に問題はありません。（多分）

```C#
    //万有引力(ベクトル)を計算
    f = (G * m * M * direction) / (r * r);
```

万有引力を取得しています。
一般的な万有引力の式に方向（ベクトル）を掛け合わせて万有引力のベクトルとしています。

```C#
   //万有引力(ベクトル)を与える
   PlayerRig.AddForce(f, ForceMode.Impulse);
```

Rigidbody経由でPlayerオブジェクトに力を加えています。
このAddForce関数は力の加え方のオプションがいろいろあるのでこちらも参考にどうぞ。
※力の加え方を変えると人工衛星の動き方が変わっておもしろいよ

> 
> Unity DOCUMENTION Rigidbody.AddForce
> https://docs.unity3d.com/ja/current/ScriptReference/Rigidbody.AddForce.html
> 


# さらに改良！

さて、これで人工衛星が惑星に引き寄せられ、万有引力が実装できましたが,,,
墜落するばかりであまり面白くありませんよね？
なので、人工衛星に初速を与えて惑星を周回してもらいましょう。

1：``[Project +タブ]`` -> ``[C# Script]``　でスクリプトを作成
※名前は「Player_InitialVelocity」で作成

2：以下のスクリプトをコピペする

```C#:Player_InitialVelocity.C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player_InitialVelocity : MonoBehaviour
{
    private Rigidbody PlRig = null;//プレイヤーのRigidbody

    public Vector3 vec = new Vector3(0, 0, 0);

    void Start()
    {
        PlRig = this.GetComponent<Rigidbody>();
        //Playerに初速を与える
        PlRig.AddForce(vec, ForceMode.VelocityChange);
    }
}
```

解説：特に解説するところはないです。Vector3型の vec変数を public にすることで初速の変更を容易に行えます。

3：Player_InitialVelocityスクリプトをPlayerにアタッチする。

4：Inspectorからvecの値を入力し、初速を決める。
※初速が第一宇宙速度を超えるとPlayerが無限遠方に飛び去ります。

さてここで一度再生ボタンをおして実行してみましょう。
PlayerがPlanetを周回すれば成功です。

# 周回軌道を描画する

PlayerがPlanetを周回するようになりましたが、ビジュアル的に少し物足らないですよね？
ではPlayerの軌道を描画させましょう。

1：Playerオブジェクトを選択する。

2：``[Inspector]`` -> ``[Add Component]`` -> ``[Effects]`` -> ``[Trail Renderer]``

3：Trail Rendererの値をお好みに設定
※私は Width = 0.35 , Time = 15.0 に設定しています。

↓　軌道に色を付けたい場合　↓
4：``[Project +タブ]`` -> ``[Material]`` でマテリアルを新規作成

5：マテリアルの ``[Shader]`` を ``[Particles/Standard Unit]`` に変更

6：作成したマテリアルを Trail Renderer のマテリアルに設定

# 惑星（重力源）を複数配置する

惑星を複数配置したい場合は、PlanetオブジェクトをコピペすればOKです。
惑星が2つの時は動きがさらに複雑になっておもしろいです。

# おわり

さて、万有引力はいかがでしたでしょうか。実装が簡単なのでゲームへの応用も期待できますね。（衛星の軌道を予測するのがとても難しいですが笑）これを機に科学やゲームの技術に興味を持っていただければ幸いです。お疲れさまでした。

# 参考文献

> 
> mizu-mi blog Unityで万有引力による運動の確認
> https://mzmlab.hatenablog.com/entry/unity-planet
> 

> 
> クイックノート Unity で万有引力を実装してみる
> https://clean-copy-of-onenote.hatenablog.com/entry/universal_gravity_in_unity
> 

> 
> エクスプラボ 【Unity】AddForceで力を加える時のモードを変えて動きを観察
> https://ekulabo.com/force-mode#outline__2_3
> 

> 
> EMANの物理学 万有引力をベクトルで表す
> https://eman-physics.net/dynamics/gravity_vector.html
> 

> 
> wikipedia 万有引力定数
> https://ja.wikipedia.org/wiki/%E4%B8%87%E6%9C%89%E5%BC%95%E5%8A%9B%E5%AE%9A%E6%95%B0
> 

> 
> ぱふの自由帳 【Unity】ベクトルを正規化する
> https://pafu-of-duck.hatenablog.com/entry/2018/02/28/145944
>