Godot4で3D風２D縦スクロールシューティングゲームを作る 12回目 アップグレードアイテムの追加

Godot4.1.3でシューティングゲームを作っていきます。

# アップグレードアイテムの追加
　プレイヤー機の性能を向上するアップグレードアイテムを追加します。アップグレードアイテムは、取るタイミングにより、レーザーの威力の向上とプレイヤー機のスピードアップを、プレイヤーが選択できるようにします。

　本記事では、アップグレードアイテムの出現までを実装します。
- シナリオで、決まったタイミングで出現
- 画面上方に出現し、下方にまっすぐ移動
- 一定周期で、パワーアップ「P」用デザインとスピードアップ「S」用デザインが交互に切り替わる

　

# github
　本記事で実装したものをgithubで公開しています。

https://github.com/footinglow/woden/tree/012_upgrade_item

# 本記事で新規追加・修正する主なノード
　アップグレードアイテム本体の「upgrade_item.gd/tscn」とアップグレードアイテムを生成するシナリオ要素「produce_upgrade_item.gd/tscn」を新規追加します。
　シナリオ要素はシナリオscenario_stage01.tscnに追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/e268661f-0db2-e57e-0a83-064a6bf88ffd.png)

　また、アップグレードアイテムのインスタンスの追加先を追加するため、game.gd/tscnとglobal.gdを修正しました。
　先にこちらから説明します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/c1981556-5792-8d48-d049-8cb54b753a36.png)

# アップグレードアイテム インスタンスの管理ノードを追加
　ゲーム中に動的に生成したインスタンスはgame.tscnのgame/DynamicNodesノードの配下に追加して、管理をしています。アップグレードアイテムを追加する適切なノードが無いため、新規に「others」を追加しました。
res://game/game.tscn
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/cd49a74c-c540-e8ee-7785-8f4f0ac02281.png)

　インスタンス追加先はglobal.gdのメンバで指定するため、others用のメンバ変数「node_others」を追加します。

res://global/global.gd
```diff_gd_script
extends Node3D

# ゲーム画面の見える範囲
# ゲーム画面の上端をtop、下端をbotommとする
var d_visible_top_z_m = -200.0
var d_visible_top_min_x_m = -75.0
var d_visible_top_max_x_m =  75.0

var d_visible_bottom_z_m = -15.0
var d_visible_bottom_min_x_m = -25.0
var d_visible_bottom_max_x_m =  25.0


# gameのインスタンス
var node_game : Node3D = null 

# 生成したインスタンスの追加先
var node_lasers : Node3D = null
var node_enemies : Node3D = null 
var node_bullets : Node3D  = null
+var node_others : Node3D  = null

# Playerのインスタンス
var node_player : Area3D = null 
```
　global.gdは、自動読み込みで「g_val」で登録したため、「g_val.node_others」で、いつでもどこからでもアクセスできます。
　game.gdから初期値として、先ほど追加したothersノードを設定します。
res://game/game.gd
```diff_gd_script
extends Node3D

var _scn_scenario_stage = preload("res://scenario/scenario_stage01.tscn")

var _node_scenario_ins

var _i_score : int = 0

func _ready():
	# gameインスタンスを保存
	g_val.node_game = self
	
	# インスタンス化したシーンの追加先を設定する
	g_val.node_lasers = $DynamicNodes/lasers
	g_val.node_enemies = $DynamicNodes/enemy/bodies
	g_val.node_bullets = $DynamicNodes/enemy/bullets
+	g_val.node_others = $DynamicNodes/others

	# Playerインスタンスを設定する
	g_val.node_player = $Player
	
	# ステージシナリオをシーンツリーに追加
	_node_scenario_ins = _scn_scenario_stage.instantiate()
	add_child(_node_scenario_ins)

func _process(delta):
	# シナリオを駆動する
	_node_scenario_ins.drive_scenario()

func _on_add_to_score(add_score):
	_i_score += add_score
	$HUD/score_value.text = "%010d" % _i_score 
```

# アップグレードアイテム本体の実装

