Godot 4.1.1中如何检测RigidBody2D开始离开Area2D的时刻?
实现检测RigidBody2D开始移出Area2D的方案
方案一:实时形状检测+自定义信号
Godot原生没有直接触发「部分移出」的信号,你可以自己在物理帧中做状态判断,抛出自定义信号:
- 在Area2D脚本里定义自定义信号:
body_started_exiting(body: Node2D) - 用字典
_body_states记录进入Area的RigidBody2D状态:键为body实例,值true代表完全在内部,false代表已开始移出 - 连接Area2D的
body_entered信号,物体进入时将其状态设为true - 在
_physics_process(delta)中遍历已记录的body:- 先判断body是否有效,避免已销毁节点报错
- 用
get_overlapping_bodies()获取当前与Area重叠的物体,若body不在列表中则从状态字典移除 - 若body在列表中,采样其关键点位(比如四个顶点),检查是否有任意点位不在Area形状内,以此判断是否开始移出
- 当状态从
true变为false时,发射自定义信号并更新状态
示例代码片段:
extends Area2D signal body_started_exiting(body: Node2D) var _body_states: Dictionary = {} func _ready(): connect("body_entered", self, "_on_body_entered") connect("body_exited", self, "_on_body_exited") func _on_body_entered(body: Node2D): if body is RigidBody2D: _body_states[body] = true func _on_body_exited(body: Node2D): _body_states.erase(body) func _physics_process(delta): var current_overlaps = get_overlapping_bodies() # 遍历字典副本,避免遍历中修改原字典 for body in _body_states.keys().duplicate(): if not is_instance_valid(body): _body_states.erase(body) continue if body not in current_overlaps: _body_states.erase(body) continue var is_fully_inside = true # 以带Sprite2D的RigidBody2D为例,采样四个顶点 if body.has_node("Sprite2D"): var sprite = body.get_node("Sprite2D") var rect = sprite.get_rect() var points = [ body.to_global(rect.position), body.to_global(rect.position + Vector2(rect.size.x, 0)), body.to_global(rect.position + rect.size), body.to_global(rect.position + Vector2(0, rect.size.y)) ] for point in points: if not get_shape_owner_shape(0).is_point_inside(to_local(point)): is_fully_inside = false break if _body_states[body] == true and not is_fully_inside: _body_states[body] = false emit_signal("body_started_exiting", body)
方案二:创建边界触发Area
自动生成一圈紧贴原Area边缘的小Area2D,当RigidBody碰到这些边界Area时,即判定为开始移出原Area:
- 获取原Area的形状矩形范围(规则形状通用,非矩形可类似调整)
- 设定边界Area的宽度,沿原Area上下左右四边各创建一个长条Area
- 给每个边界Area添加碰撞形状,位置紧贴原Area边缘
- 连接边界Area的
body_entered信号,触发时判断RigidBody是否仍在原Area内(用get_overlapping_bodies()检查),若是则发射自定义信号 - 注意设置碰撞层/掩码,避免与原Area的检测冲突
示例代码片段(在原Area2D脚本中生成边界Area):
extends Area2D signal body_started_exiting(body: Node2D) var _border_areas: Array = [] func _ready(): create_border_areas() func create_border_areas(): var area_rect = get_shape_owner_shape(0).get_rect() var border_width = 10.0 # 创建上边界Area var top_area = Area2D.new() top_area.name = "TopBorder" top_area.collision_layer = collision_layer top_area.collision_mask = collision_mask var top_shape = RectangleShape2D.new() top_shape.extents = Vector2(area_rect.size.x / 2, border_width / 2) top_area.add_shape(top_shape) top_area.position = Vector2(0, -area_rect.size.y / 2 - border_width / 2) add_child(top_area) top_area.connect("body_entered", self, "_on_border_body_entered") _border_areas.append(top_area) # 创建下边界Area var bottom_area = Area2D.new() bottom_area.name = "BottomBorder" bottom_area.collision_layer = collision_layer bottom_area.collision_mask = collision_mask var bottom_shape = RectangleShape2D.new() bottom_shape.extents = Vector2(area_rect.size.x / 2, border_width / 2) bottom_area.add_shape(bottom_shape) bottom_area.position = Vector2(0, area_rect.size.y / 2 + border_width / 2) add_child(bottom_area) bottom_area.connect("body_entered", self, "_on_border_body_entered") _border_areas.append(bottom_area) # 创建左边界Area var left_area = Area2D.new() left_area.name = "LeftBorder" left_area.collision_layer = collision_layer left_area.collision_mask = collision_mask var left_shape = RectangleShape2D.new() left_shape.extents = Vector2(border_width / 2, area_rect.size.y / 2) left_area.add_shape(left_shape) left_area.position = Vector2(-area_rect.size.x / 2 - border_width / 2, 0) add_child(left_area) left_area.connect("body_entered", self, "_on_border_body_entered") _border_areas.append(left_area) # 创建右边界Area var right_area = Area2D.new() right_area.name = "RightBorder" right_area.collision_layer = collision_layer right_area.collision_mask = collision_mask var right_shape = RectangleShape2D.new() right_shape.extents = Vector2(border_width / 2, area_rect.size.y / 2) right_area.add_shape(right_shape) right_area.position = Vector2(area_rect.size.x / 2 + border_width / 2, 0) add_child(right_area) right_area.connect("body_entered", self, "_on_border_body_entered") _border_areas.append(right_area) func _on_border_body_entered(body: Node2D): if body is RigidBody2D and body in get_overlapping_bodies(): emit_signal("body_started_exiting", body) # 临时禁用边界检测,避免重复触发 for area in _border_areas: area.monitoring = false # 0.5秒后恢复检测,可根据需求调整 await get_tree().create_timer(0.5).timeout for area in _border_areas: area.monitoring = true
两种方案优劣对比:
- 方案一更精确,支持任意形状Area,但会消耗一定物理帧计算资源
- 方案二性能更优,但仅适配规则形状Area,边界宽度需按需调整
内容的提问来源于stack exchange,提问作者vinicius de novaes
相关产品推荐
相关产品推荐

