Developer

さくさく理解する Godot 入門(ただし2Dに限る)応用編 お絵かきパズル【第14回】
2022.05.13
Lv1

さくさく理解する Godot 入門(ただし2Dに限る)応用編 お絵かきパズル【第14回】

目次

  • クリア時イフェクト
  • さいごに

クリア時イフェクト

下図に問題クリア時にシェーダによって表示されるショックウェーブのスクショを示す。

この画像だけでは分かりづらいかもしれないが、波紋が中心から発生し徐々に外側に広がっていくようになっている。

以下に、ショックウェーブのためのシェーダの設定・コードについて解説する。
なお、この内容は GODOT TUTORIAL: Shockwave shader for noobs 参考にしたものである。

「シェーダ」とは、CPUではなくGPUで実行されるプログラムのことで、いくつかの制限はあるが、 画像描画処理を様々にカスタマイズすることができ、またその処理が非常に高速である。 アプリの表現力を向上させる非常に魅力的な機構だ。

その実装方法だが、まずは下図の様に、ノードツリーに CanvasLayer ノードを追加し、その子ノードして ColorRect を追加する。 この部分がシェーダの処理対象となる。

次に下図の様に、インスペクタで ColorRect のマテリアルを開き、Material を「ShaderMaterial」とし、 Shader を「Shader」とする。

※ 上記スクショでは Shader Param > Center などの項目が表示されているが、 これは後に説明するシェーダコードを記述すると表示されるようになるもので、最初は表示されない。

「Shader」部分をクリックすると、下図の様に、シェーダコードを編集するペインがエディタ下部に表示される。

以下に、ショックウェーブのためのシェーダプログラムコードを示す。

shader_type canvas_item;
// シェーダパラメータ
uniform vec2 center;        // 波紋中心位置
uniform float force;        // 波紋スケール
uniform float size;         // 波紋サイズ
uniform float tichness;     // 波紋幅

void fragment() {       // フラグメントシェーダ
    float ratio = SCREEN_PIXEL_SIZE.x / SCREEN_PIXEL_SIZE.y;    // 縦横比率
    vec2 scaledUV = ( SCREEN_UV - vec2(0.5, 0.0) ) / vec2(ratio, 1.0) + vec2(0.5, 0.0);
    float mask = (1.0 - smoothstep(size-0.1, size, length(scaledUV - center))) *
                    smoothstep(size-tichness - 0.1, size-tichness, length(scaledUV - center));
    vec2 disp = normalize(scaledUV - center) * force * mask;
    COLOR = texture(SCREEN_TEXTURE, SCREEN_UV - disp);
}

シェーダ言語は C/C++ 風で GDScript の文法とはまったく異なる。 非常に難解で、コード量が非常に少ないにもかかわらず詳細な説明は非常に長くなるし、筆者も完全には把握できていないので、 ごく簡単に説明するに留める。
シェーディング言語の詳細は ドキュメント を参照されたし。

最初の「shader_type canvas_item;」は、シェーダ種別が2Dレンダリング用であることを指定する。 通常は、このコードのように「canvas_item」を指定する。

次の uniform で始まる行は、グローバル変数のようなものを宣言する。 ここで宣言した変数は 先のスクショのように、インスペクタで Material > Shader Param で参照・変更することが可能になる。

最後の部分は fragment() 関数の定義で、この関数によりピクセルの描画位置を変化させることができる。

COLOR は出力フラグメントの色を示す。texture(TEXTURE, UV) で各位置の色を取り出すことが可能なので、 第2引数を SCREEN_UV – disp で位置をずらすことで波紋を表示している。 disp は中心からの距離にのみ依存する値であるために、波紋が円形となる。

以上で、シェーダコードが出来たので、次にシェーダを呼び出すためのコードを下記に示す。

func _process(delta):
    .....
    if shock_wave_timer >= 0:           # ショックウェーブ処理中
        shock_wave_timer += delta       # 経過時間をプラス
        # ショックウェーブサイズ設定
        $CanvasLayer/ColorRect.material.set_shader_param("size", shock_wave_timer)
        if shock_wave_timer > 2:        # 2秒たったら
            shock_wave_timer = -1.0     # ショックウェーブ終了
func _input(event):
    .....
    if is_solved():         # クリア状態
        shock_wave_timer = 0.0      # start shock wave

クリア状態になったら shock_wave_timer に 0.0 を代入する。
各フレームの _process() で shock_wave_timer をチェックし、これが 0 以上であればショックウェーブを表示するために $CanvasLayer/ColorRect.material.set_shader_param(パラメータ名, 値) を呼んで、サイズ すなわち波紋の半径を指定する。 shock_wave_timer の値は徐々に大きくなるので、徐々に広がっていく波紋が表示され、 2.0 では波紋が完全に画面の外に出る、という仕組みだ。

shock_wave_timer が 2.0 を超えたら shock_wave_timer に -1 を代入し、ショックウェーブの処理をそれ以上行わないようにしている。

さいごに

これまで14回にもわたって、Godot による絵かきパズルアプリの実装を解説してきた。 このアプリは比較的本格的なアプリで、実装にかなりの時間・期間(2ヶ月程度)を費やしている。 本アプリは Godot のサンプルアプリとして作り始めたものなので、ここまでいろいろな機能を実装する予定はなかったのだが、 筆者の好きなパズルアプリ開発であったこともあり、予定外に作り込んでしまった。

本稿により、このようなアプリを Godot でどう実装するかを具体的に理解していただけたのではないかと思っている。

本アプリは数十人もの方々にテストプレイをしていただいた。それらの方々のフィードバックを反映させることで、 より本格的で高品質なものになったと考えている。ほんとうにありがとうございました。
また、多くの人に(本アプリを使って)問題を作成していただいだ。 特に matoya氏 には高品質で素晴らしい問題を数多く作成していただいた。この場を借りて御礼申し上げる。