Unityの衝突判定をRigidbodyなしでやる

こんにちは。ヤマヤタケシです。
Unityの話です。

Unityってやっぱりよく出来た開発環境だなと、日々噛み締めながら生きています。
RigidbodyとColliderでらくらく3D物理ではあるんですが、リアルです。
リアリティのための仕組みなので当たり前です。
よく出来ています。

とはいえ・・・

ゲームによっては、コミカルな動き、ウソな動きが多々あって、もはや物理エンジンが邪魔だ!って言うこともあります。
そんな時は、グッバイRigidbodyで外せば勝手に動かなくります。
その代償として、Rigidbodyがやってくれていた移動とかめり込んでも戻す処理とかを自前でやる必要があります。

えー、全部自前で実装するの?

いえいえ、さすがUnityです。PhysicsにはColliderに対する衝突判定関数が色々あります。
これにより8割位の労力が節約できます。

最高だぜ!

キャラとかは球で近似して判定します。
計算量が少ないし、楽ですからー。
そんなときには、 Physics.SphereCast がオススメです。
というか、球が移動するという事のために作られた関数です。たぶん。

しかし、これで、ちょっとハマったんですよね。

※下記のことはUnity5.3で体験したことです。勘違いや、将来のバージョンで変更とかあるかもしれません。

■Physics.SphereCast の始点が重なっているコライダは無視される。

SphereCastは球で始点と判定距離を指定すると、一番最初に当たった位置と法線が得られる関数です。
しかし、始点の球がすでに触れている、重なっているColliderは無視されます。
この動作は仕様なのか?と、よくよくクヨクヨ考えた結果、理にかなった仕様でした。
とはいえ、問題はすり抜けることです。
何らかの処理で、埋まってしまうと次の判定でそのコライダが無視されてすり抜けてしまいます。
埋まらない状態を維持し続けないといけないんですかー!?
できるかーい!
いろんな事情で埋まるのだよ。

■どうしたか?
移動とは別に、重なりを解消する機能を実装しました。
こんな感じです。
1. Physics.OverlapSphere を使用して、埋まっている=重なっているコライダを全て取得します。
2. Physics.Collider.ClosestPointOnBoundsを使用して、コライダ表面で、球の中心座標に一番近い点を得ます。
3. 重なった全ての点と重ならないように移動させましょう。
3-a 押し出す方向を計算します。最近点-中心のベクトルを計算します。
3-b そのベクトルを平均します。
3-c 平均ベクトルを法線にします。(normalize, 長さを1にします。)その方向へ押し出します。
3-d その法線方向が前だとして、最近点群から一番前の点を選びます。
3-e 一番前の点と法線で作る平面を作ります。new Plane(p, n);
3-f 球の中心と平面との距離を計算します。Plane.GetDistanceToPointで得られます。
3-g 平面の法線方向に球の終身座標を(球の半径 – 平面との距離)だけ動かします。

これで重なりなし!
移動先でまた重なるかもしれませんが、そこは移動を諦めるか、規定回数繰り返すか、を状況に応じて考えます。

■ふわっとした右脳のイメージ
上記のアルゴリズムを考えたときの俺の右脳イメージはこうです。
柔らかいスポンジのボールが棚とか壁とかに同時に当たって変形しています。
変形しているところは戻ろうとします。
その押し返す力でボールが離れたっ!
よかったよかった。
そんなイメージを頼りに試行錯誤したのでありました。

■まとめ
いやー、がんばった!
右脳でぼんやり考えて、左脳で実行可能なプログラムまで落としこむという頭脳労働!
俺スゲー!
と、たまに自分で自分を褒めでもいいでしょ?
傍から見てても理解されにくい仕事ですからー。

そんじゃまた。