如何在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
相关产品推荐
相关产品推荐

