Developer

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

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

目次

  • 数字ボタン押下時処理
  • 盤面セル押下時処理
  • 手がかり数字押下

■数字ボタン押下時処理

数字ボタンが押されたら、どのボタンが押されたかを記憶し、その数字ボタン位置に選択カーソルを移動する。 また、盤面に入っている同じ数字を強調(背景色黄色)する。

Button が押下されると pressed() というシグナルが発行される。 シグナルは任意の処理関数(ハンドラ)に結合(コネクト)することができる。 コネクトはエディタで行うことも出来るし、アプリ実行時にスクリプトにより行うことも可能だ。 要するにシグナルは動的バインディング可能な関数ということだ。 この仕組により、UI クラスを独立な部品として実装することが可能になっている。

数字ボタン押下時シグナルを、それを処理ハンドラにコネクトするには、 ボタンノード選択状態にてインスペクタペインで「ノード」を選び、 「pressed()」(下図参照)をダブルクリックするか、または右ボタンメニューで「接続…」を選ぶ(下図参照)。

そうすると下図のシグナル接続ダイアログが表示されるので、【接続】を押す。

以上の操作で、numButton1 ボタンが押下されると _on_numButton1_pressed() がコールされるようになる。

_on_numButton1_pressed() の実装は以下のようになる。

var cur_numButton = 1   # 選択されている数字ボタン
.....
func _on_numButton1_pressed():  # 数字ボタン1が押された場合の処理
    var rct = $CenterContainer2/VBoxContainer/HBoxContainer/numButton1.get_global_rect()
    $numCursor.set_position(rct.position)
    cur_numButton = 1
    update_cell_cursor()    # 選択数字のセル強調

内容は、数字ボタンカーソルを押下された数字ボタン位置に移動し、現数字(cur_numButton)を設定し、 選択数字のセル強調処理関数(update_cell_cursor())を呼ぶだけだ。

数字ボタン2~9 についてもシグナルを各処理関数にコネクトし、同様のコードを書く。 ただし、ボタンオブジェクト名、cur_numButton に設定する値は適切なものに置き換える。

update_cell_cursor() の実装は以下のようになる。

func show_cell_cursor(x, y, b : bool):      # (x, y) セルを強調表示
    $CenterContainer/cursorTileMap.set_cell(x, y, 0 if b else -1)
func update_cell_cursor():  # 選択数字ボタンと同じ数字のセルを強調
    for y in range(9):
        for x in range(9):
            var cn = get_cell_number(x, y)
            show_cell_cursor(x, y, cur_numButton == cn)

画面を作るときに、セルカーソルを表示する TileMap が正しく設定されていれば、set_cell(x, y, 0) で黄色背景が表示される。

また、下記のように _ready() で update_cell_cursor() をコールするようにする。

func _ready():
    set_quest("008010240090320061102805007039452700 670103092 001679380 900706108 780091020 015030600")
    update_cell_cursor()    # 選択数字セル強調

以上で、アプリを実行すると、下図のように押下された数字ボタンの数字が現数字となり、それが強調されるはずだ。

■盤面セル押下時処理

ユーザが画面をクリックしたときは、インプットイベントが発生し、_input(event) ハンドラがコールされる。 そこにクリックに対応する処理を記述する。

インプットイベントにはキーなどいくつかの種類があるので、最初にマウス押下イベントかどうかをチェックする(下記参照)。

func _input(event):
    if event is InputEventMouseButton and event.pressed:    # マウスボタン押下イベントか?
        var xy = posToXY(event.position)    # クリック位置 → 盤面座標に変換
        #print(xy)
        if xy.x >= 0:
            cell_pressed(xy.x, xy.y)

event.position でマウスクリック位置を取得できるので、posToXY(pos) をコールして、盤面座標に変換する。 盤面座標とは、最も左上のセルが (0, 0) で、セルごとに x, y 座標を1ずつ増やす座標系のことだ。 そのコードは下記の通り。

