Дизайн шаблона для проверки столкновения между фигурами
Я использую различные формы для обнаружения столкновений (Rectangle
, Circle
, Cone
, Ring
и т.д.). Все эти формы получены из базового абстрактного класса Shape
. Мои игровые объекты имеют свойство типа Shape.
class GameObject
{
(...)
public Shape CollisionShape { get; set; }
}
и во время процесса инициализации я решаю, какая форма будет использоваться для каждого объекта, например:
GameObject person = new GameObject();
person.CollisionShape = new Circle(100); // 100 is radius
Теперь, когда я хочу проверить, пересекаются ли два объекта, я использую следующий класс:
public class IntersectionChecker
{
public bool Intersect(Shape a, Shape b)
{
Type aType = a.GetType();
Type bType = b.GetType();
if( aType == typeof(Rectangle) && bType == typeof(Rectangle))
return Intersect(a as Rectangle, b as Rectangle);
if( aType == typeof(Rectangle) && bType == typeof(Circle))
return Intersect(a as Rectangle, b as Circle);
// etc. etc. All combinations
}
private bool Intersect(Rectangle a, Rectangle b)
{
// check intersection between rectangles
}
}
поэтому мой код выглядит следующим образом:
IntersectionChecker ic = new IntersectionCHecker();
bool isIntersection =
is.Intersect(personA.CollisionShape, personB.CollisionShape);
Есть ли лучший способ достичь моей цели, без десятков проверок и проверок типов в классе IntersectionChecker?
EDIT:
Пожалуйста, учтите, что метод проверки пересечения между формами A и B может использоваться для проверки пересечения между B и A. Во многих ответах (спасибо за ваши мысли!) Проверка пересечения предлагается вызывать из самой фигуры, а не из объекта IntersectionChecker. Я думаю, это заставит меня дублировать код. Теперь я могу сделать следующее:
if( aType == typeof(Rectangle) && bType == typeof(Circle))
return Intersect(a as Rectangle, b as Rectangle);
if( aType == typeof(Circle) && bType == typeof(Rectangle))
return Intersect(b as Rectangle, a as Circle); // same method as above
Ответы
Ответ 1
Вы можете использовать шаблон посетителя, вот пример С#
Это позволит вам просто использовать методы Shape.Intersect(Rectangle), Shape.Intersect(Circle),..., которые реализует каждая производная форма. Это помешает вам делать какие-либо размышления о типах за счет дополнительного вызова метода.
РЕДАКТИРОВАТЬ. Вот примерная реализация, вероятно, было бы более чистым использовать интерфейс IShape, если нет общей функциональности, которая будет идти в Shape, но я просто застрял в абстрактном базовом классе.
public class GameObject
{
private Shape _collisionShape;
public GameObject(Shape collisionShape)
{
_collisionShape = collisionShape;
}
public bool Intersects(GameObject other)
{
return _collisionShape.IntersectVisit(other._collisionShape);
}
}
public abstract class Shape
{
public abstract bool IntersectVisit(Shape other);
public abstract bool Intersect(Circle circle);
public abstract bool Intersect(Rectangle circle);
}
public class Circle : Shape
{
public override bool IntersectVisit(Shape other)
{
return other.Intersect(this);
}
public override bool Intersect(Circle circle)
{
Console.WriteLine("Circle intersecting Circle");
return false; //implement circle to circle collision detection
}
public override bool Intersect(Rectangle rect)
{
Console.WriteLine("Circle intersecting Rectangle");
return false; //implement circle to rectangle collision detection
}
}
public class Rectangle : Shape
{
public override bool IntersectVisit(Shape other)
{
return other.Intersect(this);
}
public override bool Intersect(Circle circle)
{
Console.WriteLine("Rectangle intersecting Circle");
return true; //implement rectangle to circle collision detection
}
public override bool Intersect(Rectangle rect)
{
Console.WriteLine("Rectangle intersecting Rectangle");
return true; //implement rectangle to rectangle collision detection
}
}
И пример, вызывающий его:
GameObject objectCircle = new GameObject(new Circle());
GameObject objectRect = new GameObject(new Rectangle());
objectCircle.Intersects(objectCircle);
objectCircle.Intersects(objectRect);
objectRect.Intersects(objectCircle);
objectRect.Intersects(objectRect);
Производит вывод:
Circle intersecting Circle
Rectangle intersecting Circle
Circle intersecting Rectangle
Rectangle intersecting Rectangle
Ответ 2
Вы можете отнестись к классу Shape, чтобы выполнить проверку столкновений, добавив метод IntersectsWith(Shape other)
в Shape. Я также предложил бы добавить IntersectsWith(GameObject other)
к вашему GameObject
, который позволит вам сохранить ваш CollisionShape закрытый.
Ответ 3
Если чеки должны были где-то поселиться.
Вы можете добавить метод Intersects
в Shape
:
abstract class Shape
{
public abstract Boolean Intersects(Shape other);
}
Затем создайте методы Intersect
в IntersectionChecker
public static
и примените метод Intersects
для каждой конкретной формы следующим образом:
class Rectangle : Shape
{
public override Boolean Intersects(Shape other)
{
if (other is Rectangle)
{
return IntersectionChecker.Intersect(this, (Rectangle)other);
}
else if (other is Circle)
{
return IntersectionChecker.Intersect(this, (Circle)other);
}
throw new NotSupportedException();
}
}
Ответ 4
В вашей проблеме нет простого решения. То, что вам нужно, называется "двойная отправка", которая поддерживается только в таких языках, как Smalltalk или Lisp. Все предлагаемые решения заставят вас изменить все производные классы, если вы добавите один новый класс. Этот плохой код!
Я бы подошел к такой проблеме: Реализуйте ваши производные от Shape классы без кода пересечения. Затем выполните класс пересечения следующим образом:
public class Intersection {
public bool Intersect(Shape a, Shape b) {....}
private bool Intersect(Rectangle a, Circle b) {...}
private bool Intersect(Circle a, Circle b) {...}
}
Публичные методы анализируют входящие формы и рассылки (- > двойную рассылку) для работы с соответствующим приватным методом. Которая содержит логику пересечения. Для реализации Intersect не требуется "ifs". Вы можете использовать отражение, чтобы найти наилучший метод сопоставления. Детали зависят от ваших точных требований и от того, как вы оцениваете сложность работы. Но легко начать с простой реалистичной реализации. Поскольку все инкапсулировано в одном месте, его легко оптимизировать позже. На мой взгляд, это хороший подход.; -)