コマンドDSLの拡張
コマンド列を作ってキャラを動かそうとしているわけだが、これを配列として定義することの欠点の一つは「後方参照ができない」である。
Rubyではすべてが実行されるコードであるため、変数は格納する前に参照することができない。変数はオブジェクトの参照であって、ダミーのデータを入れておくとコマンド列にダミーデータが入ってしまう。
同じ原因で、call_commandなどを使う場合は常に呼ばれるほうのコマンド列から定義しなければならない。
change_commandを使ってコマンドを切り替える場合、相互参照するようなコマンドを作ることもできないことはないが、ちょっとトリッキーなコードを書く必要がある。
これを解決する方法として、コマンド列に名前としてシンボルでキーを付け、1パス目はキーを配列に格納、2パス目で配列への参照に置き換えるという手がある。
こうするとコマンド列の定義順は自由になるし相互参照も可能となる。
無論、そんな手の込んだことをせずに素直にコマンド列をハッシュに格納してハッシュキーで参照するようにする方法もあるわけだが、今回は2パスの方法を実装してみた。
class Command attr_reader :command, :commands def self.convert(obj) if obj.commands.size == 0 obj.command else result = obj.commands[:main].map do |com| com[1] = obj.commands[com[1]] if com[1].class == Symbol com end end end def initialize @commands = {} @command = [] end def defcom(key) @command = [] @commands[key] = @command end def endcom end def method_missing(m, *arg) if m == :wait @command.push([:wait=, arg[0]]) else @command.push([m] + arg) end end end class Sprite def self.define_command(&block) c = Command.new c.instance_eval(&block) if block Command.convert(c) end end class BulletGenerator def self.define_command(&block) c = Command.new c.instance_eval(&block) if block Command.convert(c) end end
Commandクラスとdefine_commandメソッドをこのようにすると、こんな感じでコマンド列を定義することができるようになる。
class BL_8way < BulletGenerator @@command = define_command { # ←コマンドDSL defcom :main call_command :action1, 8 wait 2 rotation 12 endcom defcom :action1 fire_relative Shot, 0, 5 rotation 45 endcom } def initialize(parent, x, y) super(Objects, parent, x, y, 0, @@command) end end
こうなってくるとコマンド列を一度クラス変数に格納する意味がわからなくなってくる。
Commandクラスの機能をSpriteやBulletGeneratorに融合してしまって、実行するコマンドをシンボルで表現した方がいいかもしれない。
そうするとアニメーション関連の処理も似たような感じにしたいから、結局はMyGameのようなアニメーション指定と処理に落ち着くことになる。
このあたりはもうちょっといじりながら考えてみる必要がありそうだ。