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ゲームを作るための勉強 その19 ゲームシステムを作る その4 プレイ開始やステージ間のメッセージ表示とタッチして次のステージに進む処理を実装

Last updated at Posted at 2023-03-09

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

目的

 ゲームシステムをつくっていきます。
 いきなりゲームが始まるので、心を準備する時間やステージクリア、失敗を理解する時間もありません。
 画面タッチでプレイ開始や次のステージに進むようにします。

ベースプロジェクト

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

画面遷移を決める

 画面遷移を決めます。
 最初は「Ready」と表示します。
 画面をタッチすると、プレイ中に遷移するようにします。ゲーム開始直後は2秒くらい「Go!」と表示します。
 またステージをクリアもしくはクリア失敗した場合は、「Stage Clear」「Stage Failed」と表示をして、画面タッチすると次のステージもしくは同じステージのやり直しの「Ready」が表示されるようにします。
 GodotParts.pptx - PowerPoint 2023_03_09 20_32_00.png

 ではGameSystemに実装していきます。

タッチイベントを検知する

 タッチイベントを検出するようにします。
 res://game_system.gdを開いて下記を追加します。

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

func _input(event):
	if ( event is InputEventScreenTouch ) and event.pressed:
		m_f_touch = true

 「event is InputEventScreenTouch」は画面をタッチもしくは指を離すときに通知されるイベントです。
 タッチした時はevent.pressed=trueになるのでand条件で見ることでタッチを検出します。

func _physics_process(delta):
  :
 (省略)
  : 
	# _physics_process()の最後にm_f_touchフラグを落とす
	m_f_touch = false

 _physics_process()の一番最後にm_f_touchをfalseにすると、次の周期でm_f_touchがtrueの場合、今タッチイベントが発生したと判断できます。

Readyを表示する

 ゲーム画面の上に「Ready」の文字列を表示しましょう。
 res://game_system.tscnを開きます。

 GameSystemを右クリックして、子ノードを追加から「Node」を追加しましょう。
 2Dの文字列を表示するだけなので、Node3Dではありません。
 名称を「Messages」に変更します。

 Messagesを右クリックして、子ノードを追加から「Label」を追加しましょう。(Label3Dではありません)
 キャプチャ 2023_03_09 20_57_29.png
 名称を「Ready」に変更します。
(_) game_system.tscn - S18_GameSystem_003 - Godot Engine 2023_03_09 21_00_33.png

 「Ready」を選択した状態でインスペクタのLabel/Textに「Ready]と入力します。
 またLabelSettingsの<空>をクリックして、「新規LabelSettings」を実行します。
スクリーンショット (58).png
設定した「LabelSettings」をクリックすると詳細設定が開くので、Fontの中の「Size」を70に変更します。
スクリーンショット (59).png

 詳細設定の色が黄緑なのは、エディタ設定のインタフェース/テーマの「アクセントカラー」を変更したためです。
スクリーンショット (60).png

 画面上部の「2D」をクリックしましょう。2Dの編集画面に「Ready」という文字列が表示されています。
スクリーンショット (61).png
 画面中央に表示したいので、「ビュー」メニューの右にあるアイコンをクリックして、中央を示すアイコンを選択します。
スクリーンショット (62).png
中央に移動しました。(マウスホイール操作で表示範囲を拡大しました)
スクリーンショット (63).png

 実行してみましょう。画面中央に「Ready」が表示されました。
 3D画面より2Dの画面の方が優先して表示されるようですね。
キャプチャ 2023_03_09 21_12_24.png

READY状態とプレイ状態の遷移を作成する

 まずREADY状態の遷移をつくります。
 res://game_system.gdを開いて編集します。

extends Node3D

enum EN_GAME_STS {
	READY,
	IN_PLAY,
}
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()
	print(nodes)
	for node in nodes:
		node.queue_free()
	# idx番目のステージシーンのインスタンスを生成して、CurrentStageに追加する
	var new_stage = m_nodearray_stages[idx].instantiate()
	$CurrentStage.add_child(new_stage)

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:
				$Messages/Ready.hide()
				m_en_game_sts = EN_GAME_STS.IN_PLAY
		EN_GAME_STS.IN_PLAY:
			var nodes = $CurrentStage.get_children()
			if nodes:
				# nodesは1個か0個のどちらかなので、0番目を固定的に使用する
				if nodes[0].is_stage_clear():
					print("Stage Clear")
					# ステージは0,1,2,0,1,2,0...と繰り返す
					m_i_current_stage_idx = ( m_i_current_stage_idx + 1 ) % m_nodearray_stages.size()
					set_new_stage(m_i_current_stage_idx)

				if nodes[0].is_stage_failed():
					print("Stage Failed")
					set_new_stage(m_i_current_stage_idx)
	# _physics_process()の最後にm_f_touchフラグを落とす
	m_f_touch = false

 変更した点について説明します。

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

