スーパーボールを重ねて落とす「すっとびボール」を物理エンジン(Unity)で再現する

#概要
Unityは物理エンジンであり、
これを用いて物理実験をシミュレーションすることが可能である。
今回はUnityを用いて「すっとびボール」をシミュレーションする。

#環境
Unity 2019.3.7f1

#仕様
上の段のボール（直径1m）を高さ10mから落とす。
下の段のボール（直径1.5m）が床に着地したとき、上の段のボールが高く跳ねる。

#実装
まず、床を実装する。
ヒエラルキーウィンドウで座標が(0,0,0)でScaleが(1,1,1)の
Planeを作る。

次に、スーパーボールとなる球を実装する。
ヒエラルキーウィンドウにScaleが(1,1,1)で座標が(0,10,0)のSphereをBall1とし、
Scaleが(1.5,1.5,1.5)で座標が(0,8.75,0)のSphereをBall2として作る。

すなわち、上の段のボールがBall1、下の段のボールがBall2となる。

(Ball1とBall2のマテリアルを変更しておくと見やすい。私はBall1を赤、Ball2を青とした。)

Ball1とBall2にRigidbodyをアタッチし、
Ball1の方のRigidbodyのMassを1に、Ball2の方のRigidbodyのMassを3にする。

ボール用のPhysic Materialを作る。
プロジェクトウィンドウにPhysic Materialを作り、
Bouncinessを0.9にする。
これをBall1とBall2のSphere ColliderのMaterialに入れる。

ここまでやれば、シーンウィンドウが以下の画像のようになっているはずである。

![suttobi1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2329064/8b3aaeb4-2eb8-65ed-ff0e-d54c79ce10c4.png)


ここからスクリプトを書いていく。

スクリプトをこのように書く。

```c#:GalileanCannon.cs
public class GalileanCannon : MonoBehaviour
{
    public Rigidbody ball1;
    public Rigidbody ball2;
    private float v0;
    private float v1;
    private float v2;
    private float e1 = 0.9f;
    private float e0 = 0.9f;
    private bool flag = true;
    private bool isGrounded = false;

    void Start()
    {
        ball1.isKinematic = true;
        ball2.isKinematic = true;

        //速度を求める
        v0 = Mathf.Sqrt(2 * 9.81f * (ball2.transform.position.y - (ball2.transform.localScale.y / 2.0f)));
        v1 = ((e0 * ball2.mass - (e0 * e1 + e1 + 1) * ball1.mass) * v0) / (ball2.mass + ball1.mass);
        v2 = (((e0 * e1 + e0 + e1) * ball2.mass - ball1.mass) * v0) / (ball2.mass + ball1.mass);
    }

    void FixedUpdate()
    {
        //スペースキーを押したら開始
        if (Input.GetKeyDown(KeyCode.Space))
        {
            ball1.isKinematic = false;
            ball2.isKinematic = false;
        }

        //ball2が地面についたらball1が跳ねる
        if(isGrounded == true && flag == true)
        {
            ball2.velocity = new Vector3(0, v1, 0);
            ball1.velocity = new Vector3(0, v2, 0);
            isGrounded = false;
            flag = false;
        }
    }
    void OnCollisionEnter(Collision collision)
    {
        //ball2が地面と接触したかを一回だけ判定する
        if (collision.gameObject.name == "Ball2" && flag == true)
        {
            isGrounded = true;
        }
    }
}
```

変数を見てみよう。

```c#:GalileanCannon.cs
    public Rigidbody ball1;
    public Rigidbody ball2;
    private float v0;
    private float v1;
    private float v2;
    private float e1 = 0.9f;
    private float e0 = 0.9f;
    private bool flag = true;
    private bool isGrounded = false;
```

ball1は上の段のボール、ball2は下の段のボールである。

v0はball2が地面と接触する直前の速度、
v1はball2が地面と衝突したあとのball2の速度、
v2はball2が地面と衝突したあとのball1の速度である。

e1、e0は反発係数である。

isGroundedは地面に接触したかどうかを判定するものであり、
flagは地面と衝突した最初の1回だけシミュレーションを行うための判定である。

スクリプトの動作は、スクリプトに書いてあるので割愛する。

このスクリプトをPlaneに加え、
スクリプトのball1とball2にそれぞれBall1とBall2を入れる。

これで完成である。

あとは再生ボタンを押してスペースキーを押せばシミュレーションが始まる。
カメラの位置を見やすい位置に変更するのを忘れずに。

#まとめ
オブジェクトを作り、計算式をスクリプトに書く。
計算式さえ理解すれば、スクリプトはさほど難しくはない。
今回はこのようなスクリプトになったが、もっと良いスクリプトがありそうなので、
自分でも考えてみてください。

#参考文献
塚本浩司:「すっとびボールの研究史 物理教育 49巻6号」,2001年12月
P.537-541

Wikipedia
https://ja.wikipedia.org/wiki/%E3%81%99%E3%81%A3%E3%81%A8%E3%81%B3%E3%83%9C%E3%83%BC%E3%83%AB
(Wikipediaの途中式が異なっているので、お気をつけて)