6.7 KiB
6.7 KiB
06. 碰撞检测
Extra2D 提供了基于空间索引的高效碰撞检测系统,支持四叉树和空间哈希两种策略。
完整示例
参考示例代码:
examples/collision_demo/main.cpp- 基础碰撞检测演示examples/spatial_index_demo/main.cpp- 空间索引性能演示
启用碰撞检测
1. 创建可碰撞节点
class CollidableBox : public Node {
public:
CollidableBox(float width, float height, const Color& color)
: width_(width), height_(height), color_(color), isColliding_(false) {
// 启用空间索引 - 这是关键!
setSpatialIndexed(true);
}
// 必须实现 getBoundingBox 方法
Rect getBoundingBox() const override {
Vec2 pos = getPosition();
return Rect(pos.x - width_ / 2, pos.y - height_ / 2, width_, height_);
}
void setColliding(bool colliding) { isColliding_ = colliding; }
void onRender(RenderBackend& renderer) override {
Vec2 pos = getPosition();
// 碰撞时变红色
Color fillColor = isColliding_ ? Color(1.0f, 0.2f, 0.2f, 0.8f) : color_;
renderer.fillRect(
Rect(pos.x - width_ / 2, pos.y - height_ / 2, width_, height_),
fillColor
);
// 绘制边框
Color borderColor = isColliding_ ? Color(1.0f, 0.0f, 0.0f, 1.0f)
: Color(1.0f, 1.0f, 1.0f, 0.5f);
renderer.drawRect(
Rect(pos.x - width_ / 2, pos.y - height_ / 2, width_, height_),
borderColor, 2.0f
);
}
private:
float width_, height_;
Color color_;
bool isColliding_;
};
2. 执行碰撞检测
class GameScene : public Scene {
public:
void onUpdate(float dt) override {
Scene::onUpdate(dt);
// 清除之前的碰撞状态
for (const auto& child : getChildren()) {
if (auto box = dynamic_cast<CollidableBox*>(child.get())) {
box->setColliding(false);
}
}
// 使用场景的空间索引查询所有碰撞
auto collisions = queryCollisions();
// 处理碰撞
for (const auto& [nodeA, nodeB] : collisions) {
if (auto boxA = dynamic_cast<CollidableBox*>(nodeA)) {
boxA->setColliding(true);
}
if (auto boxB = dynamic_cast<CollidableBox*>(nodeB)) {
boxB->setColliding(true);
}
}
}
void createBox(float x, float y) {
auto box = makePtr<CollidableBox>(50.0f, 50.0f, Color(0.3f, 0.7f, 1.0f, 0.8f));
box->setPosition(Vec2(x, y));
addChild(box); // 通过 addChild 管理节点生命周期
}
};
注意:节点通过 addChild() 添加到场景后,由场景统一管理生命周期。不要额外存储 shared_ptr 到 vector 中,避免双重引用问题。
空间索引策略
切换策略
// 获取空间管理器
auto& spatialManager = getSpatialManager();
// 切换到四叉树
spatialManager.setStrategy(SpatialStrategy::QuadTree);
// 切换到空间哈希
spatialManager.setStrategy(SpatialStrategy::SpatialHash);
// 获取当前策略名称
const char* name = spatialManager.getStrategyName();
策略对比
| 策略 | 适用场景 | 特点 |
|---|---|---|
| QuadTree | 节点分布不均匀 | 分层划分,适合稀疏分布 |
| SpatialHash | 节点分布均匀 | 均匀网格,适合密集分布 |
性能演示
examples/spatial_index_demo/main.cpp 展示了空间索引的性能优势:
class SpatialIndexDemoScene : public Scene {
public:
void onEnter() override {
Scene::onEnter();
// 创建100个碰撞节点
createPhysicsNodes(100);
E2D_INFO("空间索引已启用: {}", isSpatialIndexingEnabled());
}
void onUpdate(float dt) override {
Scene::onUpdate(dt);
// 更新所有物理节点位置(通过 getChildren() 访问)
for (const auto& child : getChildren()) {
if (auto node = dynamic_cast<PhysicsNode*>(child.get())) {
node->update(dt, screenWidth_, screenHeight_);
}
}
// 使用空间索引进行碰撞检测
performCollisionDetection();
// 按 X 键切换索引策略
auto& input = Application::instance().input();
if (input.isButtonPressed(GamepadButton::X)) {
toggleSpatialStrategy();
}
}
private:
void performCollisionDetection() {
// 清除之前的碰撞状态
for (const auto& child : getChildren()) {
if (auto node = dynamic_cast<PhysicsNode*>(child.get())) {
node->setColliding(false);
}
}
// 使用引擎自带的空间索引进行碰撞检测
auto collisions = queryCollisions();
// 标记碰撞的节点
for (const auto& [nodeA, nodeB] : collisions) {
if (auto boxA = dynamic_cast<PhysicsNode*>(nodeA)) {
boxA->setColliding(true);
}
if (auto boxB = dynamic_cast<PhysicsNode*>(nodeB)) {
boxB->setColliding(true);
}
}
}
void toggleSpatialStrategy() {
auto& spatialManager = getSpatialManager();
SpatialStrategy currentStrategy = spatialManager.getCurrentStrategy();
if (currentStrategy == SpatialStrategy::QuadTree) {
spatialManager.setStrategy(SpatialStrategy::SpatialHash);
E2D_INFO("切换到空间哈希策略");
} else {
spatialManager.setStrategy(SpatialStrategy::QuadTree);
E2D_INFO("切换到四叉树策略");
}
}
// 获取物理节点数量
size_t getPhysicsNodeCount() const {
size_t count = 0;
for (const auto& child : getChildren()) {
if (dynamic_cast<PhysicsNode*>(child.get())) {
++count;
}
}
return count;
}
};
关键改进:
- 使用
getChildren()代替私有 vector 存储节点引用 - 通过
dynamic_cast筛选特定类型的子节点 - 避免双重引用,简化生命周期管理
关键要点
- 必须调用
setSpatialIndexed(true)- 启用节点的空间索引 - 必须实现
getBoundingBox()- 返回准确的边界框 - 在
onEnter()中调用Scene::onEnter()- 确保节点正确注册到空间索引 - 使用
queryCollisions()- 自动利用空间索引优化检测