Шаблон проектирования для проверки столкновения между фигурами - PullRequest
12 голосов
/ 28 мая 2011

Я использую различные формы для обнаружения столкновений (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

Ответы [ 4 ]

12 голосов
/ 28 мая 2011

Вы можете использовать Шаблон посетителя , , вот пример C #

Это позволит вам просто иметь методы 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
5 голосов
/ 28 мая 2011

Вы можете обратиться к своему классу Shape, чтобы выполнить проверку столкновения, добавив метод IntersectsWith(Shape other) в Shape. Я бы также предложил добавить IntersectsWith(GameObject other) к вашему GameObject, что позволит вам сохранить конфиденциальность вашего CollisionShape.

1 голос
/ 28 мая 2011

Нет простого решения для вашей проблемы. То, что вам нужно, называется «двойной диспетчеризацией», которая поддерживается только в таких языках, как 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 не нуждается в «если». Вы можете использовать отражение, чтобы найти лучший метод сопоставления. Детали зависят от ваших точных требований и того, как вы сравниваете сложность с производительностью. Но легко начать с простой реализации. Поскольку все инкапсулировано в одном месте, его легко оптимизировать позже. Что является хорошим подходом на мой взгляд. ; -)

1 голос
/ 28 мая 2011

Если чеки все равно должны где-то находиться.

Вы можете добавить 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();
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...