Developer

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

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

目次

  • 盤面に数字を入れる
  • 数字削除
  • 削除数字落下

■盤面に数字を入れる

空欄がクリックされた場合、その時点での現数字の数字をそのセルに入れる。
それを実装するために、まず数字を入れる関数 set_cell_number(x, y, n) を実装する。 そのコードは下記のとおり。

func set_cell_number(x, y, n):      # n: [1, 9], 0 for clear
    if n<=0:
        $CenterContainer/numTileMap.set_cell(x, y, -1)
    else:
        $CenterContainer/numTileMap.set_cell(x, y, n-1+NUM_OFFSET)

引数のnが1~9の場合はその数字を入れ、0以下であればセルの数字を消す。
TileMap.set_cell(x, y, id) の第3引数を -1 でコールするとセルの内容が空欄になる。
1以上の場合は、id 部分にその数字IDを指定するだけだ。通常数字の ‘1’ の id は NUM_OFFSET と宣言されているので、 上記のコードで数字が正しく設定される。

set_cell_number(x, y, n) が定義できれば、あとはそれを正しいタイミングで呼ぶだけだ。

func cell_pressed(x, y):    # 盤面セルがクリックされた場合
    .....
    if is_clue_cell(x, y):      # 手がかり数字セルがクリックされた場合
        .....
    else:   # 手がかり数字以外のセルがクリックされた場合
        set_cell_number(x, y, cur_numButton)
        update_cell_cursor()    # 選択数字のセル強調

コードは上記の通りで、手がかかり数字が入っていないセルがクリックされた場合に set_cell_number() をコールし、 update_cell_cursor() を呼んで、現数字強調表示を行っている。

■数字削除

すでに入っている数字と同じ数字をセルに入れた場合は、すでに入っている数字を消す。 そのコードは下記のとおり。

func cell_pressed(x, y):    # 盤面セルがクリックされた場合
    .....
    else:   # 手がかり数字以外のセルがクリックされた場合
        if get_cell_number(x, y) == cur_numButton:
            set_cell_number(x, y, 0)    # 0 for クリア
        else:
            set_cell_number(x, y, cur_numButton)
        update_cell_cursor()    # 選択数字のセル強調

get_cell_number(x, y) ですでに入っている数字を取り出し、それが現数字と同じであれば、 set_cell_number(x, y, 0) をコールして、セルに表示する数字を無しにする(TileMap.set_cell(x, y, -1) を呼ぶ)。

■削除数字落下

筆者のアプリでは、削除されたものがニュートン力学に従って自然落下するのがトレードマークになっている。 なので、このアプリでも、削除された数字を自然落下するようにしてみる。

落下するオブジェクトはひとつだけでなく、削除された時点で画面に追加する必要がある。 そのために、落下数字シーンを別に作成し、それをノードとして動的にメインシーンに追加する。

通常「シーン」というと一枚の画面をイメージするのが普通だと思うが、Godot のシーンは1枚の画面になりえるが、 それだけでなく、他のシーンに埋め込んで表示することも可能なのだ。

まずは、新規シーンを作成し、下図のように、RigidBody2D をルートノードとし、 Sprite, CollisionShape2D, VisibilityNotifier2D をその子ノードとする。

RigidBody2D は必ず「fallingNumber」にリネームしておき、スクリプトをアタッチし、今は何も修正せずにとりあえず保存しておく。

Sprite には下図のような ‘1’~’9′ を横に並べたひとつの画像をアタッチし、インスペクタで Animation > Hframes を 9 に設定する。 こうしておくと、frame プロパティを変更することにより表示する数字を切り替えることが可能になる。

CollisionShape2D は衝突判定を行うためのオブジェクトで、Shape を RectangleShape2D としておく。

VisibilityNotifier2D はオブジェクトが画面外に出たときに、シグナルを発生させ、それをハンドリングすることで、 不要になったオブジェクトを削除し、メモリリークを防ぐためのものだ。
ノードツリーで VisibilityNotifier2D を選択状態にし、インスペクタで「ノード」タブを選び、screen_exited() をダブルクリックし、 処理ハンドラを fallingNumber オブジェクトのスクリプトに追加する。

オブジェクトが画面外に出ると、_on_VisibilityNotifier2D_screen_exited() がコールされるので、 queue_free() を呼んで、メモリを解放する。

func _on_VisibilityNotifier2D_screen_exited():
    queue_free()

以下に、fallingNumber スクリプトを示す。

var rng = RandomNumberGenerator.new()

func _ready():
    rng.randomize()
    pass # Replace with function body.
func setup(num):
    $Sprite.frame = num - 1
    rng.randomize()
    var th = rng.randf_range(0, 3.1415926535*2)
    linear_velocity = Vector2(cos(th), sin(th))*100
    angular_velocity = rng.randf_range(0, 10)

乱数ジェネレータを生成し、_ready() で系列をランダムに初期化しておく。
setup(num) 関数を定義し、そこで $Sprite.frame を設定することで、表示する数字を切り替える。 また、速度と回転速度を乱数で設定している。

以上で準備ができたので、MainScene.gd の方を以下のように修正する。

var FallingNumber = load("res://fallingNumber.tscn")    # 落下数字クラスをロード
.....
func cell_pressed(x, y):    # 盤面セルがクリックされた場合
    .....
    else:   # 手がかり数字以外のセルがクリックされた場合
        if get_cell_number(x, y) == cur_numButton:
            set_cell_number(x, y, 0)    # 0 for クリア
            var ns = FallingNumber.instance()       # 落下数字クラスをインスタンス化
            ns.setup(n)                 # 数字設定
            add_child(ns)               # ノードツリーに追加
            var tm = $CenterContainer/numTileMap
            ns.position = tm.global_position + Vector2(tm.cell_size.x * (x+0.5), tm.cell_size.y * (y+0.5))
        else:
        .....

外部シーンで定義されたオブジェクトをその画面に貼り付けるには、以下の手順を踏む

1.「load(“res://シーン名.tscn”)」でシーンを読み込み、それを変数に格納。
2.「シーン変数.instance()」で外部シーンオブジェクトを生成
3.オブジェクトを add_child() でノードツリーに追加
4.オブジェクトのプロパティ設定

本コードでも、上記の手順を踏んでいる。
このコード特有なのは、落下する数字(’1’~’9’)を setup(num) 関数を呼んで設定し、位置を指定しているところだけだ。