コマンド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のようなアニメーション指定と処理に落ち着くことになる。
このあたりはもうちょっといじりながら考えてみる必要がありそうだ。