物理エンジン Chipmunk を Gosu で表示(Ruby)


2D物理エンジン「Chipmunk」を Ruby から使ってみました。表示には Gem 'gosu' を使っています。Linux Mint 18.3, Ruby 2.3.3 で確認しました。
 
gosu_chipmunk_sample2.rb (Gist)

require 'gosu'
require 'rmagick'
require 'chipmunk'

Width, Height = 600, 600
Wall_h = 20

class MyWindow < Gosu::Window
  def initialize
    super Width, Height, false
    self.caption = "Gosu + Chipmunk"
    
    @space = CP::Space.new
    @space.gravity = CP::Vec2.new(0, 250)
    
    #円の画像の生成
    circle_image = Magick::Image.new(21, 21) {self.background_color = 'transparent'}
    gc = Magick::Draw.new
    gc.fill('yellow')
    gc.circle(10, 10, 0, 10)
    gc.draw(circle_image)
    @circle = Gosu::Image.new(circle_image)

    #壁の生成
    background = Magick::Image.new(Width, Height) {self.background_color = "black"}
    create_wall(0, Height - Wall_h, Width, Height - Wall_h, Wall_h, background)
    create_wall(Wall_h, 0, Wall_h, Height, Wall_h, background)
    create_wall(Width, 0, Width, Height, Wall_h, background)
    create_wall(120, 150, 400, 200, Wall_h, background)
    create_wall(200, 450, 480, 400, Wall_h, background)
    @background_image = Gosu::Image.new(background)
    
    @circles = []
  end
  
  def create_circle
    body = CP::Body.new(1, CP::INFINITY)
    body.p = CP::Vec2.new(rand(Width / 2 + Width / 4), -20)
    body.v = CP::Vec2.new(rand * 500 - 250, 0)
    shape = CP::Shape::Circle.new(body, 10, CP::Vec2.new(0, 0))
    shape.e = 0.8
    shape.u = 0
    
    @space.add_body(body)
    @space.add_shape(shape)
    
    body
  end
  
  def create_wall(x1, y1, x2, y2, height, bg)
    sb = CP::Body.new_static
    p1 = CP::Vec2.new(x1, y1)
    p4 = CP::Vec2.new(x2, y2)
    p0 = p4 - p1
    p2 = p1 + p0.normalize.rotate(CP::Vec2.new(0, 1)) * height
    p3 = p2 + p0
    verts = [p1, p2, p3, p4]
    wshape = CP::Shape::Poly.new(sb, verts, CP::Vec2.new(0, 0))
    wshape.e = 1
    wshape.u = 0
    @space.add_shape(wshape)
    
    gc = Magick::Draw.new
    gc.fill('firebrick')
    verts = verts.map {|vt| [vt.x, vt.y]}.flatten
    gc.polygon(*verts)
    gc.draw(bg)
  end
  
  def update
    @space.step(1.0 / 60)
    @circles << create_circle if rand < 0.1 and @circles.size < 300
  end
  
  def draw
    @background_image.draw(0, 0, 0)
    @circles.each {|c| @circle.draw_rot(c.p.x, c.p.y, 1, 0)}
  end
end

MyWindow.new.show

Chipmunk で計算したものを Gosu を使って表示しています。画像の生成には RMagick を使っています。
メソッド create_wall(x1, y1, x2, y2, height, bg) の (x1, y1), (x2, y2) は壁の上面のラインを指定します。height は壁の厚みです。

おもしろいことに、上のコードで行数を取っているのはほとんどが円や壁の設定です。計算は Chipmunk が、描画は Gosu が自動的にやってくれるため、メインループ(メソッド update, draw)は数行しかありません。

全体の構造も単純です。Chipmunk の空間は @space = CP::Space.new であり、@space.gravity で重力を発生させます。円の本体は CP::Body.new で生成し、壁は動かないので CP::Body.new_static で生成します。@space.stepメソッドで時間を進めます。
Gosu はまず Gosu::Window を継承した MyWindow クラスを作り、そのインスタンスshow すれば基本的に描画されます。メインループ内のロジックは update, 描画は drawメソッド内に書きます。こうするとデフォルトで 1/60 秒ごとに描画が繰り返されます。

Ruby の強力な「オブジェクト指向プログラミング」のおかげで、じつに簡単に物理エンジンが動かせかつ可視化できていると思います。


Gosu + Chipmunk + RMagick については以下に簡単なまとめとサンプルを書いておきました。

Chipmunk については下のサイトがすごくわかりやすかったです。