0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

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

目的

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

シングルトン(自動読み込み)とは?

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

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

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

ベースプロジェクト

 下記で作成したプロジェクトをベースに機能追加をします。
 【Godot 4.0】スマホ3Dゲームを作るための勉強 その19 ゲームシステムを作る その4
 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

 Titleを選択して、右クリックします。子ノードを追加から「Label」を追加します。
スクリーンショット (193).png

 Labelを選択した状態で、インスペクタのLabel/Textにゲームタイトルを入力します。「MyGame」とします。
 LabelSettingsの右の<空>をクリックして「新規LabelSettings」を選択します。
 追加された「LabelSettings]をクリックして詳細設定を開き、 Font/Sizeに100を設定します。
スクリーンショット (76).png

 Titleを右クリックして、子ノードを追加から「Button」を追加します。
スクリーンショット (194).png

 Buttonを選択した状態で、インスペクタの「Button/Text」に「Game Start!」と入力します。
スクリーンショット (79).png

配置を決める

 画面上部の「2D」をクリックすると表示位置を確認することができます。
スクリーンショット (80).png

 シーンの「Label」を選択します。
 「ビュー」メニューの右にあるアイコンをクリックして、中央上のアイコンをクリックします
スクリーンショット (163).png

 次はシーンの「Button」を選択します。
 「ビュー」メニューの右にあるアイコンをクリックして、中央下のアイコンをクリックします。
 ボタンの位置を少し上にあげたいので、カーソルキー上を押下して、少し上に移動します。
スクリーンショット (166).png
中央上に「MyGame」が、中央下に、ボタンが表示されたと思います。
スクリーンショット (168).png

背景に3Dキャラクターを配置する

 背景を作成します。
 「Title」を右クリックして、子ノードを追加から「Node3D」を追加します。
 名称を「Background」に変更します。

 「Background」を右クリックして、子ノードを追加から「Camera3D」を追加します。
 「Background」を右クリックして、子ノードを追加から「DirectLight3D」を追加します。
スクリーンショット (195).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
スクリーンショット (199).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

 実行するとタイトルを表示します。
 何も実装していないので、「Game Start!」ボタンを押下しても何も起きません。
S20_GameSystem_005 (DEBUG) 2023_03_11 16_55_50.png

神様スクリプトを登録する

 シングルトン(自動読み込み)機能を使用して、常に常駐するスクリプトを作成して登録します。
 画面上部の「Script」をクリックして、ファイルメニューの「新規スクリプト」を実行します。
 スクリーンショット (178).png
ファイル名を「global.gd」にして「作成」ボタンを押下します。
スクリーンショット (179).png
追加されました。
スクリーンショット (202).png

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

 プロジェクトメニューの「プロジェクト設定」を開きます。
 「Autoload」タブを開きます。
 パスに「res://global.gd」を設定すると、ノード名も自動で設定されます。
 追加ボタンを押下します。
スクリーンショット (182).png

 「グローバル変数」項目の下の「有効」がチェックされていることも確認してください。
 有効にチェックが入っていると、「Global」という名前で他のスクリプトファイルからアクセスすることができるようになります。
スクリーンショット (183).png

 実行して、シーンの「リモート」をクリックすると、「Global」という名前でロードされていることが確認できます。
スクリーンショット (184).png

シーンの入れ替えを実装する

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

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
保存されました。
スクリーンショット (204).png

 次にシグナルを接続します。
 シーン内の「Button」を選択した状態で、インスペクタの横のノード/シグナルを選択します。
 「BaseButton/pressed」を右クリックして「接続」を実行します。
 スクリーンショット (187).png
「Title」を選択して、受信側メソッド名を確認して、「接続」ボタンを押下します。
スクリーンショット (205).png

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

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
 ゲーム開始しました。
 S20_GameSystem_005 (DEBUG) 2023_03_11 17_53_11.png

ステージ失敗時に、タイトルに戻る

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

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

		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スクリプト全体は下記のようになります。

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」を表示して2秒後にメッセージを消す
				$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】スマホ3Dゲームを作るための勉強 その20 ゲームシステムを作る その4 タイトルシーンを作って、神様スクリプトからタイトルシーンとプレイシーンを入れ替えるようにする」を編集 - Qiita - Google Chrome 2023_03_11 17_59_08.png
タイトルに戻ったでしょうか?
S20_GameSystem_005 (DEBUG) 2023_03_11 16_55_50.png

実行中のノード構成を確認する。

 実行してノード構成を確認します。
 実行して、シーンの「リモート」をクリックします。
 
 タイトルシーンの時は先ほども確認したように、root直下にGlobalとTitleの2つのノードが存在しています。
スクリーンショット (189).png
 「Start Game!」ボタンを押下してゲームを開始します。
 下記のように、Titleが消えてGameSystemが追加されました。
スクリーンショット (190).png

 Globalは常にありますね。

 以上です。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?