はじめに
ゲーム開発では、パフォーマンス最適化は常に課題です。特に大量の敵やエフェクトがある場合、画面外のオブジェクトまで処理を続けていると、フレームレートが大幅に低下してしまいます。VisibleOnScreenNotifier2Dは、「このオブジェクトが画面に見えているか」を自動判定し、画面外のオブジェクトの処理を停止する仕組みを提供します。
この小さなノードを活用することで、大規模なゲームを快適に動作させることが可能になります。本記事では、基本的な使い方から、実践的なパフォーマンス最適化までを段階的に解説していきます。
VisibleOnScreenNotifier2Dとは?
VisibleOnScreenNotifier2Dは、ノードが画面内に見えているかどうかを検知するセンサーです。シンプルに見えますが、信号(signal)を発火させることで、画面の外出入りをトリガーに処理を制御できます。これにより、不要な計算を削減し、ゲームのパフォーマンスを大幅に向上させられます。
「VisibleOnScreenNotifier2Dは舞台袖のセンサーのようなものです。演者(ゲームオブジェクト)が舞台(画面)に登場したか退場したかを検知してくれます。『画面に見える = 処理を続ける』『画面外 = 処理を停止する』という判定を自動化できるため、余計な負荷を削減できます。」
継承ツリー:
VisibleOnScreenNotifier2D ↓ Node2D ↓ CanvasItem ↓ Node

このノードを使うべき場面
VisibleOnScreenNotifier2Dは以下のような場面での使用に最適です:
- 画面外の敵のAIを止めて負荷軽減 – 大量の敵がいるゲームで、見えない敵の AI 演算を停止。フレームレート低下を防ぎます。
- 画面外に出た弾丸の削除 – シューティングゲームで、飛んでいった弾丸が画面外に出たら自動削除。メモリ効率向上。
- 敵が画面に入ったときに警戒状態に – RPGで、敵が視野範囲(画面)に入った時点で battle AI を開始。ゲームロジックの最適化。
- ビルボード広告のアニメーション最適化 – 画面内の広告アニメーションだけを処理。画面外の広告は静止。スマートフォンゲーム向け。
別のノードが適切な場面:「常に計算が必要」なロジック(プレイヤーのステータス管理、グローバルスコア計算など)には使用しません。また、音声再生中の敵を画面外で停止すると違和感が生じるため、音声ノードには直結させないように注意が必要です。
主なプロパティと機能
| プロパティ/メソッド/シグナル | 型 | 説明 |
|---|---|---|
rect |
Rect2 | 検知範囲を定義する矩形。このエリアが画面内に入ると screen_entered を発火 |
screen_entered |
signal | オブジェクトが画面内に入ったときに発火するシグナル。AI開始、描画開始などをトリガー |
screen_exited |
signal | オブジェクトが画面外に出たときに発火するシグナル。AI停止、描画停止などをトリガー |
is_on_screen() |
bool | 現在、このノードが画面内に見えているかどうかを返す。true=画面内、false=画面外 |
関連ノード:VisibleOnScreenEnabler2D
VisibleOnScreenEnabler2Dは、VisibleOnScreenNotifier2Dの派生クラスで、自動的にノードの process / physics_process を on/off します。明示的なシグナル接続が不要で、より簡単に使えます。
コード例1:敵のAI制御と画面外削除の基本実装
extends CharacterBody2D
@onready var notifier = $VisibleOnScreenNotifier2D
@export var ai_enabled: bool = false
@export var movement_speed: float = 100.0
@export var screen_exit_timer: float = 2.0
var time_outside_screen: float = 0.0
func _ready():
# シグナルを接続して、画面出入りイベントを処理
notifier.screen_entered.connect(_on_screen_entered)
notifier.screen_exited.connect(_on_screen_exited)
# 初期状態で画面内に見えているかチェック
if notifier.is_on_screen():
ai_enabled = true
func _process(delta):
# AI が有効な場合のみ敵の処理を実行
if ai_enabled:
update_ai(delta)
func _physics_process(delta):
# 速度を適用して物理演算を実行
velocity = Vector2.ZERO
if ai_enabled:
velocity = move_and_slide(velocity)
# 画面外に出た状態が長く続いたら敵を削除(メモリ節約)
if not notifier.is_on_screen():
time_outside_screen += delta
if time_outside_screen > screen_exit_timer:
queue_free()
func update_ai(delta):
"""敵のAI処理(画面内のみ実行)"""
# プレイヤーに向かって移動
var player = get_tree().root.find_child("Player", true, false)
if player:
var direction = (player.global_position - global_position).normalized()
velocity = direction * movement_speed
func _on_screen_entered():
"""画面に入ったときの処理"""
print("敵が画面に入った")
ai_enabled = true
time_outside_screen = 0.0
# ここでアニメーション開始やSE再生も可能
func _on_screen_exited():
"""画面から出たときの処理"""
print("敵が画面から出た")
ai_enabled = false
# ここでアニメーション停止やSE停止も可能
コード例2:複雑なカリング制御とパフォーマンス監視
extends Node
class_name GameObjectManager
var active_objects: Array[Node2D] = []
var culled_objects: Array[Node2D] = []
@export var enable_culling: bool = true
@export var cull_update_interval: float = 0.5
var cull_timer: float = 0.0
func _ready():
# シーンのすべてのVisibleOnScreenNotifier2Dを自動登録
for child in get_tree().get_nodes_in_group("game_objects"):
if child.has_node("VisibleOnScreenNotifier2D"):
var notifier = child.get_node("VisibleOnScreenNotifier2D")
notifier.screen_entered.connect(_on_object_visible.bindv([child]))
notifier.screen_exited.connect(_on_object_culled.bindv([child]))
func _process(delta):
# 定期的にカリング状態を更新
if enable_culling:
cull_timer += delta
if cull_timer >= cull_update_interval:
update_culling()
cull_timer = 0.0
func update_culling():
"""画面内外のオブジェクトを分類し、負荷を最適化"""
var active_count = active_objects.size()
var culled_count = culled_objects.size()
print("アクティブオブジェクト: %d, カリングオブジェクト: %d" % [active_count, culled_count])
# オブジェクトの状態を定期更新
for obj in culled_objects:
if obj.has_method("on_culled"):
obj.on_culled()
func _on_object_visible(object: Node2D):
"""オブジェクトが画面内に入ったとき"""
if object not in active_objects:
active_objects.append(object)
if object in culled_objects:
culled_objects.erase(object)
if object.has_method("on_visible"):
object.on_visible()
func _on_object_culled(object: Node2D):
"""オブジェクトが画面外に出たとき"""
if object in active_objects:
active_objects.erase(object)
if object not in culled_objects:
culled_objects.append(object)
if object.has_method("on_culled"):
object.on_culled()
func get_active_object_count() -> int:
return active_objects.size()
func get_culled_object_count() -> int:
return culled_objects.size()

