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


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

Rigidbody.IsSleeping

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 に代入する

動摩擦力(PhysicMaterial.DynamicFriction)を設定する

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

抗力(Rigidbody.drag)を設定する

drag.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倍速)

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)

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側の実装で)何が正しい式なのか知ってる方いたら教えてください///