ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Godot VisibleOnScreenNotifier2D 实战指南:基于渲染剔除的 2D 屏幕可见性检测

Godot VisibleOnScreenNotifier2D 实战指南:基于渲染剔除的 2D 屏幕可见性检测
  • 文档
  • 教程
  • 游戏开发

【免费下载链接】godot-docs

Godot Engine official documentation

项目地址:https://gitcode.com/GitHub_Trending/go/godot-docs
点击查看免费下载

VisibleOnScreenNotifier2D是 Godot Engine 官方文档(本仓库 godot-docs)中定义的 2D 节点类,用于检测一个矩形区域是否出现在屏幕或视口内,并在状态变化时发出screen_entered/screen_exited信号。本文以 classes/class_visibleonscreennotifier2d.rst 为核心,结合仓库内 2D/3D 官方教程与相关类的源码文档,讲解该节点的属性、信号、方法、底层原理与实战用法;读完后你将掌握用它实现「离屏对象自动回收」「按可见性控制游戏逻辑」等典型方案。

一、类概览与继承体系

VisibleOnScreenNotifier2D表示 2D 空间中的一个矩形区域:当该区域的任意部分进入屏幕(或视口)时,节点发出screen_entered信号;当没有任何部分留在屏幕上时,发出screen_exited信号。如果你希望某个节点在区域可见时被自动启用,官方推荐使用它的子类VisibleOnScreenEnabler2D(见 classes/class_visibleonscreenenabler2d.rst)。

其继承链为:

VisibleOnScreenNotifier2D └─ Node2D └─ CanvasItem └─ Node └─ Object

被继承者:

VisibleOnScreenEnabler2D(继承自 VisibleOnScreenNotifier2D)

与 3D 版本对应:仓库中的 classes/class_visibleonscreennotifier3d.rst 定义了VisibleOnScreenNotifier3D(继承自VisualInstance3D),其继承体系与 2D 版本平行,属同一「屏幕可见性检测」节点家族。

二、核心工作原理:渲染剔除驱动的可见性判定

官方文档明确说明了一个关键机制:

VisibleOnScreenNotifier2D使用渲染剔除(render culling)代码来判断它是否在屏幕上,因此除非CanvasItem.visible设置为true,否则它不会正常工作。

这意味着:

  • 该节点的可见性判定不是通过独立的几何查询,而是复用渲染管线的视锥剔除结果;
  • 将节点的visible属性设为false(或隐藏其父节点)会同时禁用屏幕可见性检测,即使矩形区域实际上仍在屏幕范围内,也不会触发screen_entered;
  • 与之对比,show_rect属性只影响编辑器中的可视化显示,不会影响剔除检测(详见下文)。

仓库中 classes/class_renderingserver.rst 展示了渲染服务器层面的相关能力,例如instance_set_ignore_culling(忽略剔除强制绘制)、viewport_set_use_occlusion_culling(视口遮挡剔除开关)等,可以看到剔除是渲染服务器对实例统一管理的行为——2D 的屏幕可见性通知正是依附于这一套渲染实例管理机制实现的。

3D 版本的文档(classes/class_visibleonscreennotifier3d.rst)额外提醒:3D 版本默认采用一种近似启发式判定,「不把墙壁等遮挡物考虑在内,除非启用了遮挡剔除」。2D 版本文档未作此说明,因此 2D 场景中也不应依赖该节点进行墙体遮挡判断。

三、核心属性:rect 与 show_rect

类型属性默认值说明
Rect2rectRect2(-10, -10, 20, 20)VisibleOnScreenNotifier2D 的包围矩形
boolshow_recttrue是否在编辑器中显示矩形区域

3.1 rect:检测区域的大小与位置

rect是检测区域本体,类型为 Godot 内置Rect2。根据 classes/class_rect2.rst 的定义,Rect2是一个二维轴对齐包围盒,由position(Vector2)与size(Vector2)两个分量构成,常用于快速重叠测试(intersects()等)。注意:Rect2不支持负的size值,负尺寸下大部分方法无法正确工作。

默认值Rect2(-10, -10, 20, 20)表示:以节点原点为中心,向四个方向各扩展 10 像素,构成一个 20×20 的方形检测区域。实战中通常需要根据精灵或碰撞体的实际尺寸来调整,例如:

