【Godot 4.0】スマホ３Dゲームを作るための勉強　その２０　ゲームシステムを作る　その５　タイトルシーンを作って、神様スクリプトからタイトルシーンとプレイシーンを入れ替えるようにする

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

# 目的
　ゲームシステムをつくっていきます。
　タイトルシーンを作りたいと思います。
　Godotの「シングルトン(自動読み込み)機能」を使用して、タイトルシーンとGameSystemシーンを切り替えます。

# シングルトン(自動読み込み)とは？
　Godotのシングルトン（自動読み込み）を使用すると、スクリプトに下記のような特徴を持たせることができます。

　・常にロードされています。
　・プレイヤー情報のような複数のシーンで共通で使用したいグローバル変数を保管できます。
　・シーンの入れ替えをする事ができます。
　・アプリケーションで唯一存在します。

　つまりシングルトン（自動読み込み）にしたスクリプトがゲームの一番ベースとなる神様スクリプトになります。
　タイトルシーンとゲームプレイシーンをまるごと入れ替えることができるようになります。

# ベースプロジェクト
　下記で作成したプロジェクトをベースに機能追加をします。
　【Godot 4.0】スマホ３Dゲームを作るための勉強　その１９　ゲームシステムを作る　その４
　https://qiita.com/FootInGlow/items/a12d1ae9b4b8e1afb053
　
　github(Godotのプロジェクトマネージャーからインポートして利用できます）
　https://github.com/footinglow/Godot4/tree/main/02_study/S19_GameSystem_004

# タイトルシーンを作成します。
　最初にタイトルシーンを作成します
　タイトル文字と「ゲーム開始」ボタンを作成します。あと背景にキャラクターを配置します。

## 新規シーンを作成してControlノードを追加する
　シーンメニューから「新規シーン」を選択して、「ユーザインタフェース」ボタンを押下します。
　Controlノードが追加されたと思います。これからボタンを追加しますが、ボタンなどのコントロールの位置は親Controlが基準になるため、ボタンなどはControlノードの子ノードとして管理したほうがよさそうです。
　名称を「Title」に変更します。
　保存しましょう。title.tscnファイルができました。
![スクリーンショット (192).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/27466839-31e3-5b41-0fbe-0e54bf28a4fb.png)

　Titleを選択して、右クリックします。子ノードを追加から「Label」を追加します。
![スクリーンショット (193).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/8310fb54-754f-59b6-845c-fb6f6d066967.png)

　Labelを選択した状態で、インスペクタのLabel/Textにゲームタイトルを入力します。「MyGame」とします。
　LabelSettingsの右の＜空＞をクリックして「新規LabelSettings」を選択します。
　追加された「LabelSettings]をクリックして詳細設定を開き、　Font/Sizeに100を設定します。
![スクリーンショット (76).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/4ab25dfe-f9ba-9cfd-2822-bdd951e07c16.png)

　Titleを右クリックして、子ノードを追加から「Button」を追加します。
![スクリーンショット (194).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/1bfee349-5f51-2930-6a41-0325e0aa665b.png)

　Buttonを選択した状態で、インスペクタの「Button/Text」に「Game Start!」と入力します。
![スクリーンショット (79).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/8446f784-2019-8fb2-8bf0-14ba8043a8bd.png)

## 配置を決める
　画面上部の「2D」をクリックすると表示位置を確認することができます。
![スクリーンショット (80).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/bf0b3f15-fb21-0626-569f-6d1ea65a2f98.png)

　シーンの「Label」を選択します。
　「ビュー」メニューの右にあるアイコンをクリックして、中央上のアイコンをクリックします
![スクリーンショット (163).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/1d857ebc-26ce-26b0-abc0-3c0ba18876bc.png)

　次はシーンの「Button」を選択します。
　「ビュー」メニューの右にあるアイコンをクリックして、中央下のアイコンをクリックします。
　ボタンの位置を少し上にあげたいので、カーソルキー上を押下して、少し上に移動します。
![スクリーンショット (166).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/324ab78e-a769-b3e4-5e54-09b114eb5b33.png)
中央上に「MyGame」が、中央下に、ボタンが表示されたと思います。
![スクリーンショット (168).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/a4003fb9-286e-fc78-5bde-0262b752d30c.png)

## 背景に3Dキャラクターを配置する
　背景を作成します。
　「Title」を右クリックして、子ノードを追加から「Node3D」を追加します。
　名称を「Background」に変更します。

