【Godot 4.0】スマホ３Dゲームを作るための勉強　その９ 敵を検知するとホーミングするようにしたい

 ゲームエンジン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)を使用しています。

# 目的
　発射できるようになったので、一定距離に敵を見つけたら向かうようにしたい。

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

　【Godot 4.0】スマホ３Dゲームを作るための勉強　その８　発射する
　https://qiita.com/FootInGlow/items/dd0f63bf7a9cd92801b7

# 敵をつくる
　CharacterBody3Dで敵を作成します。
　global.tscnを開いて、シーンのglobalを右クリックして子ノードを追加します。
　CharacterBody3Dを追加して、Enemyに名称を変更します。
![global.tscn - S09_enemy_001 - Godot Engine 2023_02_28 22_57_23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/35c3537b-2506-7e7b-c054-2d9737dd1669.png)

* Enemy
　Node3/Transform/yに0.5mを設定します。
* MeshInstance3Dに円柱（シリンダー）型を設定します。
　インスペクタ内のMeshInstance3D/Meshを新規CylinderMeshに設定します。
* CollisionShape3Dに衝突形状を設定
　MeshInstance3Dと同じ形にします。
　インスペクタ内のCollisionShape3D/Shapeに新規CylinderShape3Dを設定
* DirectionalLight3D
　敵が暗いので、Node3D/Transform/rotation/xを-10°に設定します。

　実行します。 
　発射した弾丸が敵にぶつかるとよけていきます。
　お互いCharacterBody3Dで衝突形状を設定してmove_and_slide()で動かしているので当然ですが、ある程度近づくと敵に向かうようにしたいです。
![S09_enemy_001 (DEBUG) 2023_03_01 9_58_15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/16728aed-859d-d16e-e449-176aeb37cd95.png)


