【Godot 4.0】スマホ３Dゲームを作るための勉強　その１７　ゲームシステムを作る　その２　ステージのクリア/失敗判定を実装する

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

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

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

# ステージクリアを判定する
　ステージ内にある敵の砦をすべて破壊することをステージクリアの条件にします。
![スクリーンショット (122).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/fbd2c738-108b-6e81-1127-a212eb073151.png)
　一番簡単そうなのはGodotのグループ機能を使う方法だと思います。
　今回はStageに共通のGDScriptのAPIを持たせて、GameSystemのGDScriptでチェックするようにしましょう。

## ステージクリア条件対象のEnemyシーンに「TargetEnemy」グループを設定する
　res://Characters/enemy.tscnを開きます。
　ルートノードのEnemyノードを選択して、インスペクタの横の「ノード/グループ」を選択します。
　Bullet(DesignMySoldier)との衝突判定に使用するEnemyGroupグループがすでに追加されています。
![スクリーンショット (123).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/d92035ff-92a9-e4ad-cb98-bc630576aa7f.png)

　「TargetEnemy」と入力して「追加」ボタンを押下します。
![スクリーンショット (124).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/4868697b-bd60-f99b-213f-538749736713.png)
　Enemyシーンに「TargetEnemy」グループが追加されました。
![スクリーンショット (125).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/1c49372a-2db3-2ea5-cbd5-bf602daa5f4f.png)

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

　res://Stages/stage_normal.gdとして、Stagesフォルダの下に保存します。
![スクリーンショット (128).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/509f520b-ec2a-1082-9555-76598acda48e.png)
　Stagesフォルダに追加しました。
![スクリーンショット (131).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/6344c5fd-fe9a-0c10-37b1-31af1a2dd103.png)

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

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

　確認のためにprintしてみます。
```GDScript
extends Node3D

func _physics_process(delta):
	print(get_tree().get_nodes_in_group("TargetEnemy"))
```
　get_nodes_in_groupはArray[Node]で現在アクティブなノードのうち”TargetEnemy”グループを持つNodeリストを返します。

　実行すると最初はEnemyとEnemy2の２つのEnemyを検出しています。
```
[Enemy:<CharacterBody3D#29494346964>, Enemy2:<CharacterBody3D#29729228002>]
```
　Enemy2を倒すと、１個だけになります。
```
[Enemy:<CharacterBody3D#29494346964>]
```
　２つもと倒すと空のArrayが返ってきます。
```
[]
```

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

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

```GDScript
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がすべて残っている場合は２，すべて倒した場合は０になります。

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

## GameSystemで現在のステージのステージクリアを判定する
　今ステージに追加したis_stage_clear()メソッドをGameSystemから参照しましょう。
　res://game_system.tscnを開きます。
　ルートノードのGameSystemを右クリックして「スクリプトをアタッチ」します。
![スクリーンショット (132).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/c47bde80-3132-c3c0-4746-dbe02a3c776e.png)
　追加されました。
![スクリーンショット (133).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/980e9161-e033-5d82-6a17-aee4183aa27b.png)

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

```GDSCript
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/926b34a3-d2e3-4e26-ac9d-cd06fc3901b0.png)

# ステージクリア失敗を判定する
　EnemyBullet(DesignEnemySoldier)がPlayerの陣地までたどり着いた場合、Playerの負けとします。
　Area3DをPlayerの陣地に設定して、EnemyBullet(DesignEnemySoldier)の侵入を判定しましょう。

## EnemyBullet(EnemySoldier)の侵入検知用にArea3Dを追加する
　res://Stages/stage001.tscnを開きます。
　ルートノードのStageを右クリックして、子ノードを追加から「Area3D」を追加します。名称を「EnemyBulletSensor」に変更します。
　EnemyBulletSensorを右クリックして、子ノードを追加から「CollisionShape3D」を追加します。
![スクリーンショット (136).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/f370bd93-39e1-2c75-7297-53de82490361.png)

　「CollisionShape3D」を選択した状態で、インスペクタのCollisionShape3D/Shapeに新規BoxShapeを設定します。
　今設定した「BoxShape3D」をクリックし詳細設定を開いて、Sizeのxに5m、yは2mに設定します。
![スクリーンショット (137).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/252ae088-54eb-3260-1907-9bbef07bb983.png)

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

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

　Layerは検知されたい場合に設定するため、不要です。チェックをすべて外します。
　Maskは検知対象を設定します。Enemyを設定します。
![スクリーンショット (138).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/4cbc9497-80a7-0a4c-e869-784520c73cd2.png)

　つぎに、インスペクタの横のノード/シグナルを選択します。
　検知したい対象はCharacterBody3Dという「body」なので、「body_entered(body:Node3D)」を右クリックして「接続」を実行します。
![スクリーンショット (139).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/a047937d-3c9c-79a3-cda4-5c23efb5694e.png)
　ステージのクリア/失敗はStageのスクリプトで判定するので、「Stage」を選択します。
![スクリーンショット (140).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/e9f83d23-65f2-1af3-d78d-b5a582a26fbf.png)
「func _on_enemy_bullet_sensor_body_entered(body):」メソッドが追加されました。
![スクリーンショット (141).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/dacc56c4-3914-fd0c-3d04-8450aa7af2c1.png)

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

```GDScript
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を開きます。

```GDScript
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/214d5107-096b-b647-7df8-6a65a671c6e3.png)

以上です