0
0

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ゲームを作るための勉強 その18 ゲームシステムを作る その3 ステージを増やしてクリアすると次のステージに進むようにする

Last updated at Posted at 2023-03-08

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

目的

 ゲームシステムをつくっていきます。
 ステージを追加して、クリアすると次のステージに切り替わるようにします。

ベースプロジェクト

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

StageItemsを追加する

 敵の砦以外にもStageに配置する障害物を追加します。

 StaticBody3Dをルートノードとして追加します。
 シーンメニューから新規シーンを実行します。「その他のノード」ボタンを押下してStaticBody3Dを選択します。名称を「RockyMountain](岩山)にしましょう。
 
 StageItemsフォルダに保存します。rocky_mountain.tscnという名前になると思います。
スクリーンショット (160).png

 岩山の土台を追加します。
 RockyMountainを右クリックして子ノードを追加からMeshInstance3Dを追加します。名称を「Base」に変更します。
 「Base」を選択した状態で、インスペクタのMeshInstance3D/Meshに新規BoxMeshを設定します。
 MeshInstance3D/Meshの右のアイコンをクリックして詳細設定を表示します。
 Sizeをx=1.0、y=0.5m、z=1.0に変更します。
 Node3D/Transform/position/yに0.25mを設定します。

 Materialに新規StandardMaterial3Dを設定します。
 色も設定しましょう。
 Materialの右の球アイコンをクリックして詳細設定を表示して、albedoを開いて、colorを設定します。
 濃いグレーにしました(R,G,B=60)

 次に岩山の頂上を追加します。
 Baseを右クリックして子ノードを追加からMeshInstance3Dを追加します。名称を「Top」に変更します。
 「Top」を選択した状態で、インスペクタのMeshInstance3D/Meshに新規BoxMeshを設定します。
 MeshInstance3D/Meshの右のアイコンをクリックして詳細設定を表示します。
 Sizeをx=0.5、y=0.5m、z=0.5に変更します。
 Node3D/Transform/position/yに0.25mを設定します。

 MaterialもBase同様に設定します。
スクリーンショット (150).png

 次に衝突形状を設定します。Base(土台)とx,zは同じサイズにします。高さyは高くします。
 RockyMountainを右クリックして子ノードを追加からCollisionShape3Dを追加します。
 CollisionShape3Dを選択した状態で、インスペクタのCollisionShape3D/Shapeに新規BoxShape3Dを設定します。
 Node3D/Transform/position/yに0.5mを設定します。
スクリーンショット (151).png

 ステージへの配置はあとでやりましょう。

ステージを追加する

 res://Stages/stage001.tscnを複製して、stage002.tscn、stage003.tscnを作成します。
 スクリーンショット (144).png
「stage002.tscn」という名称で保存します。
スクリーンショット (145).png

 同様の手順でstage003.tscnも複製します。
スクリーンショット (147).png

 各ステージを修正します。。

stage001.tscnを修正する

 stage001.tscnを開きます。
 Enemyを削除して、敵の砦を1個だけにします。
スクリーンショット (153).png

stage002.tscnを修正する

 stage002.tscnを開きます。
 Enemyを削除して、敵の砦を1個だけにします。
 Enemy2を選択して、真ん中あたりに移動しましょう。
 
 ファイルシステムのres://StageItems/rocky_mountain.tscnを、シーン内のStageにドラッグアンドドロップします。
 Node3D/Transform/position/xに0.5m、zに-4mを設定します。
 Node3D/Transform/rotation/yに45°を設定します。
スクリーンショット (155).png

stage003.tscnを修正する

 stage003.tscnを開きます。

 ファイルシステムのres://StageItems/rocky_mountain.tscnを、シーン内のStageにドラッグアンドドロップします。
 Node3D/Transform/position/zに-5mを設定します。
 Node3D/Transform/rotation/yに45°を設定します。
スクリーンショット (157).png

ステージクリアで次のステージに切り替わるようにする

 GameSystemのスクリプトを修正して、ステージクリアでステージが切り替わるようにします。

GameSystemシーンからStageノードを削除する

 Stageシーンはスクリプトから追加・削除するように変更するため、GameSystemシーンからStageを削除します。
 スクリーンショット (158).png
 削除しました。
 スクリーンショット (159).png