# 在 GDScript 中设置检测区域:以自身位置为中心,宽 64、高 48 $VisibleOnScreenNotifier2D.rect = Rect2(-32, -24, 64, 48) # 或直接使用 set_rect / get_rect 访问器(对应文档中的 set_get 接口) $VisibleOnScreenNotifier2D.set_rect(Rect2(-32, -24, 64, 48)) var r: Rect2 = $VisibleOnScreenNotifier2D.get_rect()

C# 对应写法:

var notifier = GetNode<VisibleOnScreenNotifier2D>("VisibleOnScreenNotifier2D"); notifier.Rect = new Rect2(-32, -24, 64, 48);

属性访问器说明:文档中rect同时提供了set_rect(value)与get_rect()两个方法;show_rect则对应set_show_rect(value)与is_showing_rect()。

3.2 show_rect:编辑器中的可视化辅助

show_rect默认为true,作用是在编辑器中以半透明品红色(translucent magenta)填充的方式显示rect所定义的区域,方便你直观地调整检测范围。文档特别强调:改变show_rect不会影响屏幕剔除检测——它与修改节点visible属性有本质区别。

上图展示了官方教程中为 Mob 场景添加可见性通知节点后编辑器内出现的品红色盒子,2D 版本在编辑器中同样以半透明品红矩形呈现检测区域。

四、信号:screen_entered 与 screen_exited

该节点只定义两个信号:

信号触发时机
screen_entered()VisibleOnScreenNotifier2D 进入屏幕时发出
screen_exited()VisibleOnScreenNotifier2D 完全离开屏幕时发出

语义要点:

  • screen_entered只要区域任意部分可见即触发;
  • screen_exited要等区域没有任何部分可见时才触发(即整个矩形完全离开屏幕或视口)。

这两种信号是生命周期类逻辑的天然钩子,最常见的用法是把离屏对象销毁掉。在官方 2D 入门教程 getting_started/first_2d_game/04.creating_the_enemy.rst 中,敌人(Mob)场景将VisibleOnScreenNotifier2D的screen_exited()信号连接到 Mob 根节点,实现「离开屏幕即自我删除」:

func _on_visible_on_screen_notifier_2d_screen_exited(): queue_free()

C# 版本(在编辑器的信号连接窗口中指定 PascalCase 函数名):

private void OnVisibleOnScreenNotifier2DScreenExited() { QueueFree(); }

queue_free()会在当前帧结束时释放该节点——这正是文档教程中描述的「mobs delete themselves when they leave the screen」方案。

五、方法:is_on_screen 与一帧延迟特性

该节点只有一个公开方法:

func is_on_screen() -> bool # const,无副作用

若包围矩形当前在屏幕上则返回true。文档给出了一个极易踩坑的注意事项:

节点被加入场景树后,需要一帧的时间才能完成可见性判定,因此在实例化之后、绘制帧(draw pass)之前调用is_on_screen()会始终返回false。

这意味着:

  • 不要在_ready()中立即调用is_on_screen()并依赖其结果来决定初始化逻辑;
  • 若需要在节点加入后马上获知可见状态,更稳妥的方式是等待screen_entered信号,或延迟一帧(await get_tree().process_frame)后再查询;
  • 3D 版本 classes/class_visibleonscreennotifier3d.rst 文档也有完全相同的「一帧延迟」提醒,说明这是整个节点家族的共同行为。

典型用法示例——轮询检测当前是否可见并驱动逻辑:

func _process(_delta: float) -> void: if $VisibleOnScreenNotifier2D.is_on_screen(): # 区域内可见时执行逻辑(例如播放音效、结算得分) pass

六、实战:敌人离屏自动销毁的完整流程

综合官方 2D 教程 getting_started/first_2d_game/04.creating_the_enemy.rst,标准接入步骤如下:

  1. 在场景树中为需要跟踪的对象(如RigidBody2D根节点Mob)添加子节点VisibleOnScreenNotifier2D;
  2. 在编辑器中调整rect属性(拖动品红矩形边缘),使其完整覆盖对象的可视外形——通常应比对象略大,避免因动画摆动导致过早触发screen_exited;
  3. 在信号面板双击screen_exited(),将其连接到目标节点;
  4. 在生成的回调中调用queue_free()。

