多関節キャラ

リクエストがあったので簡単な多関節キャラを作ってみた。
理屈としてはオブジェクトに次のオブジェクトとの接続位置を持たせて、自分の姿勢にあわせて接続位置を回転して、次のオブジェクトはその位置をベースに自分を回転させて、という処理を再帰的に行えば可能だ。
ベースパーツ用のクラスとその他のパーツ用のクラスは分けたほうがいいのかもしれないが、面倒だったので共通にした。
シンプルにするためにパーツは円形、接続位置は同じにしてあるが、長細いパーツにすれば昆虫の足ぽいものを作ることもできるだろう。
面白いからもうちょっと動きをどうにかしてサンプルに入れようと思う。
昨日のやつとは違って今回はDXRuby1.3.4で動くので是非動くところをみてみて。
※コードに目を覆うような無駄な計算があったので修正しました。

#!ruby -Ks
require 'dxruby'

class Parts < Sprite
  attr_accessor :next, :joint_xp, :joint_yp, :joint_xn, :joint_yn, :joint_angle

  def initialize
    super
    @next = nil
    @joint_xp = @joint_yp = @joint_xn = @joint_yn = @joint_angle = 0
  end

  # ベースパーツは引数省略で呼ばれる。接続パーツのupdateを呼ぶ。
  def update(jx = nil, jy = nil, angle = 0)
    if jx
      self.angle = @joint_angle + angle
      self.x = -@joint_xp + jx
      self.y = -@joint_yp + jy
      self.center_x = @joint_xp
      self.center_y = @joint_yp
      if @next
        jnx = (-@joint_xp + @joint_xn) * Math.cos(Math::PI * self.angle / 180) - (-@joint_yp + @joint_yn) * Math.sin(Math::PI * self.angle / 180) + jx
        jny = (-@joint_xp + @joint_xn) * Math.sin(Math::PI * self.angle / 180) + (-@joint_yp + @joint_yn) * Math.cos(Math::PI * self.angle / 180) + jy
        @next.update(jnx, jny, self.angle)
      end
    else
      if @next
        jnx = (@joint_xn - self.image.width / 2) * Math.cos(Math::PI * self.angle / 180) - (@joint_yn - self.image.height / 2) * Math.sin(Math::PI * self.angle / 180) + self.x + self.image.width / 2
        jny = (@joint_xn - self.image.width / 2) * Math.sin(Math::PI * self.angle / 180) + (@joint_yn - self.image.height / 2) * Math.cos(Math::PI * self.angle / 180) + self.y + self.image.height / 2
        @next.update(jnx, jny, self.angle)
      end
    end
  end

  def draw
    super
    @next.draw if @next
  end
end

# ベースパーツ
pb = Parts.new
pb.image = Image.new(60,60).circle_fill(30,30,30,[255,255,255])
pb.joint_xn = 30 # 次のパーツへの接続x
pb.joint_yn = 10 # 次のパーツへの接続y
pb.x = 300
pb.y = 300

p = pb
parts = [[255,0,0], [0,255,0], [0,0,255], [0,255,255], [255,0,255], [255,255,0]].map do |color|
  p = p.next = Parts.new # nextに次のパーツオブジェクトを入れる
  p.image = Image.new(60,60).circle_fill(30,30,30,color)
  p.joint_xp = 30 # 前のパーツからの接続x
  p.joint_yp = 50 # 前のパーツからの接続y
  p.joint_xn = 30 # 次のパーツへの接続x
  p.joint_yn = 10 # 次のパーツへの接続y
  p
end


angle = 0
angle_d = 1
Window.loop do
  angle = angle + angle_d
  if angle > 20 or angle < -20
    angle_d = -angle_d
  end
  parts.each do |p|
    p.joint_angle = angle
  end

  pb.update
  pb.draw
end