半月ほど前に関西のUnityクリエイターズの勉強会がありました。
その勉強会のLTにて、Unityで2Dアクションゲームのコリジョン関連で困っているという方の話がありました。
それは一大事だ。
よっしゃ、ここはこのおっさんが一肌脱ごう。
Unityの知識も2Dアクションゲームの知識もさしてないけど、運がよければなんとかなるかもしれない!
さっそく状況が確認できるサンプルを作ってみましたよ。
丸が操作するオブジェクトで、左右の方向キーで移動、上の方向キーでジャンプが出来ます。
いくつか丸が乗れる床のオブジェクトがあって、動く床のオブジェクトもあります。
2Dアクションゲームの基本が詰め込まれています。
さてここで本題です。
コリジョン関連で困っているというという点はどこなのか、ということです。
丸を操作して、動く床のオブジェクトの上に乗っけてみましょう。
ちなみに丸は、空中にいるときは赤色、地面に着いているときは青色になり、状態が視覚的にわかるようになっています。
床が上昇中の場合は特に問題はないのですが、問題なのは下降中のときです。
丸がすげぇ点滅してますね。
どうやら床に着いたり離れたりを繰り返してしまっているようです。
丸はRigidbody2Dの重力値を利用して、下降しています。
床はMonoBehaviourのUpdate内にてTransformのPosition値を変更することで移動しています。
ここでUnityマニュアルのイベント関数の実行順を見てみましょう。
丸は、"Physics"内の"Internal physics update"にて位置が更新されます。
床は、"Game logic"内の"Update"にて位置が更新されています。
なるほど、これでは丸が動いた後に床が動くのでうまくいきませんね。
では床の位置更新はFixedUpdateにて行うようにしましょう。
これで解決できるかも知れません。
……うまくいきません。
ではLateUpdateならどうか。
コルーチンを利用して WaitForFixedUpdateやWaitForEndOfFrameならどうか。
……全てうまくいきません。
うーむ、なぜなのでしょう。
丸と床のフレーム毎の移動量が問題なのかもしれません。
丸が床に着いたまま移動するには、1フレームの移動量が、
丸の移動量 >= 床の移動量 でなければなりません。
床は等速運動で動きます。
丸は重力値が毎回加算されていくため、等加速度運動になります。
丸の移動量が床の移動量を超えるまでは、丸は床から離れていき、
丸が加速して床の移動量を超えると、丸や床に近づいていき床に着くと。
ふーむ、なるほど。
などと学生時代に物理の科目で赤点を取っていたおっさんが、
何やら最もらしいことを言っているわけですが、これでは問題が解決しません。
そもそも、そんな小難しいことを考えなくてもよいのではないのだろうか。
そうだ!
丸が床についた瞬間に、丸を床の子オブジェクトにすればよいのではないだろうか!
ゲーム画面上では違いがよくわからないですが、UnityEditorのHierarchy上では、
丸のGameObjectが、いろんな床のGameObjectの子としてせわしなく移動しています。
この状態が視覚的にわかるように、画面左上に丸の現在の親GameObject名を表示しています。
見事問題が解決されましたね。
いやぁ、よかったよかった。
ちなみにこのやり方の問題点として、
・操作するオブジェクトがRoot上(あるいは指定のGameObject内)になければならないシステムでは使えない。
・床のオブジェクトが消えたり破棄されたりするギミックがある場合、それに乗った状態だと乗ったオブジェクトにも影響が出る。
・そもそも問題の解決法としてなんか雑である。
まあ、いいじゃないですか。
おっさんにはこれぐらいが限界なのですよ。
こんなやり方もあるのかー、ぐらいの参考していただければこれ幸いなのです。