Godot > Sudoku
Sudoku
ステップ・バイ・ステップで Godot を使った Sudoku(数独、ナンバープレース)パズルアプリの作成方法を示す。
問題を解くソルバーではなく、問題を数問用意し、それをユーザが解くという形式のパズルアプリだ。
※「数独」は株式会社ニコリの登録商標です。
筆者にとっては最初の Godot プログラムなので、過去に cocos2d-x/C++ などで作成した経験があり、なおかつ動きの少ないものを採用した。
が、Sudoku パズルアプリは数々の Godot プログラミング要素を含んでおり、最初のサンプルとしてはかなり良さげなのではないかと自負している。
なお、完成したアプリのソースコードは github にて、MITライセンスで公開している。
次章から、Sudoku アプリの具体的作成方法をステップ・バイ・ステップで示すが、ステップがかなり多く、
今なんをしているかわからなくなるかもしれないので、下記に全体の流れを示しておく。
- メイン画面:盤面・数字ボタンなどのUIオブジェクトを画面に配置
- メイン画面:スクリプトで初期化処理、イベントハンドラを実装
- (問題情報等を保持する)グローバル変数追加
- (タイトル表示・問題選択ボタンを有する)レベルシーンを追加
Godot は画面に必要なオブジェクトを配置して画面を作り、それにスクリプトを配置しボタンが押された場合などのハンドラを記述し、
アプリの動作を実装するという手順になる。
また、シーン(だいたい画面と同じ意味)を複数定義し、それらの間で遷移したり、シーンに別のシーンを部品として読み込んで表示することも行っている。
ステップ・バイ・ステップ
- プロジェクト作成
- プロジェクト名は GodotSudoku とする
- webGL へのエクスポートを行うので、GLSE 2.0 を選択
- ウィンドウサイズ設定
- 500x800 ピクセルサイズとする。横縦比は 1:1.6 いわゆる黄金比にしてみた。
- ルートノード・背景・タイトルバー設置
- ルートノードは Node2D とする
- 全体背景:ColorRect オブジェクトを配置、Rect: (0, 0, 500, 800)、色を適当に選ぶ
- タイトルバー影:ColorRect オブジェクトを配置、Rect: (0, 0, 500, 60)、灰色を適当に選ぶ
- タイトルバー:ColorRect オブジェクトを配置、Rect: (0, 0, 500, 50)、色を適当に選ぶ
- これで下図のような画面が出来上がるはずだ。
- 盤面背景設置
- 盤面背景画像を用意しておき、TextureRect を画面中央に配置
- セルサイズは 53x53。これはこの後に説明する TileMap のサイズと合わせる必要がある。
- 画面中央に配置するために CenterContainer を利用
- これで下図のような画面が出来上がるはずだ。
- セルカーソル TileMap 設置
- TileMap は2次元のグリッドで、各セルに表示する画像を簡単に切り替えることができる。
- 各セルに表示する画像は TileSet に予め登録(慣れないと手順がちょっと面倒?)しておく必要がある。
- TileMap に TileSet を設定しておくと、TileMap.set_cell(x, y, id) で、(x, y) セルに表示するタイルを切り替えることができる
- TileMap を盤面位置に設定。セルサイズを 53x53 に設定、Rect: ()
- 下図に、カーソルタイルマップを選択した場合のスクショを示す。
- TileSet の id 0 がライトイエローの矩形になっていて、タイルのサイズは 53x53 になっている。
- セル数値 TileMap 設置
- 各セルに表示する数字も TileMap を使用することとする。
- スプライトに複数の画像を登録し、切り替えるという実装方法も考えられるが、TileMap を設定してしまえばその方が取り扱いが楽だと考える。
- TileMap を盤面位置に設定。Rect: ()
- 数字ボタン設置
- センターコンテナと水平垂直ボックスコンテナを使い、画面下部中央に均等に並べる
- まず、センターコンテナを画面下部いっぱいに配置する
- そこに垂直ボックスコンテナを配置し、さらにその中に水平ボックスコンテナを3つ配置する。
- 各水平ボックスコンテナには テクスチャボタンを3つ配置し、スペースを空けて、いい感じにする。
- 数字ボタンは 1~9 について、ノーマル・押下時・ディセーブル時の画像を作成し、それぞれに設定する。
- 数字ボタンカーソル
- どの数字ボタンが選択されているかを表すカーソル
- オレンジの矩形枠図形を用意し、TextureRect のテクスチャとする
- TextureRect を数字ボタンの上に設置すると、クリックイベントが数字ボタンに届かなくので、
インスペクタで Control > Mose > Filter を「Ignore(無視)」に変更する。
- 以上で、メイン画面へのオブジェクト設置と設定は終わりだ。次に本アプリのUI方式を説明し、
その次にボタン・盤面セル押下時の処理を GDScript で記述していく。
- UI 方式説明
- 数字ボタン選択 → 数字を入れるセルクリック
- 数字は上書きされる、同じ数字を2回いれると削除される
- 手がかり数字クリック → その数字の数字ボタン選択
- 選択されている数字ボタンの数字の背景が黄色で強調
- 縦横3x3ブロックで数字が重複している場合は、数字を赤色表示
- 効果音
- 音を出すには、ノードツリーに AudioStreamPlayer2D を追加し、それに音源ファイルをアタッチする。
- 本アプリでは、セルに数字を入れた場合、数字ボタン押下時、数字を9個入れた場合、パズルをクリアした場合の4種の音源を用意する。
- これができていれば、音を鳴らしたい時に「$効果音ノード名.play()」と記述するだけだ。
- なお、「$ノート名」は「get_node("ノード名")」のシンタックスシュガーである。
- セルカーソル更新処理
- 数字ボタンカーソル更新処理
func numButtonNode(num):
var hbc = (num - 1) / 3 + 1
var name = str("MarginContainer/VBoxContainer/HBoxContainer", hbc, "/numButton", num)
#print(name)
return get_node(name)
func updateNumButtonCursor():
var btn = numButtonNode(cur_numButton)
#print(btn.rect_global_position)
$numButtonCursor.rect_position = btn.rect_global_position
数字ボタン押下時処理
- エディタで、数字ボタン1オブジェクトを選び、画面右の「ノードタブ」を選び、「pressed」を押し、
数字ボタン1が押された場合のイベントハンドラを作成・コネクトする
- 以下のようなコードを追加する。
- numButton_pressed(num) は、数字ボタン num が押下された場合の処理。
cur_numButton に値を設定し、updateNumButtonCursor() を呼んで数字ボタンカーソル位置を更新し、
update_cell_cursor() を呼んで、その数字が入っているセルにカーソルを表示して強調表示する。
- そして最後に $AudioNumButton.play() を呼んで数字ボタン押下時効果音を再生する。
func numButton_pressed(num): # 数字ボタン押下処理
cur_numButton = num # 現数字ボタン
updateNumButtonCursor() # 数字ボタンカーソル更新
update_cell_cursor() # セルカーソル更新
$AudioNumButton.play() # 効果音
func _on_numButton1_pressed():
numButton_pressed(1)
func _on_numButton2_pressed():
numButton_pressed(2)
func _on_numButton3_pressed():
numButton_pressed(3)
func _on_numButton4_pressed():
numButton_pressed(4)
func _on_numButton5_pressed():
numButton_pressed(5)
func _on_numButton6_pressed():
numButton_pressed(6)
func _on_numButton7_pressed():
numButton_pressed(7)
func _on_numButton8_pressed():
numButton_pressed(8)
func _on_numButton9_pressed():
numButton_pressed(9)
盤面セル押下時処理
重複チェック処理
問題クリア判定
グローバル変数
- GDScript にはグローバル変数は無いのだが、シングルトンを用いることで、各シーンから参照可能なグローバル変数を定義することが可能
- 手順は以下の通り
- グローバル変数のためのシーンを作成。
- ルートノードは何でもいいと思うが本稿では Node オブジェクトを設置している。
- スクリプトをアタッチし、以下のようにグローバル変数を定義
extends Node
var qNumber = 1 # 問題番号
var quest = [ # 問題データ
"008010240 090320061 102805007 039452700 670103092 001679380 900706108 780091020 015030600",
"000609000 002050790 060380040 106900004 035000970 700005608 010043020 048090100 000501000",
"003504900 096000750 040000080 000609000 600030001 000401000 080000020 054000160 007108500",
"100800007 007000050 030907100 504020900 000604000 009070304 001503040 040000600 700009003",
"009060070 000000015 600051000 000007300 706000509 003200000 000980003 890000000 020040800",
]
グローバル変数のためのシーンを「Global.tscn」とかの名前で保存
プロジェクト > プロジェクト設定メニューを選び、プロジェクト設定ダイアログ上部の「自動読み込み」タブを選ぶ
上部中央のパス右のアイコンを押下し、先に追加した「Global.tscn」を選び、上部右の「追加」を押下
上記の操作により、グローバル変数シーンが各シーンで自動的に読み込まれ、「 シーン名.メンバ変数名」で参照することができる。
レベル画面追加
- 背景・タイトル追加
- 問題ボタン設置
- 問題サムネイル表示
レベル画面スクリプト
func to_main(n):
Global.qNumber = n
get_tree().change_scene("res://Main.tscn")
func _on_questButton1_pressed():
to_main(1)
func _on_questButton2_pressed():
to_main(2)
func _on_questButton3_pressed():
to_main(3)
func _on_questButton4_pressed():
to_main(4)
func _on_questButton5_pressed():
to_main(5)
メイン画面:戻るボタン追加
まとめ