エミッタ機能追加

なんもないエミッタではちょっと寂しいので、機能を追加してみた。追加してみた、というだけで、名前とか使い勝手とか実装とかはいい加減なのだが、こんなようなことができるといいのかなあ、という雰囲気の提示である。
以下に貼り付けてあるがちょっと長くなってきた。

require 'dxruby'

class StandardParticle < Sprite
  attr_accessor :dx, :dy, :lifetime, :parent

  def initialize
    @dx = 0
    @dy = 0
    @lifetime = nil
  end

  def update
    if @lifetime
      @lifetime -= 1
      if @lifetime == 0
        self.vanish 
        return
      end
    end

    if self.alpha > 0
      self.alpha += @parent.d_alpha
      self.alpha = 0 if self.alpha < 0
    end

    self.x += @dx
    self.y += @dy
    @dy += @parent.gravity
  end
end

class ParticleFactory
  attr_accessor :particles

  @@all_objects = []

  def self.step
    obj = @@all_objects.map{|o|o.particles}
    Sprite.update obj
    Sprite.draw obj
    Sprite.clean obj
    @@all_objects.delete_if {|v| v.particles.empty?}
  end

  def initialize(particle_class)
    @particle_class = particle_class
    @particles = []
  end

  def generate(count)
    flag = @particles.empty?
    count.times do |i|
      temp = @particle_class.new
      yield temp, i
      @particles << temp
    end
    @@all_objects.push(self) if flag
  end
end

class SampleEmitter
  attr_accessor :gravity, :d_alpha, :image

  class EmitterGenerator
    attr_accessor :b, :interval, :count
    def initialize(b, interval)
      @b = b
      @interval = interval
      @count = 0
    end
  end

  def initialize(particle_class)
    @factory = ParticleFactory.new(particle_class)
    @screen = Sprite.new
    @screen.collision = [0, 0, 639, 479]
    @generator = []
  end

  def generate(count)
    @factory.generate(count) do |p, i|
      p.parent = self
      yield p, i
    end
  end

  def generate_line(count, x1, y1, x2, y2)
    diff_x = x2 - x1 + 1
    diff_y = y2 - y1 + 1
    @factory.generate(count) do |p, i|
      r = rand()
      p.x = x1 + r * diff_x
      p.y = y1 + r * diff_y
      p.parent = self
      p.image = @image
      yield p, i if block_given?
    end
  end

  def generate_circle(count, x, y, r, angle_min, angle_max)
    @factory.generate(count) do |p, i|
      angle = angle_min + rand() * (angle_max - angle_min)
      p.x = x + Math.cos(angle / 180.0 * Math::PI) * r
      p.y = y + Math.sin(angle / 180.0 * Math::PI) * r
      p.parent = self
      p.image = @image
      yield p, i, angle if block_given?
    end
  end

  def add_generator(interval, &b)
    @generator << EmitterGenerator.new(b, interval)
  end

  def update
    @generator.each do |g|
      g.count += 1
      if g.count >= g.interval
        g.count = 0
        g.b.call
      end
    end
    @factory.particles = @screen.check(@factory.particles)
  end
end

image = Image.new(3,3)
image[1,0] = [128, 255, 255, 255]
image[1,2] = [128, 255, 255, 255]
image[0,1] = [128, 255, 255, 255]
image[2,1] = [128, 255, 255, 255]
image[1,1] = C_WHITE

emitter = SampleEmitter.new(StandardParticle)
emitter.d_alpha = 0
emitter.gravity = 0.05
emitter.image = image

emitter.add_generator(1) do
  emitter.generate_line(20, 0, 300, 639, 400)
end

emitter.add_generator(5) do
  emitter.generate_circle(50, 400, 200, 50, 0, 360) do |p|
    p.dx = -1
  end
end

Window.loop do
  emitter.update
  ParticleFactory.step
end

今回いじったところだが、とりあえずParticleFactoryをArrayから継承していたのをやめた。Sprite.checkで衝突判定して画面外チェックをするとArrayが返ってくるから、そこからParticleFactoryを生成するのが面倒だったからだ。インスタンス変数に持たせておけば実用上の問題は無いのでこのほうがやっぱりいいかもしれない。

Emitterのほうは変わったというか、もともと何もなかったので機能追加というか、追加したら別物になったというか。主に画面外チェックと、生成範囲指定と、指定フレーム間隔で生成する機能を追加している。
画面外チェックはいつものSprite.checkでやる方法。生成範囲指定はgenerate_lineとgenerate_circleの追加で、それぞれ2点指定の線分上ランダムと指定円の円周上ランダムという生成パターンをサポートする。ブロックを呼ぶ前に座標などを設定してくれているので、必要な追加設定がなければブロックを渡す必要も無い。こういう数字がだらだら並んでしまう引数の場合にキーワード引数を使うとよいのだろうなと思った。あと、指定フレーム間隔での生成はadd_generatorメソッドで、渡したブロックを引数で指定したフレーム数に1回呼び出してくれる。複数登録できる。んで毎フレームEmitter#updateを呼んでおけばあとは自動で。

いまんとことりあえず書いて動かしてみたという感じだから今後大きく変わるとは思うが、こんなことができればまあいいのかなあー??といったところ。実用的に扱うにはまだ足りないものも多いだろうし、それを実現するには今の作りではいけないということもありそうで、予断を許さない状況である。