背景の描画法

例えば640*480の画面いっぱいに32*32のマップチップを敷き詰めるとしよう。
20*15で300枚の画像を描画することになる。
単純に300回のループが発生し、毎回2次元配列からマップデータ取得とイメージ配列の参照、座標位置計算が発生する。
Rubyの処理だけでかなりの重さになるし、いかにDXRubyが速度を気にして作られているからとは言っても、300枚の画像描画は無視できない負荷だ。
うちで実行しても、背景描画を有効にするだけでCPU負荷が15〜20%ぐらいあがる。
ローエンドを意識すると、背景だけで50%近いことになるので、これはどーにかしたいところ。
今まではCの拡張ライブラリという手を考えていたが、なんとなくRubyだけでもっと高速に処理ができるかもしれない、と思いついた。


1ドット単位で縦スクロールさせる場合を考える。
マップのデータは横一列サイズの画像を、縦のチップぶんだけあらかじめ生成する。
640*480の32*32チップなら、640*32の画像を16個作る。
32ドットスクロールするたびに、配列の最後のデータをpopして、マップの続きをImage#copyRectで生成して、配列の最初にunshiftで追加する。
こうすると、ループとWindow.drawの呼び出し回数は300→16に激減し、かわりに32フレームに1回だけImgae#copyRectが20回発生する。
Image#copyRectはソフトで転送しているが、この程度の転送ならたいした負荷にはならないし、もしきになるレベルであれば、1フレームに1個ずつ転送すれば負荷を分散させられる。


実際、るびまサンプル用に背景描画処理を作ってみたところ、これはなかなかよい処理速度となった。
もちろん、こういった手法というのはメリットもあればデメリットもある。
例えば今回の方法だと、処理速度は稼げるがメモリを食う。
640*480の1画面ならメインメモリとVRAMを1.2Mバイトぐらいずつ使うだろう。
スクロールさせるならもうちょい増えるはずだ。
させないならそれこそ1画面ぶんの画像を作って、Window.draw一発が速い。
DXRubyのWindow.draw系メソッドは描画面積に依存しないので、出来る限り大きな画像にするのが速度を稼ぐコツだ。
逆に、Image#draw系はソフト描画のために面積に依存する。
この特性と実行時間の差を考えて、うまく使い分けることで、実行速度を大幅に稼ぐことができそうだ。
また、今回考えた方法は、縦もしくは横スクロールを基本とする場合にしか適用できない。
全方向スクロールする場合は、例えば背景1画面を5*5に分割して、その単位でまとめて画像を生成する、とかするといいかもしれない。


るびま記事はDXRubyの紹介を目的にしているから、ゲームのコードの説明は多少するが、コードの全体を掲載して解説はしない。
ゲームの作り方を解説するときのように、少しずつ追加していくようなやりかたもしない。
そのかわり、ちょっと気合の入ったものを動かしてみせようと考えている。
Rubyでもこのぐらいのゲームが作れるんだというのを、DXRubyを知らない人達に見てもらいたい。