Godot4.1.3でシューティングゲームを作っていきます。
バグの修正
バグを2件修正します。
github
本記事で実装したものをgithubで公開しています。
バグ修正その1:画面外の敵の弾のシーンが残り続ける
現象
ボスBigFireを倒した後、画面には敵の弾が無いのに、GameシーンのDynamicNodes/enemy/bulletsに敵の弾のインスタンスが残り続ける
原因
敵の弾を消すために、enemy_bullet.tscnにVisibleOnScreenNotifier3Dノードを追加し、screen_exited()シグナルを受信するとqueue_free()を実行することで、敵の弾を消去するように実装していましたが、screen_exited()は画面内から画面外に出たときに発行されるシグナルのため、シーンインスタンスが最初から画面外で生成され、画面外を移動し続けると、screen_exited()シグナルが発行されず、queue_free()が実行されることもなく、敵の弾インスタンスが残っていました。
対策
screen_exit()シグナル受信でシーンを消去するのはやめて、_physics_processメソッドでVisibleOnScreenNotifier3Dのis_on_screenメソッドを使い、画面外に存在するか毎回チェックするようにして、画面外にいる場合はqueue_freeで敵の弾を消去するようにしました。この対応により最初から画面外で生成された敵の弾はすぐ消去されるようになりました。
VisibleOnScreenNotifier3Dノードのscreen_exitedシグナルの接続も解除しました。
res://character/enemy_bullet/enemy_bullet.gd
extends Area3D
@export var _d_speed_mps = 25.0
@export var _d_rotate_speed_degps = 180
var _v_dir = Vector3.ZERO
func set_pos(v_pos, v_target_pos):
global_position = v_pos
_v_dir = ( v_target_pos - v_pos).normalized()
func _physics_process(delta):
position += _v_dir * _d_speed_mps * delta
rotate_y(deg_to_rad(_d_rotate_speed_degps * delta))
+ # 画面外の場合は、消去する
+ if !$VisibleOnScreenNotifier3D.is_on_screen():
+ queue_free()
-func _on_visible_on_screen_notifier_3d_screen_exited():
- queue_free()
バグ修正その2:レーザーを発射すると自機が少し動く
現象
自機を動かさずレーザーだけ発射したい時に、画面上の自機の中心~前方あたりをタッチしていると、レーザーが発射されるたびに、微妙に自機が動く。
原因
2D画面上のタッチ位置を3D空間上の座標に変換するために、以下のようにしています。
- 背景に大きくて透明なArea3Dノード、「TouchDetectorArea3D」を配置
- 2Dタッチ位置をカメラからの視線方向に変換
- 視線方向の延長線上の「TouchDetectorArea3D」の衝突座標を検出
発射したレーザーもArea3Dなので、タッチした位置にレーザーがあると、レーザー上の3D位置を取得します。
「TouchDetectorArea3D」よりレーザーの方がカメラに近くなるようにZ座標を設定しています。カメラから遠いものより近いものの方が画面上大きく表示されるため、2D上でタッチした位置が同じでも、「TouchDetectorArea3D」をタッチしたときと比べて、レーザーをタッチしたときに検出した座標のxとzが小さくなります。
つまり、「TouchDetectorArea3D」をタッチしているときに、レーザーがタッチ位置を通りすぎる瞬間、移動目標位置が変化するため、自機が動いてしまいます。
対策
タッチ位置検出の対象は、StaticBody3Dを使用し、タッチ位置を検出したくないレーザーや敵などはArea3Dにすることにしました。
まず、「TouchDetectorArea3D」はArea3DからStaticBody3Dに変更し、名称も「TouchDetectorStaticBody3D」に変更しました
スクリプトを下記のように修正して、カメラからの視線がArea3Dを検出しないように変更しました。
res://character/player/player.gd
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:
# 衝突位置を取得
以上