パーティクルシステムその後
このあいだのコードでは新しい種類のParticleクラスを追加すると、それにあわせてParticleFactoryクラスも追加しなければいけなかったが、これが面倒なのでParticleFactoryは共通にしたくなった。拡張の容易さ重視である。しかしそのかわりにParticle側を作るのが難しくなったりParticleFactoryの使い方が難しくなったりParticleの生成や移動処理が遅くなったりするのはよろしくない。自分で書いておいてアレなのだが、トリレンマに真っ向から挑戦しようという話である。まあ、ソフトウェアは実装テクニックによる改善がありえるので別におかしな話ではない。努力すれば報われる。
コードは以下のようになった。
require 'dxruby' class StandardParticle < Sprite PARAMETER = {:target => nil, :image => nil, :angle_min => nil, :angle_max => nil, :speed_min => nil, :speed_max => nil, :alpha => 255, :lifetime => nil, :d_alpha => 0, :gravity => 0 } def initialize(x, y, parent) @parent = parent self.x = x self.y = y self.image = parent.image self.target = parent.target self.alpha = parent.alpha @lifetime = parent.lifetime @angle = rand() * (parent.angle_max - parent.angle_min) + parent.angle_min @speed = rand() * (parent.speed_max - parent.speed_min) + parent.speed_min @dx = Math.cos(@angle / 180.0 * Math::PI) * @speed @dy = Math.sin(@angle / 180.0 * Math::PI) * @speed 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 < Array @@all_objects = [] def self.step Sprite.update @@all_objects Sprite.draw @@all_objects Sprite.clean @@all_objects @@all_objects.delete_if {|v| v.empty?} end def initialize(particle_class) @particle_class = particle_class @parameter = {} (class << self;self;end).instance_eval do attr_accessor *(particle_class::PARAMETER.keys) end particle_class::PARAMETER.each do |k, v| self.send(k.to_s+"=", v) end end def generate(x, y, count) flag = self.empty? count.times { self << @particle_class.new(x, y, self) } @@all_objects.push(self) if flag 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 hoge = ParticleFactory.new(StandardParticle) hoge.angle_min = 0 hoge.angle_max = 360 hoge.speed_min = 0 hoge.speed_max = 5 hoge.lifetime = 120 hoge.d_alpha = -2 hoge.image = image hoge.gravity = 0.05 Window.loop do if Input.mouse_push?(M_LBUTTON) hoge.generate(Input.mouse_pos_x, Input.mouse_pos_y, 200) end ParticleFactory.step end
とりあえずParticleは今後色々作るんちゃうかってことで、StandardParticleに名前を変えた。中身は書き変わってはいるが基本的には前と変わっていない。定数PARAMETERが増えた。これはパラメータの名前シンボルと初期値のハッシュで指定する。定数PARAMETERはParticleFactoryが使う。angleとspeedの指定方法を変えたがこっちのほうがわかりやすいかなというだけで大きな意味は無い。
ParticleFactory側はごっそり変わった。まずArrayを継承するようになった。中身は配下のParticleで、ParticleはSpriteを継承して作るから、こうしておけば衝突判定とかでこいつをそのまま使えるようになる。
あと、ParticleFactory.newの引数にParticleのクラスを渡すようになった。initializeの中ではそこから定数PARAMETERを取得して、パラメータの名前シンボルを使って自身の特異メソッドとしてSetter/Getterを定義して、ついでに初期値を設定する。ここの処理が前と比べて遅くなっているはずだが、ParticleFactory.newが速度のネックになるほど呼ばれることはまず無いだろうと思う。
それから、ParticleFactory.stepを毎フレーム呼ぶようにした。メインループに隠蔽したければあとですればよいので今はこれでいい。
と、こんな感じに変更することで、ParticleFactoryを使う側はほぼ変わりなく、Particleを作る場合にParticleFactoryを追加する必要がなくなり、Particleを作る手間もさして増えていないのではなかろうか。基本的な実行速度も遅くなる要因は無いと思う。