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

如何在Box2D中将碰撞/触发事件通知到刚体绑定代码

Box2D实现类Unity2D碰撞/触发回调方案

Box2D原生提供的b2ContactListener确实是物理世界全局生效的,你只需要在这个全局监听器上做一层事件转发,就可以把碰撞、触发事件精准路由到对应刚体/碰撞体绑定的业务逻辑,和Unity底层的回调分发逻辑完全一致。

第一步:定义回调接口,绑定到碰撞体

Box2D的b2Fixture、b2Body都自带SetUserData/GetUserData接口,可以存储自定义指针,我们用这个接口绑定碰撞处理对象。首先定义通用回调基类:

class CollisionHandler {
public:
    virtual void OnCollisionEnter2D(b2Fixture* selfFixture, b2Fixture* otherFixture, b2Contact* contact) = 0;
    virtual void OnCollisionExit2D(b2Fixture* selfFixture, b2Fixture* otherFixture, b2Contact* contact) = 0;
    virtual void OnTriggerEnter2D(b2Fixture* selfFixture, b2Fixture* otherFixture, b2Contact* contact) = 0;
    virtual void OnTriggerExit2D(b2Fixture* selfFixture, b2Fixture* otherFixture, b2Contact* contact) = 0;
    virtual ~CollisionHandler() = default;
};

创建Fixture(对应Unity的Collider/Trigger)时,把对应业务逻辑对象的指针存入UserData:

// 优先绑定到Fixture,因为触发器属性是Fixture级的,一个刚体可挂载多个Fixture,粒度和Unity的Collider完全对齐
fixture->SetUserData(yourHandlerInstance);
// b2Body的UserData可独立存储其他业务数据,互不冲突

第二步:实现全局监听器做事件转发

继承b2ContactListener重写核心回调方法,在回调中取出两个碰撞Fixture绑定的处理对象,判断接触类型后双向调用对应回调,和Unity两个物体都能收到事件的行为一致:

class ContactDispatcher : public b2ContactListener {
private:
    bool IsTriggerContact(b2Contact* contact) {
        b2Fixture* fA = contact->GetFixtureA();
        b2Fixture* fB = contact->GetFixtureB();
        return fA->IsSensor() || fB->IsSensor();
    }

public:
    void BeginContact(b2Contact* contact) override {
        b2Fixture* fA = contact->GetFixtureA();
        b2Fixture* fB = contact->GetFixtureB();
        CollisionHandler* handlerA = static_cast<CollisionHandler*>(fA->GetUserData());
        CollisionHandler* handlerB = static_cast<CollisionHandler*>(fB->GetUserData());
        bool isTrigger = IsTriggerContact(contact);

        if (handlerA) {
            isTrigger ? handlerA->OnTriggerEnter2D(fA, fB, contact) 
                      : handlerA->OnCollisionEnter2D(fA, fB, contact);
        }
        if (handlerB) {
            isTrigger ? handlerB->OnTriggerEnter2D(fB, fA, contact) 
                      : handlerB->OnCollisionEnter2D(fB, fA, contact);
        }
    }

    void EndContact(b2Contact* contact) override {
        b2Fixture* fA = contact->GetFixtureA();
        b2Fixture* fB = contact->GetFixtureB();
        CollisionHandler* handlerA = static_cast<CollisionHandler*>(fA->GetUserData());
        CollisionHandler* handlerB = static_cast<CollisionHandler*>(fB->GetUserData());
        bool isTrigger = IsTriggerContact(contact);

        if (handlerA) {
            isTrigger ? handlerA->OnTriggerExit2D(fA, fB, contact) 
                      : handlerA->OnCollisionExit2D(fA, fB, contact);
        }
        if (handlerB) {
            isTrigger ? handlerB->OnTriggerExit2D(fB, fA, contact) 
                      : handlerB->OnCollisionExit2D(fB, fA, contact);
        }
    }

    // 需要实现Stay类回调可重写PreSolve,逻辑同上
    void PreSolve(b2Contact* contact, const b2Manifold* oldManifold) override {
        // 除了Stay事件,还可以在这里做自定义碰撞过滤
    }
};

第三步:注册监听器到物理世界

初始化b2World时,把实现好的转发监听器注册进去即可:

ContactDispatcher dispatcher;
b2World world(b2Vec2(0, -9.8f));
world.SetContactListener(&dispatcher);

注意事项

  • 不要在BeginContact/EndContact/PreSolve/PostSolve的回调执行过程中直接创建、销毁刚体,或者修改Fixture属性,Box2D物理步进阶段执行这类操作会触发崩溃。正确做法是在回调中把事件暂存到队列,等world.Step()执行完成后,再遍历队列派发事件、执行物理修改操作。
  • 存在UserData里的指针要保证生命周期和绑定的Fixture/Body一致,避免野指针:销毁业务处理对象前,要先把对应Fixture/Body的UserData设为nullptr。
  • 如果需要获取碰撞接触点、法线、碰撞冲量等数据,可以重写PostSolve回调拿到b2ContactImpulse参数,随事件一起转发给处理对象,就能对齐UnityCollision2D类型的参数能力。

内容的提问来源于stack exchange,提问作者Christopher Barrios Agosto

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 03:09:14