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); };
方案优势
- 所有重复的类型判断逻辑都收拢到CRTP中间层,新增碰撞盒类型时只需要在中间层加一个
dynamic_cast分支即可,不需要修改所有原有派生类的代码 - 派生类可以按需实现对应类型的碰撞逻辑,未实现的会自动调用缺省实现,符合不强制实现所有交互的要求
- 仍然保留基类指针的多态能力,支持存入基类指针数组,没有对象切片问题
- 实现逻辑比访问者模式简单得多,代码量小,易维护
主程序修改说明
原主程序代码不需要做任何修改,原有逻辑可以正常运行,同时修复了原Hitbox实现中的无限递归问题。
内容的提问来源于stack exchange,提问作者Andrei-Info
相关产品推荐
相关产品推荐

