はじめに
ツイッターをみていたところこんなツイートを見かけました。
実はrigidbodyコンポーネントを付けずcolliderコンポーネントを付けたゲームオブジェクトのtransformを動的に変更するのはかなりまずい
— たこはち (@takoroku66) 2019年2月21日
確かにどこかでColliderコンポーネントのアタッチされたGameObjectにはRigidbodyコンポーネントをつけたほうが良いと見かけたような気がします。
これが本当かどうかを実際に実験してみたいと思います!
目次
環境
Unity2018.2.20f1
検証する対象
今回は5つの種類で検証をしました。
・RigidbodyなしかつTransform.position書き換え
・RigidbodyありかつTransform.position書き換え
・RigidbodyありかつIs KinematicオンかつTransform.position書き換え
・RIgidbodyありかつvelocity書き換え
・RigidbodyありかつAddForceで移動
それぞれの検証で細かい説明を付け加えながらいきたいと思います。
検証の条件
いくつか条件をつけて実験をしました。
・0.5秒に一回FPSの計測を20回行い、10秒間の平均FPSを計測する
・再生から5秒後に計測を開始する
・RigidbodyのDragとGravityはなしにする
・用いる3DモデルはSphire
・座標や初速といった初期条件は等しくする
FPSを計測するコード
平均FPSを計測するために以下のコードを用いました。
(誤りがあれば是非指摘していただけると嬉しいです)
using UnityEngine; using System.Collections; using System.Collections.Generic; using System.Linq; using System; public class MeasureFPS : MonoBehaviour { private List<float> fpsList = new List<float>(); private int frameCount; private float prevTime; private int measureCount; private bool isMeasuring = false; private void Start() { //5秒経過後計測開始 StartCoroutine(DelayMethod(5, () => { frameCount = 0; prevTime = 0.0f; measureCount = 0; isMeasuring = true; })); } private void Update() { if (isMeasuring) { frameCount++; float time = Time.realtimeSinceStartup - prevTime; //20回計測したら平均を出力する if (measureCount == 20) { Debug.Log("avarage fps: " + fpsList.Average()); isMeasuring = false; } else if (measureCount < 20) { //0.5秒間隔でFPSを計測する if (time >= 0.5) { float fps_measure = frameCount / time; fpsList.Add(fps_measure); frameCount = 0; measureCount++; prevTime = Time.realtimeSinceStartup; } } } } private IEnumerator DelayMethod(float waitTime,Action action) { yield return new WaitForSeconds(waitTime); action(); } }
RigidbodyなしかつTransform.position書き換え
移動するコードは以下のとおりです。
using UnityEngine; public class Player : MonoBehaviour { private Transform tr; private void Start() { tr = gameObject.transform; } private void Update() { tr.position += tr.forward * Time.deltaTime; } }
RigidbodyありかつTransform.position書き換え
移動コードは同様。
RigidbodyありかつIs KinematicオンかつTransform.position書き換え
Is Kinematicとは
Is Kinematicは
有効にすると、オブジェクトは物理エンジンによって駆動されず、その Transform によってのみ操作されます。これは、プラットフォームを移動したい場合や、Hinge Joint を加えたリジッドボディをアニメーション化したい場合に便利です。
と公式に書いてあります。
これをオンにしたら変化があるかもしれないので調べてみます。
コード
移動コードは同様。
RIgidbodyありかつvelocity書き換え
加えるvelocityの大きさは?
transform.position += transform.forward * Time.deltaTime;
と同じスピードにするにはどのくらいのvelocityにすればいいのかと計測してみたところ、
gameObject.GetComponent<Rigidbody>().velocity = new Vector3(0, 0, 1);
に匹敵するようです。
あまりに綺麗でびっくりしてしまいました。
コード
移動コードは以下のとおり。
追記)空気抵抗がないのでUpdateで更新する必要はありませんでしたね・・・
using UnityEngine; public class Player : MonoBehaviour { private Rigidbody rb; private void Start() { rb = gameObject.GetComponent<Rigidbody>(); } private void Update() { rb.velocity = new Vector3(0, 0, 1); } }
RigidbodyありかつAddForceで移動
どれくらいの力を加えればいいの
空気抵抗はない条件なので、最初にドンっとやってそのまま移動するのがいいですね。
ここはやや専門的なのでスキップして全然OKです。
これもかなり綺麗にいけました!
コード
移動コードは以下の通り
using UnityEngine; public class Player : MonoBehaviour { private void Start() { gameObject.GetComponent<Rigidbody>().AddForce(new Vector3(0, 0, 1), ForceMode.Impulse); } }
結果
結果はこのようになりました。
①RigidbodyなしかつTransform.position書き換え
②RigidbodyありかつTransform.position書き換え
③RigidbodyありかつIs KinematicオンかつTransform.position書き換え
④RIgidbodyありかつvelocity書き換え
⑤RigidbodyありかつAddForceで移動
Rigidbodyの有無によってFPSの差は特に見られませんでした。
また、Rigidbody付きでTransform.positionの書き換えは他と比べてFPSが劣っているように見えます。
さいごに
正直なところ何度か検証すると平均FPS自体もばらつきがあり、頑張って中間あたりを記録しましたが正確とはいいがたいです。
ただこの結果からは
ColliderコンポーネントのアタッチされたGameObjectにはRigidbodyコンポーネントをつけるべきである
とは断言できません。
しかし公式には
「コライダーは Rigidbody コンポーネントをオブジェクトに追加することなくシーンにある床、壁など動かない要素を作成することができます。これらは 静的 コライダーと呼ばれます。一般的には静的コライダーのトランスフォームの位置の変更は物理エンジンのパフォーマンスに大きな影響をもたらすのでやめるべきです。」
と書かれています。
ぐぬぬ・・・。
おそらく検証の仕方が間違っていたのかもしれませんね・・・。
公式を信用しておいたほうが無難だとは思います。
ただ思ったより重労働だったのでもう当分はやりたくないですね笑