## シーンの新規追加
　新規シーンを作成します。ルートノードはArea3Dです。bodyとtextはMeshInstance3Dで、他は名称通りのノードです。
 
 res://character/item/upgrade_item.tscn
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/7b46b8c5-daeb-ea18-77ab-f5d78fe5c9b1.png)

　アップグレードアイテムは、球の上にアルファベット一文字が乗っているデザインにするため、bodyノードのMeshプロパティは新規SphereMeshで球にして、textノードのMeshプロパティは新規TextMeshを使用し、「P」の文字を設定しました。それぞれのmaterialプロパティには新規StandardMaterial3Dを追加して、Albedo/Colorで色を設定しました。
　
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/b097f1ec-1009-24d2-7b84-19dd673599b8.png)

## 下の方にまっすぐ移動する動きを実装
　シーンにスクリプトをアタッチし、下記のように実装します。
　またVisibleOnScreenNotifier3Dのscreen_exited()シグナルを接続して、queue_free()します。

res://character/item/upgrade_item.gd
```gdscript
extends Area3D

var _d_speed_mps = 20.0

func _physics_process(delta):
	# 移動処理
	global_position += Vector3(0, 0, 1) * _d_speed_mps * delta

func _on_visible_on_screen_notifier_3d_screen_exited():
	queue_free()
```


## ぴかぴか光るようにする
　アップグレードアイテムは、画面上で目立つようにしたいので、body(MeshInstance3D)のmaterialプロパティのEmissionセクションの各プロパティを操作して、ぴかぴか光るようにします。
　res://character/item/upgrade_item.tscnのインスペクターのmeshプロパティのmaterialプロパティのEmissionセクションを開きます。
　Enabledプロパティをオンにして、Emissionプロパティはカラーピッカーで白に変更すると光ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/235d55ef-1bf7-bd1f-f840-a83bb32d48a4.png)

　光り具合はEnergy Multiplierプロパティで操作することができます。0.0は光っていない状態です。1.0にするとPの文字が見えなくなるほど真っ白になり、やりすぎなので、光っている状態は0.2くらいでちょうどよさそうです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/e2df5c68-41ad-4a2a-9729-f3c156e82c95.png)

　次に、0.5秒くらいで、0.0と0.2の間を変化する変数を作りたいと思います。スクリプトで実装してもよいのですが、AnimationPlayerノードを使用します。 
　スクリプトにメンバ変数d_cycle_valをfloat型になるように追加します。AnimationPlayerで操作するので、exportします。
```diff_gd_script
+@export var d_cycle_val : float = 0.0	# AnimationPlayerで変更
```
　次のAnimationPlayerでアニメーションを追加します。
　アニメーションボタンから新規で「repeat」を追加します次に、「＋トラックを追加」からプロパティトラックを選択し、upgrade_itemのd_cycle_valを開きます。
　以下の吹き出しに従い、設定を変更します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/99abe1fe-a334-2ce5-937f-8ebf6c11b24f.png)

　d_cycle_valの右側を右クリックして、「キーを挿入」を実行し、３つキーを追加します。追加したキーはクリックして選択すると、インスペクターで設定を変更できます。以下のように変更します。
 - Time:0.00 、　Value:0.0
 - Time:0.25 、　Value:0.2
 - Time:0.50 、　Value:0.0
　再生ボタンを押下し、ルートノードのupgrade_itemのインスペクタを表示すると、「d_cycle_val」の変化を確認できます。
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/d09dda6d-1219-222d-b3cd-24e99a5373e0.png)

　スクリプトで、d_cycle_valの値を、Energy Multiplierプロパティを設定します。
　下記がスクリプトの実装です。
 
```diff_gd_script
extends Area3D

@export var d_cycle_val : float = 0.0	# AnimationPlayerで変更
var _d_speed_mps = 20.0

+var _material_body : StandardMaterial3D = null

+func _ready():
+	# Bodyマテリアル用
+	_material_body = ($body.mesh as SphereMesh).material
func _physics_process(delta):
	# 移動処理
	global_position += Vector3(0, 0, 1) * _d_speed_mps * delta
+	# アニメーション
+	_material_body.emission_energy_multiplier = d_cycle_val

func _on_visible_on_screen_notifier_3d_screen_exited():
	queue_free()
```

