[Unity] Rigidbodyで静止判定を取る/素早く静止させる

- 主にゴルフゲームを作る際にボールを飛ばして静止するまで次の行動をさせたくない
- この「静止している状態」をどのように取得するか

## Rigidbody.IsSleeping
- https://docs.unity3d.com/ja/current/ScriptReference/Rigidbody.IsSleeping.html
- https://docs.unity3d.com/ja/current/Manual/RigidbodiesOverview.html (Sleepingの欄参照)
- `Rigidbody.IsSleeping()` でrigidbodyが静止状態か否か取る事が出来る

![issleep.gif](https://qiita-image-store.s3.amazonaws.com/0/102664/18ae9ce7-b0a6-e7d2-0230-023c7251807b.gif)

```csharp
using UnityEngine;
﻿using UnityEngine.UI;
using System.Collections;

public class SleepTestText : MonoBehaviour
{
    public Rigidbody rb;
    public Text isSleepText;

    void FixedUpdate()
    {
        isSleepText.text = rb.IsSleeping() ? "Sleep" : "Active";
    }  
}
```

## 素早く静止させる手段
### SleepThresholdを大きめに設定する
- `SleepThreshold`の値は以下のように変更する事が出来る
  - Physics マネージャー **[Edit]=>[ProjectSettings]=>[Physics]** 内で変更する (全てのRigidbodyに対するdefault値の設定)
  - Script内で`Rigidbody.sleepThreshold` に代入する

![sleepthreshold.png](https://qiita-image-store.s3.amazonaws.com/0/102664/073fd64e-01b6-411d-a1ec-32efc03dd7e5.png)

### 動摩擦力(PhysicMaterial.DynamicFriction)を設定する
- https://docs.unity3d.com/ja/current/Manual/class-PhysicMaterial.html
- Colliderに対して、PhysicMaterialを設定する事が出来る
  - PhysicMaterialは、特定のオブジェクトの摩擦や跳ね返り効果の設定
  - **[Project]=>[create]=>[PhysicMaterial]**で生成可能
- `DynamicFriction` は動摩擦力を表す
  - この値が大きい程、物体(床含む)と衝突している限り、基本的には早く静止する
  - `FrictionCombine`(2物体間の摩擦の処理方法)  の設定により、衝突相手の摩擦力が低い場合には摩擦が大きくならないケースがある
- 余談: Physics マネージャーからdefaultのPhysicMaterialを設定することも可能

![material.gif](https://qiita-image-store.s3.amazonaws.com/0/102664/177393c8-df35-e86f-2b29-cab2d307098a.gif)

![friction.gif](https://qiita-image-store.s3.amazonaws.com/0/102664/72d2cc00-fbee-3005-a545-7b4d048eda80.gif)

### 抗力(Rigidbody.drag)を設定する
- https://docs.unity3d.com/ja/current/ScriptReference/Rigidbody-drag.html
- `Rigidbody.drag` をInspector上、もしくはScipt上で変更する
- 抗力を大きめに設定する事で速度が減衰する

![drag.png](https://qiita-image-store.s3.amazonaws.com/0/102664/577ec5f4-7762-8f77-800c-c43202c0a025.png)

![drag.gif](https://qiita-image-store.s3.amazonaws.com/0/102664/3230869c-14ff-9b3d-99e7-a0f7b3d31177.gif)


### ゲームの処理時間を早くする
- https://docs.unity3d.com/ja/current/ScriptReference/Time-timeScale.html
- 裏道的なアプローチではあるが、早送りがあるとユーザにストレスを感じさせない
- `Time.timeScale` を変更する事でゲーム内の時間の速度を早めたりお染めたり出来る
  - `Time.timeScale = 1.0f` がデフォルトの状態
  - `Time.timeScale = 2.0f` で早送り (2倍速)
  - `Time.timeScale = 0.5f` でスローモーション (0.5倍速)

![timescale.gif](https://qiita-image-store.s3.amazonaws.com/0/102664/415a7085-af36-404c-4f4d-2f0e4818077a.gif)

```csharp
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;

public class TimeScaleButton : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
    // 3倍速
    public float rate = 3.0f;
    public Image image;

    public void OnPointerDown(PointerEventData eventData)
    {
        Time.timeScale = rate;
        image.enabled = true;
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        Time.timeScale = 1.0f;
        image.enabled = false;
    }
}
```

## (要検証) 静止状態になる条件 (SleepThreshold)
- https://docs.unity3d.com/ja/current/Manual/class-PhysicsManager.html
- https://docs.unity3d.com/ja/current/ScriptReference/Rigidbody-sleepThreshold.html
- `SleepThreshold` の値が静止状態を判定する為の閾値となる
- `SleepThreshold`は「質量で正規化された運動エネルギーのしきい値」を指す
- 運動エネルギーEは `K = (1/2)mv^2` で求めることが出来る
  - m = mass (質量)
  - v = velocity (速度)
- 今回は「質量で正規化された運動エネルギー」なので質量は考慮しない
- 今回検証に使用したコードは以下
  - 「質量で正規化された運動エネルギー」の取得は[コチラ](http://forum.unity3d.com/threads/how-to-calculate-a-rigidbodys-mass-normalized-energy-for-sleepthreshold.311941/)を拝借
  - (余談: magnitude ^ 2 より sqrMagnitude のほうが高速)

```csharp
using UnityEngine;
﻿using UnityEngine.UI;
using System.Collections;

public class SleepTestText : MonoBehaviour
{
    public Rigidbody rb;
    public Text kineticEnergyText;
    public Text sleepThresholdText;
    public Text isSleepText;

    void FixedUpdate()
    {
        float e = GetMassNormalizedKineticEnergy(rb);

        kineticEnergyText.text  = "kineticEnergy: " + e.ToString();
        sleepThresholdText.text = "sleepThreshold: " + rb.sleepThreshold.ToString();
        isSleepText.text        = rb.IsSleeping() ? "Sleep" : "Active";
    }
    
    public static float GetMassNormalizedKineticEnergy(Rigidbody r)
    {
         // Linear energy
         float E = 0.5f * r.mass * Mathf.Pow(r.velocity.magnitude, 2f);
  
         // Angular energy
         E += 0.5f * r.inertiaTensor.x * Mathf.Pow(r.angularVelocity.x, 2f);
         E += 0.5f * r.inertiaTensor.y * Mathf.Pow(r.angularVelocity.y, 2f);
         E += 0.5f * r.inertiaTensor.z * Mathf.Pow(r.angularVelocity.z, 2f);
  
         // Mass-normalized
         return E /= r.mass;
     }    
}
```

- しかし (上記のフォーラムでも触れられているが) `e < sleepThreshold` を満たしてもSleepにならない...
  - (Unity側の実装で)何が正しい式なのか知ってる方いたら教えてください///

![threshold.gif](https://qiita-image-store.s3.amazonaws.com/0/102664/6a890747-304e-5387-282d-ce086f43b2bd.gif)