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の動きがこうなので何らかの判定をして座標を強制的に移動させる必要があるのではないかと考えている。
まあ、そもそもこういう動きを作るためのライブラリではない、ということなのかもしれないが。壁との衝突や重力の処理が自動化できたら楽だよなーという思いがあって試してみた。
■おしまい
とりあえずここまで。