形状碰撞C++的设计模式

Design pattern for Shape Collision C++

本文关键字:设计模式 C++ 碰撞      更新时间:2023-10-16

所以我目前正在研究从Shape继承的对象(圆形、矩形等)的最佳设计。我目前正在使用动态调度,但当添加新形状时,这需要大量修改。理想情况下,我想要一些类似的东西,这样当我添加一个新形状时,我就不必花很长时间更新多个文件。

public class Shape() {
    public:
    virtual bool detectCollision(Shape *shape);
};

然后让其他类能够以不同的方式导出,IE

public class Circle : public Shape {
    public:
    bool detectCollision(Square *square);
    bool detectCollision(Circle *circle);
};

如果可能的话,我希望避免使用长的if/else语句,并且不认为这一切的逻辑都应该在Shape内部使用某种形式的dynamic_casting来完成,因为理想情况下Shape不知道继承它的是什么。

是否有适合这些需求的解决方案?我听说过Templates,但我不确定我将如何在这里实现它,也许有人可以举一个例子吗?

你好,托马斯,谢谢你的问题,

一般来说,使用dynamic_cast更糟糕。大多数特定于类型的方法调用应该在编译时解决,而不是在运行时解决。因此,您必须使用RTTI,浪费性能,并且您的代码不像编译器检查的类型安全那样容易出错。

class Shape
{
public:
  bool colidesWithOtherShape( Shape other ) { return CollisionManager::areShapesCollding( *this, other );  }
}
class CollisionManager
{
public:
  template< typename _T, typename _N >
  static bool areShapesColliding( _T, _N );
};
template <>
bool CollisionManager::areShapesColliding( Circle c, Circle c2 )
{
  return Point::distance( c.getMidPoint(), c.getMidPoint() ) < c.getRadius() + c2.getRadius(); 
}

这样,您的代码是类型安全的,如果您愿意,所有的冲突检测都可以在一个或多个文件中实现,但请注意您的命名空间,这可能会导致方法查找中出现一些奇怪的行为。

如果有多个形状,则将具有特定形状作为第二个参数的所有方法映射到具有该形状作为第一个参数的同一方法。

template< typename _T >
bool CollisionManager::areShapesColliding( _T shape, Circle c )
{
  return CollisionManager::areShapesColliding( c, shape );
}

现在,只需从Circle为所有其他形状实现所有碰撞检测。Ie.对于轴对齐的矩形:

template<>
bool CollisionManager::areShapesColliding( Circle circle, Rectangle rectangle )
{
  Point closestPointToCircle = circle.midPoint();
  if( closestPointToCircle.x() > rectangle.right() ) closestPointToCircle.setX( rectangle.right() );
  else if( closestPointToCircle.x() < rectangle.left() ) closestPointToCircle.setX( rectangle.left() );
  if( closestPointToCircle.y() > rectangle.bottom() ) closestPointToCircle.setY( rectangle.bottom() );
  else if( closestPointToCircle.y() < rectangle.top() ) closestPointToCircle.setY( rectangle.top() );
  return point_distance( closestPointToCircle, circle.midPoint() ) <= circle.radiant();
}
template<>
bool CollisionManager::areShapesColliding( Rectangle rect1, Rectangle rect2 )
{
  Point distancePoint = rect1.getMidPoint() - rect2.getMidPoint();
  unsigned long xAxisDistance = abs( distancePoint.x() );
  unsigned long yAxisDistance = abs( distancePoint.y() );
  return xAxisDistance <= ( rect1.width() + rect2.width() ) / 2 &&
         yAxisDistance <= ( rect1.heigth() + rect2.height() ) / 2;
}

如果使用C++11(-std=C++11 Linkeflag),则可以在编译时检测未实现的冲突检测。

template< typename _T, typename _N >
bool CollisionManager::areShapesColliding( )
{
  static_assert( false, "your assert" );
}

对于运行时解决方案,您需要在运行时求解类型。因为我没有时间了,这里只是一个例子:

template<>
bool CollisionManager::areShapesColliding( Shape* shape1, Shape* shape2 )
{
  if( ( Circle* c1 = dynamic_cast<Circle*>( shape1 ) ) != nullptr )
  {
    if( ( Circle* c2 = dynamic_cast<Circle*> ) != nullptr )
    {
      return CollisionManager::areShapesColliding( *c1, *c2 );
    }
    else if( ( Square* s2 = dynamic_cast< Square* >( shape2 ) ) != nullptr )
    {
      return CollisionManager::areShapesColliding( *c1, *s2 );
    }
  }
  //else... if first is square...
  return false; //no matching type found
}

这不是一个很好的解决方案,但它封装了你的问题,不会把你的代码弄乱。

我希望我能帮助你。还有什么问题,请告诉我。

Michael

您需要首先决定是否需要在编译时知道对象的类型。也就是说,这种方法:

public class Shape{
    public:
    virtual bool detectCollision(Square *square);
    virtual bool detectCollision(Circle *circle);
};
Shape* shape1,shape2;
shape1 = GetNewCircleOrSquareByRandom();
shape2 = GetNewCircleOrSquareByRandom();
shape1->detectCollision(shape2);

将不起作用,因为编译器不知道(也不能)实际存在哪个对象。

正如@linuxfever所指出的,这是一个已知的双重调度问题。维基百科文章中的链接提供了一个解决方案(与您的非常相似)。

然而,如果你知道什么与编译时的内容冲突,你可以使用模板,正如你正确猜测的那样:

// In header
template<class _T1, class _T2> bool detectCollision(_T1* object1, _T2* object2) {
    // Generic implementation
    // Or just an #error "Sorry! Don't know how to collide those two objects"
}
template<> bool detectCollision(Square* object1, Circle* object2) {
    // Specific implementaiton of Square vs. Circle
}
template<> bool detectCollision(Circle* object1, Square* object2) {
    return detectCollision(object2,object1);
}
// etc

我认为您无法绕过专门的算法来检测/改进碰撞检测。对于一些形状A和B,你需要一个知道这两种类型的函数来应用正确的算法。这导致:

class Shape() {
    public:
    // Not virtual!
    // Avoiding pointers.
    bool detectCollision(const Shape&) const;
};
bool Shape::detectCollision(const Shape& other)  const {
   if(is_rectangle(*this) && is_rectangle(other)) 
       return rectangle_rectangle_collision(*this, other); 
       // Where rectangle_rectangle_collision is a local function
       // in a source file 
       // and so on ...
}

一般来说,你可以有一个碰撞函数的映射,在Shape中注册新算法,并在detectCollision中选择一个专门或通用的算法(基于多边形)。

您可能会想使用模板进行冲突检测,但您的模板在动态多态性中是无用的(除非您调度)。