Godot4.1.3でシューティングゲームを作っていきます。
レーザーのパワーアップ!
アップグレードアイテムが、パワーアップの時にゲットした場合、プレイヤー機が発射するレーザーのビジュアルと威力がアップするように実装します。
初期段階は、正面方向にレーザー2本発射します。
パワーアップを取ると、左右斜め方向にリング型のレーザーを発射します。
さらにパワーアップを取ると、正面方向のレーザーの本数が増えて威力も強くなり、5段階目は一番強力な半月型のレーザーにします。
github
本記事で実装したものをgithubで公開しています。
レーザーのシーンの追加
my_laser.tscnを複製して、4つの新しいレーザー用シーンを作成します。
下図で選択されているのが、新規に追加したレーザー用シーンと、5段階目のレーザーシーンで使用するテクスチャ画像です。
ファイル名 | 用途 |
---|---|
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)シグナルが接続されていることを確認
- VisibleOnScreenNotifier3Dのscreen_exited()シグナルが接続されていることを確認
リング型レーザーの追加
リングの形は、MeshInstance3Dのmeshプロパティで選択できるTorusMeshを使いました。
res://character/player/my_ring_laser.tscn
MaterialプロパティにStandardMaterial3Dを設定して、AlbedoセクションのColorプロパティを設定しました。
リングの形を縦横に伸縮するようなアニメーションを設定したかったので、AnimationPlayerで、0.4秒間にScaleのx、zプロパティの値を1.0~0.5まで変更するようにしました。
3、4段階目のレーザー用シーンの追加
MeshInstance3Dを増やしたのみです。
5段階目のレーザ用シーンの追加
5段階目のレーザーはテクスチャ画像を使用するため、MeshInstance3DのmeshプロパティをPlaneMeshにしました。
res://character/player/my_laser5.tscn
Materialプロパティは新規StandardMaterial3Dを追加し、AlbedoセクションのTextureプロパティにres://assets/texture/laser/laser_half_moon.pngを設定しました。
画像の透明部分を有効にしたいので、TransparencyセクションのTransparencyをAlphaに変更しました。
レーザースクリプトの修正
レーザー用の機能は移動方向と威力が異なるだけで共通の処理が多いため、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
実行
パワーアップすると、隕石を簡単に破壊できるようになりました。
以上です。