func posToXY(pos):  # 画面座標 → 盤面座標変換
    pos -= $CenterContainer/numTileMap.global_position  # 盤面相対座標に変換
    var xy = $CenterContainer/numTileMap.world_to_map(pos)  # セル座標に変換
    if xy.x >= 9 || xy.y >= 9 || xy.y < 0:  # 範囲チェック
        return Vector2(-1, -1)
    else:
        return xy

盤面には TileMap を配置しているので TileMap.world_to_map(pos) を使って、盤面座標に変換する。 引数の pos は TileMap 位置からの相対座標で、event.position は画面左上が原点のグローバル座標系なので、 変換を行う必要がある。その処理は簡単で、コードのようにグローバル座標から TileMap.global_position を減算するだけだ。

world_to_map() は有効な盤面外をクリックされた場合も値を返すので、範囲チェックを行い、盤面外であれば x 座標を -1 に設定している。

下記に cell_pressed() のとりあえずの実装を示しておく。次節以降から、ここにコードを追加していき、 機能を徐々に実装していく。

func cell_pressed(x, y):    # 盤面セルがクリックされた場合
    print("(", x, ", ", y, ")")
    pass

■手がかり数字押下

盤面の手がかり数字がクリックされた場合は、その数字ボタンが押下されたものとみなす。
その実装のために、いくつかの関数を定義する。

最初は、セルに手がかり数字が入っているかどうかを判定する is_clue_cell(x, y) を実装する。 そのコードは下記のとおり。

const NUM_OFFSET = 9*2
.....
func is_clue_cell(x, y):
    var n = $CenterContainer/numTileMap.get_cell(x, y) 
    return n >= 0 && n < NUM_OFFSET

数字タイルは ‘1’~’9′ が4セットあり、前半2セットが手がかり数字用なので、NUM_OFFSET を 9*2 と定義しておき、 TileMap.get_cell(x, y) で取得したタイルIDが 0 以上 NUM_OFFSET 未満であれば、手がかり数字と判定する。

次に、数字 n の数字ボタンオブジェクトを取得する関数と、そこにカーソルを移動する関数を定義する。

func get_numbutton(n):
    var hbn = (n-1)/3 + 1   # HBoxContainer 番号
    var name = "CenterContainer2/VBoxContainer/HBoxContainer%d/numButton%d" % [hbn, n]
    return get_node(name)
func updateNumButtonCursor():   # 現数字ボタンカーソル位置更新
    var btn = get_numbutton(cur_numButton)
    $numCursor.set_position(btn.get_global_rect().position)

数字ボタンは「CenterContainer2/VBoxContainer/HBoxContainer%d/numButton%d」という形式で、 HBoxContainer の番号は 1-3 が 1, 4-6 が 2, 7-9 が 3 なので、(n-1)/3 + 1 で計算し、numButton 番号は n をそのまま用いる。 こうして作成したノード名を get_node() に引数として渡せば、数字ボタンオブジェクトを取得可能だ。

updateNumButtonCursor() は、現在選択されいる数字ボタンの位置に数字ボタンカーソルを移動する関数だ。 数字ボタンオブジェクトを get_numbutton() で取得し、そのグローバル座標を取得し、それを数字ボタンカーソルの位置とする。

以上で、準備が整ったので、cell_pressed(x, y) の中身を実装しよう。 最初に n に現在セル入っている数字を格納しておき、is_clue_cell(x, y) で手がかり数字がクリックされたかどうかを判定する。 そうであれば、現数字を更新し、updateNumButtonCursor(), update_cell_cursor() をコールして、 その数字ボタンを選択状態にし、現数字と同じ盤面に入っている数字を強調する。

func cell_pressed(x, y):    # 盤面セルがクリックされた場合
    print("(", x, ", ", y, ")")
    var n = get_cell_number(x, y)
    if is_clue_cell(x, y):      # 手がかり数字セルがクリックされた場合
        cur_numButton = n       # 選択数字設定
        updateNumButtonCursor() # 数字ボタン選択カーソル位置更新
        update_cell_cursor()    # 選択数字のセル強調

TechProjin Godot入門 関連連載リンク

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

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