同一节点中的碰撞检测和重叠检测? [第2部分]

Collision detection and overlapping detection in same node? [part 2]

的延续

我们如何从 body_set_force_integration_callback 中检测碰撞?

对于上下文,我们有 body RID:

var _body:RID

然后我们使用 body_set_force_integration_callback 设置回调:

Physics2DServer.body_set_force_integration_callback(_body, self, "_body_moved", 0)

func _body_moved(state:Physics2DDirectBodyState, _user_data) -> void:
    pass

在继续之前,我想指出 body_set_force_integration_callback 的最后一个参数是我们在 _user_data 中得到的。但是,如果我将它设置为 null Godot 不会将两个参数传递给调用,在这种情况下我应该只使用 state 参数定义 _body_moved

如果 body 的状态处于活动状态(即未休眠),Godot 将调用我们的 _body_moved 每个物理帧。


注意:我们需要调用body_set_max_contacts_reported设置我们要上报的联系人数量,例如:

Physics2DServer.body_set_max_contacts_reported(_body, 32)

现在,在Physics2DDirectBodyState我们获取联系人,我们可以询问每个联系人的一些事情,包括body:

func _body_moved(state:Physics2DDirectBodyState, _user_data) -> void:
    for index in state.get_contact_count():
        var body:RID = state.get_contact_collider(index)
        var instance:Object = state.get_contact_collider_object(index) 

如果是PhysicsBody2D的body,那么instance就有了。

如果我们想要实现 body_enteredbody_exited,我们需要跟踪主体。我会保留实例的字典(即 PhysicsBody2D),我也会用它来报告 get_colliding_bodies

然后我们需要跟踪 body_shape_enteredbody_shape_exited 的形状,而不仅仅是物体。我们可以这样找到它们:

func _body_moved(state:Physics2DDirectBodyState, _user_data) -> void:
    for index in state.get_contact_count():
        var body:RID = state.get_contact_collider(index)
        var instance:Object = state.get_contact_collider_object(index) 
        var body_shape_index:int = state.get_contact_collider_shape(index)
        var local_shape_index:int = state.get_contact_local_shape(index)

注意他们不是 RID。它们是形状在 body 中的位置(因此 0 是 body 的第一个形状,1 是第二个,依此类推)。这意味着我们不能与主体分开跟踪形状,因为如果不知道它们属于什么 body ,它们的形状索引就没有意义。这意味着我们不能像以前那样简单地使用两个物体数组。

此外,如果我们只有一种形状——这是先前答案的情况——我们可以忽略 local_shape_index,因为它始终是 0。在这种情况下,我只需要 body_shape_index:intbody:RID 索引的 Dictionary

如果我不接受那个假设,我很难决定数据结构。

  • 我可以使用由 body:RID 索引的 Dictionary 索引的 Dictionary 索引由 local_shape_index:intbody_shape_index:int 索引,在这种情况下,我想要辅助方法来处理有了它,这促使我为它制作 class。
  • 我可以使用由 body_shape_index:intlocal_shape_index:int 的元组 body:RID 索引的 Dictionary。除了没有元组类型,所以我会作弊并使用 Vector2.

你知道吗?我会作弊并使用 Vector2.

signal body_entered(body)
signal body_exited(body)
signal body_shape_entered(body_rid, body, body_shape_index, local_shape_index)
signal body_shape_exited(body_rid, body, body_shape_index, local_shape_index)

var colliding_instances:Dictionary = {}
var colliding_shapes:Dictionary = {}

func _body_moved(state:Physics2DDirectBodyState, _user_data) -> void:
    var old_colliding_shapes:Dictionary = colliding_shapes
    var new_colliding_shapes:Dictionary = {}
    colliding_shapes = {}
    var instances:Dictionary = {}
    for index in state.get_contact_count():
        # get contact information
        var body:RID = state.get_contact_collider(index)
        var instance:Object = state.get_contact_collider_object(index) 
        var body_shape_index:int = state.get_contact_collider_shape(index)
        var local_shape_index:int = state.get_contact_local_shape(index)
        var vector := Vector2(body_shape_index, local_shape_index)

        # add to instances
        instances[body] = instance

        # add to colliding_shapes
        if not colliding_shapes.had(body):
            colliding_shapes[body] = [vector]
        else:
            colliding_shapes[body].append(vector)

        # remove from old_colliding_shapes or add to new_colliding_shapes
        # there is room for optimization here
        if (
            old_colliding_shapes.has(body)
            and old_colliding_shapes[body].has(vector)
        ):
            old_colliding_shapes[body].erase(vector)
            if old_colliding_shapes[body].size() == 0:
                old_colliding_shapes.erase(body)
        else:
            if not new_colliding_shapes.had(body):
                new_colliding_shapes[body] = [vector]
            else:
                new_colliding_shapes[body].append(vector)    

    for body in old_colliding_shapes.keys():
        # get instance from old dictionary
        var instance:Object = colliding_instances[body]
        # emit
        if not instances.has(body):
            emit_signal("body_exited", body)

        for vector in old_colliding_shapes[body]:
            emit_signal(
                "body_shape_exited",
                body,
                instance,
                vector.x,
                vector.y
            )

    for body in new_colliding_shapes.keys():
        # get instance from new dictionary
        var instance:Object = instances[body]
        # emit
        for vector in old_colliding_shapes[body]:
            emit_signal(
                "body_shape_entered",
                body,
                colliders[body],
                vector.x,
                vector.y
            )

        if not colliding_instances.has(body):
            emit_signal("body_entered", body)

    # swap instance dictionaries
    colliding_instances = instances

func get_colliding_bodies() -> Array:
    return colliding_instances.values()

变量old_colliding_shapes以已知碰撞的形状开始,在迭代中我们将删除我们看到的每个形状。所以最后,它具有碰撞但不再碰撞的形状。

变量 new_colliding_bodies 开始为空,在迭代中我们添加了每个我们没有从 old_colliding_shapes 中删除的形状,所以最后它有我们没有删除的碰撞形状之前就知道了。

注意 old_colliding_shapesnew_colliding_bodies 是互斥的。如果 body 在一个中,则它不在另一个中,因为我们仅在 old_colliding_shapes 中不存在时才将 body 添加到 new_colliding_bodies。但是由于它们有形状而不是主体,所以 body 可以出现在两者中。这就是为什么我需要额外检查以发出 "body_exited""body_entered".