　「Background」を右クリックして、子ノードを追加から「Camera3D」を追加します。
　「Background」を右クリックして、子ノードを追加から「DirectLight3D」を追加します。
![スクリーンショット (195).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/56c184b3-66af-ad68-cc22-27f30fbbda12.png)


　ファイルシステムの下記4つのシーンをCtrlキーを押下しながらクリックして選択し、シーンのBackGroundにドラッグアンドドロップします。
　・res://Design/design_cannon.tscn
　・res://Design/design_enemy_fort.tscn
　・res://Design/design_enemy_soldier.tscn
　・res://Design/design_my_soldier.tscn
![スクリーンショット (196).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/9bab7cbc-7090-b53f-5952-4051b43c964a.png)
![スクリーンショット (199).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/d9c9f231-4b0c-acc9-0053-b9310c6e6120.png)

　各シーンのNode3D/Transform/positionを下記のように変更しました。
|シーン名|x|y|z|
|:-|:-|:-|:-|
|Camera3D|0m|0m|5m|
|DirectLight3D|-10m|0m|0m|
|design_cannon.tscn|-0.5m|-2m|1.5m|
|design_enemy_fort.tscn|2m|0m|0m|
|design_enemy_soldier.tscn|1.5m|0m|-4m|
|design_my_soldier.tscn|-2m|0m|-2m|

# 初期画面をtitle.tscnに変更する
　アプリ起動時の初期画面はtitle.tscnにします。
　プロジェクトメニューの「プロジェクト設定」を開き、アプリケーション/実行にあるメインシーンを「res://title.tscn」に変更します。
![スクリーンショット (201).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/e8bb1644-b479-c226-fc05-7ad25a25e4f8.png)

　実行するとタイトルを表示します。
　何も実装していないので、「Game Start！」ボタンを押下しても何も起きません。
![S20_GameSystem_005 (DEBUG) 2023_03_11 16_55_50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/8714d0a7-e7a2-37d8-560c-f6bc967c7bf0.png)

# 神様スクリプトを登録する
　シングルトン（自動読み込み)機能を使用して、常に常駐するスクリプトを作成して登録します。
　画面上部の「Script」をクリックして、ファイルメニューの「新規スクリプト」を実行します。
　![スクリーンショット (178).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/d0b4083a-d5c1-2b1d-fa1c-9bf88cb63ee5.png)
ファイル名を「global.gd」にして「作成」ボタンを押下します。
![スクリーンショット (179).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/d7a8e75a-d6ae-9eaf-df83-9aab63707383.png)
追加されました。
![スクリーンショット (202).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/e2acc574-1dc3-ec6c-fbfe-418557d5b866.png)

　「global.gd」を神様スクリプトとして登録します。

　プロジェクトメニューの「プロジェクト設定」を開きます。
　「Autoload」タブを開きます。
　パスに「res://global.gd」を設定すると、ノード名も自動で設定されます。
　追加ボタンを押下します。
![スクリーンショット (182).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/8f1c1235-43ce-f03d-4fc9-b4219575eaa6.png)

　「グローバル変数」項目の下の「有効」がチェックされていることも確認してください。
　有効にチェックが入っていると、「Global」という名前で他のスクリプトファイルからアクセスすることができるようになります。
![スクリーンショット (183).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/c31ce5b7-1fe8-b7a1-9f84-125a84ab425d.png)

　実行して、シーンの「リモート」をクリックすると、「Global」という名前でロードされていることが確認できます。
![スクリーンショット (184).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/8bafbebf-3adb-bb71-c1cd-fdcecf11ce8c.png)

## シーンの入れ替えを実装する
　res://global.gdを開きます。
　シーンの入れ替えをするメソッドを、下記のように実装します。

```GDScript
extends Node

func goto_scene(scene_path):
	call_deferred("_deferred_goto_scene", scene_path)

func _deferred_goto_scene(scene_path):
	get_tree().change_scene_to_file(scene_path)
```

**func goto_scene(scene_path):**
　scene_pathはシーンファイルを指定します。
　scene_pathには今回、res://game_system.tscnもしくはres://title.tscnが指定されます。
* call_deferred("_deferred_goto_scene", scene_path)

