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

C++多重分发运行时多态场景下有没有更优雅的碰撞检测实现方案?

核心问题

给定一个抽象基类和多个需要互相交互的派生类,应当如何实现二者的交互逻辑?本问题以2D游戏的碰撞盒(Hitbox)检测场景为示例。

现有实现代码

头文件代码

#pragma once

class Hitbox {
public:
    virtual bool isColliding(Hitbox* otherHtb);
};

class CircleHitbox : public Hitbox {
public:
    friend class SquareHitbox;

    bool isColliding(Hitbox* otherHtb) override;
    bool isColliding(CircleHitbox* otherHtb);
    bool isColliding(SquareHitbox* otherHtb);
};

class SquareHitbox : public Hitbox {
public:
    friend class CircleHitbox;

    bool isColliding(Hitbox* otherHtb) override;
    bool isColliding(CircleHitbox* otherHtb);
    bool isColliding(SquareHitbox* otherHtb);
};

主程序代码

#include <stdio.h>
#include <vector>

#include "Header.h"


bool Hitbox::isColliding(Hitbox* otherHtb) {
    printf("Hitbox to hitbox.\n");
    return this->isColliding(otherHtb);
}

bool CircleHitbox::isColliding(Hitbox* otherHtb) {
    printf("Circle to hitbox.\n");

    // Try to cast to a circle.
    CircleHitbox* circle = dynamic_cast<CircleHitbox*>(otherHtb);
    if (circle) {
        return this->isColliding(circle);
    }

    // Try to cast to a square.
    SquareHitbox* square = dynamic_cast<SquareHitbox*>(otherHtb);
    if (square) {
        return this->isColliding(square);
    }

    // Default behaviour.
    return 0;
}

bool CircleHitbox::isColliding(CircleHitbox* otherHtb) {
    printf("Circle to circle.\n");

    // Suppose this function computes whether the 2 circles collide or not.
    return 1;
}

bool CircleHitbox::isColliding(SquareHitbox* otherHtb) {
    printf("Circle to square.\n");

    // Suppose this function computes whether the circle and the square collide or not.
    return 1;
}

// This class is basically the same as the CircleHitbox class!
bool SquareHitbox::isColliding(Hitbox* otherHtb) {
    printf("Square to hitbox.\n");

    // Try to cast to a circle.
    CircleHitbox* circle = dynamic_cast<CircleHitbox*>(otherHtb);
    if (circle) {
        return this->isColliding(circle);
    }

    // Try to cast to a square.
    SquareHitbox* square = dynamic_cast<SquareHitbox*>(otherHtb);
    if (square) {
        return this->isColliding(square);
    }

    // Default behaviour.
    return 0;
}

bool SquareHitbox::isColliding(CircleHitbox* otherHtb) {
    printf("Square to circle.\n");

    // Suppose this function computes whether the square and the circle collide or not.
    return 1;
}

bool SquareHitbox::isColliding(SquareHitbox* otherHtb) {
    printf("Square to square.\n");

    // Suppose this function computes whether the 2 squares collide or not.
    return 1;
}


int main() {
    CircleHitbox a, b;
    SquareHitbox c;
    std::vector<Hitbox*> hitboxes;

    hitboxes.push_back(&a);
    hitboxes.push_back(&b);
    hitboxes.push_back(&c);
    
    // This runtime polymorphism is the subject here.
    for (Hitbox* hitbox1 : hitboxes) {
        printf("Checking all collisions for a new item:\n");
        for (Hitbox* hitbox2 : hitboxes) {
            hitbox1->isColliding(hitbox2);
            printf("\n");
        }
    }

    return 0;
}
现有实现的问题与约束

核心问题

现有实现中每个派生类都需要在重写的虚函数中手动执行dynamic_cast类型判断,大量重复逻辑提升了维护成本。同时原Hitbox基类的isColliding实现会触发无限递归,存在运行错误。

替代方案的不足

目前常用的访问者设计模式不适用本场景:

  • 实现逻辑过重,和简单的碰撞检测需求不匹配
  • 后续新增碰撞盒类型时需要修改所有原有派生类的代码,维护成本高

必须保留的特性

  • 不强制派生类实现与任意其他派生类的交互逻辑,可按需实现
  • 支持将所有派生类对象存入基类指针数组,不会出现对象切片问题,保留运行时多态能力
优化实现方案

我们可以通过CRTP(奇异递归模板模式)把重复的类型判断逻辑抽离到中间模板层,派生类只需要实现对应类型的碰撞逻辑即可,无需重复编写类型判断分支。

修改后的头文件

#pragma once

// 前置声明所有碰撞盒类型
class CircleHitbox;
class SquareHitbox;

class Hitbox {
public:
    virtual ~Hitbox() = default;
    virtual bool isColliding(Hitbox* otherHtb) = 0;
};

// CRTP中间层,封装公共类型判断逻辑
template <typename Derived>
class HitboxImpl : public Hitbox {
public:
    bool isColliding(Hitbox* other) override {
        Derived* derived_this = static_cast<Derived*>(this);
        // 按优先级尝试类型转换,匹配对应碰撞逻辑
        if (CircleHitbox* circle = dynamic_cast<CircleHitbox*>(other)) {
            return derived_this->isColliding(circle);
        }
        if (SquareHitbox* square = dynamic_cast<SquareHitbox*>(other)) {
            return derived_this->isColliding(square);
        }
        // 未匹配到对应实现,默认返回不碰撞
        return false;
    }

    // 缺省碰撞实现,派生类未实现对应逻辑时自动调用
    bool isColliding(CircleHitbox* other) { return false; }
    bool isColliding(SquareHitbox* other) { return false; }
};

class CircleHitbox : public HitboxImpl<CircleHitbox> {
public:
    friend class SquareHitbox;
    // 只需要实现具体类型的碰撞逻辑,不需要重写接受Hitbox*的虚函数
    bool isColliding(CircleHitbox* otherHtb);
    bool isColliding(SquareHitbox* otherHtb);
};

class SquareHitbox : public HitboxImpl<SquareHitbox> {
public:
    friend class CircleHitbox;
    bool isColliding(CircleHitbox* otherHtb);
    bool isColliding(SquareHitbox* otherHtb);
};

方案优势

  1. 所有重复的类型判断逻辑都收拢到CRTP中间层,新增碰撞盒类型时只需要在中间层加一个dynamic_cast分支即可,不需要修改所有原有派生类的代码
  2. 派生类可以按需实现对应类型的碰撞逻辑,未实现的会自动调用缺省实现,符合不强制实现所有交互的要求
  3. 仍然保留基类指针的多态能力,支持存入基类指针数组,没有对象切片问题
  4. 实现逻辑比访问者模式简单得多,代码量小,易维护

主程序修改说明

原主程序代码不需要做任何修改,原有逻辑可以正常运行,同时修复了原Hitbox实现中的无限递归问题。

内容的提问来源于stack exchange,提问作者Andrei-Info

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 10:24:05