Transform.position (rotation) と、
Rigidbody.position (rotation) は別物です。
ここを混同したままRigidbodyなどを使っていて、思わぬところでバグを生んだことがこれまで何度もありました。
通常のトランスフォームとRigidbodyのトランスフォームが別になっているため、Transform.position(rotation)を更新してもRigidbody.position(rotation)はすぐには更新されません(逆も同様)。
すぐに更新されないものはRigidbodyだけでなく、Collider.boundsなども同様です。
テスト用スクリプトをつくってみました。
スクリプトの中身
(※今回はtform.transformとrbody.transformは同じtransformを指しているのでアクセスを分けてるのは意味不明ですが、後で親子関係絡みのテストをするためにこうなってただけなので気にしないでください。
ヒエラルキー
Rigidbodyコンポーネントを持ったゲームオブジェクト(初期状態では(0,0,0)に置かれている)のTransform.positionを書き換えて移動させ、
移動後にTransform.positionとRigidbody.positionがどうなっているかを確かめるテスト。
これを実行すると、Transform.positionを移動しても、Rigidbody.positionの方はすぐには移動しないことが分かります。
また、コライダーのBounds(ワールド座標)なども更新されません。
移動直後と最初のFixedUpdateではRigidbody.positionは移動していない。
しかし2回目のFixedUpdateからは反映されている。
なぜこうなるのか?それはTransformの変更は物理演算のタイミングになるまで反映されないからです。
Unity公式リファレンスのPhysics.AutoSyncTransformsの説明には以下のようにあります。
Physics.autoSyncTransforms
Transformコンポーネントが変更されるたびに、Transform 変更を物理システムと自動的に同期するかどうか。
Transformコンポーネントが変更されると、 Transformへの変更に応じて、そのTransformまたはその子上のRigidbodyまたはCollider の位置変更、回転、またはスケールが必要になる場合があります。このプロパティを true に設定することで、 Transformに加えられた変更が正しいコンポーネントに自動的に適用されるかどうかを制御できます。 false に設定すると、同期はFixedUpdate中の物理シミュレーション ステップの前にのみ発生します。Physics.SyncTransformsを使用して、変換の変更を手動で同期することもできます。注: autoSyncTransforms が true に設定されている場合、Transform を繰り返し変更してから物理クエリを実行すると、パフォーマンスが低下する可能性があります。
https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Physics-autoSyncTransforms.html より(※Google機械翻訳、一部省略)
「FixedUpdate中の物理シミュレーションステップの前」と書いてありますが、1回目のFixedUpdateではまだ更新が通ってないことと合わせて考えると、
おそらく下の図でいうところの「Internal physics update」の中あたりで同期されるのでしょうか?あくまで推測ですが・・・。
このように、本来は物理演算アップデートのタイミングでしか行われない同期処理をすぐに呼び出したい場合、二つの方法があります。
TransformからRigidbodyへのトランスフォーム変更を常に即時反映させたい場合、
その方法の一つは、Project SettingsのPhysics設定にある「Auto Sync Transforms」を有効化することです。
これにより、移動直後からRigidbody.positionにも反映されるようになりました。
しかし、このやり方だと、前述の公式リファレンスの説明にもあるように、
Transformに変更が加わるたびに毎回Rigidbody等への伝達が行われるので、パフォーマンス的に無駄が出てしまいます。
もう1つの方法として、Physics.SyncTransforms()を呼び出すことで、即時同期したいときだけSyncできるようになります。
即時同期されるようになった
とはいっても、そもそも物理演算のリジッドボディはFixedUpdate内でAddForceしたりMovePosition等を使って動かすもので、transformを直で書き換えたりすることはほとんどないはず。公式でも「Rigidbodyではtransformを直で書き換えしないでください」みたいな感じの扱いだったはずです。
しかし、これまで使っていてtransformを書き換える方が実用的だと思ったのが「親ゲームオブジェクトの下にまとめられたRigidbody群を丸ごとどこかにワープさせたいとき」です。
Rigidbody同士を結び付けているのは実際にはヒエラルキー/Transform上の親子関係ではなくてJoint接続であるため、Rigidbodyは理想としては親オブジェクトなしでシーンに直で散らばっている方が良いのですが、それでは色々と管理が大変です。
そのためフォルダー代わりのゲームオブジェクトでまとめるということをよくやっていました。
しかし、ヒエラルキー上の親子関係と実際の接続関係が一致していないRigidbodyゆえ、親オブジェクトをワープさせても子オブジェクトのRigidbodyはその場に取り残されてしまって問題になります。
(そして物理演算アップデートの一瞬で、長大な距離を隔てたJointフォースがかかって吹っ飛んだり・・・)
しかしこのように、オブジェクト自体ではなく、それを子として持つ「フォルダー代わりの親オブジェクト」をワープさせてからSyncTransforms()したときにも、子のRigidbodyにSyncの影響が即時に及んでくれるのでしょうか?
答えはYES。試したところ問題ありませんでした。AutoSyncTransformsまたはPhysics.SyncTransformsは、親子関係上の子のRigidbody等にも即時伝達されます。
上とスクリプトは同じですが、Transformコンポーネントしかもっていない親オブジェクトの子としてリジッドボディをアタッチしてテストしてみました。
Parentの位置は(0,0,0)にしてあります
Rigidbodyの位置は(1,1,1)としました。
親Transformが(0,0,0)から座標(1,2,3)に移動されたので、Rigidbodyの位置は親の(1,2,3)+(1,1,1)で(2,3,4)となりました。
親のTransform変更が子のRigidbody.positionにも即時伝わっていることがわかります。
最初にも述べましたが、この同期処理を行うまで同期されないものはRigidbodyだけでなく、Collider.boundsも同様です(他にもあるかも)。