Chipmunkのエッセンス(8)

Wiki野良ビルドChipmunkを最新にしておいた。これでブロック渡しのCollisionHandlerの引数が1個〜2個の場合にもきちんと動作するはずだ。
さて、今回は衝突ハンドラのもうちょっと凝った使い方の話である。

■衝突ハンドラのオブジェクト渡し
衝突ハンドラはブロック渡しの書き方で説明してきたが、ブロックじゃなくオブジェクトを渡す書き方もある。add_collision_handlerメソッドの第3引数はブロックもしくはオブジェクトを受け付ける。Space#add_collision_handler(type1, type2, collision_handler_object)という呼び方ができる、ということである。
この第3引数に渡す衝突ハンドラオブジェクトは、

class CollisionHandler
  def begin       # 当たっていなかったオブジェクトが当たったときに呼ばれる
  end
  
  def pre_solve   # いまからaとbの衝突反応計算を始める(ブロック渡しはこれになる)
  end
  
  def post_solve  # 衝突計算したので衝突後の処理をどうぞ
  end
  
  def separate    # 離れたときに呼ばれる
  end
end

というクラスのオブジェクトで、このクラスから生成したオブジェクトを渡してやれば、4種類の呼び出しタイミングでメソッドをそれぞれ呼び出してくれる。呼び出しタイミングはコメントに書いた通りで、メソッドの引数は衝突ハンドラのブロックと同じ(0〜3個)である。
と、マニュアルには書いてあるのだが、書いてないことも多い。
・add_collision_handlerに渡したタイミングで定義されていたメソッドが呼ばれる。定義されていないメソッドは呼ばれない。add_collision_handlerした後でメソッドを定義しても呼ばれない。
・空メソッドの戻りはnilで、nilを返すと衝突キャンセルになってしまうので、使わないメソッドは定義しないこと。trueだけ書くのもいいが処理の無駄である。
・CollisionHandlerというクラスがChipmunkに定義されているわけではないし、メソッドさえ呼べればいいので、モジュールメソッドを持ったモジュールを渡してもよい。わざわざオブジェクトを作る必要は無い。
という感じだ。

■使用サンプル

require './cp'

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

# 重力を設定する(yを+方向に)
# CP::Vec2はベクトルを表すオブジェクト。newの引数はxとy
space.gravity = CP::Vec2.new(0, 500)

# 箱を作る
box = CPBox.new(100, 100, 30, 30, 1)
box.shape.e = 0.0 # 弾性(0.0〜1.0)
box.shape.u = 1.0 # 摩擦(0.0〜1.0)
box.body.moment = CP::INFINITY # 回転しないように

# 壁を作る
wall1 = CPStaticBox.new(40, 400, 599, 479) # 床
wall1.shape.e = 0.0 # 弾性(0.0〜1.0)
wall1.shape.u = 0.2 # 摩擦(0.0〜1.0)
wall2 = CPStaticBox.new(100, 300, 400, 320) # 浮いてる壁
wall2.shape.e = 0.0 # 弾性(0.0〜1.0)
wall2.shape.u = 0.2 # 摩擦(0.0〜1.0)

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

$ground = false
box.body.velocity_func do |body, gravity, damping, dt|
  body.v += CP::Vec2.new(Input.x * 10, 0) + gravity * dt
  body.v = CP::Vec2.new(body.v.x, -400) if Input.key_push?(K_SPACE) and $ground
  body.w = body.w * damping
end

# 衝突関連
module CollisionHandler
  def self.begin(a, b)
    $ground = a.body.v.y >= 0 # 下降中に新規接触したときだけ衝突が発生する
  end

  def self.separate    # 離れたときに呼ばれる
    $ground = false
  end
end

box.shape.collision_type = 1
wall1.shape.collision_type = 2
wall2.shape.collision_type = 2
space.add_collision_handler(1, 2, CollisionHandler)

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

このサンプルでは壁に対する衝突判定に条件をつけてある。Spaceキーでジャンプできるが、下からは貫通し、上からは衝突するようになっている。ジャンプできる条件は面倒だったからグローバル変数に入れてある。
beginメソッドは新規衝突のみ呼ばれるが、ここでfalseを返すとそれ以降の衝突は次に新規衝突するまで発生しないので、この動きを活用している。
問題点は2つあって、下降中に横からぶつかった瞬間にSpaceを押すとジャンプできることと、高いとこから着地したときにめり込む動きをすることだ。前者はジャンプできる条件を工夫する必要があるし、後者はChipmunkの動きがこうなので何らかの判定をして座標を強制的に移動させる必要があるのではないかと考えている。
まあ、そもそもこういう動きを作るためのライブラリではない、ということなのかもしれないが。壁との衝突や重力の処理が自動化できたら楽だよなーという思いがあって試してみた。

■おしまい
とりあえずここまで。