Godot4で3D風２D縦スクロールシューティングゲームを作る 17回目 ボスの攻撃 全方位に弾発射

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

# ボスキャラBigFireに全方位弾を発射させる
　今回は攻撃方法を実装します。ボスBigFireの中心から一定周期で全方位（20°毎）に弾を発射するように実装します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/b4aac24f-a855-4ade-9364-0aae65e2f233.png)

# 方針
　シーンやスクリプト間の関係はなるべく疎にしたいと思います。
　一定周期で全方位弾を発射するだけなので、全方位弾を発射するために必須とするボスBigFire情報は「発射位置」のみです。位置だけなら、ボスBigFireシーンの子ノードは自動的に親の位置を引き継ぐことができますので、一定周期で全方位弾を生成するシーンを1個作って、BigFireシーンに子ノードとして追加することで、勝手に全方位弾を発射するようにします。
 
# github
　本記事で実装したものをgithubで公開しています。

 https://github.com/footinglow/woden/tree/017_boss_bigfire_fires_bullets_in_all_directions

# 修正および新規追加ファイル
　全方位弾発射用シーン「fire_360_bullet_periodically.tscn」とスクリプトを新規実装して、ボスBigFileシーン「boss_bigfile.tscn」に子ノードとして追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/5998b4be-1edd-f010-5c7a-2499a376d1e6.png)

## 全方位弾発射用シーンを追加
　一定時間毎に全方位弾を発射するシーンを追加します。
　res://character/enemy_bulletに新規シーンを追加して、fire_360_bullet_periodically.tscnに名称変更します。ルートノードはMarker3Dにします。子ノートにTimerノードを追加して名称をFireTimerに変更しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/5b0a68e6-ab67-fb56-a0f0-9fb645516344.png)
　発射周期を管理するTimerノードのタイムアウト時間は、スクリプトからタイマーを開始する時に設定するので、Wait Timeプロパティは、ディフォルトの設定のままで良いです。
　タイムアウト後は自動でタイマーを再開してほしいので、One Shotプロパティもディフォルトのままオフです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/01e0db33-92f8-186e-d4c7-495827155bf9.png)


## 全方位弾発射用シーンのスクリプトを実装
　ルートノードにスクリプトをアタッチして下記のように修正します。

res://character/enemy_bullet/fire_360_bullet_periodically.gd
```gdscript
extends Marker3D

var _d_fire_interval_sec = 2.0
var _i_fire_interval_deg = 20

var _scn_bullet : PackedScene = preload("res://character/enemy_bullet/enemy_bullet.tscn")

func _ready():
	$FireTimer.start(_d_fire_interval_sec)	

func _on_fire_timer_timeout():
	for deg in range(0, 360, _i_fire_interval_deg):
		var ins = _scn_bullet.instantiate()
		var dir = Vector3(cos(deg_to_rad(deg)), 0.0, sin(deg_to_rad(deg)))		
		ins.set_pos( global_position, global_position + dir)
		g_val.node_bullets.add_child(ins)
```
　スクリプトの説明をします。
　_readyメソッド内で、全方位弾を発射するためのタイマーを開始するために、周期時間を指定してstartメソッドを実行します。
```gdscript
var _d_fire_interval_sec = 2.0

func _ready():
	$FireTimer.start(_d_fire_interval_sec)	
```
　次にタイムアウト時に、全方位弾のインスタンスを生成するように実装します。
　まず、タイムアウトを検知したいので、FireTimerノードのtimeoutシグナルをスクリプトの_on_fire_timer_timeoutメソッドに接続します。
　最初のfor文では、「range(0, 360, 20)」分繰り返します。つまり、0～360を20刻みなので、0, 20, 40, ....320, 340まで18回ループします。
　ループの中では、敵の弾のインスタンスを生成したあと、dirに弾が移動する向きを設定しています。XZ平面上で、ある角度を向いている方向のx座標はcos関数、z座標はsin関数で得ることができるので、
 
 dir = Vector3( cos(角度), 0, sin(角度) )

で移動する向き、dirを計算することができます。sinとcosの引数の単位はラジアンなので、角度はdeg_to_rad関数でラジアンに変換する必要があります。
　あとは敵の弾のインタンスのset_posメソッドを使って、現在の位置と移動方向の位置を設定します。

　現在の位置　　：global_position
　移動方向の位置：global_positionにdirを足した値
 
 最後に敵の弾のインスタンスを動的インスタンスの管理ノードに追加します。
```gdscript
var _i_fire_interval_deg = 20
var _scn_bullet : PackedScene = preload("res://character/enemy_bullet/enemy_bullet.tscn")

func _on_fire_timer_timeout():
	for deg in range(0, 360, _i_fire_interval_deg):
		var ins = _scn_bullet.instantiate()
		var dir = Vector3(cos(deg_to_rad(deg)), 0.0, sin(deg_to_rad(deg)))		
		ins.set_pos( global_position, global_position + dir)
		g_val.node_bullets.add_child(ins)
```

## 全方位弾発射用シーンをボスBigFireに追加
　ボスBigFireシーン(res://character/boss/boss_bigfire.tscn）に、全方位弾発射用シーン(re_360_bullet_periodically.tscn)を追加するだけです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/38f36e06-3a9e-1b98-918c-6e7ce271974a.png)


以上です。