2
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ゲームを作るための勉強 その17 ゲームシステムを作る その2 ステージのクリア/失敗判定を実装する

Last updated at Posted at 2023-03-08

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

目的

 ゲームシステムをつくっていきます。
 ステージのクリア/失敗を判定して、GameSystemが知るところまで実装します。

ベースプロジェクト

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

ステージクリアを判定する

 ステージ内にある敵の砦をすべて破壊することをステージクリアの条件にします。
スクリーンショット (122).png
 一番簡単そうなのはGodotのグループ機能を使う方法だと思います。
 今回はStageに共通のGDScriptのAPIを持たせて、GameSystemのGDScriptでチェックするようにしましょう。

ステージクリア条件対象のEnemyシーンに「TargetEnemy」グループを設定する

 res://Characters/enemy.tscnを開きます。
 ルートノードのEnemyノードを選択して、インスペクタの横の「ノード/グループ」を選択します。
 Bullet(DesignMySoldier)との衝突判定に使用するEnemyGroupグループがすでに追加されています。
スクリーンショット (123).png

 「TargetEnemy」と入力して「追加」ボタンを押下します。
スクリーンショット (124).png
 Enemyシーンに「TargetEnemy」グループが追加されました。
スクリーンショット (125).png

Stage001のスクリプトでEnemyノードの数を判定する

 res://Stages/stage001.tscnを開きます。
 ルートノードのStageを右クリックして、「スクリプトをアタッチ」を実行します。

 res://Stages/stage_normal.gdとして、Stagesフォルダの下に保存します。
スクリーンショット (128).png
 Stagesフォルダに追加しました。
スクリーンショット (131).png

 現在アクティブなシーンの中から「TargetEnemy」グループを持つノードを検索するのは「get_tree().get_nodes_in_group("TargetEnemy")」を使用します。
 get_tree()がSceneTreeという現在アクティブなシーンツリーを取得します。
 GameSystemノード配下のすべてのシーンが対象になります。
 (本スクリプトがアタッチされているStageノードは、/GameSystem/CurrectStage/Stageの位置にあります)

GODOT DOC Scripting Groupsを参考にしました。
https://docs.godotengine.org/en/latest/tutorials/scripting/groups.html

 確認のためにprintしてみます。

extends Node3D

func _physics_process(delta):
	print(get_tree().get_nodes_in_group("TargetEnemy"))

 get_nodes_in_groupはArray[Node]で現在アクティブなノードのうち”TargetEnemy”グループを持つNodeリストを返します。

 実行すると最初はEnemyとEnemy2の2つのEnemyを検出しています。

