Chipmunkのエッセンス(6)

物理演算ライブラリであるChipmunkの用途は、まあ、物理演算することなのだが、画面内に物体を出現させてそれをぶつけて眺めるだけではゲームにはならない。ゲームであれば、例えば衝突したときにダメージを受けて壊れるとか、アイテムを取得するだとか、何かしらの物理演算以外の反応が必要になる。それに、敵が弾をばら撒いたりした場合に、敵の弾同士がぶつかって変な動きをされても困るし、敵が敵の弾で死んでも困る。そういう制御も必要である。ともあれ、物理的な動きの計算以外の色々なことができてほしい。
Chipmunkには、そういった諸々のユーザの要求をサポートするための機能がある。様々な機能があって、それぞれ柔軟な仕掛けなのできちんと理解して応用していかないと思ったものは作れない。今回からその辺を見ていこう。
ところでChipmunkのRubyバインダには困ったバグが存在していて、昔からあるバグが1件、最新のやつの修正で発生したやつが1件、発見したので直したやつをWikiに置いておいた。うちの開発環境の都合によりRuby2.0用のみだが。

■デフォルト衝突ハンドラ
最も基本的な衝突検知の手段として、デフォルト衝突ハンドラをブロック渡しで設定する、というのがある。これを使うとChipmunkのSpace内での衝突すべてに対して、衝突したShapeAとShapeB、衝突情報であるArbiterオブジェクトが渡されてブロックが実行される。まずはここからいってみよう。
デフォルト衝突ハンドラはこのように使う。ブロックの引数のaとbが衝突したShapeオブジェクト、arbがArbiterオブジェクトである。arbには「何と何がどこでどんなふうに衝突した」という細かい情報が格納されているので、便利に使える。

contact_image = Image.new(3, 3, C_RED)
space.set_default_collision_handler do |a, b, arb|
  arb.points.each do |c|
    Window.draw(c.point.x - 1, c.point.y - 1, contact_image, 100)
  end
  true
end

このコードは衝突点に赤い小さな四角を描画する。Arbiter#pointsは衝突点情報(CP::ContactPointオブジェクト)の配列を返す。これのpointメソッドが衝突点のグローバルな位置を返す。丸い物体だと衝突点は1つだが、Shape::Polyで作った矩形などの場合は2つ以上返る場合がある。
カーソルキーで上下左右に自由に動けるようにしたサンプルコードに組み込んでみるとこうなる。動かして壁にぶつけてみると衝突位置に赤い四角が見えるはずだ。

require './cp'

# Spaceオブジェクトを作る
space = CP::Space.new

# 箱を作る
box = CPBox.new(100, 100, 30, 30, 1)
box.shape.e = 0.2 # 弾性(0.0〜1.0)
box.shape.u = 1.0 # 摩擦(0.0〜1.0)

# 壁を作る
wall1 = CPStaticBox.new(40, 400, 599, 479)
wall1.shape.e = 0.2 # 弾性(0.0〜1.0)
wall1.shape.u = 1.0 # 摩擦(0.0〜1.0)

wall2 = CPStaticBox.new(200, 300, 400, 320)
wall2.shape.e = 0.2 # 弾性(0.0〜1.0)
wall2.shape.u = 1.0 # 摩擦(0.0〜1.0)

# Spaceに追加
space.add(box)
space.add(wall1)
space.add(wall2)

# 箱の移動処理
box.body.velocity_func do |body, gravity, damping, dt|
  body.v = CP::Vec2.new(Input.x * 120, Input.y * 120)
  body.w = body.w * damping
end

# デフォルト衝突ハンドラ
contact_image = Image.new(3, 3, C_RED)
space.set_default_collision_handler do |a, b, arb|
  arb.points.each do |c|
    Window.draw(c.point.x - 1, c.point.y - 1, contact_image, 100)
  end
  true
end

# Space#stepで時間を進める。引数は秒。
Window.loop do
  space.step(1.0/60.0)
  box.draw
  wall1.draw
  wall2.draw
end

デフォルト衝突ハンドラは真偽値を返し、false(or nil)だとChipmunkの衝突処理を省略する。この中で条件判定を行って、衝突計算をして欲しくない場合にはfalseを返してやれば、それらは衝突せず素通りすることになる。
ちなみに、発生していたバグの1つはこの衝突ハンドラの処理で、ブロックが2回呼ばれていた。

■おしまい
ArbiterオブジェクトはChipmunk内で生成された衝突情報のC構造体をRubyオブジェクト化したもので、この情報はSpace#stepすると書き換えられるので、Rubyオブジェクトとして保持しておいても使い物にならないし、下手したらSEGVで落ちたりするかもしれない。ブロックの中だけで使うように気をつけよう。