【Godot 4.0】スマホ３Dゲームを作るための勉強　その２５　Bulletが増殖するエリアをつくる

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

# 目的
　ステージアイテムを増やします。
　通過すると増殖するエリアをつくります。

# ベースプロジェクト
　下記で作成したプロジェクトをベースに機能追加をします。
　【Godot 4.0】スマホ３Dゲームを作るための勉強　その２４　Androidアプリケーションとしてエクスポートする・調整する
　https://qiita.com/FootInGlow/items/2f3fc2e60b8ddf9e7594
　
　github(Godotのプロジェクトマネージャーからインポートして利用できます）
　https://github.com/footinglow/Godot4/tree/main/02_study/S24_AndroidAppExport

# 方法
　Bulletが通過すると増殖するエリアMultiplicationAreaを作成します。
　Area3Dでエリアを作成します。倍率をLabel3Dで表示しましょう。
　BulletにArea3D検出用のDetecterを追加することで、MultiplicationAreaを検知します。

# MultiplicationAreaシーンを作成する
　シーンメニューから新規シーンを選択して、「その他のノード」ボタンを押下します。
　Area3Dを選択します。
　名称を「MultiplicationArea」に変更します。
　StageItemsの中に保存しましょう。
　res://StageItems/multiplication_area.tscnが追加されます。
![スクリーンショット (223).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/a7f728bd-4264-f41b-20eb-47fe282d3bc7.png)

## 直方体を設定する
　「MultiplicationArea」を右クリックして、子ノードを追加からMeshInstance3Dを追加します。
　MeshInstance3Dを選択した状態で、インスペクタのMeshInstance3D/Meshの＜空＞をクリックして、「新規BoxMesh」を選択します。
　設定された立方体アイコンをクリックして詳細設定を表示して、Sizeのzに0.3mを設定します。
　またNode3D/Transform/Positionのyに0.5mを設定します。
　![スクリーンショット (224).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/2fe2fbb6-4d15-d398-b50e-3f792597fe9d.png)

## 透明の青にする
　Meshの詳細設定を開いている状態で、Materialの右の＜空＞をクリックして新規StandardMaterial3Dを設定します。
![スクリーンショット (225).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/c83a4d5a-c067-35ec-ec8f-e8745db9e5bb.png)
　Materialに設定された球をクリックしてStandardMaterial3Dの詳細設定を表示して、Albedoを開きます。
　白い長方形をクリックしてカラーピッカーを表示して青い色(R=0,G=0,B=255)にします。
　またA(Alpha）に透明度を設定しましょう。A=128にしました。0が透明、255が非透明なので真ん中の値にしました。
![スクリーンショット (226).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/f972502d-1fa6-c49d-8a3d-3d6ad56b7159.png)
　透明設定を有効にします。
　albedoの設定の3つ上の「Transparency」を開いて、Transparencyを「Alpha]に設定すると、Alpha値が有効になり見た目が半透明になります。
　![スクリーンショット (228).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/b729bcae-6d77-68bf-87e1-66310d8a480c.png)

## 倍率を表示する
　「MultiplicationArea」を右クリックして、子ノードを追加からLabel3Dを追加します。
　MeshInstance3Dを選択した状態で、インスペクタのLabel3D/textに「x2」と文字列を入力して、Font Sizeを50pxにします。
　Node3D/Transpose/Positionのyを1mにします。
　Node3D/Transpose/Rotationのxを-90°にします。
![スクリーンショット (230).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/752bced2-86e6-fda4-82d6-1343856311e7.png)
![スクリーンショット (233).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/73fa3691-c3d3-f495-124b-444e82b01f2c.png)

　ここを通り過ぎたら2倍に増えそうです。

## MeshInstance3Dと同じ大きさ・位置のCollisionShape3Dを追加する
　「MultiplicationArea」を右クリックして、子ノードを追加からCollisionShape3Dを追加します。
　CollisionShape3Dを選択した状態で、インスペクタのCollisionShape3D/Shapeの右の＜空＞をクリックして新規BoxShape3Dを設定します。
　Shapeの右に設定された「BoxShape3D」をクリックして詳細設定を表示して、Sizeのzを0.3mにします。
　Node3D/Transform/Positionのyを0.5mにします。
![スクリーンショット (234).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/98a81052-adbc-ab7b-00b2-53d34e02cb71.png)

## 検出されるための設定をする
　MultiplicationAreaが検出されるための設定をします。
　MultiplicationArea専用のCollision Layerを追加します。

　プロジェクトメニューからプロジェクト設定を開いて、「Layer Names/３D物理」のLayer 5に「MultiplicationArea」を設定します。
![スクリーンショット (237).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/83537540-6c0f-6e84-3efc-d26471557c99.png)

　res://StageItems/multiplication_area.tscnを開きます。
　ルートノードMultiplicationAreaを選択して、インスペクタのCollisionObject3D/Collisionを開きます。
　検出されたい側なので、Layerの5:MultiplicationAreaを有効にします。
　検出することはないので、Maskは未選択にします。
![スクリーンショット (238).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/4a001878-bb48-8f82-fd0c-e9a8a69f4859.png)


# Bulletを分裂する
　MultiplicationAreaを通過するとBulletを追加して、分裂したように見えるようにします。
## MultiplicationArea検出用にArea3Dを追加する
　res://Characters/bullet.tscnを開きます。
　ルートノードのBulletを右クリックして、子ノードを追加からArea3Dを選択します。
　名称を「MultiplicationAreaDetecter」にします。

　Bullet本体と同じCollisionShape3DをArea3Dノードにも追加します。
　ルートノードBulletの子ノードのCollisionShape3Dを右クリックしてコピーします。
　MultiplicationAreaDetecterを右クリックして貼り付けをします。
　大きさ、位置を確認します。
　![スクリーンショット (240).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/a8b73b32-97d7-2174-5a45-6e7942b7d08a.png)

　MultiplicationAreaDetecterを選択して、インスペクターの「CollisionObject3D/Collistion」を開きます。
　検出される必要はなのでLayerは未選択です。
　MultiplicationAreaを検出したいので、Maskの5:MultiplicationAreaにチェックを入れます。
　![スクリーンショット (243).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/b4ba71b8-081f-ca81-e1ca-a211cb9b7f84.png)

　MultiplicationAreaDetecterを選択して、インスペクターの横のノード/シグナルを選択します。
　今回はArea3Dを検出したいので、area_entered(area:Area3D)を右クリックして「接続」を実行します。
![スクリーンショット (245).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/a2ec16c8-9da8-1703-6690-5ca863c6c7d2.png)
　Bulletを選択して、受信側メソッド名を確認して「接続」ボタンを押下します。
![スクリーンショット (246).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/ea0e9519-ea7a-9881-c56e-957fec3cf972.png)
res://Characters/bullet.gdに「func _on_multiplication_area_detecter_area_entered(area):」が追加されました。

## Bulletの分身を作る
 bullet.gdに追加された_on_multiplication_area_detecter_area_entered(area)を下記のように実装します。
```GDScript
func _on_multiplication_area_detecter_area_entered(area):
	var avater = duplicate()
	avater.transform.origin += Vector3(0.1, 0, 0)
	add_sibling(avater)
```

* **var avater = duplicate()**
　今まではPackedSceneからinstantiateして生成していましたが、今回は自分自身を複製するのでduplicateメソッドを使用します。
　Node3D/Transform/Positionなどはコピーされていそうですが、GDScript内の変数は初期化されていそうです。
:::note
 Node.duplicate()のヘルプを見ると、Object._init()を使用している場合うまく動かないようです
:::
* **avater.transform.origin += Vector3(0.1, 0, 0.1)**
　まったく同じところにPhysicsBody3Dを追加するとどっかに飛んで行ってしまうため、左右と前後方向に少しずらして追加します。
* **add_sibling(avater)**
　増殖元のBulletと同じ階層に追加します。

## ステージにMultiplicationAreaDetecterを追加する
　res://Stages/stage001.tscnを開きます。
　Stageを右クリックして、Instantiate Child Sceneを実行します。
![スクリーンショット (247).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/b4b61cc3-fa05-2a1c-dd20-c78b90099a87.png)
　「StageItems/multiplication_area.tscn」を開きます。
![スクリーンショット (248).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/c69fa206-5410-72fc-724b-240ba8b14837.png)
　追加されました。
![スクリーンショット (251).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/d2910b19-317b-2454-6b51-eea493609518.png)

　MultiplicationAreaDetecterを選択した状態で、インスペクターのNode3D/Transform/Positionのｚを-3mに設定ます。
![スクリーンショット (255).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/0be1d6c4-bdd2-c289-aaa9-afb10c2720f8.png)

　実行します。BulletがMultiplicationAreaを通過すると2倍以上にたくさん増えましたね。
　これは増殖したBulletがMultiplicationAreaを検出して増殖するのを繰り返しているためです。
![スクリーンショット (258).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/78cde8d4-5d8b-afa3-8fb5-f8a841e57b7a.png)
　一度通過したMultiplicationAreaを記憶して、2回目以降は増えないようにします。

## 一度通過したMultiplicationAreaには反応しないようにする
　ノードはそれぞれ、インスタンスIDというユニークなIDを持っているので、一度追加したMultiplicationAreaのインスタンスIDを記憶しておいて、通過済みを判定できるようにします。分裂したBulletにも反映します。

res://Characters/bullet.gdを開き、辞書を追加します。
```GDScript
# 通過したMultiplicationAreaを記憶するための辞書
var m_dict_multiarea_ids = {}
```
　_on_multiplication_area_detecter_area_enteredメソッドを下記のように修正します。
　
```GDSCript
func _on_multiplication_area_detecter_area_entered(area):
	if m_dict_multiarea_ids.has(area.get_instance_id()):
		pass
	else:
		m_dict_multiarea_ids[area.get_instance_id()] = true
		var avater = duplicate()
		avater.m_dict_multiarea_ids = m_dict_multiarea_ids.duplicate(true)
		avater.transform.origin += Vector3(0.1, 0, 0)
		add_sibling(avater)
```

* **if m_dict_multiarea_ids.has(area.get_instance_id()):**
「area.get_instance_id()」でオブジェクト固有のインスタンスＩＤが取得できます。
 hasメソッドでm_dict_multiarea_ids辞書に「area.get_instance_id()」がある場合は、すでに通過したMultiplicationAreaと判断します。

* **m_dict_multiarea_ids[area.get_instance_id()] = true**
　初めて通過するMultiplicationAreaをキーに辞書に登録します。キーに対応する値は何でもよいのですが、trueとしています。

* **avater.m_dict_multiarea_ids = m_dict_multiarea_ids.duplicate(true)**
　分身の辞書は初期値なので、オリジナルのm_dict_multiarea_idsをコピーします。
　GDScript（によらず）辞書は参照コピーになるため、duplicate(true)で（深い）コピーを作成して渡します。


　実行します。今度はひとりがふたりになります。
　また実装オリジナルの座標は変更していませんが、分裂したBulletと衝突して押し合うのでオリジナルも左に移動します。
![スクリーンショット (263).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/7045bc43-fe0e-6e49-45d1-b56dcdf70f0a.png)

# MultiplicationAreaの倍率をインスペクターで設定できるようにする
　MultiplicationAreaの倍率をインスペクタで変更できるようにします。

　res://StageItems/multiplication_area.tscnを開きます。
　MultiplicationAreaを右クリックして、「スクリプトをアタッチ」を実行します。
　StageItemsディレクトリ配下を確認して保存します。
　![スクリーンショット (264).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/7744f093-6f6d-b773-0d63-b27f6f931ca9.png)
「res://StageItems/multiplication_area.gd」が追加されました。
![スクリーンショット (267).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/dcc40591-455b-4625-7f29-d9a2e8257bf6.png)

　下記のように、倍率の変数を追加してexportします。
　またシーン生成時に呼ばれる_ready()メソッドでLabel3Dに反映します。
```GDScript
extends Area3D

@export var m_i_magnification = 2.0		# 倍率

func _ready():
	$Label3D.text = "x%d" % m_i_magnification
```
* **"x%d" % m_i_magnification**
　%dが整数です。m_i_magnificationの値に置換されます。

　インスペクタに「M I MAgnification」として「２」が表示されました。
![スクリーンショット (269).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/65b5bbbf-9240-6bab-7ec9-1c9bc28b4313.png)

　ｘ３の倍率のMultiplicationAreaをステージに追加します。
　res://Stages/stage001.tscnを開きます。
　Stageを右クリックして、Instantiate Child Sceneを実行します。
![スクリーンショット (270).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/a7cc1722-d22d-d77f-c9dc-085e49c6c7a4.png)
　「StageItems/multiplication_area.tscn」を開きます。
![スクリーンショット (248).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/c69fa206-5410-72fc-724b-240ba8b14837.png)
　シーン内のMultiplicationAreaはMultiplicationArea_x2に名称変更します。
　MultiplicationArea2はMultiplicationArea_x3に名称変更します。
![スクリーンショット (272).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/b724f489-b1e7-e9cb-64c8-3e8317ed1c7d.png)

　MultiplicationArea_x2を選択した状態で、インスペクタのmultiplication_area.gd/M I Magnificationが「２」であることを確認します。 
![スクリーンショット (273).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/2c1805bd-c421-7658-bc51-4df2af4555b0.png)
　MultiplicationArea_x3を選択した状態で、インスペクタのmultiplication_area.gd/M I Magnificationを「３」に設定します。
![スクリーンショット (274).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/5c6d92ea-130c-f7cd-47cd-7ca24ce73592.png)
　位置を移動します。
　MultiplicationArea_x3を選択した状態で、Node3D/Transform/PositionのXを0.5m、zを-5mにします。
![スクリーンショット (275).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/389543aa-23bc-5a77-fd8b-ab3692ccb0ef.png)
![スクリーンショット (281).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/606b8589-a333-327b-5610-1a607b84f656.png)

　実行します。
![スクリーンショット (276).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/4a8cec4e-6719-16df-4f4c-e84d09b660ff.png)
　新たに追加したほうは「ｘ３」と表示されていますね。

# MultiplicationAreaの倍率に従って増殖するようにする

　res://Characters/bullet.gdを開いて、_on_multiplication_area_detecter_area_enteredメソッドを修正します。


```GDScript
func _on_multiplication_area_detecter_area_entered(area):
	if m_dict_multiarea_ids.has(area.get_instance_id()):
		pass
	else:
		# 倍率2の場合1人、倍率2倍の場合ふたりと倍率-1人増える
		var i_add_num = area.m_i_magnification - 1
		var d_add_x = -0.15
		var d_add_z = 0.1
		for i in range(i_add_num):
			m_dict_multiarea_ids[area.get_instance_id()] = true
			var avater = duplicate()
			avater.m_dict_multiarea_ids = m_dict_multiarea_ids.duplicate(true)
			avater.transform.origin += Vector3(d_add_x, 0, d_add_z)
			add_sibling(avater)
			
			d_add_z = -d_add_z
			d_add_x += 0.1
```
* **var i_add_num = area.m_i_magnification - 1**
　通過したMultiplicationAreaのm_i_magnificationを読みだしています。
　増殖する人数は倍率ー１（2倍なら1名、5倍なら4名）をi_add_numに設定します。

* **var d_add_x = -0.15 ,var d_add_z = 0.1**
　増殖するときに少しずつずれた場所に追加するためのパラメータ初期値です。

* **for i in range(i_add_num):**
　i_add_num分繰り返します。iは未使用です。

* **avater.transform.origin += Vector3(d_add_x, 0, d_add_z)**
　d_add_x、d_add_zで増殖したBulletの位置をずらしています。

* **d_add_z = -d_add_z, d_add_x += 0.1**
　追加するたびに少しずつ位置をずらしています。
　d_add_zは+0.1,-1.0を繰り返して前後方向をずらしています。
　d_add_xは初期値-0.15から0.1ずつ右にずれていきます。

　実行しましょう。
　x2の通過すると一人が二人に、x3を通過すると一人が3人に増殖します。

![スクリーンショット (279).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/124ffa70-16e6-b6a4-4993-570737f741b8.png)

 res://Characters/bullet.gd全体は下記のようになりました。
```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_sensing_pos = null
var m_v3_enemy_pos = null

# 通過したMultiplicationAreaを記憶するための辞書
var m_dict_multiarea_ids = {}

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()
	
	# バトル判定
	var battle_pos = null
	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()

			# バトル中に相手の位置を保持
			battle_pos = enemy.transform.origin

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

	# アニメーションの設定
	if battle_pos:
		$DesignMySoldier.animation_play("Battle")
	else:
		$DesignMySoldier.animation_play("Walk")
	
	# バトル中の相手、センシングした敵の順に目標位置を設定する
	if battle_pos:
		m_v3_enemy_pos = battle_pos
	elif m_v3_sensing_pos:
		if (m_v3_sensing_pos - transform.origin).length() < 0.1:
			m_v3_sensing_pos = null
		m_v3_enemy_pos = m_v3_sensing_pos
	else:
		m_v3_enemy_pos = null
	
func discover_enemy(enemy_pos):
	m_v3_sensing_pos = enemy_pos
	
func _on_visible_on_screen_notifier_3d_screen_exited():
	queue_free()

func _on_multiplication_area_detecter_area_entered(area):
	if m_dict_multiarea_ids.has(area.get_instance_id()):
		pass
	else:
		# 倍率2の場合1人、倍率2倍の場合ふたりと倍率-1人増える
		var i_add_num = area.m_i_magnification - 1
		var d_add_x = -0.15
		var d_add_z = 0.1
		for i in range(i_add_num):
			m_dict_multiarea_ids[area.get_instance_id()] = true
			var avater = duplicate()
			avater.m_dict_multiarea_ids = m_dict_multiarea_ids.duplicate(true)
			avater.transform.origin += Vector3(d_add_x, 0, d_add_z)
			add_sibling(avater)
			
			d_add_z = -d_add_z
			d_add_x += 0.1

```

　以上