Nexus Lab. Blog

0から始めるUnity物理演算⑤衝突と力積

f:id:nexusyuumilo:20180127121402p:plain

Milo です。
いよいよ残りわずか、第5回です。
今回は、力の加え方に関するお話で、「力積」について触れます。
【Unity道場】物理シミュレーション完全マスターで勉強したことを元にしています。

この記事を書いたひと
千葉生まれ。今年こそ海外旅行に行こうと計画中です



目次です
今回の内容は結論は単純ですが、その過程が若干むずいです。


講義のスライドを見ながらですと理解しやすいです!

衝突は積分ではシミュレーション不可!

みなさん、例えばボールが置いてあったとして、それに大きな力でキックをして吹っ飛ばすサッカーゲームを作りたいとします。
その時。ボールに加える力を

if (hit) {
     rigidbody.Addforce(force);
}

と書いても、おそらく思い通りの動作はしません。
これは、キックのように「一瞬で発生する大きな力」による影響を考える時、
Unityが用意しているdeltaTimeはあまりに長すぎる時間、だからです。
だから、瞬間的な力がある間だけAddForceをしようとしても、その瞬間を作り出せないと言う理屈です。
では、別なアプローチを考えましょう。

まず運動方程式を変形

f:id:nexusyuumilo:20180127150510p:plain

スライドにある通り、運動方程式を変形して
 F*dt=m*dv
の形を作ります(結果だけわかれば良い)
今までは加速度から速度を求めていましたが、
この式を使えば「力積」から、速度が求まると言う理解をしてください。

現実世界での2つの運動を考えましょう。

1:AddForceで計算できる、物体の落下運動(ボールを落とす)
2:AddForceで計算できない、物体の衝突(ボールをキック)
物体の運動を知りたい⇨速度を求めたい、ので、それぞれの運動の力積を求めましょう!

1、落下運動について

1の運動では、時間経過とともに
ボールにはこのような力が加わります。
f:id:nexusyuumilo:20180127145756p:plain

そして、力積の大きさはグラフが囲む面積と同じ値になります!これはとりあえずそう言うものだと思ってください。
f:id:nexusyuumilo:20180127145759p:plain

この面積を求めるにはどうすれば良いでしょうか?そう、積分です。
微小区間に区切って四角形を作り、その四角形をいっぱい足していきます。
f:id:nexusyuumilo:20180127145825p:plain

あれ、幅、でかくね...??
そう!微小区間に当たるdeltaTimeは、結構大きい値なんですよww
これは物理シミュレーションの限界です。でもまぁ、四角形に一致してくれてます!よかったよかった。

2、衝突について。

2の運動では、時間経過とともに
ボールにはこのような力が加わります。
f:id:nexusyuumilo:20180127145805p:plain
ボールを蹴った瞬間、めっちゃ強い力が加わります。
蹴ってボールに触れた瞬間はかすってる程度の力ですが、芯を捉えると強烈な力が加わっているのがわかると思います。

では、こいつの面積を積分して求めましょう。
f:id:nexusyuumilo:20180127145801p:plain
幅を小さくとって、いっぱい足します。
f:id:nexusyuumilo:20180127145804p:plain

いい感じですね。
これなら、FixedUpdate内で何度か F * deltaTimeを足し合わせることで力積=グラフの面積が求まりそうです。

でも、これはあくまで「dtがめっちゃ小さい、髪の毛一本よりも薄い幅」だった時の話です。
今、UnityのdeltaTimeはめっちゃ大きいと言いましたよね。

f:id:nexusyuumilo:20180127145812p:plain

はい、現実はこう。とてもじゃないけど、ずれが大きい。
こんなものを何回も足したら値がめちゃくちゃになるのは目に見えてます。
仮に F * deltatTimeを一回足したら?動きますけど、インチキではあります(笑)それでも”一瞬で加わる大きな力”を作り出せてはいませんからね。
しかも、Update関数内に書いてしまったら、環境によって挙動が大きく変わる可能性があります(Time.deltaTimeは可変のため)

結局、力を単純に足そうとしても、直接力積を求めようとしても、無理ゲーだったわけです。

じゃあどうするか?

ForceMode.Impulseの活用

f:id:nexusyuumilo:20180127152024p:plain

単純です。Addforceの引数に ForceMode.Impulseを加えるだけです。
これは内部的に、F*dtの計算を一切していません。だってdeltaTimeはめちゃ大きいからね。
つまり、
「力を直接足そうとしても、力積を求めようとしても、これらはdeltaTimeが大きすぎるために無駄だ!じゃあ、直接、運動量の変化から運動の様子を予測しよう!」
と言う感じです。

ちなみに、その「運動量の変化」mdxはどこから来ているかと言うと、

rigidbody.Addforce(force, ForceMode.Impulse)

のforceを、直接運動量の変化mdvとして扱っているようです。これは公式のスクリプトリファレンスに書いてありました。

docs.unity3d.com

"このモードでは、力のパラメーターの単位は、質量*距離/時間としてリジッドボディに適用されます。"

まぁ、要するに
「今までAddForce()の引数にしていた「力force」を、Impulseでは「運動量の変化」として扱うね」
ってことでしょうか。

まとめ

  • 衝突のような「一瞬」の出来事は特殊な処理が必要。
  • その特殊な処理はただ、ForceMode.Impulseを書き加えるだけ!

以上です。参考になりましたか?