Godot4で3D風２D縦スクロールシューティングゲームを作る 13回目 スピードアップ

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

# スピードアップ！
　アップグレードアイテムが、スピードアップの時にゲットした場合、プレイヤー機の速度がアップするように実装します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/cb011318-44f6-fc23-bb8d-5443aec68e49.png)

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

https://github.com/footinglow/woden/tree/013_pick_up_items_to_speed_up

# Playerシーンにアップグレードアイテム検知用のArea3Dを追加
　Playerシーンが、アップグレードアイテムの衝突を検知できるようにします。
　敵との当たり判定用のCollisionShape3Dは小さくて、アイテムを取り逃してしまう可能性があるため、アップグレードアイテム専用のArea3Dを追加します。
　player.tscnを開き、Area3Dの「item_sensor」を追加し、子ノードにCollisionShape3Dを追加します。
res://character/player/player.tscn
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/8239d368-9891-a6f5-cc0f-506eae78e4cb.png)
　CollisionShape3DのShapeプロパティは新規SphereShape3Dを設定し、半径を3mにしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/b81d8e1d-0985-1dfb-b7d1-5f725dacde64.png)

# playerシーンとアップグレードアイテムのCollision設定をして衝突を検知する
　playerシーンに追加したArea3Dのitem_sensorが、upgrade_itemシーンの衝突を検知できるように、Collisionセクションを設定します。

## アップグレードアイテム用のLayer名を追加
　プロジェクト設定のLayer Names/3D物理を開き、Layer6に「UpgradeItem」を追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/d2808270-79df-8a03-193f-961e4e1e326a.png)

## upgrade_itemシーンのCollision設定
　res://character/item/upgrade_item.tscnを開き、ルートノードを選択して、インスペクターのcollisionセクションのLayerプロパティの6番をチェックします。
　アップグレードアイテム自体は、衝突を検知しないので、Maskプロパティはチェック不要です。
res://character/item/upgrade_item.tscn
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/3e018907-f6d5-29f9-0208-d176c9726cf2.png)

## playerシーンのCollision設定
　追加したArea3Dのitem_sensorで、アップグレードアイテムを検知したいので、item_sensorを選択した状態で、Collisionセクションを設定します。
 　アップグレードアイテムを検知したいので、Maskの6番のみチェックします。検知されることは想定していないので、Layerはチェック無しです。
  res://character/player/player.tscn
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691050/e75c2088-8c44-2389-ccf9-ef829cf0e0e1.png)

# スピードアップアイテム取得すると、Player機を速くする
　以下の式を使います。
 
 　　　Player機の速度　＝　基準速度[m/s] × 倍率

　基準速度は25m/s、倍率の初期値は1.0にして、スピードアップアイテムを取るたびに、倍率を0.4ずつ上げます。最大3.0倍にします。

 　初期値　　25m/s × 1.0 = 25m/s
 　１段階目　25m/s × 1.4 = 35m/s
 　２段階目　25m/s × 1.8 = 45m/s
 　３段階目　25m/s × 2.2 = 55m/s
 　４段階目　25m/s × 2.6 = 65m/s
 　５段階目　25m/s × 3.0 = 75m/s

　player.gdを修正します。
　まず、Area3Dの「item_sensor」を選択した状態で、インスペクターの横のノード/シグナルの「area_entered(area : Area3D)」をダブルクリックして、player.gdに接続します。
　下記のように修正します。