# 衝突をCollision Layer/Maskでコントロールする
　今はディフォルトの設定をしているので、すべてのCharacterBody3D/StaticBosy3D(RigidBody3D）が衝突し、Area3Dがあれば衝突を検知する状態ですが、ノードをグループ分けして適切に衝突制御、検知をするようにしたいと思います。

## Collision Layerを定義する
　衝突・検知の観点でノードのグループを定義します。
　プロジェクトメニューのプロジェクト設定でLayer Names/3D物理を開きます。
　Layer1～４までLayer名を入力します。

![スクリーンショット (17).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/928c6248-d700-71f0-c20c-23fca43f8760.png)

|Layer名|対象|
|:--------------------|:----------------------|
|FloorAndObstacles|床（と先々追加する障害物）|
|Player|プレイヤー|
|Enemy|敵|
|Bullet|プレイヤーが発射したもの|

## Player、StaticBody3D(床）、Enemy、BulletにCollision LayerとMaskを設定する
　衝突・検知対象のノード、Player、StaticBody3D(床）、Enemy、BulletにLayerとして種別を設定します。

　自身が属するLayer（グループ）を設定します。
　Maskには自身が衝突/検出する対象を設定します。

　Layerごとに衝突・検出したいものをまとめました。

|Layerに設定するもの|Maskに設定するもの（衝突・検出したいもの）|
|:----------------------------|:----------------------|
|FloorAndObstacles|なし（検出されますが、自ら衝突/検出したいということはない）|
|Player|FloorAndObstacles（そのうち左右に壁を作って移動制限をしたい）|
|Enemy|なし（今のところ移動予定はないので）|
|Bullet|FloorAndObstacles、Enemy、Bullet※に衝突してほしい。検出はしない ※Bullet同士も重ならないようにしたい|

　今のところPlayerは誰からも衝突、検出されませんが一応設定してあります。　

　シーンで対象のノードを選択して、インスペクタで設定します。
　LayerもMaskも対象の番号をチェックしますが、右側の「...」をクリックすると先ほどプロジェクト設定で設定した名称が表示されるので、こちらの方が設定しやすいと思います。

1. StaticBody3D(床）
　インスペクタのCollisionObject3D/Collisionで設定します。
　FloorAndObstaclesのLayerは1番です。Maskは未選択。
![スクリーンショット (25).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/12575bdc-b81f-9516-f156-d261709719ca.png)

2. Player
　インスペクタのCharacterBody3D/Collisionで設定します。
　PlayerはLayerは２です。Maskは1:FloorAndObstaclesを選択しています。
![スクリーンショット (27).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/bf2acab6-425b-6421-80e8-e50dbb88f0ab.png)

3. Enemy
　インスペクタのCharacterBody3D/Collisionで設定します。
　EnemyはLayer3番です。Maskは未選択。
![スクリーンショット (28).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/dc1c6487-bd82-ac44-92ea-cdb43b252d09.png)

4. Bullet
　インスペクタのCharacterBody3D/Collisionで設定します。
　BulletはLayer4番です。
　Bulletは1:FloorAndObstacles、3:Enemy、4:Bulletと衝突します。
![スクリーンショット (23).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/7d2106b7-a54c-fe1a-d45e-97e13cd52829.png)


# ある程度近づいたら敵に向かうようにしたい
## Enemyに円を設定する
　Bulletを賢くして、ある程度近づいたらEnemyに向かうようにします。
　Area3Dをセンサーとして使用します。

　global.tscnのEnemyを右クリックして、Area3Dを追加します。
　名称を「BulletSensor」に変更します。
　BulletSensorを右クリックしてCollisionShape3Dを追加します。
![bullet.tscn - S09_enemy_001 - Godot Engine 2023_03_01 21_26_51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/1c54ff02-7eda-0dd7-a003-c47a265835a3.png)

　Enemyの半径3m以内に近づいたらBulletを検出したいので、CollisionShape3Dに3mの球を設定します。

　BulletSensorの子ノードのCollisionShape3Dを選択します。
　インスペクタ内のCollisionShape3D/Shapeに新規SphereShape3Dを設定。
　SphereShape3Dをクリックして、Raduisを3.0mにします。
![(_) global.tscn - S09_enemy_001 - Godot Engine 2023_03_01 10_44_43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/6f97eede-6896-0e09-40b3-11d1f68006b2.png)

　３D表示の左上を「上面図」を選択すると、Emnemyのいる位置の周囲に円が表示されています。
　![global.tscn - S09_enemy_001 - Godot Engine 2023_03_01 10_46_29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/5e4e1e1a-849e-79b2-7f64-e89eee5f96af.png)

　またデバッグメニューの「コリジョン形状を表示」にチェックを入れてから実行しても設定した円が確認できます。
　![キャプチャ 2023_03_01 10_46_23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/ae174ce1-f5d0-8bbe-5f67-00c434d6d6d3.png)

## Enemyの円でBulletを検出する
　EnemyがBulletを検出できるようにします。

　BulletSensorを選択してCollision LayerとMaskを設定します。
　BulletSensorは誰からも衝突/検出される必要がないので、Layerは未選択です。
　Bulletを検出したいので、Maskは4を選択します。
![スクリーンショット (29).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/22282dac-f9b5-fcab-ce1f-ef4d31cea0ba.png)

　シグナルを接続するためにEnemyノードにスクリプトが必要です。
　Enemyを右クリックしてスクリプトをアタッチします。
　
　extendsの1行のみ残してください。

``` GDScript
extends CharacterBody3D
```
　あらためてBulletSensorを選択して、（インスペクタの横の）ノードタブのシグナルを選択します。
　PhysicsBody3Dノードを検出したいので、body_entered(body:Node3D)を右クリックして接続します。
![スクリーンショット (30).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/bd64e031-aed6-539d-0a80-8979516a2fa7.png)

::: note
 CharacterBody3DやRigidBody3Dなど検出したい場合は、body_xxx シグナルです。
 Area3Dを検出したい場合は、area_xxx シグナルを使用するようです。
:::
　Enemyのスクリプトを選択して、接続を押下します。
![スクリーンショット (31).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/b40aed85-9f90-a357-a00f-c37b3d595063.png)

　BulletSensorがBulletを検知すると、Enemyのスクリプトの_on_bullet_sensor_body_enteredが呼ばれるようになりました。print分を追加して、実行して確認します。
``` GDScript
extends CharacterBody3D

func _on_bullet_sensor_body_entered(body):
	print(body)
```

　実行後、シーンのリモートをクリックしてPlayerなどが見えるようにします。
　Bulletを発射すると、1発目はBulletという名前でシーンインスタンスが追加されます。2発目以降は、@Bullet@2、@Bullet@3...となります。
　BulletがBulletSensorの円の中にはいると、コンソールにノード名がprintされます。

![スクリーンショット (41).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/daca8785-1015-57f4-b1d1-a7524e17cb4d.png)

　_on_bullet_sensor_body_enteredシグナルで通知されている「body」のはBulletノードインスタンス本体なので、Bulletのメンバーにアクセスしたり、メソッドを呼ぶことができます。


## Enemyの位置をBulletに通知する
　Enemyのスクリプトをこのように変更して、Bulletのdiscover_enemyメソッドを呼びEnemyの位置を通知します。

``` GDScript
extends CharacterBody3D

func _on_bullet_sensor_body_entered(body):
	body.discover_enemy(transform.origin)
```

　bullet.gdスクリプトを開いて、discover_enemyメソッドを実装しましょう。
　Enemyの位置を保存するメンバ変数を宣言して、discover_enemyメソッドが呼ばれたときに保存するようにします。
``` GDScript
var m_v3_enemy_pos = null
```
``` GDScript
func discover_enemy(enemy_pos):
	m_v3_enemy_pos = enemy_pos
```

　つづいて、Enemyを見つけるまでは今まで通り直進、Enemnyを見つけた場合はEnemyに向かうように修正します。

``` GDScript
extends CharacterBody3D

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

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

func discover_enemy(enemy_pos):
	m_v3_enemy_pos = enemy_pos
	
func _on_visible_on_screen_notifier_3d_screen_exited():
	queue_free()
```

**メンバ変数初期値のVector3(0, 0, -1)をVector3.FORWARDに修正**
* @export var m_v_dir = Vector3.FORWARD
　Vector3に定義されている定数に変更しました
　値としてはVector3(0, 0, -1)なので同じです。

**func _physics_process(delta):の修正**
　進む方向を示すm_v_dir(Vector3)に設定する方向はEnemyを見つけた場合はEmenyの方向を設定するようにしました

* 	if m_v3_enemy_pos:
　m_v3_enemy_posが設定されている場合
*	m_v_dir = m_v3_enemy_pos - transform.origin
　目標位置から現在位置を引くと、現在位置から目標位置へ向かう方向になります。
*	else:
　m_v3_enemy_posが設定されていない場合
*	m_v_dir = Vector3.FORWARD
　今まで通り前進（Z軸マイナス方向）します

　実行します。
　BulletがEnemyに近づくと、Enemyに向きを変えて、最終的にくっつくようになりました。
![Screenshot 2023_03_01 11_35_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/71a7b425-9356-581d-57f6-9fba06a09fbd.png)

以上