Spriteと衝突判定

DXRuby1.4で導入されたSpriteクラスはほぼデータ保持用という作りになっている。かろうじて描画機能があるが、結局のところ通常の描画機能と比べて描画用のパラメータを引数で渡すか、オブジェクトが保持している値を使うかの違いでしかない。この部分に関してはRubyで書いても別に問題は無いのだが、それなりの規模のゲームを作ろうと思うととりあえずそういったクラスを作ることになるだろうから、それだったらライブラリが提供してあるやつをみんなが使ったほうが効率としてはいい。導入の動機はそういう感じである。
とはいえそれではさすがにちょっとアレなので衝突判定機能を気合入れて作りこんである。

今回はSpriteと衝突判定について。


なにはともあれSpriteについて理解を深めよう。Spriteが保持するC構造体は以下のものである。

struct DXRubySprite {
    VALUE vx;
    VALUE vy;
    VALUE vz;
    VALUE vimage;
    VALUE vtarget;
    VALUE vangle;
    VALUE vscale_x;
    VALUE vscale_y;
    VALUE vcenter_x;
    VALUE vcenter_y;
    VALUE valpha;
    VALUE vblend;
    VALUE vvisible;
    VALUE vshader;
    VALUE vcollision;
    VALUE vcollision_enable;
    VALUE vcollision_sync;
    int vanish;
};

ほぼ全部VALUE型。Rubyから設定された値をそのまま持つだけ。代入するときに型チェックもしていない。なぜそうなっているのかというと、おそらくFloat型の値が代入されることが多いだろうということでそれらの変換や取り出し時のオブジェクト生成負荷を排除するためだ。描画などで使うときに変なものが入っていたら例外が発生する。
また、これらはインスタンス変数にはなっていないから、Spriteを継承してもメソッド呼び出しでしか代入・取得ができない。このへんは仕様として悩みどころではあったが、C側からインスタンス変数をアクセスするのは大変遅く、単発のアクセスならいいが全オブジェクトの情報を一気に参照する衝突判定でそれはかなり痛いという理由、つまり実装の都合でこうなった。俺個人としては別にめっちゃ使いにくいということも無いからあまり問題は無い。Spriteが持ってるインスタンス変数と、自分で追加したインスタンス変数がごちゃまぜになるのは気持ち悪いし、追加するときに同じ名前のインスタンス変数がSpriteに既にあるかどうかを気にするのも面倒だ。

さて、衝突判定は機能としてはSpriteの一部なのだが、Spriteとは別にCollision.[ch]というソースにわけてある。単純に関数がでかいからで、そのへんをここにまるごと上げると大変なことになってしまう。まあ、抜粋する方向で。衝突判定の計算はとても数学的なもので式をマクロにして作ってあって、それを説明するのはこの記事の趣旨から外れる。そのあたりの計算についてはWeb上にたくさん出ているので適当に検索するとよい。

まずはアーキテクチャ的なところの概要から。
処理の順番は以下のようになる。
(a) 判定対象のAABBボリュームを計算する。
(b) AABBボリューム同士で判定する。
(c) 当たってたものについて詳細の判定をする。
なんというか、普通である。これを指定された全オブジェクト同士の組み合わせについて行う。
AABBボリュームというのは、あるオブジェクトの衝突範囲をすべて含む最小の回転していない矩形のことだ。範囲そのものが回転していない矩形だった場合、AABBボリュームは範囲と同じになるが、例えば45度回転しているとそのぶん大きくなる。円や三角形だった場合もそれらの図形を囲むように大きな矩形が作られる。このようなことをする理由は、当たっている可能性があるかどうかをザックリと判定してから詳細の判定をしたほうが効率がいいからである。ザックリ判定の軽さと、詳細判定の重さがある程度の比率以上であれば価値がでてくる。形状が複雑だとか、3Dだとか。DXRuby1.4のSpriteでは複数の形状を組み合わせた衝突判定範囲を作ることができるから、それらをまとめたAABBボリュームを作るのは有効な負荷対策になる。
ちなみにそういった処理のことを枝狩りと言って、AABB以外にも回転した矩形で判定したり、最小の円を作ったりと色々なやり方がある。扱うオブジェクトの形状や複雑さにより、枝狩りのコストを上げてでも詳細判定を減らしたいときにはそういった方法のほうがよい場合がある。更にはオブジェクトが存在する場所により判定する必要の無いものを省略するとか、その場所の判定方法、区切り方なども先端のほうでは色々と考えられているらしい。
DXRubyではそもそも描画できる数がそんなに多くないし2Dだから、そこまでは必要ないだろうということで、とりあえずAABBで全パターン判定してしまっている。これで遅いという問題がでてくるようなら考える。

DXRubyでは単体の衝突判定範囲をCollision構造体、まとめたものをCollisionGroup構造体として扱っている。これらの関係とデータの持ち方は少々ややこしいことになっているが、これはあとからCollisionGroupを追加したからである。はじめからそれを考慮していればもう少しマシになったかもしれない。とはいえ多少マシになったからといってどうにかなるような気もしない。
次回はそのへんに踏み込んでみる。