もっと使いこなす:カスタマイズできるパラメータ
| 設定項目 | 説明 | 推奨値・用途 |
|---|---|---|
rect のサイズ調整 |
検知範囲を手動で拡大・縮小 | キャラクター本体より大きめ(敵キャラなら 50×50 など)。銃弾なら 5×5 で十分 |
VisibleOnScreenEnabler2D の使用 |
シグナル接続不要で自動 on/off | 複雑なロジック不要で良い場合に。手動制御が必要なら VisibleOnScreenNotifier2D |
| マージン設定 | 検知範囲を画面より少し外側に拡張 | ビューポート外で敵を準備中に。-50〜50px のマージンが標準 |
| オブジェクトプール | 敵を削除せず、再利用するプール管理 | 敵が頻繁に出現・消滅する場合。メモリ効率と再生成コスト削減 |
| グループの活用 | カテゴリ分け(敵、銃弾、エフェクトなど)で個別制御 | 敵だけカリング、銃弾は音声出力のみなど、きめ細かい制御可能 |
まとめ
VisibleOnScreenNotifier2Dは、小さなノードながら、ゲームのパフォーマンス最適化に極めて重要な役割を果たします。画面外のオブジェクトの処理を停止することで、CPU負荷を劇的に削減できます。大規模なゲーム、敵やエフェクトが多い場面では、このノードの活用が開発品質を大きく左右します。正しく活用すれば、モバイルデバイスでも快適にプレイできるゲームを実現できます。
- VisibleOnScreenNotifier2Dは画面内外の判定をシグナルで通知し、処理の on/off を制御
- screen_entered / screen_exited シグナルを接続して、カリング処理を実装
- 画面外の敵 AI 停止、銃弾削除など、パフォーマンス最適化に活用
次回はRemoteTransform2Dについて解説します。別のノードに自分の位置・回転・スケールを遠隔転送し、複雑な追従ロジックを簡潔に実装するテクニックを学びます。
シリーズ:Godot 4 ノード解説
- 001〜092:各種ノード
- 091:TileMap
- 092:Camera2D
- 093:VisibleOnScreenNotifier2D(この記事)
- 094:RemoteTransform2D
- 095:PointLight2D
この記事はGodot 4.xをもとに執筆しています。


コメント