キャラ移動の仕組み

久しぶりに更新。
何かやっていたかというと、Ruby1.9.1のVMを調べたり、そんな感じ。
別にそれで何かすごいことを発表しようというわけではなく、前から考えていたキャラ移動をコマンド列でラクに作ろうという試みのプロトタイプができたかなー、という話。
画面はパッとしないがとりあえず。

require 'dxruby'

class Sprite
  attr_accessor :x, :y, :wait, :dx, :dy
  attr_reader :angle, :speed, :delete
  TO_RADIAN = 1 / 180.0 * Math::PI

  def initialize(x, y, angle, speed, image, command)
    @x = x
    @y = y
    @angle = angle
    @speed = speed
    @image = image
    @pc = 0
    @wait = 0
    @dx = Math.cos(@angle * TO_RADIAN) * @speed
    @dy = Math.sin(@angle * TO_RADIAN) * @speed
    @command = command
    @delete = false
    @width = @image.width
    @height = @image.height
  end

  def update
    while(@wait == 0) do
      c = @command[@pc]
      self.__send__ c[0], *c[1]
      @pc += 1
      @pc = 0 if @pc == @command.size
    end
    @x += @dx
    @y += @dy
    self.out_screen if @x + @width < 0 or @x > 639 or @y + @height < 0 or @y > 479
    @wait -= 1
  end

  def set(x,y,angle,speed)
    @x = x
    @y = y
    @angle = angle
    @speed = speed
    @dx = Math.cos(angle * TO_RADIAN) * speed
    @dy = Math.sin(angle * TO_RADIAN) * speed
  end

  def angle=(angle)
    @angle = angle
    @dx = Math.cos(angle * TO_RADIAN) * @speed
    @dy = Math.sin(angle * TO_RADIAN) * @speed
  end

  def rotation(d_angle)
    @angle += d_angle
    @dx = Math.cos(@angle * TO_RADIAN) * @speed
    @dy = Math.sin(@angle * TO_RADIAN) * @speed
  end

  def speed=(speed)
    @speed = speed
    @dx = Math.cos(@angle * TO_RADIAN) * speed
    @dy = Math.sin(@angle * TO_RADIAN) * speed
  end

  def accel(d_speed)
    @speed += d_speed
    @dx = Math.cos(@angle * TO_RADIAN) * @speed
    @dy = Math.sin(@angle * TO_RADIAN) * @speed
  end

  def draw
    Window.drawRot(@x, @y, @image, @angle - 90)
  end

  def out_screen
    @delete = true
  end
end

class Shot < Sprite
  @@image = Image.new(10,10,[255,255,0])
  @@command = [
    [:wait=, -1],
  ]

  def initialize(x, y, angle)
    super(x, y, angle, 5, @@image, @@command)
  end

  def out_screen
    @dx = -@dx if @x + @width < 0 or @x > 639
    @dy = -@dy if @y + @height < 0 or @y > 439
  end
end

class Enemy < Sprite
  @@image = Image.new(10,10,[255,255,255])
  @@command = [
    [:wait=, 2],
    [:rotation, 10],
    [:fire, nil],
  ]

  def initialize(x, y)
    super(x, y, 0, 2, @@image, @@command)
  end

  def fire
    Objects.push(Shot.new(@x, @y, @angle))
  end
end

a = Enemy.new(200,200)

Objects = [a]
font = Font.new(32)

Window.loop do
  Objects.each do |obj|
    obj.update
  end
  Objects.delete_if do |obj|
    obj.delete
  end
  Objects.each do |obj|
    obj.draw
  end
  Window.drawFont(0, 0, Window.getLoad.to_i.to_s + " %", font, :z => 100.0)
  Window.drawFont(0, 32, Objects.size.to_s + " objects", font, :z => 100.0)
end

まだ基本機能すら揃ってない状態ではあるが、動くので。
コマンドはメソッド名のシンボルで、それと引数をまとめて配列にしたものを配列にしたものがコマンド列となる。
ようするにひたすらメソッドを呼ぶだけだ。
ただのメソッドコールだから、派生クラスでコマンドを勝手に作ることもできる。
あとは、直後のコマンドを指定回数繰り返すとか、別のコマンド列をコールするとか、そういうことができるようになれば、そこそこ柔軟になるかな。
見ればわかると思うがwait=コマンドが来るまで繰り返しコマンドを実行し、wait=で指定されたフレーム分だけそのまま移動する。wait=にマイナスを指定すると無限回数となる。
実際にはずっと動かしていればFixnumがオーバーフローするが、60fpsで動かしていてオーバーフローするってどんだけー。