Unity では描画フレームレートと物理演算フレームレートが別になっています。
これは例えば、描画はなめらかにしたいが、物理演算は適当に手を抜きたい…といった場合、物理演算の回数を減らすことで負荷の軽減を図る事ができるでしょう。
…と、思っていました。そう、この現象を見るまでは。
いつのまにか、自機の動きがカクついていた
まずはこちら。カクつかない映像です。
次はこちら。中央のオブジェクト(自機)がカクついているのがわかるでしょうか。
わからない(というより気にしない)人もいるかもしれません。でも、気になっちゃう人はどこまでも気になる。そんなカクつきです。
困ったことに、コマ送りで表示してみるとカクつかないというオマケもついています。
原因を考える
思い当たるフシがあるとすれば、カクついてない頃は transform.position を直接変更して自機移動(それにカメラを追従)していました。
カクつきだしたのは、それを Rigidbody.AddForce に変更してからです。
(実際そうだったか確認するために、新旧2つのプロジェクトを比べてみました)
transform.position は描画フレーム、Rigidbody.AddForce は物理演算フレーム
Project Settings > Quality > V Sync Count
を Don't Sync に設定、その後スクリプトで直接 Application.targetFrameRate = 60; と設定した場合、描画フレームが 60fps となります。
ですが物理演算フレームはこれとは別の初期値になっています。Project Settings > Time > Fixed Timestep
は通常 0.2 となっていて、こちらは 50fps で処理されていることになります。
左から右に時間が経っていると考えてください。
このうち、赤い部分だけ、見た目と物理演算(移動)にズレがあります。
描画6回目と物理演算5回目で一致するのでだいたい 0.16 秒ごとにズレが補正されますが、それでも誤魔化すことは出来ず、動画のようなカクつきとなってしまうわけです。
対処法
このカクつきを完全に修正する方法はありませんが、いくつか手段を示します。
Rigidbody を使わない(無理)
自前でコリジョンを実装し、Update で処理を済ませるならば完全一致させる事は可能です…が、これは現実的ではありません。
FixedTimeStep を 0.1666666 にする
描画が 60fps なら 物理演算も 60fps にすればいいじゃん! というわけで近似値を入れます。
この修正でだいぶカクつきを抑えることができます。
30fps のゲームであれば 0.3333333 を入れればいいでしょう。
近似値にすることが出来るだけで、完全に一致するわけではない事に注意してください。これは unity の場合、システム構造的に避けられないようです。
Rigidbody の Interpolate を変更する
通常 None ですが、Interpolate にすることでオブジェクトの挙動が補完されるようになります。
また、Extrapolate を使うことで更に補完機能が強化されます。
ただし、これは処理コストが増加する諸刃の剣です。
本当に必要なオブジェクトのみ(例えば自機のみとか)に適用することをおすすめします。
それほど効果を感じられないかも
それ以外にも気にしておく事
移動値には Time.deltaTime を掛けておく
移動値には必ず Time.deltaTime を掛け、描画フレームがどうなろうとも一定の移動値になるよう調整してください。
これを忘れるとカクつきもそうですが、昔のビデオゲームのようにフレーム落ちした途端、全ての速度がスローモーションになってしまいます…。
カメラの移動を LateUpdate に記述
Rigidbody.AddForce とカメラの移動を同じ Update(やコルーチン)で行うと、まだ Rigidbody.position の値が更新されていないためか、若干カクツキが残ります。
おそらく Update > カメラの移動 > 物理演算
となってしまうせいかと思うのですが、それならとカメラの移動を LateUpdate に変えたところ、カクつきは緩和されました。
AddForce に入れる Vector3 の値を Update でスタックしておき、AddForce は FixedUpdate で呼び出した方が理には適っているかもしれません。