ゲーム状態をenumで定義しました。
m_en_game_stsに現在のゲーム状態を保持するようにします。初期値はREADYです。

func _physics_process(delta):
	match m_en_game_sts :
		EN_GAME_STS.READY:
			# 画面タッチされたら、Ready表示を刑してプレイ中にする
			if m_f_touch:
				$Messages/Ready.hide()
				m_en_game_sts = EN_GAME_STS.IN_PLAY
		EN_GAME_STS.IN_PLAY:
       :
      (以下今までの_physics_processの内容)

 _physics_process()メソッドはゲーム状態によって処理内容が変わります。

  • m_en_game_stsがEN_GAME_STS.READYの場合
     「if m_f_touch:」によりタッチ判断をします。タッチされるまでは何もしません。
     タッチ検出した場合、「$Messages/Ready.hide()」でReadyの表示を消します。hide()関数でノードの表示を消すことができます。
     次の周期ではプレイ中の処理を実行したいので、m_en_game_sts = EN_GAME_STS.IN_PLAYに設定することで、次に_physics_process()が呼ばれたときはEN_GAME_STS.READYの場合の処理を実行するようになります。
  • m_en_game_stsがEN_GAME_STS.READYの場合
     今までと同じステージクリア/失敗の処理を実行します。

 実行します。
 タッチするとREADYの文字が消えます。
「【Godot 4.0】スマホ3Dゲームを作るための勉強 その19 ゲームシステムを作る その4」を編集 - Qiita - Google Chrome 2023_03_09 21_34_47.png

 しかし放っておくと敵の砦からEnemyBullet(敵ソルジャー)がどんどん出てきます。

プレイ中に遷移してから敵の砦が攻撃開始するように変更する

 原因はEnemy.tscnのTimer_fireノードのAutoStartをONにしているためです。
 初期値はAutoStartをOFFにして、ゲーム状態がIN_PLAY(プレイ中)に遷移するときにAutoStartをONにするようにしましょう。

 res://Characters/enemy.tscnを開きます。
 Timer_fireを選択して、インスペクタの「Timer/AutoStart」をOffにします。
S18_GameSystem_003 (DEBUG) 2023_03_09 21_44_12.png

 プレイ中開始/停止を指示したいノードを一括して制御するために、グループ機能の「call_group」メソッドを使います。

GODOT DOC Scripting Groups
https://docs.godotengine.org/en/latest/tutorials/scripting/groups.html#managing-groups

 プレイ開始/中止を制御するノードに「StartStopControl」グループを設定して、play_start/play_stopメソッドを実装しましょう。

 res://Characters/enemy.tscnを開きます。
 Enemyを選択して、インスペクタの右のノード/グループを選択します。
 スクリーンショット (65).png
「StartStopControl」を入力して、「追加」ボタンを押下します。
スクリーンショット (66).png
追加されました。Enemyには合計3つのグループが登録されています。
スクリーンショット (67).png

 res://Characters/Enemy.gdを開いて、play_start/play_stopメソッドを追加します。

func game_start():
	$Timer_fire.start()
	
func game_stop():
	$Timer_fire.stop()

 それぞれTimer_fireタイマーを開始、停止するメソッドになっています。

 ではGameSystemから制御しましょう。
 res://game_system.gdを開きます。

		EN_GAME_STS.READY:
			# 画面タッチされたら、Ready表示を刑してプレイ中にする
			if m_f_touch:
				$Messages/Ready.hide()
				m_en_game_sts = EN_GAME_STS.IN_PLAY
				# "GameControl"グループを持つ、PlayerとEnemyのスクリプトのgame_start()メソッドを起動する
				get_tree().call_group("StartStopControl", "game_start")
		EN_GAME_STS.IN_PLAY:
  • get_tree().call_group("StartStopControl", "game_start")
    get_tree()で取得したアクティブなノードに対して、"StartStopControl"グループを持つノードにアタッチされたスクリプトの「game_start」メソッドを起動します。

 実行しましょう。
 タッチするまでは敵が攻撃しなくなりました。