GameSystemのスクリプトを修正する

 res://game_system.gdを開いて、下記のように修正しましょう。

 stage001から始まり、クリアするとstage002、stage003に進み、stage003をクリアするとstage001に戻ります。
 ステージクリア失敗すると同じステージのやり直しです。

extends Node3D

# ステージシーンリスト(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

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 _ready():
	set_new_stage(m_i_current_stage_idx)
	
func _physics_process(delta):
	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)

 スクリプトの詳細を説明します。

ステージシーンの一覧を作成する

ステージシーンの一覧を定義しています。

var m_nodearray_stages = [
	preload("res://Stages/stage001.tscn"),
	preload("res://Stages/stage002.tscn"),
	preload("res://Stages/stage003.tscn"),
]

 preload関数を使用してステージシーンを読み込み、配列に定義しています。
 シーンを読み込むと、PackedScene型になります。
 配列なのでm_nodearray_stages[0]、m_nodearray_stages[1]というようにアクセスできます。

ステージシーンの入れ替えメソッドの実装

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)
  • var nodes = $CurrentStage.get_children()
     CurrentStageノードの子ノードをすべて取得します。
     すべてといってもステージシーンは削除して1個追加の繰り返しなので、
     起動時直後の子ノードがない場合は、
      []
     ステージを追加した後で、次のステージに進む場合、もしくはステージをやり直す場合は
      [Stage:]
     のように0個か1個のノードの配列がnodesに設定されます。

  • for node in nodes:
    nodesにあるNode型の配列の1個ずつ処理します。nodesが空の場合は何もせずにforブロックを抜けます。

  • node.queue_free()
    Nodeを削除(依頼)します。

  • var new_stage = m_nodearray_stages[idx].instantiate()
     m_nodearray_stages[idx]はPackedScene型のシーンデータが格納されているので、instantiate()でインスタンスを作成します。
     BulletやEnemyBulletを追加した時と同じです。

  • $CurrentStage.add_child(new_stage)
     CurrentStageノードの子ノードとしてステージシーンのインスタンスを追加します。

var m_i_current_stage_idx = 0

func _ready():
	set_new_stage(m_i_current_stage_idx)
  • set_new_stage(m_i_current_stage_idx)
     起動直後の初期設定で、新しいシーンを設定しています。
func _physics_process(delta):
	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)

 ステージシーンは削除・追加を繰り返すため、ステージシーンが存在しない場合も考慮しています。

  • func _physics_process(delta):
     _physics_process()の中で毎周期判定します。

  • var nodes = $CurrentStage.get_children()
     CurrentStageの子ノードをすべて取得します。

  • if nodes:
     ステージシーンが存在することを確認しています。
     CurrentStageの子ノードが1個以上あるとtrueになります。

  • if nodes[0].is_stage_clear():
     nodesには0個以上のNode型の要素(Stageシーン)が並びますが、今回は0個か1個のどちらかなので、nodes[0]固定で使用します。
     現在プレイしているステージのres://Stages/stage_normal.gdスクリプトのis_stage_clear()メソッドを利用します。

  • m_i_current_stage_idx = ( m_i_current_stage_idx + 1 ) % m_nodearray_stages.size()
     ステージクリアしたので、次のステージに進む処理です。
     m_i_current_stage_idxをカウントアップしますが、m_nodearray_stages.size()の余りを代入するので、0,1,2,の次は0になります。m_nodearray_stagesにはステージシーン3つ設定しているので、size()は3を返します。

  • set_new_stage(m_i_current_stage_idx)
     m_i_current_stage_idxで指定した番号のステージシーンに差し替えます

  • if nodes[0].is_stage_failed():
     nodesには0個以上のNode型の要素(Stageシーン)が並びますが、今回は0個か1個のどちらかなので、nodes[0]固定で使用します。
     現在プレイしているステージのres://Stages/stage_normal.gdスクリプトのis_stage_failed()メソッドを利用します。

* set_new_stage(m_i_current_stage_idx)
 ステージクリア失敗した場合、同じステージをやり直すので、m_i_current_stage_idxの値は変更せずに同じステージシーンに差し替えをします。

 実行します。
 敵の砦をすべて倒すと次のシーンにいきます。
 EnemyBulletがPlayerの陣地に到達すると同じステージのやり直しになります。

 以上です。

0
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?