2Dゲームを作るにあたり、頭を悩ませることになるのが坂道の実装である。坂の形状との交差判定自体は別に何も難しくない。 坂を滑らかに登ったり下りたりできるような挙動の実装が難しいのである。
坂道のない、矩形のブロックのみの衝突判定については別の記事で詳述しているので、まずはそちらを読んで欲しい。 (リンク) この記事で解説した「座標軸別移動衝突法」をベースに説明する。
考慮すべき問題
主に3つある。 順に見ていく。
1. 座標軸別移動衝突法と坂道判定の相性について
座標軸別移動衝突法は、縦軸と横軸の移動・衝突を分離することで実装を単純化するテクニックであった。これと坂道の実装の相性はあまりよくない。 坂道というのはそもそも横移動を縦移動に変換するものだからである。完全な分離は成立しようがない。
まず、愚直に座標軸別移動衝突法を適用しようとすると、以下のようになる。
- 坂道を歩いて登ろうとする。(横軸移動)
- 押し戻される。(横軸衝突判定)
これでは登れない。
なので、解決策としては「横移動でも坂にめり込んだ場合は縦に押し戻す」という例外的な処理を働かせるしかない。
が、注意深い人は「そんなことをして問題ないのか」と訝しがることだろう。
~なぜそんなことをしていいのか、という理屈の整理~
考え方だが、縦軸と横軸の完全な分離は諦め、縦軸と横軸の値の依存関係を意識することにする。
改めてベースの座標軸別移動衝突法を確認すると、以下のような処理である。
- X軸の移動
- X軸の衝突・位置補正
- Y軸の移動
- Y軸の衝突・位置補正
この元々のアルゴリズムにおいて、X軸の移動衝突とY軸の移動衝突の順序は入れ替えてもほぼ何の違いもないものであった。しかし坂道においてはそうはいかない。
平地を歩くと横の移動だけが起こるが、坂を歩くと横だけでなく縦の移動も起こる。それはつまり、「X軸の衝突・位置補正」のパートにおいてY軸の変化も起こるということである。
この結果を勘定に入れて衝突・位置補正を行うためには、必ずX軸の移動衝突→Y軸の移動衝突の順でなければならない。
Y軸の移動衝突計算はX軸の計算に依存することになる。これを念頭に置けば、今やろうとしていることは「計算順序の固定と引き換えに、X軸補正パートでのY軸移動を正当化する」行為として理解できると思う。
~気になるであろう点~
- Q. 「縦移動が横移動に変換される」ことは考えなくてよいのか?
- A. 両方考えると単なる処理順序だけでは整理が付かなくなるので、シンプルな実装で済ますには考えない方が良いと思う。
例えば滑る地面を作るとすると、坂道を重力で滑り落ちるみたいな挙動も作りたくなってくる。しかしそれは「坂道の上に立っている」フラグなどを特別に用意して、最初から横方向の力が働くものとして実装した方が楽なのではないかと思う。
縦速度→横速度/横速度→縦速度という変換を同時に考えるところまでやろうとすると、まともな物理エンジンに近い実装が必要になってくる。そうした解説記事は世にそこそこあるし、初心者が気軽に実装・デバッグできる内容を目指している本記事としては望むところではない。
ただ一つ言っておくと、アクションでなく例えばRPGのフィールドやシューティングゲームのステージのような場面であれば「重力で下方向に落ちる」とか「歩くのは横方向」みたいな前提はない。処理順を横軸→縦軸とする必然性はないし、もう少し違う実装もありうると思う。
2. 下り坂で地面に貼りつくようにする
坂道判定をナイーブに実装すると必ず発生するのが下り坂を降りるときにガクガクする問題である。
下り坂を真横に移動すれば、必ず宙に浮く状態が発生する。これが曲者である。下り坂を降りる最中常に接地しているためには、必ず工夫がいる。自分が知る限り、主に3つの方法がある。
- 坂をなだらかにする。または、重力を上げる。重力だけで十分接地が発生するようにする。
- 特別な実装が(ほぼ)要らないという意味で楽な方法。
- 重力、移動速度、坂の角度のパラメータ設定に制約が生じるものであるから、ゲーム設計の自由度は低くならざるを得ない。
- 通常時は重力を極端に高くして、ジャンプ時のみ重力を低くするという実装も考えられる。
- 直前のフレームにおいて坂に接地していた場合、歩行時の移動方向を真横ではなく斜め下の向きとする。
- 坂の角度は自由に設定できる。坂よりきつい角度で下りるようにすれば、必ずめり込むので接地判定が成立する。
- 坂の下が平らな床になっていれば良いが、途中でプツリと切れている場合は下がりすぎることになり、やや挙動がおかしくなる。
- 坂よりきつい角度でさえあれば良いので、別に必ずしも坂の角度を計算に入れる必要はない。例えば「45度よりきつい坂はなく、自機の1フレームでの最大横移動は0.1マスである」としたら、問答無用で1フレームに0.2マス下げることにすれば必ず45度よりきつい角度で降りてくれる。衝突判定で位置調整は勝手に済まされる。
- 直前のフレームにおいて接地していた場合、下の地面を探して存在すれば接地させる。
- 言ってしまえば、2.の方法に加えて「下移動をしても地面にめり込まないなら普通の真横移動に戻す」という処理を加えた実装。
- 坂の角度は自由に設定できるし、坂が途切れていても問題ない。ゲーム設計の自由度は高い。今のところ自分が辿り着いている中で最善の方式だと思っている。
3. 上り坂で突っかからないようにする
上り坂を上っている最中にマスの境目を跨ぐ場合について考えよう。
まず、横移動をする。
次に衝突判定を行う。このとき安直な方法で判定をしてしまうと、どうなるだろうか。
なんと坂の下の壁マスに阻まれてしまうのである。これでは滑らかに上り坂を登れない。
この問題の解決策もいくつか考えられる。
- 自機の下部が坂にめり込んだ場合だけでなく、他の部分が坂にめり込んだ場合でも乗り上げるようにする。
- これは以下のような地形に横からぶつかったときにも乗り上げてしまう問題がある。これは本来なら壁に阻まれてほしいところだろう。 「直前のフレームで接地していた場合」という条件分岐を加えれば解決する。ただしやや実装が面倒。
- 壁ブロックの判定の大きさを、横衝突判定時のみ縦に小さくする。
- これは図で見ないとピンとこないかもしれない。以下のように判定を小さくする。
- 縦衝突判定では通常通りに判定するので、めり込んだりはしない。
- といいつつ、2つのタイルへの乗り上げ判定をする必要があるので、矩形ブロック判定→坂判定の順でそれぞれやらないと1fでちゃんと乗り上げない気がする。
- 横から当たると、当たり方次第でブロックに乗り上げたり押し下げられたりする。
十字型(ダイヤ型)判定
ここまでの例では基本的に「主人公は矩形の判定である」ことを前提にしていたが、結構採用例があるのが「十字型(ダイヤ型)」判定だ。 言及しない訳には行かないのでこれも説明する。
これは、主人公の判定を矩形ではなく足元の1点+横の2点で行う方法である。
十字型判定の利点1: 実装の簡易さ
矩形判定では、例えば接地を判定するには左下と右下の2点(ないしそれを結ぶ直線)を考える必要があったが、十字型判定なら1点でよい。
十字型判定の利点2: 立ち位置の問題
矩形判定では、以下のようにグラフィック次第で違和感のある立ち位置になる場合がある。
角の部分で坂に接しているので主人公はあたかも体の端で全体重を支えているかのようになってしまう。 これはあくまでグラフィックの問題なので表示の仕方で対応は出来るだろうが、十字型判定であればそもそも中心軸で坂に接することになる。
深い工夫をせずとも比較的違和感のない立ち方になる。
十字型判定の欠点: すり抜けの危険
大きさを持たない1点の判定はすり抜けを生じやすい。ブロックの境目の挙動には細心の注意を払う必要がある。
十字型判定の欠点: 通常の矩形ブロックとの兼ね合い
矩形判定をベースとしていれば、「ブロックの端に立つ」という挙動を期待したくなる。
しかし、十字型判定ではそのような立ち方は出来ない。左右にずり落ちることになる。
対策として以下のような判定も考えられる。
- 主人公~坂道間の判定は常に十字型判定とする
- 坂道に乗っているときのみ通常のブロックとの間も十字型判定とする
なお、マリオ3は「通常は矩形判定、坂道の存在するステージでは完全な十字判定とする」というストロングな解決策を取っている模様だ。(参考)
最後に
この記事で挙げた実装方法は、あくまで自分がネットで見つけられたものと自分が思いつけたものに限られる。
坂道の実装方法は他にも思いつきようがあると思うので、色々試してみると良いと思う。ただバグらせやすいので、仕組みを自分で整理してどこでなぜどういう挙動をするのか把握しておいた方が良いのは間違いない。
先行研究など
- 2Dプラットフォーマー実装ガイド - 2dgames.jp
- 2D 斜め坂あたり判定 - 猫と狐と最上川
- 坂道の当たり判定 - NGBlog
- アクションサンプルプログラム(坂道あり) - DXライブラリ置き場
- アクションゲームの斜め坂の実装方法 - ゲームプログラマーを目指すひと