Developer

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

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

目次

  • 盤面クリック処理
  • 手がかり数字更新
  • ミニマップ

■盤面クリック処理

インプットマップでマウス左ボタン・右ボタンクリックを設定したので、次はクリックされた場合の処理を実装する。 下記がそのコードだ。

var g = Global

func posToXY(pos):
    var xy = $BoardBG/TileMap.world_to_map(pos - $BoardBG/TileMap.global_position)
    if xy.x >= g.N_IMG_CELL_HORZ || xy.y < 0 || xy.y >= g.N_IMG_CELL_VERT:
        xy.x = -1   # キャンパスエリア外
    return xy
func _input(event):
    if event is InputEventMouseButton:
        if( event.is_action_pressed("click") ||     # left mouse button
                event.is_action_pressed("rt-click") ):      # right mouse button
            var xy = posToXY(event.position)
            print(xy)

マウスクリックなどの入力イベントが発生した場合、_input(event) 関数がコールされる。 「event is InputEventMouseButton」でマウスクリックイベントかどうかを判定することができる。

インプットマップに登録されているアクションは「event.is_action_pressed(“click”)」でボタン押下状態かどうかを判定することができる。

event.position にマウスクリックされた座標が入っているので、それを実引数に指定して、posToXY(pos) をコールする。

posToXY(pos)は、クリックされた座標から盤面セル座標(これをマップ座標と呼ぶ)に変換する関数だ。 TileMap.world_to_map(pos) で座標値からセル単位のマップ座標値に変換することができる。 ただし、座標は TileMap 位置からの相対なので、「pos – $BoardBG/TileMap.global_position」で相対座標に変換している。

また、キャンパスエリア以外の場合、マイナス座標だったり、盤面サイズを超えているかどうかをチェックし、 範囲外の場合は x 座標を -1 にしている。

ここまでを実行すると、下図のように、盤面等をマウスクリックするとセル単位のマップ座標値がアウトプットに表示される。

セルの状態を黒またはバツに変更するコードは下記のようになる。

func _input(event):
    .....
            var xy = posToXY(event.position)
            print(xy)
            if xy.x >= 0:       # キャンパスエリアがクリックされた場合
                var v0 = $BoardBG/TileMap.get_cell(xy.x, xy.y)
                var v = v0
                if event.is_action_pressed("click"):        # left mouse button
                    v = TILE_BLACK if v0 != TILE_BLACK else TILE_NONE;
                else:
                    v = TILE_CROSS if v0 != TILE_CROSS else TILE_NONE
                $BoardBG/TileMap.set_cell(xy.x, xy.y, v)

TileMap.get_cell(x, y) でセルの状態を取得し、TileMap.set_cell(x, y, v) でセルの状態を変更する。
黒の状態のセルを左ボタンクリックした場合は空欄に、黒以外の状態のセルを左ボタンクリックした場合は黒に変更するので、 コードは上記のようになる。

以上で、下図のようにキャンパスエリアに黒・バツを自由に配置することが可能になった。

■手がかり数字更新

キャンパス部分に黒・バツを配置可能になったので、次にそれに対応する手がかり数字を表示するようにする。

以下にそのためのコードを示す。ちょっと行数が多いが、それぞれの処理は単純なものばかりなので、 地道に読んでみてほしい。

# セル状態取得
func get_h_data(y0):
    var data = 0
    for x in range(g.N_IMG_CELL_HORZ):
        data = data * 2 + (1 if $BoardBG/TileMap.get_cell(x, y0) == TILE_BLACK else 0)
    return data
# 手がかり数字更新
func update_clues(x0, y0):
    update_h_clues(y0)
    update_v_clues(x0)
func update_h_cluesText(y0, lst):
    var x = -1
    for i in range(lst.size()):
        $BoardBG/TileMap.set_cell(x, y0, lst[i] + TILE_NUM_0 if lst[i] != 0 else TILE_NONE)
        x -= 1
    while x >= -g.N_CLUES_CELL_HORZ:
        $BoardBG/TileMap.set_cell(x, y0, TILE_NONE)
        x -= 1
func update_h_clues(y0):
    # 水平方向手がかり数字更新
    var data = get_h_data(y0)
    var lst = data_to_clues(data)
    h_clues[y0] = lst;
    update_h_cluesText(y0, lst)
func update_v_cluesText(x0, lst):
    .....
func update_v_clues(x0):
    .....
# 101101110 → [3, 2, 1]    下位ビットの方が配列先頭とする
func data_to_clues(data : int) -> Array:
    if !data:
        return [0]
    var lst = []
    while data != 0:
        var b = data & -data        # 値が1の一番右のビット
        data ^= b
        var n = 1
        b <<= 1
        while (data & b) != 0:      #1が続く間
            data ^= b
            b <<= 1
            n += 1
        lst.push_back(n)
    return lst

get_h_data(y0) は指定行(水平方向)の黒の状態を2進数で取得する関数だ。 TileMap.get_cell(x, y) でセルの状態を取得し、黒であればビットの値を1に、黒でなければ0としている。

update_clues(x0, y0) が、仮引数位置の状態が変更されたときにコールされる関数だ。 中身は単純で、水平・垂直方向の手がかり数字を更新する関数をたらいまわし的に呼んでいるだけだ。

update_h_clues(y0) は指定行のデータを get_h_data(y0) で取得し、data_to_clues(data) をコールし、 手がかり数字配列に変換し、最後に update_h_cluesText(y0, lst) をコールし、画面の手がかり数字部分を更新している。
手がかり数字は TileMap で表示するのだが、手がかり数字部分はマップ座標がマイナスなので、 それを考慮して -1, -2, … の順に処理している。

以上で準備が整ったので、入力イベント処理でセルの状態を変更した直後に update_clues() をコールして、 手がかり数字の表示を更新する。

func _input(event):
    .....
            $BoardBG/TileMap.set_cell(xy.x, xy.y, v)
            update_clues(xy.x, xy.y)

以上で、設定した黒に対応する手がかり数字がリアルタイムに更新されるようになった(下図参照)。 これで、最低限のお絵かきパズルの問題作成機能が実装できたことになるわけだ。

■ミニマップ

盤面左上に縮小画像を表示する。そのために下図のように TileMap を設置する。

BoardBG の子ノードとして、TileMap を追加し、「MiniTileMap」にリネームする。 セルサイズは 6×6 とし、領域中央に表示されるように、位置を 35×35 とする。 また、TileSet を作成し、それに 6×6 の黒矩形を登録する。

下記コードがミニマップのために追加するコードだ。

func update_miniTileMap():
    for y in range(g.N_IMG_CELL_VERT):
        for x in range(g.N_IMG_CELL_HORZ):
            var img = 0 if $BoardBG/TileMap.get_cell(x, y) == TILE_BLACK else TILE_NONE
            $BoardBG/MiniTileMap.set_cell(x, y, img)
func _input(event):
    .....
            $BoardBG/TileMap.set_cell(xy.x, xy.y, v)
            update_clues(xy.x, xy.y)
            update_miniTileMap()

update_miniTileMap() では、x, y を for 文で回して、TileMap.get_cell(x, y) でセルの状態を取得し、 MiniTileMap.set_cell(x, y, img) でミニマップを描画している。

以上で、盤面の状態を変更すると、下図のようにミニマップが表示されるようになる。

TechProjin Godot入門 関連連載リンク

Godotで学ぶゲーム制作
さくさく理解するGodot入門 連載目次

標準C++ライブラリの活用でコーディング力UP!
「競技プログラミング風」標準C++ライブラリ 連載目次