Canvas要素で透過キャラクタを作成

Canvas要素は、「透明な描画領域」です。コンテキストを取得し、描画を行うと描画処理の対象となったピクセルに色が設定されWebブラウザ上や他のCanvasに描画した時に「描画結果が反映されたピクセル」が見えるようになります。

つまり、描画処理の対象とならなかった部分は見えない、透過状態なんですね。たとえば32*32ピクセルのCanvas要素に半径10の円を描いたとします。そして、そのCanvasを他のCanvasにdrawImage()で描くと何が起こるのでしょうか? そう

描画処理で「色が付けられたピクセル」のみが描画対象となったCanvasに反映される

つまり、円の部分以外は透明として扱われるのです。「透過キャラクタ」になるわけですね。

透過GIFやPNGなどキャラクタ画像を用意しなくても、JavaScriptのコードだけで透過キャラクタを作成できるわけです。ゲームで使うちょっとしたグラフィックを作成するのに非常に好都合といえるでしょう。

ただし、ImageDataで「全体のピクセルを設定」したCanvasを他のCanvasに描くと、透明度(アルファ値)が完全に透明のピクセルであっても「描画対象となるCanvasの背景設定」に対して、つまりスタイルシートなどで設定されている背景色に対する透過処理となるので注意してください。「現在描画対象Canvas上に描かれているグラフィック」は、反映されません。

透過キャラクタのCanvasを作る際は、塗りつぶし図形やパスを使ってキャラクタを描く必要があるわけですね。

実際、どんな感じになるのか、320*320ピクセルのCanvas上に32*32ピクセルの透過キャラクタを描いて試してみます。キャラクタは、正方形の枠の内部を透過(何も描画しない)状態とし、その中央に小さな正方形を描いてみました。

キャラクタ用の32*32ピクセルのCanvas要素は、JavaScriptからのDOM操作(document.createElement())で動的に作成し、widthとheightプロパティで大きさを設定しています。

上の動作例を見ると、中抜きの正方形キャラクタが描画され、中央の透明部分に下(背景)のCanvasの星空が見える状態になっていると思います。

<canvas id="cv_char_view" width="320" height="320"></canvas>

<script>

// 表示Canvas
var cv_char_view_elm = document.getElementById("cv_char_view");
var cv_char_view_context = cv_char_view_elm.getContext("2d");

// キャラクタ用Canvas作成
var cv_char_cv = document.createElement("canvas");

// キャラクタ用Canvasの大きさ設定
cv_char_cv.width = 32;
cv_char_cv.height = 32;

var cv_char_context = cv_char_cv.getContext("2d");

var cv_char_bg;

var cv_char_x = 160;
var cv_char_y = 160;

cv_char_init();

// 初期化処理
function cv_char_init() {

    // キャラクタCanvasにキャラクタ描画
    cv_char_drawChar();

    // 背景用ImageData作成
    cv_char_bg = cv_char_view_context.createImageData(320, 320);

    var i;

    // 背景画像に星空描画
    for (i = 0;i < 320 * 320;i++) {

        var v = Math.random();

        if (v < 0.01) {

            cv_char_bg.data[i * 4] = 64 + Math.floor(Math.random() * 128);
            cv_char_bg.data[i * 4 + 1] = 64 + Math.floor(Math.random() * 128);
            cv_char_bg.data[i * 4 + 2] = 64 + Math.floor(Math.random() * 128);

        } else {

            cv_char_bg.data[i * 4] = 16 + Math.floor(Math.random() * 16);
            cv_char_bg.data[i * 4 + 1] = 16 + Math.floor(Math.random() * 16);
            cv_char_bg.data[i * 4 + 2] = 16 + Math.floor(Math.random() * 16);

        }

        cv_char_bg.data[i * 4 + 3] = 255;

    }

    // キャラクタ移動表示開始
    cv_char_move();

}

// キャラクタを移動表示
function cv_char_move() {

    // キャラクタ表示位置を乱数で更新
    cv_char_x += -8 + Math.random() * 16;
    cv_char_y += -8 + Math.random() * 16;

    if (cv_char_x < 0) {
        cv_char_x = 0;
    }

    if (cv_char_x > 320) {
        cv_char_x = 320;
    }

    if (cv_char_y < 0) {
        cv_char_y = 0;
    }

    if (cv_char_y > 320) {
        cv_char_y = 320;
    }

    // 背景を描画
    cv_char_view_context.putImageData(cv_char_bg, 0, 0);

    // キャラクタを描画
    cv_char_view_context.drawImage(cv_char_cv, cv_char_x, cv_char_y);

    // 100ms後に再度実行
    setTimeout("cv_char_move()", 100);

}

// キャラクタ(中抜き正方形)を作成
function cv_char_drawChar() {

    cv_char_context.fillStyle = "#00cccc";

    cv_char_context.fillRect(0, 0, 32, 4);
    cv_char_context.fillRect(0, 28, 32, 4);

    cv_char_context.fillRect(0, 4, 4, 24);
    cv_char_context.fillRect(28, 4, 32, 28);

    cv_char_context.fillStyle = "#cc00cc";

    cv_char_context.fillRect(12, 12, 8, 8);

}

</script>