You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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:
    1. 先判断body是否有效,避免已销毁节点报错
    2. 用get_overlapping_bodies()获取当前与Area重叠的物体,若body不在列表中则从状态字典移除
    3. 若body在列表中,采样其关键点位(比如四个顶点),检查是否有任意点位不在Area形状内,以此判断是否开始移出
    4. 当状态从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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 17:43:17