さくさく理解する Godot 入門(ただし2Dに限る)応用編 お絵かきパズル【第12回】
目次
- レベル画面
- 背景・タイトル
- 問題一覧
- ドラッグスクロール処理
- 進捗を消す
- 問題を作る
レベル画面
下図にレベル画面のスクショを示す。
レベル画面は、アプリを起動すると最初に表示される画面だ。 上部にはタイトル等が表示され、中央部には縦スクロール可能な問題一覧が表示される。 そして下部には2つのボタンが配置されている。
以下、これらの実装方法について解説する。
■背景・タイトル
まずは、なにはともあれ背景だ。ColorRect を設置し、サイズを画面いっぱいにし、色を設定する。
タイトルは Label を設置し、ちょっと特殊なフォントを使用し、影付き文字として表現している。 タイトルロゴを作れる人は画像を配置してもよい。筆者には見栄えのよいロゴを自分で作るのは敷居が高いので、このようにしてみた。
タイトルフォントの設定は、下図のようにしている。Colors > Font Color Shadow をオンにし、 Fonts > Font を DynamicFont とし、サイズを指定し、Font > Font Data で TTF フォントを指定している。
これらの設定は初見では見つけるのが難しいかもしれないが、慣れればどうってことはないはずだ。
■問題一覧
ScrollContainer を設置し、その子ノードとして VBoxContainer を設置する。 そして、問題パネルインスタンスを生成し、その子ノードとすれば、自動的に垂直スクロールしてくれる。
各問題パネルの初期化コードは下記の通り。
var QuestPanel = load("res://QuestPanel.tscn") func _ready(): ..... for qix in g.quest_list.size(): # 問題パネルセットアップ g.qix2ID[qix] = g.quest_list[qix][g.KEY_ID] var panel = QuestPanel.instance() # 問題パネルインスタンス作成 panel.set_number(qix+1) # 問題番号 ..... if g.solvedPat.has(g.qix2ID[qix]): # クリア済み or 途中経過あり var lst = g.solvedPat[g.qix2ID[qix]] if lst.size() <= N_IMG_CELL_VERT || lst[N_IMG_CELL_VERT] > 0: solved = true panel.set_title(g.quest_list[qix][g.KEY_TITLE]) else: panel.set_title(g.quest_list[qix][g.KEY_TITLE][0] + "???") panel.set_ans_image(lst) if lst.size() > N_IMG_CELL_VERT: # クリアタイムあり panel.set_clearTime(lst[N_IMG_CELL_VERT]) if solved: if lst[N_IMG_CELL_VERT] < diffi * 60 * 0.5: ns = 3 elif lst[N_IMG_CELL_VERT] < diffi * 60: ns = 2 elif lst[N_IMG_CELL_VERT] < diffi * 60 * 2: ns = 1 else: panel.set_clearTime(0) else: panel.set_title(g.quest_list[qix][g.KEY_TITLE][0] + "???") panel.set_clearTime(0) if solved: nSolved += 1 score += diffi * (10 + ns*2) panel.set_star(ns) panel.set_author(g.quest_list[qix][g.KEY_AUTHOR]) $ScrollContainer/VBoxContainer.add_child(panel) # パネルインスタンスをコンテナに追加 # ボタン押下時シグナルをハンドラに接続 panel.connect("pressed", self, "_on_QuestPanel_pressed")
各ボタンは QuestPanel という名前で別シーンとして作成しているので、instance() でインスタンス化し、 問題番号等を設定し、add_child() で垂直方向コンテナに追加している。
問題がクリア済み、または途中経過が保存されている場合は、問題タイトルを設定し、 panel.set_ans_image() を呼んで、イラストのサムネイル表示を行う。set_ans_image() の実装は次章で解説する。
問題が未クリアの場合は、ネタバレにならないようにタイトルの先頭文字+"???" をタイトルとして表示している。
次に、問題ボタンが押された場合に呼ばれるハンドラのコードを示す。
func _on_QuestPanel_pressed(num): if mouse_pos == null || dialog_opened: # ダイアログがオープン状態の場合など return var v = $ScrollContainer.scroll_vertical g.lvl_vscroll = $ScrollContainer.scroll_vertical # スクロール位置保存 g.solveMode = true; # 問題を解くモード g.qNumber = num # 問題番号 get_tree().change_scene("res://MainScene.tscn") # パズルを解くメインシーンに遷移
このシーンに戻ってきたときのために、スクロール位置を保存し、モードを設定し、クリックされた問題の番号をグローバル変数に保存し、 get_tree().change_scene() をコールしてパズルを解くメインシーンに遷移している。
■ドラッグスクロール処理
マウスボタンが押下されると、下記の _input(event) がコールされる。
func _input(event): if event is InputEventMouseButton: if event.is_action_pressed("click"): # left mouse button if $ScrollContainer.get_global_rect().has_point(event.position): # in ScrollContainer mouse_pushed = true; mouse_pos = event.position scroll_pos = $ScrollContainer.get_v_scroll() # 押下時点でのスクロール位置を保存 elif event.is_action_released("click"): mouse_pushed = false; mouse_pos = null elif event is InputEventMouseMotion && mouse_pushed: # mouse Moved $ScrollContainer.set_v_scroll(scroll_pos + mouse_pos.y - event.position.y)
_input(event) はキー押下などでもコールされるので、最初に「event is InputEventMouseButton」 でマウスボタンイベントかどうかをチェックする。
次に「event.is_action_pressed("click")」で押下されたかどうかをチェックし、 そうであれば、スクロールコンテナ内がクリックされた場合は mouse_pushed フラグを true にし、ドラッグモードにする。
マウスボタンがリリースされた場合は、event.is_action_released("click") が真になる。 その場合は、mouse_pushed フラグを false にする。
マウスが押下されたまま移動、すなわちドラッグされた場合は「event is InputEventMouseMotion && mouse_pushed」が真になる。 この場合は ScrollContainer.set_v_scroll() をコールし、ドラッグ量に応じた垂直スクロールを行う。
なお、問題パネル押下検出処理は、問題パネルクラスの方で行い、pressed() シグナルを発行するようになっている。 その説明は次章で行う。
■進捗を消す
【進捗を消す】が押下された場合に呼ばれるハンドラの実装を以下に示す。
func _on_ClearButton_pressed(): $ClearProgressDialog.window_title = "SakuSakuLogic" $ClearProgressDialog.dialog_text = "Are you sure to clear Progress ?" $ClearProgressDialog.popup_centered() dialog_opened = true
確認ダイアログは「ClearProgressDialog」というノード名でノードツリーに追加されているので、 タイトルとテキストを設定し、popup_centered() をコールし、画面中央に表示する。
確認ダイアログで【OK】が押下されると _on_ClearProgressDialog_confirmed() がコールされる。 この接続はエディタの右側のインスペクタで「ノード」を選び、confirmed(), popup_hide() シグナルを、 それぞれの処理ハンドラに接続しておく(下図参照)。
_on_ClearProgressDialog_confirmed() の実装は下記の通り。
func _on_ClearProgressDialog_confirmed(): g.solvedPat = {} var dir = Directory.new() dir.remove(g.solvedPatFileName) # 進捗データファイルを消去 # for i in g.quest_list.size(): # 問題パネルセットアップ var qix = i var panel = $ScrollContainer/VBoxContainer.get_child(i) panel.set_title(g.quest_list[qix][g.KEY_TITLE][0] + "???") panel.set_clearTime(0) panel.set_ans_image([]) #panel.update() $scoreLabel.text = "SCORE: 0" $solvedLabel.text = "Solved: 0/%d (0%)" % g.quest_list.size()
ダイアログが閉じられると _on_ClearProgressDialog_popup_hide() がコールされるので、 下記の様に、ダイアログオープンフラグをオフにしておく。
func _on_ClearProgressDialog_popup_hide(): dialog_opened = false
■問題を作る
【問題を作る】ボタンが押されると、_on_EditButton_pressed() がコールされる。
func _on_EditButton_pressed(): g.lvl_vscroll = $ScrollContainer.scroll_vertical g.solveMode = false; get_tree().change_scene("res://MainScene.tscn")
戻ってきたときのために、スクロール位置をグローバル変数に保存し、 問題を解くモードかどうかのフラグをオフにし、 get_tree().change_scene(シーン名) でシーンを切り替えているだけだ。
TechProjin Godot入門 関連連載リンク
Godotで学ぶゲーム制作
さくさく理解するGodot入門 連載目次
標準C++ライブラリの活用でコーディング力UP!
「競技プログラミング風」標準C++ライブラリ 連載目次