【Godot 4.0】スマホ３Dゲームを作るための勉強　その２１　ゲームシステムを作る　その６　設定画面をPopupPanelで作成する

　ゲームエンジンGodot4.0で3Dスマホゲームを作りたいと思いますが、その前にお勉強しています。
　2023/3/1にstable版がリリースされました。
 [Godot_v4.0-stable_win64.exe.zip](https://godotengine.org/download/windows/)を使用しています。

# 目的
　ゲームシステムをつくっていきます。
　設定仮面をつくりましょう。ポップアップダイアログで表示／消去するまでです。

# 設定画面について
　設定画面はタイトルと同じように新規にシーンを作成して、global.gd経由で切り替えることもできますが、PopupPanelという機能を使用して表示しましょう。

:::note warn
　2023/3/1リリースのGodot4.0 stable版はPopupPanelにいくつか問題がありそうです。
　以下の記事では2つ問題がありつつも実装をすすめていますが、不安な方は別の方法(Window/Popup/PopupPanelを使用しない方法）で実装した方がよいと思います。
:::

# ベースプロジェクト
　下記で作成したプロジェクトをベースに機能追加をします。
　【Godot 4.0】スマホ３Dゲームを作るための勉強　その２０　ゲームシステムを作る　その５　タイトルシーンを作って、神様スクリプトからタイトルシーンとプレイシーンを入れ替えるようにする
　https://qiita.com/FootInGlow/items/08a31719f0077d09d4eb
　
　github(Godotのプロジェクトマネージャーからインポートして利用できます）
　https://github.com/footinglow/Godot4/tree/main/02_study/S20_GameSystem_005

# 設定ダイアログを作成します。
　プレイヤーがBulletを発射するタイミングは固定で１秒ですが、その間隔を0.1秒～1.0秒の間で設定できるようにします。

## 新規シーンを作成します。
　シーンメニューから新規シーンを作成します。「その他のノード」ボタンを押下します。
![スクリーンショット (212).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/657dfb82-5bf6-e2a1-decb-f3c118774b7b.png)
「PopupPanel」を追加します。
![スクリーンショット (213).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/0df29198-2fc7-c165-4e9a-b6a0b417c13b.png)
名称を「SettingsPopupPanel」に変更して、保存します。
![スクリーンショット (214).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/36e1e885-d0ef-e12e-a39f-c74dba759cf2.png)
「res://settings_popup_panel.tscn」として保存されました。
![スクリーンショット (216).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/bb52b689-c1b2-801c-b2f5-6c47db96e947.png)

## 設定パネルのサイズを決めるControlを追加します。
　この後追加するボタンなどのコントロールの親となるControlノードを追加します。
　このControlのサイズがPopupPanel全体のサイズになります。

　「SettingsPopupPanel」を右クリックして、「Contorl」ノードを追加します。
　名称を「ControlBaseSize」に変更します。
![スクリーンショット (217).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/a1e7ba41-d5fb-f1e0-10d5-ac306cd446dc.png)
　「ControlBaseSize」を選択した状態で、インスペクタのControl/Custom Minimum Sizeのx=300px、y=200pxを設定します。
![スクリーンショット (218).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/b2ef4054-4a16-58b3-98e1-068589d42d9d.png)

:::note warn
　2023.3.1リリースのGodot4.0ではPopupPanelのサイズ変更がGUI上でできません。そのためContolのインスペクタでサイズを直接設定しました。
　下図のようにGodot3.5の場合はPopupPanelの変更ができるようです。
![スクリーンショット (81).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/9c127ac1-b96d-bbaf-7ebb-135e61b232db.png)
　PopupPanelのGODOT DOCを参照すると、「similar to how PanelContainer works」とあるので、GUI上でもサイズ変更できるのが正しいような気がします。（PanelContainerにするとGUIでサイズ変更可能）
:::

## コントロールを配置します。
　コントロール３つ表示することにします。

|コントロール|用途|
|:-|:-|
|Label|「発射間隔1秒」のように現在の設定値をテキスト表示する|
|HSlider|スライダーで発射間隔を0.1秒から1.0秒まで0.1秒刻みで調整できるようにします。その結果はLabelに表示します|
|Button|設定完了時に押下します|

　Godotのコントロールの配置はコンテナというのを利用して、Controlパーツを縦/横に並べるように配置していきます。
　今回は縦に3つ並べたいので、VBoxContainerを使用します（横に並べる場合はHBoxContainerを使用します）

　「ControlBaseSize」を右クリックして子ノードを追加から「VBoxContainer」を追加します。
　「VBoxContainer」を右クリックして子ノードを追加から下記の3つのコントロールを追加して各種設定をします。
* Label
　インスペクタのLabel/Textに「発射間隔1秒」と設定します。
　あとでスクリプトから文字列を設定しますが、位置・サイズを見たいので設定します。
* HSlider
* Button
　インスペクタのLabel/Textに「設定完了」と設定します。
![スクリーンショット (82).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/8f27094a-a709-c01e-b942-0082d50b98b7.png)
上部の2Dをクリックすると表示イメージを確認することができます。
![スクリーンショット (220).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/53d61560-becc-232f-2671-39eb89f0315e.png)

:::note
　表示イメージが表示されない場合、「SettingsPopupPanel」を選択した状態で、インスペクタのWindow/FlagsのVisibleをオンにします。
:::

# タイトル画面から設定画面を呼び出すように修正します。

## タイトル画面に「設定」ボタンを追加します。
　res://title.tscnを開きます。
　Titleを右クリックして、子ノードを追加から「Button」を追加します。
　名称を「SettingsButton」に変更します。
　インスペクタのButton/Textに「設定」と入力します。
![スクリーンショット (85).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/25025a31-af81-7b69-c1ab-96ddd87ea6e2.png)

　シーン内でSettingsButtonをドラッグしてButtonの下に移動しましょう。
　![スクリーンショット (84).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/3971515e-31fb-498f-2cd4-048e1f88cd56.png)

## 「設定」ボタンと「Game Start!」ボタンの配置を変更します。
　画面上部の2Dをクリックします。
　SettingsButtonが選択されている状態で、ビューの右のアイコンをクリックして中央下のアイコンをクリックします。
![スクリーンショット (86).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/e5b8d96b-c405-d600-f4b1-81189cc3d814.png)
「Game Start！」ボタンの下に「設定」ボタンが移動しました。
![スクリーンショット (87).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/0833a3bf-a3b4-7f85-582b-2f22946a54eb.png)

　「Game Start！」ボタンと「設定」ボタンをマウスでドラッグして横に並べましょう。
![スクリーンショット (88).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/5b4b32a2-63d9-1c26-b92e-3c1f379df7aa.png)
画面全体で確認するとこのような感じにしました。
![スクリーンショット (89).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/f4dfdc57-c034-83ee-411e-a86dee622fc9.png)

## 「設定」ボタンを押下するとPopupPanelを表示するようにします。
　res://title.tscnを開きます（開いていると思います）。
　ファイルシステム内の「res://settings_popup_panel.tscn」をシーン内の「Title」にドラッグアンドドロップして追加します。
　![スクリーンショット (91).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/02cf94de-9eb2-31cf-1a58-90921babd133.png)
　追加されました。
　ディフォルトでは非表示にしたいので、シーン内のSettingsPopupPanelの右の「目」のアイコンをクリックして、目を閉じたアイコンに変更します。
![スクリーンショット (93).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/fb4eabb5-b00f-1930-562e-694ec460dbb1.png)

　「設定」ボタンのシグナルを受信する処理を実装します。
　シーン内の「SettingsButton」を選択した状態で、インスペクタの横のノード/シグナルをクリックしてください。
　BaseButton/pressedシグナルを右クリックして「接続」を実行します。
　![スクリーンショット (94).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/134c7a32-d790-f1e1-1bfc-5eeaa4519f7a.png)
　Titleを選択します。受信側メソッドを確認して、「接続」ボタンを押下します。
![スクリーンショット (95).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/083902a2-132f-c860-a364-48bde0054a81.png)

　res://title.gdに_on_settings_button_pressed()メソッドが追加されました。
　res://title.gdを下記のように修正します。

```GDScript
extends Control

func _on_button_pressed():
	Global.goto_scene("res://game_system.tscn")

func _on_settings_button_pressed():
	$SettingsPopupPanel.open()
```

　「設定」ボタンが押下されたシグナルを受信したときに、SettingsPopupPanelのスクリプト内のopen()メソッドを実行するようにします。

　次に、SettingsPopupPanelにスクリプトを追加してopen()メソッドを実装します。
　res://settings_popup_panel.tscnを開きます。

　シーン内のSettingsPopupPanelを右クリックして、「スクリプトをアタッチ」を実行します。
　![スクリーンショット (96).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/18739435-5418-f111-f6e4-a16ef7ad4ea7.png)
　「作成」ボタンを押下するとファイルが追加されます。
![スクリーンショット (97).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/08afde0e-f8e9-6f5b-842a-455da8f99817.png)


```GDScript
extends PopupPanel

func open():
	popup_centered()
```

　実行しましょう。
　「設定」ボタンを押下すると、PopupPanelが表示されます。「設定完了」ボタン押下やスライダーの処理は実装していないため、操作はできますが何も起きません。PopupPanelの外をクリックするとPopupPanelの表示が消えるのはPopupPanelの機能です。
　ちなみにPopupPanelが開いているときに「Game Start!」ボタンを押下するとゲームが開始してしまいますが、これは後で修正します。
![スクリーンショット (98).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/eaa734d5-76ca-da3c-ffc2-ed9fef2e1407.png)

:::note warn
　PopupPanelを表示するとタイトルの「MyGame」の「M」が消えてしまいます。2023/3/1リリースのGodot4.0にはまだ問題があります。
:::

# スライダーの実装
　スライダーを変更すると、発射間隔を変更するようにします。
　0.1から1.0まで、0.1刻みで選択できるようにします。
　
　res://settings_popup_panel.tscnを開きます。

　HSliderを選択した状態でインスペクタのRangeのMin Valueは0.1、Max Valueは1、Stepは0.1にします。
![スクリーンショット (103).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/6d020bec-afe9-4949-11b5-380f0f838c04.png)

　HSliderを選択した状態でインスペクタの横のノード/シグナルの「drag_ended(value_changed:bool)」を右クリックして、「接続」を実行します。
![スクリーンショット (101).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/0ce6dd34-db3a-5cef-8c04-3dda7643dd2c.png)
SettingsPopupPanelを選択して、受信側メソッドを確認後、「接続」ボタンを押下します。
![スクリーンショット (102).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/521eee40-50a0-650c-2045-23d5f1fdb307.png)

　res://settings_popup_panel.gdに_on_h_slider_drag_endedメソッドが追加されるので下記のように修正しましょう。

```GDScript
extends PopupPanel

func open():
	popup_centered()

func _on_h_slider_drag_ended(value_changed):
	$ControlBaseSize/VBoxContainer/Label.text = "発射間隔 %.1f秒" % $ControlBaseSize/VBoxContainer/HSlider.value
```

* func _on_h_slider_drag_ended(value_changed):
　スライダーを操作して、離したときに呼ばれます。

* $ControlBaseSize/VBoxContainer/HSlider.value
　スライダーの値はHSlider.valueに格納されています。
　0.1～1.0の値が取得できます。

* "発射間隔 %.1f秒" % $ControlBaseSize/VBoxContainer/HSlider.value
 文字列を編集するための[format](https://docs.godotengine.org/ja/stable/tutorials/scripting/gdscript/gdscript_format_string.html)です。
　スライダーの値は浮動小数点なので、%fで受けています。間にある「.1」により小数点第1位まで表示します。

* $ControlBaseSize/VBoxContainer/Label.text
　Label.textに文字列を設定するとLabelの表示を変更できます。

　実行しましょう。
　スライダーを操作するとLabel表示が変更します。
![スクリーンショット (104).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/17b8ca8d-ed25-c419-7453-9d4e46fa1fd3.png)

# 「設定完了」ボタンを実装する
　「設定完了」ボタン押下でPopupPanelの表示を消すようにします。

　res://settings_popup_panel.tscnを開きます。
　Buttonを選択した状態でインスペクタの横のノード/シグナルの「BaseButton/pressed()」を右クリックして、「接続」を実行します。
![スクリーンショット (105).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/7ed6a5f5-8acd-0091-7157-2262e72c0a70.png)
SettingsPopupPanelを選択して、受信側メソッドを確認後、「接続」ボタンを押下します。
![スクリーンショット (106).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/c7ccf9b3-4e58-e7b5-4a67-9e313db9047e.png)

　res://settings_popup_panel.gdに_on_button_pressedメソッドが追加されました。
　res://settings_popup_panel.gdを開いて下記のように実装します。

```GDScript
extends PopupPanel

func open():
	popup_centered()

func _on_h_slider_drag_ended(value_changed):
	print($ControlBaseSize/VBoxContainer/HSlider.value)
	$ControlBaseSize/VBoxContainer/Label.text = "発射間隔 %.1f秒" % $ControlBaseSize/VBoxContainer/HSlider.value

func _on_button_pressed():
	hide()
```

　_on_button_pressed()メソッドが呼ばれたときに「hide()」を実行して、PopupPanelの表示を消します。

　設定完了ボタンを押下するとPopupPanelが消える様になりました。
![スクリーンショット (107).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/6779a53f-8722-c245-ed60-88df647b635e.png)


# PopupPanel表示中に「Game Start!」ボタンを押せないようにする
　PopupPanel表示中に背景のボタン（Game Start！ボタン、設定ボタン）を押下できないようにします。

## SceneTreeを停止する
　res://settings_popup_panel.gdを開きます。
　get_tree()でSceneTree（アクティブなシーン全体）を取得してpausedフラグをtrueにすると、シーンが停止します。
　子ノードも親シーンに従って停止するので、アクティブなシーン全体が停止します。

```GDScript
func open():
	get_tree().paused = true
	popup_centered()
```

## PopupPanelが消えるときにSceneTreeを再開する
　PopupPanelは設定完了ボタン以外にも背景をクリックすると消えるので、PopupPanelが消えるときに発行されるシグナルを利用して、SceneTreeを再開します。

　SettingsPopupPanelを選択した状態でインスペクタの横のノード/シグナルの「Popup/popup_hide()」を右クリックして、「接続」を実行します。
![スクリーンショット (108).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/848f1161-09d6-4bbd-3258-18d708d232a4.png)
SettingsPopupPanelを選択して、受信側メソッドを確認後、「接続」ボタンを押下します。
![スクリーンショット (109).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/01ebb805-709e-05a2-3426-95826af2b1e2.png)

 res://settings_popup_panel.gdに_on_popup_hideメソッドが追加されるので、下記のように修正します。
```GDScript
func _on_popup_hide():
	get_tree().paused = false
```
　res://settings_popup_panel.gd全体は下記のようになりました。
```GDScript
extends PopupPanel

func open():
	get_tree().paused = true
	popup_centered()

func _on_h_slider_drag_ended(value_changed):
	print($ControlBaseSize/VBoxContainer/HSlider.value)
	$ControlBaseSize/VBoxContainer/Label.text = "発射間隔 %.1f秒" % $ControlBaseSize/VBoxContainer/HSlider.value

func _on_button_pressed():
	hide()

func _on_popup_hide():
	get_tree().paused = false
```
## PopupPanelは動けるようにする
　SceneTreeを停止してもPopupPanelが動けるようにします。

　SettingsPopupPanelノードを選択してインスペクタの一番下にある「Node/Process/Mode」を「Always」に変更します。
![スクリーンショット (110).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/79d16d79-2061-b83b-1fca-4fa3435e8d79.png)

　実行します。
　PopupPanel表示中は背景が停止して、PopupPanelを閉じると背景が動き出すのがわかります。
　またPopupPanel表示中に「Game Start!」ボタンを押下してもゲームが開始しなくなりました。

# Controlをまとめる
:::note
 PopupPanelを表示するとタイトルの「MyGame」の「M」が消える問題について試行錯誤したときにルートノードのTitleとLabel、Button、SettingsButtonの間にControlを親ノードとして追加したものが残っていました。
　あまり意味はないのですが、その２２以降にもあるので作業手順として残しておきます。
:::
　res://title.tscnのLabel、Button、SettingsButtonの親Controlを作成します。
　Titleを右クリックして、子ノードを追加からControlを追加します。
　Label、Button、SettingsButtonをドラッグしてControlの子ノードにします。
　Titleの下に移動します。
![スクリーンショット (184).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/f30b69b9-25f6-92a9-7857-7c218b3fa89e.png)

以上です。