この記事は、AmusementCreators AdventCalender 通称 ACAC 16日目(の予定だった)記事です。(テスト期間という言葉にかまけて全く手を付けていなかった不届き物です。)
担当は AmusementCreators 2年のランです。
当たり判定って何?
「当たり判定」という考えは、ゲーム特有のものだと思います。現実世界ではこのような言葉を使う場面に遭遇することはなかなかないでしょう。
「当たったかどうかを判定する」
この一点に尽きるわけですが、現実世界では……
「この石に当たり判定はないからすり抜けるよ」
というホラー現象は起こらない。現実世界の物理的なルールで、物と物はぶつかってしまうのです。
この、ぶつかるというイベントを起こすためだけに、ゲームでは当たり判定を使用します。
当たり判定
↓
当たった
↓
当たった時のイベント処理
というのが、当たり判定を導入し、使用したときの大まかな処理になるでしょう。
今回は、この当たり判定について自分なりに試してみたものを書いていこうと思います。
Altseedでの当たり判定
Altseedには、すでに当たり判定をつけるためのサンプルコードがあります。
実際にコードをコピペして動かしてみると、円に当たり判定がついている様子がうかがえると思います。図形が重なると色が変わるのでとてもわかりやすいです。
円と円、円と四角形、円と三角形、他の図形同士の当たり判定も実装されているようなので、幾何学的に「この直線と点の距離は~」「半径がこうで~」とか考える必要がなくてとても楽です。
横スクロールゲームなどで、「障害物に当たり判定イベントをつけたい」というときによく使えると思います。
互いに動いている物体の当たり判定
このサンプルでは静止している物体との当たり判定を実装できるわけですが、果たして互いを動かしたらどうなるのか気になりますよね。
互いに物体を動かすと「すりぬけ」が生じる?
現実世界では決してありえないすりぬけですが、ゲームを作っているとわりとありがちです。
Altseedでカバーしている当たり判定は、実際にはこう言い換えることができるでしょう。
「物体が互いに重なっているかの判定」
しかし、ゲームではその限りではありません。
「物体は互いに重なっていないが、実は重なっていた」
そんなことが起きてしまうことがあります。
これを私は「すりぬけ」と思っているのですが、「すりぬけ」が生じる主な原因は、プログラムが作る世界では、時間が連続でないという点にあります。
これの説明として、アニメを例にとってみるとアニメは連続した動きを見せてくれるように思えますが、その連続した動きは、有限個の絵がパラパラと変わることで実現しているだけです。その間は脳が勝手に補間してくれます。
ゲームも同じで、パラパラと次のプログラムを動かすことでゲーム上の物体を動かします。つまり、ゲーム上の物体はコマ送りで動いているに過ぎず、**「コマとコマの間でのみ物体が重なっていた」**という場面を認識することができないのです。
解決策1: コマの間隔を短くして、コマとコマの間をできるだけ小さくする。
Unityで物理エンジンを使用していたりするとこの考えがしっくりくるのではないでしょうか。
Altseedは、基本的な使い方をしていると、1つのプログラム上のループが1つの描画コマに対応するので、描画して互いに重なっている場合は当たった。という判定になると思います。
しかし、描画するコマ数よりも、プログラム上のループ数は多くして、描画されないコマでも当たり判定だけは考えることができると思います。(描画出力には結構なコストがかかりますが、出力をしないプログラムだけならコマ数増やしても大丈夫だろうという考え)
これによって、**「コマとコマの間でのみ物体が重なっていた」**という場面は、コマ数の増加に比例して少なくなると思われます。
しかし、これをゲーム上全てのオブジェクトに対して実行するのはいかがなものか。と考え、「当たりそうなところだけコマ数増やして当たり判定をする」というところに持っていきたいと思ったり。
解決策2: 物体の動きが速くなりすぎないようにする
コマとコマの間隔を狭めるよりも前にやることがあるでしょ。という感じの解決策。
ゲームだから物体のスピードをいかようにもできてしまうわけですが、それを抑えて、いい感じに当たり判定をしてくれるスピードに落とし込むのも手かと思います。物理的には、空気抵抗などの抵抗力を仮想的に混ぜ込んであげるとスピードが抑えられて、とても良くなると思います。
結局、どっちの解決策も取り入れてみる
当たり判定の不自然さをちまちまとデバッグしつつ、両方の解決策を混ぜ込んでいく方向に落ち着きました。
実際の実装はどうなのか
四角形を操作して、飛んでくる球を打つ。というゲームの原型をつくってみようということで、これまでの理想論をもとに実装していくことにしました。ちゃんとしたゲームにできなかったのは完全に自分の力不足です……。
ぶち当たった点を思い出しながら挙げていき、大雑把に回顧していこうと思います。
当たり判定がなぜか効かない問題
Altseedの当たり判定がさっきと違って全く動かない!サンプルと違う!なんで!
原因: 円ではなく四角形をマウス操作にしたから。
これはバグなのかわかりませんが、四角形の当たり判定は、DrawingAreaを絶対座標として疑わない模様。つまりオブジェクトのPositionを参照してくれないので、
「マウスを動かしたときに、四角形の当たり判定の範囲が一緒に動いてくれなくて困る。」
という話。
解決策: 自分で当たり判定を実装する。
辛いけど自分で実装する。でも描画範囲はPositionに対応してくれているようだったので、そのまま矩形描画に使用しました。
四角形、円との衝突イベント(反射)がうまくいかない問題
おいおい、円の反射の仕方おかしくないか?
原因: 四角形の当たる面によって、円に加えるべき力の方向が変わる。
**「当たったら円の速度反転させるだけでいいでしょ」**と安易に考えた結果の問題。ここで、浮彫になったのは、Altseedの当たり判定用ツールを使用しても、当たった面の情報は獲得できない点。Altseedでブロック崩しを実装するのは向かないのではなかろうか……。
解決策: 当たった面を把握して、面に対する円の速度を反転させる。
四角形が静止している場合はだいたいこれでうまくいったので良しとします。概ねできている感じでいい。
四角形の角付近で、円の反射が怪しい問題
あれ、角から円が四角形内部に侵入してる……?
原因: 四角形と円の当たり判定は、面と円だけではなかった。
角と円の当たり判定もあったんです……。これに必要なのは角の座標で、円と円同士のような当たり判定を実装しなければなりません。
解決策: 当たり判定の範囲はそのままに、挙動だけを変える作戦。
角の座標をわざわざ取って別の当たり判定をするのが非常に面倒に思えてしまい、当たり判定の範囲だけそのままにしてしまいました。ここで考えを変えて、角の当たり判定の範囲を**「2つの面と当たっている場所」**とし、角の場合はナナメ方向の面があると仮定して、円の反射挙動を作りました。意外と自然な挙動になったので満足しています。
四角形を大きく動かすと、円がすり抜ける問題
円をでっかく打ち上げるぞ! ……すり抜けた?
原因: 円の反射挙動に、四角形の位置が考慮されていない。
当たり判定云々の前に、物体同士がすり抜けるなどあってはならないこと。うまいこと、四角形の位置に応じて円の位置も変えてあげる必要がありそうです。
解決策: 四角形によって円が押し出されるように、円の動きを四角形の動きに同期させる。
この辺は試行錯誤しまくっていてコードを見返しても良くわかっていないので困りものです。
理想としては、当たったとされる時点での、**「円の四角形に対する相対座標」**を保持して、円が四角形をすり抜けてしまう場合に応じて円の位置を更新するといった感じです。この辺はデバッグしながら「ここだけはなんとかしたい」という点を見つけていくしかないように思えました……。
できあがったもの
一応githubにコードだけ挙げています。
https://github.com/gmcgsokdeuvmt/articles/tree/master/gamejam4
Altseed用のバイナリ等を配置すれば実行することもできます。
EventManagerとかいうクラスでひどいネストになってるのをどうにかできないものかと思ったりします。
以上、Altseed-C#でなんちゃって当たり判定でした。