[Enemy:<CharacterBody3D#29494346964>, Enemy2:<CharacterBody3D#29729228002>]

 Enemy2を倒すと、1個だけになります。

[Enemy:<CharacterBody3D#29494346964>]

 2つもと倒すと空のArrayが返ってきます。

[]

 Nodeリスト(Array[Node])のサイズが0個になれば、ステージクリアと判断できそうです。

 ステージクリアの判定はStage内(のスクリプト)で実装します。
 res://Stages/stage_normal.gdを開いて下記のように修正します。

extends Node3D

func is_stage_clear():
	return get_tree().get_nodes_in_group("TargetEnemy").size() == 0
  • get_tree().get_nodes_in_group("TargetEnemy").size()
     size()関数はNodeリスト(Array[Node])内の要素数を取得するので、Enemyがすべて残っている場合は2,すべて倒した場合は0になります。

  • get_tree().get_nodes_in_group("TargetEnemy").size() == 0
     すべて倒した場合は0==0でtrueをreturnします。
     敵砦が2つ残っている場合は、2なので、2==0はfalseとなります。

GameSystemで現在のステージのステージクリアを判定する

 今ステージに追加したis_stage_clear()メソッドをGameSystemから参照しましょう。
 res://game_system.tscnを開きます。
 ルートノードのGameSystemを右クリックして「スクリプトをアタッチ」します。
スクリーンショット (132).png
 追加されました。
スクリーンショット (133).png

 以下のようにしましょう。

extends Node3D

func _physics_process(delta):
	if $CurrentStage/Stage.is_stage_clear():
		print("Stage Clear")

「 $CurrentStage/Stage」はシーン内の「Stage」をスクリプト内にドラッグアンドドロップすると挿入されます。

 実行しましょう。
 敵の砦(Enemy、Enemy2)を2つとも倒すと「Stage Clear」がprint出力されました。

スクリーンショット (134).png

ステージクリア失敗を判定する

 EnemyBullet(DesignEnemySoldier)がPlayerの陣地までたどり着いた場合、Playerの負けとします。
 Area3DをPlayerの陣地に設定して、EnemyBullet(DesignEnemySoldier)の侵入を判定しましょう。

EnemyBullet(EnemySoldier)の侵入検知用にArea3Dを追加する

 res://Stages/stage001.tscnを開きます。
 ルートノードのStageを右クリックして、子ノードを追加から「Area3D」を追加します。名称を「EnemyBulletSensor」に変更します。
 EnemyBulletSensorを右クリックして、子ノードを追加から「CollisionShape3D」を追加します。
スクリーンショット (136).png

 「CollisionShape3D」を選択した状態で、インスペクタのCollisionShape3D/Shapeに新規BoxShapeを設定します。
 今設定した「BoxShape3D」をクリックし詳細設定を開いて、Sizeのxに5m、yは2mに設定します。
スクリーンショット (137).png

EnemyBullet(DesignEnemySoldier)の侵入を検知したsignalを受信する

 先ほど追加したArea3DにEnemyBullet(DesignEnemySoldier)が侵入した場合、signalを受信するようにします。

 Area3Dが検知する対象を設定します。
 EnemyBulletSensorを選択した状態で、インスペクタのCollisionObject3D/Collisionを設定します。

 Layerは検知されたい場合に設定するため、不要です。チェックをすべて外します。
 Maskは検知対象を設定します。Enemyを設定します。
スクリーンショット (138).png

 つぎに、インスペクタの横のノード/シグナルを選択します。
 検知したい対象はCharacterBody3Dという「body」なので、「body_entered(body:Node3D)」を右クリックして「接続」を実行します。
スクリーンショット (139).png
 ステージのクリア/失敗はStageのスクリプトで判定するので、「Stage」を選択します。
スクリーンショット (140).png
「func _on_enemy_bullet_sensor_body_entered(body):」メソッドが追加されました。
スクリーンショット (141).png

 signalを受信したことを覚えておくためにメンバ変数を追加します。
 またGameSystemのスクリプトからアクセスできるように、APIも追加します。

extends Node3D

var m_f_is_entered_enemy_bullet = false

func is_stage_clear():
	return get_tree().get_nodes_in_group("TargetEnemy").size() == 0

func is_stage_failed():
	return m_f_is_entered_enemy_bullet
	
func _on_enemy_bullet_sensor_body_entered(body):
	m_f_is_entered_enemy_bullet = true
  • var m_f_is_entered_enemy_bullet = false
     EnemyBulletの侵入を検知したことを記憶するためのフラグを追加しました。
     一度侵入を検知するとtrueにするフラグです。

  • func is_stage_failed():
     GameSystemがステージクリア失敗を知るためのAPIを追加します。
     「return m_f_is_entered_enemy_bullet」でtrue/falseのフラグを返却するだけです。

  • func _on_enemy_bullet_sensor_body_entered(body):
     EnemyBulletの侵入を検知した場合、m_f_is_entered_enemy_bulletにtrueを設定します。

GameSystemがステージクリア失敗を知る

 GameSystemは_physics_process()関数の中で周期的にステージクリア/失敗をチェックするようにします。
 とりあえずprintで表示するようにします。

 res://game_system.gdを開きます。

extends Node3D

func _physics_process(delta):
	if $CurrentStage/Stage.is_stage_clear():
		print("Stage Clear")

	if $CurrentStage/Stage.is_stage_failed():
		print("Stage Failed")
  • if $CurrentStage/Stage.is_stage_failed():
     stageのスクリプトに先ほど追加したis_stage_failed()がtrueの場合、printします。

 実行します。EnemyBullet(EnemySoldier)がPlayerの陣地に侵入すると「Stage Failed」のprint文が大量に表示されます。

スクリーンショット (143).png

以上です

2
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
2
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?