DXRubyWSを使ったマップエディタ

シンプルなもので使い勝手はよくないが。

開発中のDXRubyWSのサンプルとして作ってみたところ。
https://github.com/mirichi/DXRubyWS

サンプルSTGがひとつのウィンドウの中に描画されていて、そのマップ情報を別のウィンドウで書き換えることができる。これはコード的にはクラス単位で分離させてはいるが、単一のVMインスタンスで動かしているから自由にアクセスできるという話で、逆にどれかがエラーでコケたら全部まるごとコケるというなんだかWindows3.1のような問題も発生する。
DXRubyWSで作ったウィンドウは毎フレームupdateが呼ばれるが、それを使わずシステムから呼ばれるマウスイベントや、そこから発生するシグナルに対する処理だけ書くことで、Window.loopで作るアクションゲームっぽくないイベントドリブンのコードを書くことができる。
このサンプルはSTGサンプルに組み込んだのでメインはSTGコードになっていて、描画先をウィンドウのクライアント領域に差し替えているだけである。マップ編集関連は新たにウィンドウのクラスを作って定義してあるから、ゲームとは切り離したコードでエディタ部分を構築できる。
コードはこんな感じに。

# ウィンドウシステム用クラス定義
module WS
  # ゲームのメインウィンドウ
  class GameWindow < WSWindow
    def draw
      self.client.image.draw(-$myship.x/5,0,$rt)
      super
    end
  end

  # マップ編集ウィンドウ
  class MapEditWindow < WSWindow
    def initialize(*args)
      super
      @mapdata = Map.class_variable_get(:@@map)
      @images = Map.class_variable_get(:@@images)
      @position = 0    # 描画の基点

      # スクロールバー
      sb = WSScrollBar.new(508, 0, 16, 700-16)
      client.add_control(sb, :sb)
      sb.total = 30        # 全体サイズが30
      sb.unit_quantity = 1 # ボタンを押したときに1動く
      sb.screen_length = client.height.quo(32) # 表示される範囲
      sb.add_handler(:slide) {|obj, pos| @position = pos}

      # マップの画像
      wsimage = WSImage.new(0, 0, 512, 480)
      wsimage.image = RenderTarget.new(512, 480)
      client.add_control(wsimage, :wsimage)
      wsimage.add_handler(:resize) do
        wsimage.image.resize(wsimage.width, wsimage.height)
        sb.screen_length = client.height.quo(32)
      end

      # オートレイアウト
      layout(:hbox) do
        add wsimage, true, true
        add sb, false, true
      end

      # クリック時の編集
      wsimage.add_handler(:mouse_down) do |obj, tx, ty|
        @lbutton = true
        @mapdata[(ty + @position * 32) / 32][tx / 32] = WS.desktop.mappartswindow.select_number
      end
      wsimage.add_handler(:mouse_up) do
        @lbutton = false
      end
      wsimage.add_handler(:mouse_move) do |obj, tx, ty|
        @mapdata[(ty + @position * 32) / 32][tx / 32] = WS.desktop.mappartswindow.select_number if @lbutton
      end
    end

    def draw
      # マップ描画
      client.wsimage.image.draw_tile(0, 0, @mapdata, @images, 0, @position*32, 16, 30)

      # 描画枠の描画
      x1 = $myship.x / 5
      x2 = x1 + 360-1
      y1 = ($map.y + $map.count      ) % 960 - @position * 32
      y2 = ($map.y + $map.count + 480) % 960 - @position * 32
      client.wsimage.image.draw_line(x1, y1, x2, y1, C_YELLOW)
      client.wsimage.image.draw_line(x1, y2, x2, y2, C_YELLOW)
      if y1 < y2
        client.wsimage.image.draw_line(x1, y1, x1, y2, C_YELLOW)
        client.wsimage.image.draw_line(x2, y1, x2, y2, C_YELLOW)
      else
        client.wsimage.image.draw_line(x1, 0, x1, y2, C_YELLOW)
        client.wsimage.image.draw_line(x2, 0, x2, y2, C_YELLOW)
        client.wsimage.image.draw_line(x1, y1, x1, 960-1, C_YELLOW)
        client.wsimage.image.draw_line(x2, y1, x2, 960-1, C_YELLOW)
      end
        
      super
    end
  end

  # マップパーツウィンドウ
  class MapPartsWindow < WSWindow
    attr_reader :select_number

    def initialize(*args)
      super
      @images = Map.class_variable_get(:@@images)
      client.extend Clickable
      client.add_handler(:click) do |obj, tx, ty|
        @select_number = (tx / 32 + ty / 32 * (self.client.width / 32))
      end
      @selected_image = Image.new(32,32).box(0,0,31,31,C_WHITE)
      @select_number = 0
    end

    def draw
      # パーツ描画
      x = y = 0
      @images.each do |o|
        client.image.draw(x, y, o)
        x += 32
        if x > self.client.width-32
          x = 0
          y += 32
        end
      end

      # セレクトボックス描画
      client.image.draw(@select_number % (client.width / 32) * 32, @select_number / (client.width / 32) * 32, @selected_image)

      super
    end
  end
end

という定義をサンプルSTGに追加したうえで、

# ウィンドウシステムのWindowオブジェクト
WS.desktop.add_control(WS::GameWindow.new(50,100,360,480+16+6, "GameMainWindow"), :gamewindow)
WS.desktop.add_control(WS::MapEditWindow.new(420,10,528,700, "MapEditer"), :mapeditwindow)
WS.desktop.add_control(WS::MapPartsWindow.new(960,10,294,342, "MapParts"), :mappartswindow)

という感じにウィンドウを生成して、

  WS.update

という一文をWindow.loopの中に書けばこのように動く。