0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Godot4で3D風2D縦スクロールシューティングゲームを作る 14回目 レーザーパワーアップ

Posted at

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

レーザーのパワーアップ!

 アップグレードアイテムが、パワーアップの時にゲットした場合、プレイヤー機が発射するレーザーのビジュアルと威力がアップするように実装します。
 初期段階は、正面方向にレーザー2本発射します。
 パワーアップを取ると、左右斜め方向にリング型のレーザーを発射します。
 さらにパワーアップを取ると、正面方向のレーザーの本数が増えて威力も強くなり、5段階目は一番強力な半月型のレーザーにします。
image.png

github

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

レーザーのシーンの追加

 my_laser.tscnを複製して、4つの新しいレーザー用シーンを作成します。
 下図で選択されているのが、新規に追加したレーザー用シーンと、5段階目のレーザーシーンで使用するテクスチャ画像です。
image.png

ファイル名 用途
laser_half_moon.png my_laser5.tscn用のテクスチャ画像
my_laser3.tscn 第3段階目の3本のレーザー用シーン
my_laser4.tscn 第4段階目の4本のレーザー用シーン
my_laser5.tscn 第5段階目の半月型のレーザー用シーン
my_ring_laser.tscn 第2弾目の前方斜めに発射するリング型レーザー用シーン

my_laser.tscnを複製後、共通で以下のことを実施しています。

  • ファイル名変更
  • CollisionShape3Dの大きさを修正
  • ルートノードのArea3Dのarea_entered(Area:Area3D)シグナルが接続されていることを確認
  • image.png
  • VisibleOnScreenNotifier3Dのscreen_exited()シグナルが接続されていることを確認
    image.png

リング型レーザーの追加

 リングの形は、MeshInstance3Dのmeshプロパティで選択できるTorusMeshを使いました。
res://character/player/my_ring_laser.tscn
image.png
 MaterialプロパティにStandardMaterial3Dを設定して、AlbedoセクションのColorプロパティを設定しました。
image.png
 リングの形を縦横に伸縮するようなアニメーションを設定したかったので、AnimationPlayerで、0.4秒間にScaleのx、zプロパティの値を1.0~0.5まで変更するようにしました。

image.png
image.png

3、4段階目のレーザー用シーンの追加

 MeshInstance3Dを増やしたのみです。

5段階目のレーザ用シーンの追加

 5段階目のレーザーはテクスチャ画像を使用するため、MeshInstance3DのmeshプロパティをPlaneMeshにしました。
res://character/player/my_laser5.tscn
image.png
image.png
 Materialプロパティは新規StandardMaterial3Dを追加し、AlbedoセクションのTextureプロパティにres://assets/texture/laser/laser_half_moon.pngを設定しました。
 画像の透明部分を有効にしたいので、TransparencyセクションのTransparencyをAlphaに変更しました。
image.png

レーザースクリプトの修正

 レーザー用の機能は移動方向と威力が異なるだけで共通の処理が多いため、my_laser.gdをすべてのレーザーで共有します。そのため移動方向と威力を外部から変更するようにします。
 自分ルールで、アンダースコアで始まる変数は、スクリプトファイル内部用としていたので、外部に公開する変数、_d_power_pointと_v_dir、は先頭のアンダースコアを削除します。
 ついでに、レーザーの速度が遅いので、25m/sから50m/sに変更しました。
 
res://character/player/my_laser.gd
(変更点のほとんどは、変数名を変更しただけ)

extends Area3D



-@export var _d_speed_mps = 25.0		# 単位はm/s 
-@export var _d_power_point = 1
-@export var _v_dir = Vector3(0, 0, -1)
+var _d_speed_mps = 50.0		# 単位はm/s 
+var d_power_point = 1
+var v_dir = Vector3(0, 0, -1)

func set_pos(player_pos):
	global_position = player_pos
	
func _physics_process(delta):
-	global_position += _v_dir * _d_speed_mps * delta
-	if _d_power_point <=0:
+    global_position += v_dir * _d_speed_mps * delta
+	if d_power_point <=0:
		queue_free()

# 画面外に出た場合、消える
func _on_visible_on_screen_notifier_3d_screen_exited():
	queue_free()

func _on_area_entered(area_enemy):
-	_d_power_point = area_enemy.calc_damage(_d_power_point)
+    d_power_point = area_enemy.calc_damage(d_power_point)

レーザーの発射処理の修正

extends Area3D

# Player
@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		# 最新のタッチ位置・ドラッグ位置

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

# 画面をタッチした位置から、検索する3Dオブジェクトまでの最大距離
@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

+# パワーアップ段階(レベル)の定義
+var _i_laser_power_level = 0
+
+# レーザー用シーンと威力の定義
+var _aa_laser_scn_and_pow=[
+	 [ preload("res://character/player/my_laser.tscn"), 1 ],
+	 [ preload("res://character/player/my_laser.tscn"), 1 ],
+	 [ preload("res://character/player/my_laser3.tscn"), 2 ],
+	 [ preload("res://character/player/my_laser4.tscn"), 3 ],
+	 [ preload("res://character/player/my_laser5.tscn"), 4 ]
+	]
+
+# リングレーザ用ーシーン
+var _scn_ring_laser : PackedScene = preload("res://character/player/my_ring_laser.tscn")