GameSystem/Messagesのメッセージ表示をすべてhide()するメソッドの追加

 ゲーム状態の実装を進める前に、GameSystem/Messagesのメッセージ表示をすべてhide()するメソッドを追加します。
 res://game_system.gdを開いて修正します。

func hide_all_messages():
	# メッセージを表示をすべて隠す
	var nodes = $Messages.get_children()
	for node in nodes:
		node.hide()
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

 「$Messages/Ready.hide()」から「hide_all_messages()」に変更しました。

ゲーム状態を実装する

メッセージを追加する

 res://game_system.tscnを開きます。
 「Ready」ノードをコピーしてから、Messagesノードを右クリックして「貼り付け」します。
 合計3つ張り付けて、名称を「Go」「StageClear」「StageFailed」にします。
スクリーンショット (69).png

 「Go]を選択して、インスペクタのLabel/Textに「Go!」を設定します。
 同様に、StageClearは「Stage Clear」、StageFailedは「Stage Failed」にします。
スクリーンショット (73).png
スクリーンショット (74).png
スクリーンショット (75).png

 初期状態はReadyのみ表示したいので、シーン内のGo,StageClear,StageFailedの右に表示されている目のアイコンをクリックして、目をつむっているアイコンに変更してください。
スクリーンショット (71).png

ゲーム状態遷移を実装する

 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:					
				# 次のステージに進む
				# ステージは0,1,2,0,1,2,0...と繰り返す
				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:
				set_new_stage(m_i_current_stage_idx)
				# ゲーム状態を「レディ」状態にして、「Ready」を表示する
				m_en_game_sts = EN_GAME_STS.READY
				hide_all_messages()
				$Messages/Ready.show()
	# _physics_process()の最後にm_f_touchフラグを落とす
	m_f_touch = false

 変更点を説明します。

enum EN_GAME_STS {
	READY,
	IN_PLAY,
	STAGE_CLEAR,
	STAGE_FAILED,
}

 ゲーム状態「STAGE_CLEAR」と「STAGE_FAILED]を追加しました。

 次に「_physics_process()」メソッドの中のEN_GAME_STS.READY状態の処理です。

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()

 「Ready」表示中の処理です。
 タッチ検出した処理の最後に処理を追加して、「$Messages/Go.show()」で「Go!」を表示するようにしました。 
 また2秒後に「Go!」の表示を消したいのですが、awaitを使用して、「await get_tree().create_timer(2.0).timeout」とすると2秒経過後に次の行「hide_all_messages()」を実行します。
 2秒待っている間も_physics_process()は周期的に呼び出されています。

GODOT3.5の時のyield関数が、Godot4.0ではawaitに変更されています。

		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()

 プレイ中の処理です
 ステージクリア/ステージ失敗判定時の処理が、状態遷移処理に変更しました。
 m_en_game_stsに新しいゲーム状態を設定して、hide_all_messages()でメッセージ表示(何も表示されていないと思いますが)を消した後、StageClearもしくはStageFailedのメッセージを表示します。

		EN_GAME_STS.STAGE_CLEAR:
			# 画面タッチされたら、Readyに遷移する
			if m_f_touch:					
				# 次のステージに進む
				# ステージは0,1,2,0,1,2,0...と繰り返す
				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()

 「Stage Clear」を表示中の状態です。
 タッチされるまではなにもしません。
 タッチを検出すると、次のステージに進む処理を実行します。
 その後READY状態に遷移(m_en_game_sts = EN_GAME_STS.READY)して、メッセージ表示を消した後、「Ready」を表示します。

		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()

 「Stage Failed」を表示中の状態です。
 処理に内容は「Stage Clear」表示中の時とほぼ同じ処理です。

 実行しましょう。
 実行するとReadyが表示されて、タッチするとGo!が2秒間表示されます。
 ステージをクリアすると「Stage Clear」と表示されて、タッチすると次のステージに進み「Ready」が表示されます。
 ステージ失敗すると「Stage Failed」が表示されて、タッチすると次のステージに進み「Ready」が表示されます。
S19_GameSystem_004 (DEBUG) 2023_03_09 22_50_52.png

以上

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?