res://character/player/player.gd
```diff_gd_script
extends Area3D

# Player
-@export var _d_speed_mps = 100.0
+@export var _d_speed_mps = 25.0
@export var _d_hp = 1.0

# ドラッグ操作検知用
var _f_is_screen_touch = false		# 画面にタッチ（ドラッグ含む）している場合true、タッチしていない場合はfalse
var _v2_drag_pos = Vector2.ZERO		# 最新のタッチ位置・ドラッグ位置

# タッチしたときの、３D上のタッチ位置とPlayer位置
var _v_first_touch_pos   = null
var _v_first_touch_player_pos = Vector3.ZERO

# 画面をタッチした位置から、検索する３Dオブジェクトまでの最大距離
@export var _d_ray_length_m = 1000		# [m]十分な長さにする

# レーザーの発射タイミング
@export var _scn_laser : PackedScene
@export var _d_firing_interval_sec = 0.2
var _d_firing_remain_time_sec = 0

+# アップグレードアイテムで性能
+const _d_speed_magnification_max : float = 3.0
+const _d_speed_magnification_min : float = 1.0
+var _d_speed_magnification = _d_speed_magnification_min

func _input(event):
	# 画面にタッチしている状態と、タッチしていない状態を判断する
	if event is InputEventScreenTouch:
		_f_is_screen_touch = event.is_pressed()
		# ドラッグ位置（タッチ位置）を更新する
		_v2_drag_pos = event.position	
	elif event is InputEventScreenDrag:
		# ドラッグしているということは、タッチしている。（参考：InputEventScreenDragの場合、event.is_pressed()は常にfalse)
		_f_is_screen_touch = true
		# ドラッグ位置（タッチ位置）を更新する
		_v2_drag_pos = event.position	
	else:
		pass

func _physics_process(delta):
	if _f_is_screen_touch:
		var camera = get_viewport().get_camera_3d()

		# カメラを利用して3D空間のカメラの３D位置と、カメラからタッチしたピクセルを見た方向の1000m先の３D位置を計算する
		var from3d = camera.project_ray_origin(_v2_drag_pos)
		var to3d = from3d + camera.project_ray_normal(_v2_drag_pos) * _d_ray_length_m

		# 3D ray physics queryの作成
		var query = PhysicsRayQueryParameters3D.create(from3d, to3d)
		query.collide_with_areas = true		# Area3Dを検知できるようにする
		
		#　spaceと呼ばれる、物理３D空間状態の情報を利用して、Area3Dを含む衝突位置を計算する
		var space_state = get_world_3d().direct_space_state
		var result = space_state.intersect_ray(query)
		if result:
			# 衝突位置を取得
			var v_drag_pos = result.position
			v_drag_pos.y = global_position.y	# Y方向には移動しないための設定
			if _v_first_touch_pos == null :
				# 初めてタッチしたときの、3D位置を記憶する
				_v_first_touch_pos = v_drag_pos
				# 初めてタッチしたときの、プレイヤーの3D位置を記憶する
				_v_first_touch_player_pos = global_position
			else:
				pass
			
			# ３D空間の移動目標位置
			var v_target_pos = _v_first_touch_player_pos + ( v_drag_pos - _v_first_touch_pos )
			
			# Player位置から移動目標位置までの相対位置を計算
			var v_target : Vector3 = v_target_pos - global_position
			# 移動目標位置に向かう速度ベクトルを生成
-			var v_velocity : Vector3 = v_target.normalized() * _d_speed_mps
+			var v_velocity : Vector3 = v_target.normalized() * _d_speed_mps * _d_speed_magnification
			if v_target.length() < ( v_velocity * delta ).length():
				# 目的地までの距離が、delta当たりの移動量よりも小さい場合は、飛び越えてしまうため、Playerの位置を目標位置にする
				global_position = v_target_pos
			else:
				# CharacterBody3Dのvelocityに速度を設定して動かす
				position += v_velocity * delta
		else:
			# タッチ位置に衝突したものが無い
			pass
	else:
		# タッチしていない場合はnullを設定することで、次にタッチしたときに、新しいタッチ位置とPlayer位置を記憶する
		_v_first_touch_pos = null
		
	if _f_is_screen_touch:
		# タイミングをとりながらlaser発射
		if _d_firing_remain_time_sec > 0:
			# 次の発射までの残り時間を減算するのみ
			_d_firing_remain_time_sec -= delta
		else:
			# レーザーを発射する
			var scn : Area3D = _scn_laser.instantiate()
			scn.set_pos(global_position)
			g_val.node_lasers.add_child(scn)
			# 発射間隔を設定
			_d_firing_remain_time_sec = _d_firing_interval_sec
	else:
		# タッチしたときにすぐ、レーダーが発射するようにする
		_d_firing_remain_time_sec = 0

	# HPが0になった場合、Playerは消滅する
	if _d_hp <= 0:
		queue_free()
		
func _on_area_entered(area):
	# 敵もしくは敵の弾の攻撃があたったため、HPを0にする
	_d_hp = 0.0

+func _on_item_sensor_area_entered(area):
+	# アップグレードアイテム
+	if area.en_item_kind == g_val.EnItemKind.SPEED_UP :
+		# パワーアップアイテム
+		_d_speed_magnification = clampf(_d_speed_magnification + 0.4, _d_speed_magnification_min, _d_speed_magnification_max)
+	else:
+		pass
+	area.queue_free()

```
　変更点ですが、まず、Player機の基準速度を_d_speed_mpsに設定します。
```diff_gd_script
-@export var _d_speed_mps = 100.0
+@export var _d_speed_mps = 25.0
```
　倍率は_d_speed_magnificationです。初期値の1.0を設定しています。
```diff_gd_script
+# アップグレードアイテムで性能
+const _d_speed_magnification_max : float = 3.0
+const _d_speed_magnification_min : float = 1.0
+var _d_speed_magnification = _d_speed_magnification_min
```
　またplayer機の速度の計算する式に、倍率_d_speed_magnificationを掛け算するようにしました。 
```diff_gd_script
-			var v_velocity : Vector3 = v_target.normalized() * _d_speed_mps
+			var v_velocity : Vector3 = v_target.normalized() * _d_speed_mps * _d_speed_magnification
```
　下記が、アップグレードアイテムをゲットしたときの処理です。
　areaはupgrade_item.tscnシーンなので、upgrade_item.gdのen_item_kindを参照することで、スピードアップかパワーアップの判別が可能です。
　スピードアップの場合、倍率に0.4を加算します。clampfで1.0～3.0にクランプします。
　最後に、アップグレードアイテムシーンを、queue_freeで解放します。
```diff_gd_script
+func _on_item_sensor_area_entered(area):
+	# アップグレードアイテム
+	if area.en_item_kind == g_val.EnItemKind.SPEED_UP :
+		# パワーアップアイテム
+		_d_speed_magnification = clampf(_d_speed_magnification + 0.4, _d_speed_magnification_min, _d_speed_magnification_max)
+	else:
+		pass
+	area.queue_free()
```
# 終わりに
　次はレーザーをパワーアップするようにします。

 以上です。