# スピードアップ倍率の定義
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空間のカメラの3D位置と、カメラからタッチしたピクセルを見た方向の1000m先の3D位置を計算する
		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と呼ばれる、物理3D空間状態の情報を利用して、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
			
			# 3D空間の移動目標位置
			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 * _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()
+			var scn : Area3D = _aa_laser_scn_and_pow[_i_laser_power_level][0].instantiate()
			scn.set_pos(global_position)
+			scn.d_power_point = _aa_laser_scn_and_pow[_i_laser_power_level][1]
			g_val.node_lasers.add_child(scn)

+			# リングレーザーを発射する
+			if _i_laser_power_level > 0:
+				# 左斜め前方に発射
+				var scn_l : Area3D = _scn_ring_laser.instantiate()
+				scn_l.set_pos(global_position)
+				scn_l.v_dir = Vector3(-0.5, 0, -1)
+				g_val.node_lasers.add_child(scn_l)
+
+				# 右斜め前方に発射
+				var scn_r : Area3D = _scn_ring_laser.instantiate()
+				scn_r.set_pos(global_position)
+				scn_r.v_dir = Vector3( 0.5, 0, -1)
+				g_val.node_lasers.add_child(scn_r)
			else:
				pass

			# 発射間隔を設定
			_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)
+	elif area.en_item_kind == g_val.EnItemKind.POWER_UP :
+		_i_laser_power_level = clampi(
+						_i_laser_power_level+1,
+						0,
+						_aa_laser_scn_and_pow.size() - 1)
	else:
		pass
	area.queue_free()

 変更点について説明すると、まず、パワーアップアイテムのレベルを保持するためのメンバ変数、_i_laser_power_levelを追加しました。初期値が0でパワーアップアイテムをゲットするごとに1ずつ増えます。
 前方に進むレーザー用のPackedSceneと威力の2つの情報は、パワーアップのレベルによって変わるため、2次元配列にしました。以下の表のようにアクセスします。

パラメータ データアクセス方法
レーザー用シーン(PackedScene) _aa_laser_scn_and_pow[_i_laser_power_level][0]
レーザーの威力 _aa_laser_scn_and_pow[_i_laser_power_level][1]
-@export var _scn_laser : PackedScene

+# パワーアップ段階(レベル)の定義
+var _i_laser_power_level = 0
+
+# レーザー用シーンと威力の定義
+var _aa_laser_scn_and_pow=[
+	 [ preload("res://character/player/my_laser.tscn"), 1 ],
+	 [ preload("res://character/player/my_laser.tscn"), 1 ],
+	 [ preload("res://character/player/my_laser3.tscn"), 2 ],
+	 [ preload("res://character/player/my_laser4.tscn"), 3 ],
+	 [ preload("res://character/player/my_laser5.tscn"), 4 ]
+	]

 また、リング型レーザーのPackedSceneも定義しました。

+# リングレーザ用ーシーン
+var _scn_ring_laser : PackedScene = preload("res://character/player/my_ring_laser.tscn")

 前方に進むレーザーのPackedSceneの持ち方を変更したので、instanciateするところを変更します。
 また、レーザーの威力を設定する行を追加しました。

			# 主砲のレーザーを発射する
-   			var scn : Area3D = _scn_laser.instantiate()
+			var scn : Area3D = _aa_laser_scn_and_pow[_i_laser_power_level][0].instantiate()
			scn.set_pos(global_position)
+			scn.d_power_point = _aa_laser_scn_and_pow[_i_laser_power_level][1]
			g_val.node_lasers.add_child(scn)

 パワーアップの2段階目から、リング型レーザーを発射します。_i_laser_power_leveは0始まりなので、1以上(0より大きく)なったら発射するようにします。
 生成したレーザーインスタンスのv_dirに、前方斜め方向を設定します。

+			# リングレーザーを発射する
+			if _i_laser_power_level > 0:
+				# 左斜め前方に発射
+				var scn_l : Area3D = _scn_ring_laser.instantiate()
+				scn_l.set_pos(global_position)
+				scn_l.v_dir = Vector3(-0.5, 0, -1)
+				g_val.node_lasers.add_child(scn_l)
+
+				# 右斜め前方に発射
+				var scn_r : Area3D = _scn_ring_laser.instantiate()
+				scn_r.set_pos(global_position)
+				scn_r.v_dir = Vector3( 0.5, 0, -1)
+				g_val.node_lasers.add_child(scn_r)

 アップグレードアイテムを検知する処理に、パワーアップアイテムの場合を追加します。
 パワーアップアイテムをゲットすると、_i_laser_power_levelに1加算します。値の範囲は_aa_laser_scn_and_pow配列の範囲にしたいので、0以上、_aa_laser_scn_and_pow.size()-1にクランプします。 

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)
+	elif area.en_item_kind == g_val.EnItemKind.POWER_UP :
+		_i_laser_power_level = clampi(
+						_i_laser_power_level+1,
+						0,
+						_aa_laser_scn_and_pow.size() - 1)
	else:

シナリオにアップグレードアイテム用シナリオ要素をたくさん追加

 デバッグ用に、たくさん追加しました。
res://scenario/scenario_stage01.tscn
image.png

実行

 パワーアップすると、隕石を簡単に破壊できるようになりました。
image.png

以上です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?