BGアニメーションを考える

mieki256さんが大変なものを作っていたのでご紹介。
DXRubyでSTGサンプルを作成
爆発エフェクトは派手だわShaderを使った背景は派手だわすごいもんだ。デカいボスもおるし、途中の壁は斜めの見た目に合わせて丁寧な衝突判定してるし、音こそ無いものの、ここまで作れる人はなかなかおらん。基本的な構造やDXRubyの使い方としては非常に素直でスタンダードな感じだから、STGを作りたい人は参考にするとよいだろう。コードはコメントも多くて見やすい。
できることならStage6ぐらいまで作って仕上げていただきたいところだが、そこまでする気が無いからサンプルとしてリリースしてるのかもしれん。
気になることがあるとすれば、Window.draw_tileの描画が非常に遅いところぐらいか。俺が悪いのだが。これはDXRuby1.4でImage#load_tilesのデフォルト動作を変更したのが原因で、元はテクスチャを全部共有した状態でImageを生成していたのだが、そうするとShaderに食わせたときにテクスチャ座標が0〜1にならないのでハマる原因になるだろうと言うことで、別々のテクスチャとして生成しなおしているせいだ。BGはまとめて描画するからすべて同じテクスチャだと速く、テクスチャを変えると遅い。PCも速くなったからそれでいいかなと思っていたのだが、うちの以前より遅くなったPCではかなり重かった。見過ごせる差ではないので次のリリースでは元に戻すことにしよう。遠い将来にはまた変更するかもしれないが。

さて、今日の話題はそれではなくて、mieki256さんの日記でもチラリと言及されていたBGのアニメーション処理について。ほんとはAdventCalendarのネタにしようと書いてあったのだが、手違いで消してしまってボツったネタである。
DXRubyのWindow.draw_tileは二次元配列のマップデータと配列の画像データを使ってタイル描画する。普通に使うと背景の絵をアニメーションすることはできないが、何らかの手段で切り替えることによってそれを実現することはできる。その手法について考えてみる。
まず、まったくもって普通に考えてマップデータを複数持つパターン。

require 'dxruby'

map_image = []
map_image << Image.new(32, 32, [50, 200, 50])
map_image << Image.new(32, 32, [50, 200, 50]).triangle_fill(5, 0, 0, 31, 31, 31, [200, 100,100])
map_image << Image.new(32, 32, [50, 200, 50]).triangle_fill(15, 10, 0, 31, 31, 31, [200, 100,100])
map_image << Image.new(32, 32, [50, 200, 50]).triangle_fill(25, 0, 0, 31, 31, 31, [200, 100,100])

map = [
  [
    [0, 0, 0, 0],
    [0, 1, 1, 0],
    [0, 1, 1, 0],
    [0, 0, 0, 0]
  ],
  [
    [0, 0, 0, 0],
    [0, 2, 2, 0],
    [0, 2, 2, 0],
    [0, 0, 0, 0]
  ],
  [
    [0, 0, 0, 0],
    [0, 3, 3, 0],
    [0, 3, 3, 0],
    [0, 0, 0, 0]
  ],
  [
    [0, 0, 0, 0],
    [0, 2, 2, 0],
    [0, 2, 2, 0],
    [0, 0, 0, 0]
  ]
]

count = 0

Window.loop do
  count += 1
  Window.draw_tile(0, 0, map[(count / 30) % 4], map_image, 0, 0, 4, 4)
  break if Input.key_push?(K_ESCAPE)
end

マップチップ内にアニメーション用のデータを配置する必要がある。4×4程度のマップデータなら別に問題は無いが、これがデカいデータになってくると手で書くのは大変である。そもそもこういうデータを生成できるマップエディタは無いので、マップデータからアニメーション用のデータを生成してやる必要があるだろう。

次に、マップ画像配列を切り替えるパターン。

require 'dxruby'

map_image = []
map_image << [Image.new(32, 32, [50, 200, 50]), 
              Image.new(32, 32, [50, 200, 50]).triangle_fill(5, 0, 0, 31, 31, 31, [200, 100,100])]
map_image << [Image.new(32, 32, [50, 200, 50]),
              Image.new(32, 32, [50, 200, 50]).triangle_fill(15, 10, 0, 31, 31, 31, [200, 100,100])]
map_image << [Image.new(32, 32, [50, 200, 50]),
              Image.new(32, 32, [50, 200, 50]).triangle_fill(25, 0, 0, 31, 31, 31, [200, 100,100])]
map_image << [Image.new(32, 32, [50, 200, 50]),
              Image.new(32, 32, [50, 200, 50]).triangle_fill(15, 10, 0, 31, 31, 31, [200, 100,100])]

map = [
  [0, 0, 0, 0],
  [0, 1, 1, 0],
  [0, 1, 1, 0],
  [0, 0, 0, 0]
]

count = 0

Window.loop do
  count += 1
  Window.draw_tile(0, 0, map, map_image[(count / 30) % 4], 0, 0, 4, 4)
  break if Input.key_push?(K_ESCAPE)
end

マップデータに対して画像の配列は小さい(と思う)ので、こっちのほうがリソース的にはリーズナブルである。ただし、1つのマップチップ画像では実現できないので、部分的に書き換えたマップチップ画像を複数用意する必要がでてくる。画像データのリソース食いはマップデータ配列の比ではないのでそういう意味ではまったくリーズナブルではないということになる。

最後にマップチップ画像配列を動的に生成するパターン。

require 'dxruby'

map_image = [Image.new(32, 32, [50, 200, 50])]

map_anime = [
  [Image.new(32, 32, [50, 200, 50]).triangle_fill(5, 0, 0, 31, 31, 31, [200, 100,100])],
  [Image.new(32, 32, [50, 200, 50]).triangle_fill(15, 10, 0, 31, 31, 31, [200, 100,100])],
  [Image.new(32, 32, [50, 200, 50]).triangle_fill(25, 0, 0, 31, 31, 31, [200, 100,100])],
  [Image.new(32, 32, [50, 200, 50]).triangle_fill(15, 10, 0, 31, 31, 31, [200, 100,100])]
]

map = [
  [0, 0, 0, 0],
  [0, 1, 1, 0],
  [0, 1, 1, 0],
  [0, 0, 0, 0]
]

count = 0

Window.loop do
  count += 1
  Window.draw_tile(0, 0, map, map_image + map_anime[(count / 30) % 4], 0, 0, 4, 4)
  break if Input.key_push?(K_ESCAPE)
end

以前RGSSのマップ描画をDXRubyで実現するコードを書いたが、アニメーション処理にはこの方法を使った。マップチップの画像配列の一部を動的に差し替える。これをするにはマップエディタで使うマップチップ画像の最後のほうにアニメーション用画像を集中して配置しておいて、実行時にはそのへんを切り離して配列に入れておいて、描画時にパターンにより画像を選択して繋げる形でよいだろう。

もっとレアケースとして、1.5devでは地味にマップチップ画像配列にRenderTargetオブジェクトを入れることができるようになっていて、実行時にマップチップを作るような真似ができる。とはいえチップのサイズと数、描画負荷を考えるとそれで背景を作るのは現実的ではない。大きなマップチップで繰り返し描画という用途があるなら有効かもしれないが、いまのところ使える用途は思いつかない感じである。

背景の絵がにぎやかに動いているとそれだけでリッチ感がでるわけで、それなりに気合を入れて作るゲームであればBGアニメーションを取り入れるのを検討するとよさげである。