Ruby2.0のprependでHitRangeを使う
DXRuby1.5devにはHitRangeモジュールをサンプルとして添付してあるが、これはSpriteを継承したクラスに対してincludeして使う。
RubyのModule#includeは実行されたときのself(つまりなんかのクラス)に対して、継承階層の上位にモジュールを挿し込む。といってもモジュールは継承できないので、正確にはモジュールをクラス化した特殊なクラスが挿し込まれる。
Module#includeを使う場合の問題点は、指定したクラスの上に入るために、そのクラスで実装したメソッドが先に動いてしまう点だ。まあ、superを最初に書けばいいのかもしれないが、そうするとさらにその上位のsuperまで先に動いてしまうので、そのあたりが何かと面倒な制限になる。また、DXRubyのSprite#drawメソッドのようにsuperしてくれないメソッドをモジュールのメソッドで拡張しようとすると、わざわざSpriteを継承してから派生クラスのほうにincludeしないといけない。そういうわけでHitRangeモジュールはSpriteの派生クラスにincludeして使わざるを得ないのである。
こんな感じだ。
class TestSprite < Sprite include HitRange end s = TestSprite.new(100,100)
これだと、たとえばSprite全部の衝突判定範囲を描画したい場合に、派生したすべてのクラスにincludeを書かなければならないとか、そのためにわざわざ派生クラスを一段多く作らなければならないとか、そういう話になってしまうので、なんとかできるとうれしい。
Ruby2.0なら、できる。
class Sprite prepend HitRange end s = Sprite.new(100,100)
追加されたModule#prependは、クラスの継承階層の下位側にモジュールを追加する。実装の詳細は知らないがおそらくモジュールを内部でクラス化したものを追加しているのだろう。ともかくこれを使えばSpriteクラスにHitRangeをprependするだけですべてのSpriteの衝突判定範囲が描画されるようになる。非常に楽である。
こんな感じに一発で描画できるようになる。
しかし困ったことにHitRangeはDXRuby1.5dev用に追加したメソッドを使っているのでそのままでは1.4.0では使えないし、Ruby2.0用にDXRuby1.5devは用意してないのだ。いまいち意味の無い記事である。
しょうがないので続きにDXRuby1.4.0で使えるHitRangeモジュールをおいておく。一部コメントアウトしただけなんだけど。
なんかバグってて毎回drawのたびに描画範囲画像を生成してて重くなってたのでそれも修正してある。
require 'dxruby' module HitRange def draw super if self.visible and self.collision_enable and !self.vanished? if @hitrange_image == nil or @hitrange_collision != self.collision or @hitrange_image_width != self.image.width or @hitrange_image_height != self.image.height @hitrange_image_width = self.image.width @hitrange_image_height = self.image.height @hitrange_image.dispose if @hitrange_image != nil color = [255, 255, 255, 0] color_fill = [100, 255, 255, 0] if self.collision == nil @hitrange_collision = nil @hitrange_image = Image.new(self.image.width, self.image.height) @hitrange_image.box_fill(0, 0, @hitrange_image.width-1, @hitrange_image.height-1, color_fill) @hitrange_image.box(0, 0, @hitrange_image.width-1, @hitrange_image.height-1, color) @hitrange_x = @hitrange_y = 0 else @hitrange_collision = [] self.collision.each do |v| if v === Array @hitrange_collision << v.dup else @hitrange_collision << v end end temp = self.collision temp = [temp] if temp[0].class != Array x1 = y1 = 0 x2 = y2 = 0 temp.each do |col| case col.size when 2 # point x1 = [x1, col[0]].min y1 = [y1, col[1]].min x2 = [x2, col[0]].max y2 = [y2, col[1]].max when 3 # circle x1 = [x1, col[0] - col[2]].min y1 = [y1, col[1] - col[2]].min x2 = [x2, col[0] + col[2]].max y2 = [y2, col[1] + col[2]].max when 4 # box x1 = [x1, col[0], col[2]].min y1 = [y1, col[1], col[3]].min x2 = [x2, col[0], col[2]].max y2 = [y2, col[1], col[3]].max when 6 # triangle x1 = [x1, col[0], col[2], col[4]].min y1 = [y1, col[1], col[3], col[5]].min x2 = [x2, col[0], col[2], col[4]].max y2 = [y2, col[1], col[3], col[5]].max end end @hitrange_image = Image.new(x2 - x1 + 1, y2 - y1 + 1) @hitrange_x = x = x1 @hitrange_y = y = y1 temp.each do |col| case col.size when 2 # point @hitrange_image[col[0] - x, col[1] - y] = color when 3 # circle @hitrange_image.circle_fill(col[0] - x, col[1] - y, col[2], color_fill) @hitrange_image.circle(col[0] - x, col[1] - y, col[2], color) when 4 # box @hitrange_image.box_fill(col[0] - x, col[1] - y, col[2] - x, col[3] - y, color_fill) @hitrange_image.box(col[0] - x, col[1] - y, col[2] - x, col[3] - y, color) when 6 # triangle @hitrange_image.triangle_fill(col[0] - x, col[1] - y, col[2] - x, col[3] - y, col[4] - x, col[5] - y, color_fill) @hitrange_image.triangle(col[0] - x, col[1] - y, col[2] - x, col[3] - y, col[4] - x, col[5] - y, color) end end end end hash = {} if self.collision_sync hash[:scale_x] = self.scale_x hash[:scale_y] = self.scale_y hash[:center_x] = self.center_x - @hitrange_x hash[:center_y] = self.center_y - @hitrange_y hash[:angle] = self.angle end hash[:z] = self.z + 1 ox = oy = 0 # if self.offset_sync # ox = self.center_x # oy = self.center_y # end target = self.target ? self.target : Window target.draw_ex(self.x - ox + @hitrange_x, self.y - oy + @hitrange_y, @hitrange_image, hash) end end end if __FILE__ == $0 # class TestSprite < Sprite # include HitRange # end class Sprite prepend HitRange end s = Sprite.new(100,100) s.image = Image.new(100,100,C_BLUE) # s.offset_sync=true s.center_x = 0 s.center_y = 0 y = 90 Window.loop do y += Input.y s.collision = [10,10,90,0,50,y] s.angle += 1 s.draw end end