search
LoginSignup
2
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

GameMaker Studio Advent Calendar 2020 Day 3

posted at

updated at

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

この記事では、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 を使うとよいのかもしれません(※未検証)

参考

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
What you can do with signing up
2
Help us understand the problem. What are the problem?