　_readyメソッドで、bodyノードのmesh(Meshクラス)を取得しますが、Meshクラスのままではmaterialにアクセスできないので、実体のSphereMeshクラスにキャストして、materialメンバ変数を取得します。material(Materialクラス)からEmissionセクションのプロパティにアクセスするためにStandardMaterial3Dにキャストが必要ですが、代わりに代入先の_material_bodyをStandardMaterial3D型で宣言しています。　 
```diff_gd_script
+	_material_body = ($body.mesh as SphereMesh).material
```

## 一定周期で、パワーアップ「P」用デザインとスピードアップ「S」用デザインが交互に切り替わる
- 2秒周期でデザインを変更する
- パワーアップの場合、bodyは赤で、文字は「P」にする
- スピードアップの場合、bodyは青で、文字は「S」にする

　2秒周期の切り替えは、Timerノードのtimeout()シグナルでスクリプトに通知します。
　Timerノードのインスペクターで、Wait Timeプロパティを2sに変更し、AutoStartプロパティをオンにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/32967cad-b005-521f-f536-17e51fe276f9.png)

　Timerノードのtime_out()シグナルをスクリプトに接続します。
　アップグレードアイテムの状態は、global.gdに追加したenum値で管理します。

res://global/global.gd
```diff_gd_script
extends Node3D

+# アップグレードアイテムの種別
+enum EnItemKind{
+	POWER_UP,
+	SPEED_UP	
+}

# ゲーム画面の見える範囲
# ゲーム画面の上端をtop、下端をbotommとする
  :
```
　_on_timer_timeout()メソッドが呼ばれた直後の、スピードアップとパワーアップのenum値の切り替えは、3項式を使いました。
res://character/item/upgrade_item.gd
```diff_gd_script
extends Area3D

@export var d_cycle_val : float = 0.0	# AnimationPlayerで変更
var _d_speed_mps = 20.0

var _material_body : StandardMaterial3D = null
+var _mesh_text : TextMesh = null
+var en_item_kind = g_val.EnItemKind.SPEED_UP

func _ready():
	# Bodyマテリアル用
	_material_body = ($body.mesh as SphereMesh).material
+	# P/Sのテキスト変更用
+	_mesh_text = $body/text.mesh

func _physics_process(delta):
	# 移動処理
	global_position += Vector3(0, 0, 1) * _d_speed_mps * delta
	# アニメーション
	_material_body.emission_energy_multiplier = d_cycle_val

func _on_visible_on_screen_notifier_3d_screen_exited():
	queue_free()

+func _on_timer_timeout():
+	# アップグレードアイテムの種別を切り替える
+	en_item_kind = g_val.EnItemKind.POWER_UP if en_item_kind == g_val.EnItemKind.SPEED_UP else g_val.EnItemKind.SPEED_UP
+	
+	# アップグレードアイテムの種別に応じて、デザインを変更する
+	if en_item_kind == g_val.EnItemKind.SPEED_UP:
+		# Speedに変更
+		_mesh_text.text = "S"
+		_material_body.albedo_color = Color(0.5, 0.5, 1, 1)
+	else:
+		# Powerに変更
+		_mesh_text.text = "P"
+		_material_body.albedo_color = Color(1, 0, 0, 1)
```
# アップグレードアイテムのシナリオ要素をシナリオに追加
　produce_upgrade_item.tscnをシナリオに追加します。
res://scenario/scenario_stage01.tscn
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/84934656-a902-25ce-03fa-e01be6209f3b.png)

# 実行結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/d698a829-d282-5747-9dc6-c7e8544a2726.png)

# まとめ
　アップグレードアイテムを出現させることができるようになりました。
　次は、プレイヤー機がアップグレードアイテムをゲットすると、プレイヤー機の性能が向上するようにします。
 
 　以上です。