　call_deferred関数を使用して、_deferred_goto_scene(scene_path）の実行を予約します。
 「goto_scene」メソッドを起動するのは、タイトルシーンです。ここでタイトルシーン削除してGameシーンを追加するのは良い方法ではありません。なぜならタイトルシーンはまだやることがあるかもしれないためです。
　call_deferred関数を利用することで、タイトルシーンの処理が終了してからシーンの入れ替えを実行します。

　このような順序になるようです。
　1. title.tscnがgoto_scene（scene_path）メソッドを呼び出し
　2. call_deferredで_deferred_goto_scene(scene_path)呼び出しを予約（まだ実行しない）
　2. title.tscnは必要な処理を実行する。
　2. _deferred_goto_scene(scene_path)呼び出し

　ノードを削除する時に使用するqueue_free()メソッドも同じような目的で使用しています。いきなりfree()でノードが削除されるとそのノードを参照している人が困ってしまいますので、free()のかわりにqueue_free()を使用することで、対象ノードは処理がひととおり終わった後に削除されるような仕組みになっています。

**func _deferred_goto_scene(scene_path):**
* get_tree().change_scene_to_file(scene_path)
　get_tree()でSceneTreeを取り出して、scene_pathで指定したシーンファイルに入れ替えます。

>参考
>GODOT4.0 DOC Change scenes manually
>https://docs.godotengine.org/en/latest/tutorials/scripting/change_scenes_manually.html
>GODOT3.5 DOC シーンを手動で変更する
>https://docs.godotengine.org/ja/stable/tutorials/scripting/change_scenes_manually.html


## タイトルの「Game Start！」ボタン押下でゲーム開始する
　タイトルのボタンを押下したときのシグナルを受信して、ゲームシーンへの入れ替え指示まで実装します。

　res://title.tscnを開きます。
　「Title」ノードを右クリックして、「スクリプトをアタッチ」を実行します。
　「title.gd」という名称で保存します。
![スクリーンショット (203).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/f7816a6d-e78a-206a-41be-908b66820f4f.png)
保存されました。
![スクリーンショット (204).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/ad3bbf4c-70ba-3553-bab6-aebd5dbd5a34.png)

　次にシグナルを接続します。
　シーン内の「Button」を選択した状態で、インスペクタの横のノード/シグナルを選択します。
　「BaseButton/pressed」を右クリックして「接続」を実行します。
　![スクリーンショット (187).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/4b021b60-ee83-e205-b7f7-ed40961ebba3.png)
「Title」を選択して、受信側メソッド名を確認して、「接続」ボタンを押下します。
![スクリーンショット (205).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/59c5d5f3-fb83-d1ac-7c16-4166ae453beb.png)

　res://title.gdに「func _on_button_pressed():」が追加されました。
　下記のように修正します。

```GDScript
extends Control

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

　「Global」は先ほどAutoloadに追加したGlobalです。
　goto_sceneメソッドを呼び出して、GameSystem.tscnへの入れ替えを指示しています。

　実行しましょう。
　タイトル画面で「Game Start！」ボタンを押下します。
![S20_GameSystem_005 (DEBUG) 2023_03_11 16_55_50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/1cd8746e-a56e-c36f-3341-917d6468f233.png)
　ゲーム開始しました。
　![S20_GameSystem_005 (DEBUG) 2023_03_11 17_53_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/ca06062b-831d-95c4-04e7-9de81be8bd3b.png)

## ステージ失敗時に、タイトルに戻る
　ステージクリアを失敗した場合、タイトルに戻るようにスクリプトを修正します。

　res://game_system.gdを開きます。
　下記のように修正します。

```diff
		EN_GAME_STS.STAGE_FAILED:
			# 画面タッチされたら、Readyに遷移する
			if m_f_touch:
-				set_new_stage(m_i_current_stage_idx)
-				# ゲーム状態を「レディ」状態にして、「Ready」を表示する
-				m_en_game_sts = EN_GAME_STS.READY
-				hide_all_messages()
-				$Messages/Ready.show()
+				Global.goto_scene("res://title.tscn")
```

 res://game_system.gdスクリプト全体は下記のようになります。
```GDScript
extends Node3D

enum EN_GAME_STS {
	READY,
	IN_PLAY,
	STAGE_CLEAR,
	STAGE_FAILED,
}
var m_en_game_sts = EN_GAME_STS.READY

# ステージシーンリスト（PackedScene型）
var m_nodearray_stages = [
	preload("res://Stages/stage001.tscn"),
	preload("res://Stages/stage002.tscn"),
	preload("res://Stages/stage003.tscn"),
]

# 現在実行中のステージ番号
var m_i_current_stage_idx = 0

# タッチイベント処理
var m_f_touch = false

func _input(event):
	if ( event is InputEventScreenTouch ) and event.pressed:
		m_f_touch = true
			
func set_new_stage(idx):
	# CurrentStageにあるステージをすべて削除する
	var nodes = $CurrentStage.get_children()
	for node in nodes:
		node.queue_free()
	# idx番目のステージシーンのインスタンスを生成して、CurrentStageに追加する
	var new_stage = m_nodearray_stages[idx].instantiate()
	$CurrentStage.add_child(new_stage)

func hide_all_messages():
	# メッセージを表示をすべて隠す
	var nodes = $Messages.get_children()
	for node in nodes:
		node.hide()
		
func _ready():
	set_new_stage(m_i_current_stage_idx)

func _physics_process(delta):
	match m_en_game_sts :
		EN_GAME_STS.READY:
			# 画面タッチされたら、Ready表示を刑してプレイ中にする
			if m_f_touch:
				hide_all_messages()
				m_en_game_sts = EN_GAME_STS.IN_PLAY
				# "GameControl"グループを持つ、PlayerとEnemyのスクリプトのgame_start()メソッドを起動する
				get_tree().call_group("StartStopControl", "game_start")
				# 「Go」を表示して２秒後にメッセージを消す
				$Messages/Go.show()
				await get_tree().create_timer(2.0).timeout
				hide_all_messages()
		EN_GAME_STS.IN_PLAY:
			var nodes = $CurrentStage.get_children()
			if nodes:
				# nodesは1個か0個のどちらかなので、0番目を固定的に使用する
				if nodes[0].is_stage_clear():
					# ゲーム状態を「ステージクリア」状態にして「Stage Clear」を表示
					m_en_game_sts = EN_GAME_STS.STAGE_CLEAR
					hide_all_messages()
					$Messages/StageClear.show()
				if nodes[0].is_stage_failed():
					# ゲーム状態を「ステージ失敗」状態にして「Stage Failed」を表示
					m_en_game_sts = EN_GAME_STS.STAGE_FAILED
					hide_all_messages()
					$Messages/StageFailed.show()
		EN_GAME_STS.STAGE_CLEAR:
			# 画面タッチされたら、Readyに遷移する
			if m_f_touch:					
				# 次のステージに進む
				m_i_current_stage_idx = ( m_i_current_stage_idx + 1 ) % m_nodearray_stages.size()
				set_new_stage(m_i_current_stage_idx)
				# ゲーム状態を「レディ」状態にして、「Ready」を表示する
				m_en_game_sts = EN_GAME_STS.READY
				hide_all_messages()
				$Messages/Ready.show()
		EN_GAME_STS.STAGE_FAILED:
			# 画面タッチされたら、Readyに遷移する
			if m_f_touch:
				Global.goto_scene("res://title.tscn")
	# _physics_process()の最後にm_f_touchフラグを落とす
	m_f_touch = false
```

 実行します。
　ゲームクリア失敗の画面でタッチするとタイトルに戻ります。
![「【Godot 4.0】スマホ３Dゲームを作るための勉強　その２０　ゲームシステムを作る　その４　タイトルシーンを作って、神様スクリプトからタイトルシーンとプレイシーンを入れ替えるようにする」を編集 - Qiita - Google Chrome 2023_03_11 17_59_08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/f679e5c0-c458-cc9b-6898-407c2366bbbf.png)
タイトルに戻ったでしょうか？
![S20_GameSystem_005 (DEBUG) 2023_03_11 16_55_50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/d18c0537-89c5-78c9-149c-e897b5ad142b.png)

# 実行中のノード構成を確認する。
　実行してノード構成を確認します。
　実行して、シーンの「リモート」をクリックします。
　
　タイトルシーンの時は先ほども確認したように、root直下にGlobalとTitleの２つのノードが存在しています。
![スクリーンショット (189).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/ae8500b8-250a-c8b3-2fd4-0fe1a9b98ab6.png)
　「Start Game！」ボタンを押下してゲームを開始します。
　下記のように、Titleが消えてGameSystemが追加されました。
![スクリーンショット (190).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/7ce14b5c-af90-2dfe-faeb-b9f3eb42f977.png)

　Globalは常にありますね。

　以上です。