LoginSignup
7
2

More than 3 years have passed since last update.

【GameMaker Studio2】マスク処理の実装方法と使い方について

Last updated at Posted at 2020-12-02

この記事では、Gamemaker Studio2でのマスク処理の実装方法について書いていきます。

マスク処理とは

マスク処理とは、画像の特定の部分をくり抜いて描画する処理です。

名称未設定.png

この処理を使うことで、以下のようにくり抜き部分を広げていく、というアニメーションを実装できます。

mask.gif

マスク処理で効果的なのがキャラクターのカットイン演出ですね。顔に近い部分をくり抜いて表情に注目させる、という使い方がよくされます。

cutin.gif

マスクの実装手順

マスクスクリプトの追加

まず、scr_rmask スクリプトを作成して以下の記述をします。

scr_rmask
/// @description start(end) to write rendering mask.
/// @param xarea
/// @param yarea
/// @param warea
/// @param harea
function rmask_write() {
    var enable = argument[0];

    if(enable) {
        // start to write rendering mask.
        var xarea = 0;
        var yarea = 0;
        var warea = display_get_gui_width();
        var harea = display_get_gui_height();

        if(argument_count == 5) {
            xarea = argument[1];
            yarea = argument[2];
            warea = argument[3];
            harea = argument[4];
        }

        // clear alpha channel.
        gpu_set_blendenable(false); // disable blend mode.
        gpu_set_colorwriteenable(false, false, false, true); // alpha channel only.
        draw_set_alpha(0); // write alpha 0.
        draw_rectangle(xarea, yarea, warea, harea, false);
        draw_set_alpha(1); // reset alpha.
    }
    else {
        /// end to write rendering mask.
        gpu_set_blendenable(true); // enable blend mode.
        gpu_set_colorwriteenable(true, true, true, true); // reset color channel setting.
    }
}

/// @description enable rendering mask.
/// @param enable
function rmask_enable(enable) {
    if(enable) {
        /// enable rendering mask.
        gpu_set_blendmode_ext(bm_dest_alpha, bm_inv_dest_alpha); // alpha only.
        gpu_set_alphatestenable(true); // enable alpha test.
    }
    else {
        /// disable rendering mask.
        gpu_set_alphatestenable(false); // disable alpha test.
        gpu_set_blendmode(bm_normal); // reset blend mode.
    }
}

この処理は以下のページのコードを参考にしました。

マスク処理の呼び出し

スクリプトに定義したマスク処理は以下の手順で呼び出します

  1. くり抜き部分の作成: rmask_write()
  2. くり抜いた部分への描画: rmask_enable()

rmask_write() がくり抜き範囲の指定で、rmask_enable() がくり抜いた部分への描画を行う際に呼び出す処理です。
例えば以下のように使用します。

// くり抜き開始
rmask_write(true);

// 左上(0, 0) - 右下(120, 80) の範囲をくり抜く
draw_rectangle(0, 0, 120, 80, false);

// くり抜き終了
rmask_write(false);

// くり抜いた部分への描画開始.
rmask_enable(true);

// スプライトを描画。くり抜いた部分にしか描画されない
draw_sprite(spr_001, 0, 0, 0);

// くり抜き描画を終了
rmask_enable(false);

注意点として、rmask_enable(false) で終了しないと、くり抜いた部分以外には描画できなくなります。また、透過値を指定して半透明でくり抜くことはできません。

これで、いつでもどこでもマスク処理が使えるようになります!


おまけ:円弧描画でくり抜く

おまけとして、円弧描画でくり抜く処理を作ってみます。
draw_arcスクリプトを作成して以下のコードを追加します。

draw_arc
/// @description 円弧を描画する.
/// @param cx 中心座標(X).
/// @param cy 中心座標(Y)
/// @param radius 半径.
/// @param start_rot 開始角度.
/// @param end_rot 終了角度.
/// @param color 色.
/// @param divide 円の分割数(大きくするほど滑らかになります)
function draw_arc(cx, cy, radius, start_rot, end_rot, color, divide) {

    // 現在設定している色を取得する
    var prev_color = draw_get_color();

    // 色を設定
    draw_set_color(color);

    // 角度差を計算
    var dir = sign(end_rot - start_rot);

    // 開始角度を設定
    var rot = start_rot;

    // 1ポリゴンあたりの角度
    var drot = dir * real(360) / divide;
    for(var i = 0; i < divide; i++) {

        var r1 = rot;
        var r2 = rot + drot;
        if(dir > 0) {
            // 角度差がプラス
            if(r2 >= end_rot) {
                r2 = end_rot; // 終了角度を超えた
            }
        }
        else {
            // 角度差がマイナス
            if(r2 <= end_rot) {
                r2 = end_rot; // 終了角度を超えた
            }
        }

        var x1 = cx + radius * dcos(r1);
        var y1 = cy - radius * dsin(r1);
        var x2 = cx + radius * dcos(r2);
        var y2 = cy - radius * dsin(r2);

        // ポリゴンを描画
        draw_triangle(cx, cy, x1, y1, x2, y2, false);

        // 次の角度に進める
        rot += drot;

        if(dir > 0) {
            // 角度差がプラス
            if(rot >= end_rot) {
                break; // 終了角度を超えた
            }
        }
        else {
            // 角度差がマイナス
            if(rot <= end_rot) {
                break; // 終了角度を超えた
            }
        }
    }

    // 色を戻す
    draw_set_color(prev_color);
}

これは以下のように、円を指定の角度の範囲で描画する関数です。

arc.gif

これを使って円弧を使ったマスクを実装してみます。

まずはCreateイベントを以下のように記述します。

Createイベント
timer = 0;
bg_spr = spr_001; // 背景スプライトを設定
fg_spr = spr_002; // 前面に描画するスプライトを設定

そして、Drawイベントに以下のように記述します

Drawイベント

// タイマー更新
timer += 1;
if(timer > 90) {
    // 4倍した値を使うので90度で終了
    timer = 0;
    // 背景と前面のスプライトを交換する
    var tmp = bg_spr;
    bg_spr = fg_spr;
    fg_spr = tmp;
}

// 背景スプライトを赤色で描画
draw_sprite_ext(bg_spr, 0, 0, 0, 1, 1, 0, c_red, 1);

// くり抜きの円弧を描画
var cx = room_width/2;
var cy = room_height/2;
var radius = room_width;
rmask_write(true);
draw_arc(cx, cy, radius, 90, 90-timer*4, c_white, 32);
rmask_write(false);

// くり抜いた部分に前面のスプライトを描画
rmask_enable(true);
draw_sprite(fg_spr, 0, 0, 0);
rmask_enable(false);

これを実行すると以下のように、円弧でくり抜いた映画風の演出ができます。

arc2.gif

サンプルプロジェクト

今回作成したプロジェクトは以下のリンクからダウンロードできます。

注意点

このマスク処理は、あくまでエミュレートなのでくり抜き部分の描画に半透明部分のある画像を重ねると、描画がおかしくなります。
bug.gif
上記の例では、キャラクターの背後に影を表示するようにしたのですが、画像の縁にアルファ値が入っているためなのか、キャラクターの前面にその縁取り線が出てしまっています。

このマスク処理のエミュレートの仕組みを完全には理解していませんが、おそらくアルファ値を使ってマスクしているため、アルファ値を使った描画ができないような気がしています。そのため、もしアルファ値があるマスクを使用したい場合には、サーフェースに描画した結果をマスクするか、シェーダーを書く必要がありそうです。
もしくはシーケンサーの Clipping Mask を使うとよいのかもしれません(※未検証)

参考

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2