Godot入門:VisibleOnScreenNotifier2Dの使い方と活用法






Godot入門:VisibleOnScreenNotifier2Dの使い方と活用法


はじめに

ゲーム開発では、パフォーマンス最適化は常に課題です。特に大量の敵やエフェクトがある場合、画面外のオブジェクトまで処理を続けていると、フレームレートが大幅に低下してしまいます。VisibleOnScreenNotifier2Dは、「このオブジェクトが画面に見えているか」を自動判定し、画面外のオブジェクトの処理を停止する仕組みを提供します。

この小さなノードを活用することで、大規模なゲームを快適に動作させることが可能になります。本記事では、基本的な使い方から、実践的なパフォーマンス最適化までを段階的に解説していきます。

VisibleOnScreenNotifier2Dとは?

VisibleOnScreenNotifier2Dは、ノードが画面内に見えているかどうかを検知するセンサーです。シンプルに見えますが、信号(signal)を発火させることで、画面の外出入りをトリガーに処理を制御できます。これにより、不要な計算を削減し、ゲームのパフォーマンスを大幅に向上させられます。

「VisibleOnScreenNotifier2Dは舞台袖のセンサーのようなものです。演者(ゲームオブジェクト)が舞台(画面)に登場したか退場したかを検知してくれます。『画面に見える = 処理を続ける』『画面外 = 処理を停止する』という判定を自動化できるため、余計な負荷を削減できます。」

継承ツリー:

VisibleOnScreenNotifier2D
  ↓
Node2D
  ↓
CanvasItem
  ↓
Node




VisibleOnScreenNotifier2Dの検知範囲
図1:画面内・画面外の判定エリア。矩形が画面に重なった時点で screen_entered シグナル発火

このノードを使うべき場面

VisibleOnScreenNotifier2Dは以下のような場面での使用に最適です:

  1. 画面外の敵のAIを止めて負荷軽減 – 大量の敵がいるゲームで、見えない敵の AI 演算を停止。フレームレート低下を防ぎます。
  2. 画面外に出た弾丸の削除 – シューティングゲームで、飛んでいった弾丸が画面外に出たら自動削除。メモリ効率向上。
  3. 敵が画面に入ったときに警戒状態に – RPGで、敵が視野範囲(画面)に入った時点で battle AI を開始。ゲームロジックの最適化。
  4. ビルボード広告のアニメーション最適化 – 画面内の広告アニメーションだけを処理。画面外の広告は静止。スマートフォンゲーム向け。

別のノードが適切な場面:「常に計算が必要」なロジック(プレイヤーのステータス管理、グローバルスコア計算など)には使用しません。また、音声再生中の敵を画面外で停止すると違和感が生じるため、音声ノードには直結させないように注意が必要です。

主なプロパティと機能

プロパティ/メソッド/シグナル 説明
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()




画面外の敵AI制御によるパフォーマンス改善
図2:画面内敵の AI は稼働、画面外敵は停止。CPU負荷を大幅削減

もっと使いこなす:カスタマイズできるパラメータ

設定項目 説明 推奨値・用途
rect のサイズ調整 検知範囲を手動で拡大・縮小 キャラクター本体より大きめ(敵キャラなら 50×50 など)。銃弾なら 5×5 で十分
VisibleOnScreenEnabler2D の使用 シグナル接続不要で自動 on/off 複雑なロジック不要で良い場合に。手動制御が必要なら VisibleOnScreenNotifier2D
マージン設定 検知範囲を画面より少し外側に拡張 ビューポート外で敵を準備中に。-50〜50px のマージンが標準
オブジェクトプール 敵を削除せず、再利用するプール管理 敵が頻繁に出現・消滅する場合。メモリ効率と再生成コスト削減
グループの活用 カテゴリ分け(敵、銃弾、エフェクトなど)で個別制御 敵だけカリング、銃弾は音声出力のみなど、きめ細かい制御可能

まとめ

VisibleOnScreenNotifier2Dは、小さなノードながら、ゲームのパフォーマンス最適化に極めて重要な役割を果たします。画面外のオブジェクトの処理を停止することで、CPU負荷を劇的に削減できます。大規模なゲーム、敵やエフェクトが多い場面では、このノードの活用が開発品質を大きく左右します。正しく活用すれば、モバイルデバイスでも快適にプレイできるゲームを実現できます。

  • VisibleOnScreenNotifier2Dは画面内外の判定をシグナルで通知し、処理の on/off を制御
  • screen_entered / screen_exited シグナルを接続して、カリング処理を実装
  • 画面外の敵 AI 停止、銃弾削除など、パフォーマンス最適化に活用

次回はRemoteTransform2Dについて解説します。別のノードに自分の位置・回転・スケールを遠隔転送し、複雑な追従ロジックを簡潔に実装するテクニックを学びます。

シリーズ:Godot 4 ノード解説

この記事はGodot 4.xをもとに執筆しています。


コメント