地形チップを並べてマップを描いたりスクロールを行う場合、地形グラフィックは「一枚の画像」として持たせたいところです。ただ、その場合は描画の際に「一枚の画像から描画しようとする地形チップの部分を切り出して描画する」必要があり、これが性能上のボトルネックになるのでは、という気がしないでもありません。
そこで、今回は40*40ピクセルの地形チップを12*12並べた表示画面に対して以下の描画処理を行い、描画処理の時間を計測してみることにしました。「全体の一部」を描画する場合は「個別の画像をそのまま」描画する場合の性能、そしてCanvas、Image、ImageDataという描画対象の種別による性能の差を比べてみましょう。
- 8*8チップの全体画像を320*320ピクセルのImageとして保持しdrawImage()で一部を描画
- 8*8チップの全体画像を320*320ピクセルのCanvasとして保持しdrawImage()で一部を描画
- 地形チップを個別に切り出して40*40ピクセルのCanvasとして保持し描画
- 地形チップを個別に切り出して40*40ピクセルのImageとして保持し描画
- 地形チップを個別に切り出して40*40ピクセルのImageDataとして保持し描画
マップの描画は、12*12チップの画面を左スクロールさせる形で描くことにしましょう。実際の描画処理では、スクロールで可視範囲に入ってくる分も含めて13*12=156チップ分の描画処理を行うことになります。
描画対象となる地形チップの画像データは、11式RPG制作機の地形チップ画像です。まずこのpng画像をImageに読み込み、これを全体画像とします。ついでに同サイズのCanvasも作成してこちらにもコピーしておきましょう。
次に、この全体画像を8*8の地形チップ、つまり40*40ピクセルの個別地形グラフィックとして分離し、documentオブジェクトのcreateElement()で作成した64個のCanvasに描画していきます。これを個別地形チップのCanvas配列に格納していくわけです。
さらにこの個別地形チップのCanvasからImageとImageDataも作成し、配列に格納して行きます。
以上で、マップの地形チップに関して
- すべての地形(8*8)チップを一枚のImageとして保持するmtipbc_tips_image
- すべての地形(8*8)チップを一枚のCanvasとして保持するmtipbc_tips_canvas
- 個別地形チップを保持するImageを格納したmtipbc_tip_imageList配列
- 個別地形チップを保持するCanvasを格納したmtipbc_tip_canvasList配列
- 個別地形チップを保持するImageDataを格納したmtipbc_tip_imageDataList配列
ができました。
この5つの地形チップをボタンで選んで描画し、描画性能を比べてみることにしましょう。
描画処理は、13*12チップのマップデータ配列mtipbc_map[]から描画すべき地形チップ番号を取得して、表示マップのCanvasに描画していく単純なものです。
// 表示マップに地形チップを描画
for (i = 0;i < 12;i++) {
for (j = 0;j < 13;j++) {
// 描画する地形チップ番号を取得
var no = mtipbc_map[j + i * 13];
switch (mtipbc_status) {
// 全体Imageで描画
case 1:
context.drawImage(mtipbc_tips_image, (no % 8) * 40, Math.floor(no / 8) * 40, 40, 40, -mtipbc_scroll_pos + j * 40, i * 40, 40, 40);
break;
// 全体Canvasで描画
case 2:
context.drawImage(mtipbc_tips_canvas, (no % 8) * 40, Math.floor(no / 8) * 40, 40, 40, -mtipbc_scroll_pos + j * 40, i * 40, 40, 40);
break;
// 個別チップのImageで描画
case 3:
context.drawImage(mtipbc_tip_imageList[no], -mtipbc_scroll_pos + j * 40, i * 40);
break;
// 個別チップのCanvasで描画
case 4:
context.drawImage(mtipbc_tip_canvasList[no], -mtipbc_scroll_pos + j * 40, i * 40);
break;
// 個別チップのImageDataで描画
case 5:
context.putImageData(mtipbc_tip_imageDataList[no], -mtipbc_scroll_pos + j * 40, i * 40);
break;
}
}
}
この描画処理を1チップのスクロールが完了するまで41回繰り返し、処理にかかった合計時間を求めてみました(処理を終えるたびにsetTimeout()で10msのウエイトを入れてありますが、ウエイトの時間は合計時間に含めていません)。
実際に私の環境で試してみると、以下のような結果でした。
Firefox9 | IE9 | Chrome16 | iPhone4 | |
---|---|---|---|---|
全体Image | 24ms | 48ms | 32ms | 2140ms |
全体Canvas | 350ms | 39ms | 18ms | 1205ms |
分離Image | 24ms | 40ms | 23ms | 2115ms |
分離Canvase | 351ms | 37ms | 7ms | 1060ms |
分離ImageData | 278ms | 262ms | 41ms | 584ms |
Webブラウザによるばらつきが大きいですが、iPhone4以外は素直にImageを描画すれば問題ない描画速度ですね。drawImage()で「大きな画像の一部」を描画するのも画像全体を描画するのも速度面ではそれほど差がないのは、意外でした。
ただCanvasやImageDataにすると、環境によっては無視できない速度低下が起こる可能性もあるので要注意でしょうか。
iPhone4は、かなり低速ですがImageDataにしてやるとある程度は描画速度を向上させられるようです。
というわけで、今のところRPGなどで「地形チップで構成されるマップを描画する」際は、とりあえず素直にImageで描画してみて、iPhone4などで性能的な問題があるようならImageDataによる描画を試してみる、というのが有効なのではないでしょうか。