该教程还补充了对象池(object pooling)的背景知识:当反复实例化/销毁对象时,可用「预创建对象数组并循环复用」的池化技术规避创建销毁开销;而使用 GDScript 时通常不需要对象池,因为 GDScript 采用引用计数内存管理,不存在 GC 语言(如 C#)的停顿问题。

七、延伸:VisibleOnScreenEnabler2D 自动启用目标节点

如果不想自己处理信号,可直接使用子类VisibleOnScreenEnabler2D。根据 classes/class_visibleonscreenenabler2d.rst,它包含一个矩形区域和一个目标节点:区域可见时,目标节点通过其Node.process_mode属性被自动启用;否则自动禁用。典型场景是「玩家靠近时才激活敌人」。

属性默认值说明
enable_mode(EnableMode枚举)0决定目标节点如何被启用,对应Node.ProcessMode;禁用时统一使用PROCESS_MODE_DISABLED
enable_node_path(NodePath)NodePath("..")目标节点路径(相对于 Enabler 自身)

EnableMode枚举的三个取值:

  • ENABLE_MODE_INHERIT = 0:对应Node.PROCESS_MODE_INHERIT;
  • ENABLE_MODE_ALWAYS = 1:对应Node.PROCESS_MODE_ALWAYS;
  • ENABLE_MODE_WHEN_PAUSED = 2:对应Node.PROCESS_MODE_WHEN_PAUSED。

enable_node_path的目标节点会被缓存:仅在设置该属性时(若 Enabler 已在场景树中)以及每次 Enabler 进入场景树时赋值。路径为空则不影响任何节点;路径无效会报错。从源码结构看,这种缓存设计可以避免每帧解析 NodePath 的开销,但也意味着运行时动态更换目标需要重新设置属性才能生效。

八、与 3D 版本及性能优化的关系

  • 2D/3D 分工:2D 场景使用VisibleOnScreenNotifier2D,3D 场景使用VisibleOnScreenNotifier3D(基于AABB(-1, -1, -1, 2, 2, 2)包围盒与is_on_screen()),两者 API 高度对称,方便跨维度迁移经验。
  • 性能优化:官方性能指南 tutorials/performance/optimizing_3d_performance.rst 提到,动画/顶点动画(蒙皮、变形)成本高昂,可通过VisibleOnScreenEnabler3D与VisibleOnScreenNotifier3D来「降低远处或被遮挡网格的动画频率,或直接暂停玩家不太可能注意到的动画」。这一思路同样适用于 2D:用VisibleOnScreenNotifier2D暂停屏幕外对象的处理逻辑,从而减少每帧的 CPU 开销。
  • 大世界考量:同一性能文档指出,大型世界与小型游戏有不同的优化考量,屏幕可见性通知正是「只处理玩家能看到的内容」这类优化手段的基础设施。

九、Godot 4 迁移中的命名变化

如果你从 Godot 3 迁移而来,需要注意 tutorials/migrating/upgrading_to_godot_4.rst 中的重命名对照表:

Godot 3 旧名Godot 4 新名
VisibilityNotifier2DVisibleOnScreenNotifier2D
VisibilityNotifier3DVisibleOnScreenNotifier3D
VisibilityEnablerVisibleOnScreenEnabler3D

迁移时若遇到VisibilityNotifier2D相关的旧脚本,直接替换为VisibleOnScreenNotifier2D即可;同样,3D 中旧的ProximityGroup也可用VisibleOnScreenNotifier3D作为替代方案。

小结:何时使用、何时慎用

推荐使用的场景:

  • 离屏对象回收(连接screen_exited→queue_free);
  • 按可见性切换逻辑(配合is_on_screen()或screen_entered/screen_exited);
  • 需要「可见即启用」的自动化行为(直接选用VisibleOnScreenEnabler2D)。

需要谨慎的地方:

  • 判定完全依赖渲染剔除,CanvasItem.visible为false时功能失效(见 classes/class_visibleonscreennotifier2d.rst 的 Note);
  • 节点加入场景树后首帧内is_on_screen()恒为false,不能依赖即时结果;
  • 它判定的是「屏幕可见性」而非「物理碰撞」或「遮挡关系」,不要用它替代Camera2D视口判断之外的自定义可见性逻辑。
  • 文档
  • 教程
  • 游戏开发

【免费下载链接】godot-docs

Godot Engine official documentation

项目地址:https://gitcode.com/GitHub_Trending/go/godot-docs
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进