「弾を撃つ」処理を作るのは楽しいですが、「弾を消す」処理を作るのは忘れがちです。
しかし、画面外へ飛んでいった弾丸が、何千発もメモリに残っていたら…?
当然、ゲームはカクカクになり、最終的にはフリーズしてしまうかもしれません。
通常は「座標がX以上になったら消す」といったコードを書きますが、ステージの広さが変わるたびに書き直すのは面倒ですよね。
そこで今回は、Godotの便利機能 VisibleOnScreenNotifier をラップ(包み込み)して、**「見えなくなったら親を消す」**という機能をコンポーネント化します。
どんなことができるの?
この「AutoDespawn(オート・デスポーン)」ノードを、弾丸やエフェクトの子として追加するだけです。
- 自動消滅: カメラの範囲(画面)から完全に出た瞬間に、親オブジェクトを
queue_free()(削除)します。 - 判定エリア調整: 「画面ギリギリで消えて違和感がある」という場合も、エディタ上で判定ボックスを広げるだけで調整できます。
- コード不要: 弾丸のスクリプトに「消滅処理」を書く必要がなくなります。
ステップ1:スクリプトの作成
今回は2D用として作成します。
auto_despawn_component.gd という名前でスクリプトを作成し、以下のコードをコピペしてください。
このコンポーネントは、通常の Node ではなく、VisibleOnScreenNotifier2D を継承して作ります。こうすることで、エディタ上で判定範囲(四角い枠)を視覚的に編集できるようになります。
class_name AutoDespawnComponent
extends VisibleOnScreenNotifier2D
## 画面(カメラ範囲)から出た瞬間に、親ノードを自動削除するコンポーネント
## 弾丸、使い捨てのエフェクト、破片などに最適です。
# 画面外に出てから削除されるまでの猶予時間(秒)
# 0.0 なら即座に消えます。
# 「画面外に出た直後にまた戻ってくる敵」などに対応したい場合は少し長めに設定します。
@export var margin_time: float = 0.0
func _ready() -> void:
# シグナルを接続
screen_exited.connect(_on_screen_exited)
func _on_screen_exited() -> void:
# 猶予時間が設定されている場合
if margin_time > 0.0:
# タイマーを作成して待つ(ワンショット)
await get_tree().create_timer(margin_time, false, false, true).timeout
# 待っている間に「画面内に戻ってきた」場合は消さないようにチェック
if is_on_screen():
return
# 親ノードを削除
# 親が既に消されようとしているかチェックしてから実行
var parent = get_parent()
if parent and is_instance_valid(parent) and not parent.is_queued_for_deletion():
parent.queue_free()
ステップ2:実際に使ってみよう
1. ノードを追加する
弾丸(Bullet)やエフェクトのシーンを開き、**「子ノードを追加」**から、今回作った AutoDespawnComponent を選択して追加します。
(検索に出てこない場合は、一度プロジェクトをビルドするか、Godotを再起動してみてください)
2. 「ピンクの枠」を調整する(超重要!)
シーン画面(2Dビュー)を見ると、追加したコンポーネントの場所にピンク色の四角い枠が表示されているはずです。これが「画面内かどうかの判定エリア」です。
- 判定エリアが小さすぎると…
- 大きな画像の端っこがまだ画面に映っているのに、「判定エリア」が画面外に出た瞬間にパッと消えてしまいます(違和感の原因)。
- 調整方法
- インスペクターの
Rectプロパティをいじるか、画面上の赤いハンドルをドラッグして、親オブジェクトの画像(スプライト)全体がすっぽり入る大きさに広げてください。
- インスペクターの
これだけで設定完了です!
弾を撃ってみて、画面外に出た瞬間に「リモート」タブ(実行中のノード一覧)から弾丸ノードが消えていれば成功です。
ステップ3:3Dで使いたい場合
3Dゲームの場合は、継承するクラスを変えるだけで同じことができます。
- スクリプトの
extends VisibleOnScreenNotifier2Dをextends VisibleOnScreenNotifier3Dに書き換える。 Rectの代わりにAABB(3次元の箱)というプロパティで大きさを調整する。
これだけで3Dのミサイルやデブリにも対応できます。
補足:なぜコンポーネントにするの?
Godotにはもともと VisibleOnScreenNotifier というノードがあります。「それを使えばいいのでは?」と思うかもしれません。
しかし、毎回それを追加して、シグナル画面を開いて、screen_exited をダブルクリックして、親のスクリプトに接続して、queue_free() と書く…という作業をすべての弾丸やエフェクトで行うのは非常に面倒です。
今回のようにコンポーネント化しておけば、**「ドラッグ&ドロップして、枠の大きさを合わせる」**だけで作業が完了します。この「手軽さ」こそが、開発効率を上げるカギなのです。
