
AI Assisted
この記事は筆者の実装経験をもとに実装コードをベースで執筆し、AIによる校閲・推敲を経て公開しています。
5日目で、ラインを消して無限に遊べるようになりました。 しかし、今のままでは上まで積み上がっても何も起きず、エラーが出るか、虚無の時間が続くだけです。また、頑張って消しても誰も褒めてくれません。
今日のゴールはこれです!
まずは見た目から作ります。
GodotでUIを作る時は、Control ノード(緑色のアイコン)を使います。
Main シーンを開き、以下のようにノードを追加しました。
CanvasLayer を使うことで、ゲーム画面の上に透明なレイヤーとしてUIを表示できます。
Main
├── TileMapLayer
├── CanvasLayer (追加)
│ ├── ScoreLabel (Label)
│ ├── GameOverLabel (Label)
│ └── RestartButton (Button)
画面の中央に文字を置くとき、手動でドラッグするのは大変です。 Godotの アンカープリセット(Anchor Presets) 機能を使えば、一発で画面の中央や右上に固定できました。

UIができたら、それを動かすためのコードを書きます。 今回は3つのファイルすべてに手を入れました。
board.gd にシグナルを追加し、ライン消去時に通知するようにしました。
# board.gd
signal lines_cleared(count: int)
func clear_full_rows() -> int:
# ... (消去処理) ...
if cleared_count > 0:
lines_cleared.emit(cleared_count) # Mainに報告!
return cleared_count
piece.gd では、出現した瞬間に場所が空いているかチェックします。
# piece.gd
signal game_over
func _ready() -> void:
# ... (初期化) ...
if not is_position_valid(position, current_cells):
game_over.emit() # 詰んだ!
queue_free()
そして main.gd でこれらを受け取り、スコア加算やゲームオーバー画面の表示を行います。
# main.gd
func _on_board_lines_cleared(count: int) -> void:
score += count * 100
score_label.text = "Score: %d" % score
func _on_game_over() -> void:
game_over_label.show()
restart_button.show()
get_tree().paused = true # ゲームの時間を止める!
実装中に2つの大きな落とし穴にハマりました。
ゲームオーバー時に get_tree().paused = true でゲームを止めると、リトライボタンまで一緒に止まってしまい、押せなくなりました。
解決策:
インスペクターでボタンの Process Mode を Inherit(親に従う)から Always(いつでも動く)に変更しました。これで「世界は止まっていてもボタンだけは動く」状態になりました。
最初は add_child(piece) をしてからシグナルを接続していましたが、これでは遅すぎました。
add_child した瞬間に Piece の _ready が走り、シグナルが発信されてしまうため、Main が接続する頃にはすべて終わっていたのです。
解決策: 「先に接続してから、世に出す(add_child)」 という順序に入れ替えることで解決しました。
# 正しい順序
piece.game_over.connect(_on_game_over) # 1. 先に繋ぐ
add_child(piece) # 2. その後に出現させる
これですべての機能が繋がりました。

たった6日間で、ゼロからここまで作ることができました!
ゲームを書き出して、「WindowsやMacのアプリとして遊べるファイル」 にする方法(エクスポート)と、これまでの振り返りを行います。 友達に配れるようにしましょう!
スポーツ×ITの会社でバックエンドエンジニア兼マネージャーとして勤務。インテル関連の情報を中心に発信しています。
最終更新: 2026年1月18日
© 2025 nero15.dev. All rights reserved.