ChipmunkとMagickCanvasで弾むボール

December 26, 2019

https://ruby.tnantoka.com/entry/2019/12/24/222934

次は物理演算したい。

ということでやりました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# frozen_string_literal: true

require 'bundler/setup'
Bundler.require

class Canvas < MagickCanvas::Base
  attr_accessor :ball, :space, :circle_body

  Ball = Struct.new(
    :radius, :x, :y, :speed_x, :speed_y, :direction_x, :direction_y
  ) do
    def dx
      speed_x * direction_x
    end

    def dy
      speed_y * direction_y
    end

    def left
      x - radius
    end

    def overflow_x?(width)
      x < radius || x > width - radius
    end

    def overflow_y?(height)
      y < radius || y > height - radius
    end

    def turn_x(width)
      self.direction_x *= -1 if overflow_x?(width)
    end

    def turn_y(height)
      self.direction_y *= -1 if overflow_y?(height)
    end
  end

  def initialize
    super
    self.space = CP::Space.new
    space.gravity = CP::Vec2.new(0, 300)
    add_ball
    add_wall(0, height - 1, width, height)
    add_wall(width - 1, 0, width, height)
  end

  def options
    {
      width: 300,
      height: 300,
      number_of_frames: 100
    }
  end

  def update(_frame_count)
    space.step(1.0 / 30)
    ball.x = circle_body.p.x
    ball.y = circle_body.p.y
  end

  def draw(image, _frame_count)
    gc = Draw.new
    gc.stroke('white').stroke_width(1)
    gc.circle(ball.x, ball.y, ball.left, ball.y)
    gc.draw(image)
  end

  private

  def add_ball
    self.ball = Ball.new(30, center.x, center.y, 6, 3, 1, 1)
    body = init_ball_body(ball)
    shape = init_ball_shape(ball, body)
    space.add_body(body)
    space.add_shape(shape)
    self.circle_body = body
  end

  def init_ball_body(ball)
    CP::Body.new(10, CP::INFINITY).tap do |body|
      body.p = CP::Vec2.new(ball.x, ball.y)
      body.v = CP::Vec2.new(100, 300)
    end
  end

  def init_ball_shape(ball, body)
    CP::Shape::Circle.new(body, ball.radius, CP::Vec2.new(0, 0)).tap do |shape|
      shape.e = 0.8
    end
  end

  def add_wall(x1, y1, x2, y2) # rubocop:disable Naming/MethodParameterName
    body = CP::Body.new_static
    verts = [
      CP::Vec2.new(x1, y1),
      CP::Vec2.new(x1, y2),
      CP::Vec2.new(x2, y2),
      CP::Vec2.new(x2, y1)
    ]
    shape = CP::Shape::Poly.new(body, verts, CP::Vec2.new(0, 0))
    shape.e = 0.6
    space.add_shape(shape)
  end
end

https://github.com/tnantoka/ruby.tnantoka.com/blob/616188967e627d6913f4d3a7cbd4db4b58284284/chipmunk/ball.rb

Chipmunkの使い方は以下の記事が大変参考になりました。

https://mirichi.hatenadiary.org/entry/20131105/p1

Chipmunk、属性が1文字アルファベットなものが多くて辛い…。