Developer

さくさく理解する Godot 入門(ただし2Dに限る)応用編 シーズン2(数独パズル)【第4回】
2021.11.29
Lv1

さくさく理解する Godot 入門(ただし2Dに限る)応用編 シーズン2(数独パズル)【第4回】

目次

  • スクリプトとは
  • メイン画面スクリプト
  • 効果音
  • 手がかり数字表示

スクリプトとは

この解説シリーズの最初にも書いたが、Godot では画面にオブジェクトを配置して画面を作成し、 それにアプリの動作を記述するスクリプトをアタッチ(添付)する。

スクリプトは、標準的には「GDScript」というパイソンライクな独自言語を用いる。 本稿ではその文法等については詳しくは説明しないので 公式ドキュメント などを読んでほしい。
また、GDScript だけでなく、C# や C++、または VisualScript というビジュアルプログラミング言語(?)でもスクリプトを記述できるが、 本稿では説明しない(と言うか、筆者はまだ未勉強で説明できない)。興味がある人は公式ドキュメントなどを自分で勉強してほしい。

スクリプトは、全部のオブジェクトにアタッチするのではなく、 ルートノードからその子ノードを容易にアクセスできるので、通常はシーンのルートノードのみにアタッチする。
アタッチする方法は簡単で、ノードツリーでアタッチしたいノードを選択し、右ボタンメニューで「スクリプトをアタッチ」を選択するか、 シーンパネル右上の「+巻物」のようなアイコンを押すだけだ(下図参照)。

なお、スクリプトがアタッチされているノードの右には巻物のようなアイコンが表示され、 スクリプトがアタッチされていることがわかるようになっている。 それをクリックすることで、そのスクリプトを編集することも可能だ。

下記に、スクリプトをアタッチしたとき、自動的に生成されるコードを示す。

extends Node2D

# Declare member variables here. Examples:
# var a = 2
# var b = "text"

# Called when the node enters the scene tree for the first time.
func _ready():
    pass # Replace with function body.

# Called every frame. 'delta' is the elapsed time since the previous frame.
#func _process(delta):
#   pass

最初の行は、「extends」に続けてスクリプトをアタッチしたノードのクラス名が書かれている。 これは、このスクリプトがノードのクラス Node2D をサブクラス化するという意味だ。

次に3行コメントアウトされているが、意味は「ここでメンバ変数を宣言しなさい」で、a, b の宣言が例として書かれている。 ちなみに、変数だけでなく、定数も「const PAI = 3.14」のように定義することが出来る。

次は _ready() の定義だ。このメンバ関数は本クラスのノードオブジェクトがシーンに追加されたとき、 最初に1回だけ呼ばれるものだ。クラスオブジェクトのなんらかの初期化を行いたいとき、ここにコードを追加する。

パイソンは、C系のように { } ブロックで範囲を指定するのではなく、行末にコロンを書き、 次の行からインデントをひとつ深くすることでブロック範囲を指定する。パイソンライクな GDScript もそれと同じ書式だ。 関数定義の場合は、同レベルにインデントされている範囲が関数実装コードとなる。

最後の部分もコメントアウトされているが、各フレーム(通常、1秒間に60回処理される)ごとにコールされる _process(delta) 関数の定義だ。 ただし、物理オブジェクトに対する各フレームでの処理を記述したい場合は _physics_process(delta) を実装する。

スクリプトからは画面に配置した各ノードを「$ノード名」で参照することができる。 これはそのスクリプトを配置しているノードからの相対指定となる。
また、孫ノードを参照したい場合は「$子ノード名/孫ノード名」のように、Unix のディレクトリ指定と同じ様に記述する。

たとえば、messLabel のテキストを書き換えたい場合は「$messLabel.text = “hello”」のように記述する。

なお、「$ノート名」は「get_node(“ノード名”)」のシンタックスシュガーである。 変数に格納されている数値により参照するノード名を変えたい場合などは後者を用いるのが便利だ。
例えば、n の値により「button1」、「button2」、「button3」・・・ というノードを取得したい場合は、 「get_node(“button%d” % n)」と記述するとよい。

メイン画面スクリプト

というわけで、いよいよこれからメイン画面のスクリプトについて解説していく。
スクリプトの何箇所かで効果音を再生するようにしているので、まずは効果音の説明から始める。

■効果音

音を出すには、ノードツリーに AudioStreamPlayer2D を追加し、それに音源ファイルをアタッチする。

効果音の音源ファイルは、必ず、インポートタブで Loop のチェックを外し、【再インポート】を押下しておく。
もちろん、エンドレスに繰り返し流すBGM の音源ファイルの場合はループをONにしておく。

本アプリでは、セルに数字を入れた場合、数字ボタン押下時、数字を9個入れた場合、 パズルをクリアした場合の4種の音源を用意しておき、下図のように AudioStreamPlayer2D ノードをノードツリーに追加している。

それぞれの AudioStreamPlayer2D ノードには、音源ファイルをアタッチする。 アタッチ方法は、当該ノードを選択し、インスペクタの Stream プロパティに音源ファイルをドロップするだけだ。

以上ができていれば、音を鳴らしたい時に「$効果音ノード名.play()」と記述するだけだ。

■手がかり数字表示

まずは (x, y) セルに、手がかり数字 n を表示する関数を実装する。コードは下記のとおりだ。

func set_cell_clue(x, y, n):    # 手がかり数字表示
    $CenterContainer/numTileMap.set_cell(x, y, n-1)

画面を作ったときに、盤面にタイルマップを配置し、そのタイルセットに ‘1’~’9′ の数字を4セット (手がかり文字、プレイヤーが入れた数字、及びそれらが重複している場合)設定した。 手がかり数字の ‘1’ が id0 に設定されてるはずなので、数字タイルマップに対して TileMap.set_cell(x, y, n-1) をコールすると手がかり数字 n が表示される。

次に、問題テキストから手がかり数字を全て表示する関数 set_quest(q) を実装する(下図参照)。 問題は “008010240 090320061 <中略> 015030600” のようなテキスト(詳しくは後術)で与えられるものとする。

func set_quest(q):
    var ix = 0
    for ch in q:
        if ch != ' ':   # 空白は無視
            if ch < '1' || ch > '9':
                ch = '0'
            var n = ch as int
            set_cell_clue(ix%9, ix/9, n)    # 手がかり数字表示
            ix += 1

処理は簡単で、問題テキストを最初からスキャンし、空白は無視し、’1’~’9′ であればその値を、 それ以外であれば0を引数にして先に定義した set_cell_clue(n) を呼ぶだけだ。

手がかり数字を表示する準備が整ったら、最初に自動的にコールされる _ready() 関数から set_quest() をコールする。
問題はとりあえず固定とし、テキストで指定する。 左上から1行ごとに手がかり数字を指定する。手がかり数字が無い場所は ‘0’ を指定するものとし、 半角空白は無視する(1行の区切りが人間にわかりやすいようにするため)ものとする。 なお、この問題は筆者が以前書いた問題生成プログラムにより生成した初級問題である。

set_quest(q) の準備ができたら、下記のように _ready() からそれを呼び問題を表示する。

func _ready():
    set_quest("008010240 090320061 102805007 039452700 670103092 001679380 900706108 780091020 015030600")

以上を実行すると、下図のように問題の手がかり数字が表示される。
もし正しく表示されない場合は、TileMap の TileSet の設定に失敗している可能性があるので、よーく見直してほしい。