【Godot 4.0】スマホ３Dゲームを作るための勉強　その１０　敵を増やしました。またBullet vs Enemyの処理を実装しました。

 ゲームエンジンGodot4.0で3Dスマホゲームを作りたいと思いますが、その前にお勉強しています。
 [Godot_v4.0-rc1_win64.exe.zip](https://downloads.tuxfamily.org/godotengine/4.0/rc1/Godot_v4.0-rc1_win64.exe.zip)を使用しています。

# 目的
　敵をシーンファイル化して、複数設定できるようにします。
　またBulletがEnemyに当たった場合、お互い攻撃してHPが０になったら消える様にします。

# ベースプロジェクト
　下記で作成したプロジェクトをベースに機能追加をします。

　【Godot 4.0】スマホ３Dゲームを作るための勉強　その９ 敵を検知するとホーミングするようにしたい
　https://qiita.com/FootInGlow/items/5b1c78669e2d56d98f1b

　github(Godotのプロジェクトマネージャーからインポートして利用できます）
　https://github.com/footinglow/Godot4/tree/main/02_study/S09_enemy_001

# Enemyブランチをシーンファイルにする
　Enemyを右クリックして「ブランチをシーンとして保存」を実行します。
　ダイアログが表示されるので問題なければそのまま保存します。
　![スクリーンショット (44).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/8c61454d-d652-e9d9-435c-a6be00f88435.png)
 Enemyとその子ノードがひとつのシーンになりました。映画のカチンコ🎬アイコンが表示されます。
![(_) global.tscn - S10_enemy_002 - Godot Engine 2023_03_01 21_52_27 - コピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/a656fda5-3ee2-4ad3-dcaa-e422d2ed3cd8.png)
　またファイルシステムにはenemy.tscnとして保存されました。
![(_) global.tscn - S10_enemy_002 - Godot Engine 2023_03_01 21_52_27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/6b257dff-fdb6-22f3-35cc-a2ce41107f69.png)

# Enemyを増やす
　Enemyを追加しましょう。
　globalを右クリックして、「Instantiate Child Scene」を実行します。
　![スクリーンショット (45).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/21c1d5cf-6657-4583-b302-605b4ddaafd4.png)
　enemy.tscnを選択して「開く」ボタンを押下します。
![スクリーンショット (46).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/009c4d54-593d-047f-2c52-2af5fb5f7eeb.png)
　Enemy2が追加されました。
![スクリーンショット (47).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/1cc2ca96-a8c4-29f8-5fc7-5232d44caa57.png)

　Enemy2を選択して、インスペクタのNode3D/Transform/positionのX=-2.0m、Z=-8mを設定します。
![(_) global.tscn - S10_enemy_002 - Godot Engine 2023_03_01 22_01_07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/598467b2-eb9f-c801-3b20-0a90f8b3d4d2.png)

# Enemyを倒す
　Enemy、Bulletに攻撃力とHPを設定してHPが0になったら消えるようにしましょう。
　攻撃力は1秒間に削るHP量にします。

enemy.gdに追加
```
@export var m_attack_power_ps = 1.0
@export var m_hp = 10
```

bullet.gdに追加
```
@export var m_attack_power_ps = 1.0
@export var m_hp = 1
```
# BulletがEnemyとバトルする
　攻撃力を1秒間に削るHP量にしたので、_physics_processで継続的にBulletがEnemyを接触しているか判定するようにします。

## Enemyを「EnemyGroup」グループに設定する
　Bulletが衝突したノードが攻撃対象であるか判定を簡単にするため、グループ機能を利用します。
　グループに登録したノードは検索できるようになります。

　enemy.tscnを開いてEnemyを選択します。
　インスペクタの横のノード/グループを選択します。
　「EnemyGroup」と入力して、追加ボタンを押下します。
![enemy.tscn - S10_enemy_002 - Godot Engine 2023_03_01 22_37_52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/9f5a503e-2f25-1d5c-a31a-baef4bc13274.png)
　追加されました。
![enemy.tscn - S10_enemy_002 - Godot Engine 2023_03_01 22_37_54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/76c78a65-efd8-1009-a97c-dbf156ccda5e.png)
　シーンのEnemyノードの右にはグループに参加していることを表す四角の中に●があるアイコンが表示されました。このアイコンの上をマウスオーバーすると「Node is in this group: EnemyGroup」と表示されるので、「EnemyGroup」グループに登録されたことがわかります。
![スクリーンショット (49).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/4fcdef08-3ee1-8366-3bb2-232099d8cfa4.png)

## bullet.gdにバトル処理を追加
　_physics_process()のmove_and_slide()実行後にバトル処理を追加しました。

```GDScript
extends CharacterBody3D

@export var m_v_dir = Vector3.FORWARD
@export var m_d_speed_mps = 2.0

@export var m_attack_power_ps = 1.0
@export var m_hp = 1

var m_v3_enemy_pos = null

func _physics_process(delta):
	if m_v3_enemy_pos:
		m_v_dir = m_v3_enemy_pos - transform.origin
	else:
		m_v_dir = Vector3.FORWARD
	velocity = m_v_dir.normalized() * m_d_speed_mps
	move_and_slide()
	
	# バトル判定
	for index in range(get_slide_collision_count()):
		var collision = get_slide_collision(index)
		if (collision.get_collider() == null):
			continue
		if collision.get_collider().is_in_group("EnemyGroup"):
			var enemy = collision.get_collider()
			
			# Bulletの攻撃
			enemy.m_hp -= m_attack_power_ps * delta
			# Enemyの攻撃
			m_hp       -= enemy.m_attack_power_ps * delta

	# HPが0になったら消える
	if m_hp < 0:
		queue_free()
	
func discover_enemy(enemy_pos):
	m_v3_enemy_pos = enemy_pos
	
func _on_visible_on_screen_notifier_3d_screen_exited():
	queue_free()
```

　move_and_slide()の後がバトルの処理です。

**衝突情報をチェックして、Enemyを検索します。**

* for index in range(get_slide_collision_count()):
 get_slide_collision_count()により、move_and_slide()の結果、衝突した回数（衝突により方向を変えた回数）を取得し、forループします

* var collision = get_slide_collision(index)
　index番目の衝突情報をcollisionとして取得

* if (collision.get_collider() == null):
　collision衝突情報がない場合continueして次を検索

* if collision.get_collider().is_in_group("EnemyGroup"):
　衝突したノードが「EnemyGroup」に属しているかチェックします

* var enemy = collision.get_collider()
　「EnemyGroup」グループに所属しているノードインスタンスを取得します。

**Enemyと接触している場合、お互いの攻撃力を使用してHPを削ります。**
　1秒間でHPを削る量なので、deltaを掛けた攻撃量をHPから減算しています。

* enemy.m_hp -= m_attack_power_ps * delta
* m_hp       -= enemy.m_attack_power_ps * delta

**ループを抜けた後、Bulletのhpが0になっていたらBulletを消します。**

* if m_hp < 0:
*   queue_free()

# EnemyのHPチェック処理を追加
　enemy.gdを開いてhpをチェックする処理を追加します。EnemyのHPが0になったら、Enemyを削除します。
　_physics_process()が追加した処理です。

``` GDScript
extends CharacterBody3D

@export var m_attack_power_ps = 1.0
@export var m_hp = 10

func _physics_process(delta):
	if m_hp < 0.0:
		queue_free()
	
func _on_bullet_sensor_body_entered(body):
	body.discover_enemy(transform.origin)
```

　実行しましょう。
　Bulletが10個ぶつかるとEnemyが消えるようになりました。

# バグ修正：Bulletが残る
　Enemyが消えたあとに、残るBulletがいます。
　Enemyを倒してもその前にBulletに設定した目標位置m_v3_enemy_posが残っているためです。
　やり方はいくつかありそうですが、ほぼ目標位置に到達した場合（Enemyがいる場合はぶつかって止まる）目標位置を削除することで対処します。

``` GDScript
extends CharacterBody3D

@export var m_v_dir = Vector3.FORWARD
@export var m_d_speed_mps = 2.0

@export var m_attack_power_ps = 1.0
@export var m_hp = 1

var m_v3_enemy_pos = null

func _physics_process(delta):
	if m_v3_enemy_pos:
		m_v_dir = m_v3_enemy_pos - transform.origin
		# 向かっている途中でEnemyが消えた場合、ほぼm_v3_enemy_posの位置まできたら目標位置を消して再び前進する
		if m_v_dir.length() < 0.1:
			m_v3_enemy_pos = null
	else:
		m_v_dir = Vector3.FORWARD
	velocity = m_v_dir.normalized() * m_d_speed_mps
	move_and_slide()
	
	# バトル判定
	for index in range(get_slide_collision_count()):
		var collision = get_slide_collision(index)
		if (collision.get_collider() == null):
			continue
		if collision.get_collider().is_in_group("EnemyGroup"):
			var enemy = collision.get_collider()
			
			# Bulletの攻撃
			enemy.m_hp -= m_attack_power_ps * delta
			# Enemyの攻撃
			m_hp       -= enemy.m_attack_power_ps * delta

	# HPが0になったら消える
	if m_hp < 0:
		queue_free()
	
func discover_enemy(enemy_pos):
	m_v3_enemy_pos = enemy_pos
	
func _on_visible_on_screen_notifier_3d_screen_exited():
	queue_free()
```

　_physics_process()メソッドのm_v3_enemy_posが設定されている場合の処理の中に下記の処理を追加しました。
　0.1は十分近づいた値として適当に設定したものです。

``` GDScript
		# 向かっている途中でEnemyが消えた場合、ほぼm_v3_enemy_posの位置まできたら目標位置を消して再び前進する
		if m_v_dir.length() < 0.1:
			m_v3_enemy_pos = null
```

　実行しましょう。
　Bulletがとまることがなくなりました。


以上