この記事では、Gamemaker Studio2でのマスク処理の実装方法について書いていきます。
マスク処理とは
マスク処理とは、画像の特定の部分をくり抜いて描画する処理です。
この処理を使うことで、以下のようにくり抜き部分を広げていく、というアニメーションを実装できます。
マスク処理で効果的なのがキャラクターのカットイン演出ですね。顔に近い部分をくり抜いて表情に注目させる、という使い方がよくされます。
マスクの実装手順
マスクスクリプトの追加
まず、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.
}
}
この処理は以下のページのコードを参考にしました。
マスク処理の呼び出し
スクリプトに定義したマスク処理は以下の手順で呼び出します
- くり抜き部分の作成: rmask_write()
- くり抜いた部分への描画: 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スクリプトを作成して以下のコードを追加します。
/// @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);
}
これは以下のように、円を指定の角度の範囲で描画する関数です。
これを使って円弧を使ったマスクを実装してみます。
まずはCreateイベントを以下のように記述します。
timer = 0;
bg_spr = spr_001; // 背景スプライトを設定
fg_spr = spr_002; // 前面に描画するスプライトを設定
そして、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);
これを実行すると以下のように、円弧でくり抜いた映画風の演出ができます。
サンプルプロジェクト
今回作成したプロジェクトは以下のリンクからダウンロードできます。
注意点
このマスク処理は、あくまでエミュレートなのでくり抜き部分の描画に半透明部分のある画像を重ねると、描画がおかしくなります。
上記の例では、キャラクターの背後に影を表示するようにしたのですが、画像の縁にアルファ値が入っているためなのか、キャラクターの前面にその縁取り線が出てしまっています。
このマスク処理のエミュレートの仕組みを完全には理解していませんが、おそらくアルファ値を使ってマスクしているため、アルファ値を使った描画ができないような気がしています。そのため、もしアルファ値があるマスクを使用したい場合には、サーフェースに描画した結果をマスクするか、シェーダーを書く必要がありそうです。
もしくはシーケンサーの Clipping Mask を使うとよいのかもしれません(※未検証)