2011-05-28 10 views
12

utilizo varias formas para la detección de colisión (Rectangle, Circle, Cone, Ring etc.) Todas esas formas se derivan de la clase base abstracta Shape. Mis objetos del juego tienen propiedad de tipo Shape.Diseño modelo para la comprobación de colisión entre las formas

class GameObject 
{ 
    (...) 
    public Shape CollisionShape { get; set; } 
} 

y durante el proceso de inicialización que decida qué forma se utilizará para cada objeto, como:

GameObject person = new GameObject(); 
person.CollisionShape = new Circle(100); // 100 is radius 

Ahora cuando quiero comprobar si dos objetos se cruza utilizo la clase siguiente:

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 
    } 
} 

por lo que mi código es el siguiente:

IntersectionChecker ic = new IntersectionCHecker(); 
bool isIntersection = 
    is.Intersect(personA.CollisionShape, personB.CollisionShape); 

¿Hay alguna forma mejor de lograr mi objetivo, sin docenas de controles 'if' y controles de tipo en la clase IntersectionChecker?

EDIT:

Por favor tome en cuenta, que el método que se compruebe la intersección entre la forma A y B se puede utilizar para comprobar la intersección entre B y A también. En muchas respuestas (¡gracias por todos sus pensamientos!), Se propone que la comprobación de intersección se invoque desde la propia forma en lugar del objeto IntersectionChecker. Creo que me obligará a duplicar el código. Ahora puedo hacer de la siguiente manera:

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 
+0

Consulte mi propuesta para usar Reflection. Es fácil utilizar el mismo método para objetos de tipo Rectángulo/Círculo y Círculo/Rectángulo si usa Reflection para enviar la llamada. – Achim

Respuesta

12

podría utilizar el Visitor Pattern, here is a C# example

Eso le permitiría simplemente tener Shape.Intersect (Rectangle), Shape.Intersect (Circle), ... métodos que implementa cada forma derivada. Evitaría tener que reflexionar sobre los tipos a costa de una llamada a un método adicional.

EDIT - Aquí hay una implementación de ejemplo, probablemente sería más limpio usar una interfaz IShape si no hay una funcionalidad compartida que entraría en Shape, pero simplemente me quedé en una clase base abstracta.

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 
    } 
} 

Y ejemplo de código llamándolo:

GameObject objectCircle = new GameObject(new Circle()); 
GameObject objectRect = new GameObject(new Rectangle()); 

objectCircle.Intersects(objectCircle); 
objectCircle.Intersects(objectRect); 
objectRect.Intersects(objectCircle); 
objectRect.Intersects(objectRect); 

produce la salida:

Circle intersecting Circle 
Rectangle intersecting Circle 
Circle intersecting Rectangle 
Rectangle intersecting Rectangle 
+0

patrón de visitante era una cosa que estaba buscando! De todos modos, tuve que mezclar tu solución con mi código actual para evitar el código duplicado. – zgorawski

5

Se podría diferir de su clase Shape para realizar el control de colisión, la adición de un método IntersectsWith(Shape other) a la forma. También le sugiero agregar un IntersectsWith(GameObject other) a su GameObject que le permite mantener su CollisionShape en privado.

1

Si los controles tendrían que residir en algún lugar de todos modos.

Se podría añadir un Intersects método para Shape:

abstract class Shape 
{ 
    public abstract Boolean Intersects(Shape other); 
} 

luego hacer su Intersect métodos en IntersectionCheckerpublic static e implementar Intersects método para cada forma concreta así:

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(); 
    } 
} 
+1

Este 'if..else if ... else' basado en el tipo de objeto normalmente suena para ser reemplazado por una función virtual. –

+0

@Uwe Keim, sí, y en este caso tendrías una recursión infinita, porque esa función virtual sería la misma implementación 'Shape.Intersects'. ¿O te di mal? –

+0

El 'IntersectionChecker' ya parece verificar el tipo, ¿así que haz una doble comprobación aquí? –

1

No hay acumulación de fácil solución para su problema. Lo que necesita se llama "envío doble", que solo se admite en idiomas como Smalltalk o Lisp. Todas las soluciones propuestas te obligarán a cambiar todas las clases derivadas si agregas una nueva clase. ¡Eso es un código malo!

Me gustaría abordar el problema de esta manera: Implemente sus clases derivadas de forma sin ningún código de intersección. A continuación, implementar una clase de intersección de esta manera:

public class Intersection { 
    public bool Intersect(Shape a, Shape b) {....} 

    private bool Intersect(Rectangle a, Circle b) {...} 

    private bool Intersect(Circle a, Circle b) {...} 
} 

Los métodos públicos los análisis de las formas de entrada y despachos (-> doble de despacho) el trabajo con el método privada correspondiente. Que contiene la lógica de intersección sin procesar. La implementación de Intersect no necesita "ifs". Puede usar la reflexión para encontrar el mejor método de coincidencia. Los detalles dependen de sus requisitos exactos y de cómo pondere la complejidad frente al rendimiento. Pero es fácil comenzar con una implementación simple y directa. Como todo está encapsulado en un solo lugar, es fácil optimizarlo más adelante. Lo cual es un buen enfoque en mi opinión. ;-)

+0

Su comentario "double dispatch only only supported" es increíblemente engañoso. Double dispatch, aunque realmente no es compatible con los lenguajes tipo C, aún se puede simular. [wiki double dispatch] (http://en.wikipedia.org/wiki/Double_dispatch) Su solución es un código incorrecto porque debe escribir casi el doble de las funciones que realmente necesita: 'Intersecar (Rectángulo, Círculo)' y 'Intersecar (círculo, rectángulo)'. El patrón Visitor funciona bien si mantiene los cambios futuros mínimos. Para el registro, "Todo está encapsulado en un solo lugar" no es un buen paradigma a seguir. – josaphatv

+0

¿Podría explicar por qué el patrón de visitante necesitaría menos funciones para cambiarse? ¡Ni siquiera menciono las funciones que das como ejemplo! – Achim

+0

El visitante no necesitaría menos cambios. Estoy diciendo que si solo tiene algunas cosas que apoyar como Circle, Rect y Triangle, y no planea agregar más que eso, el patrón de visitante funcionará bien. Sé que no menciona las funciones que di como ejemplo.No estoy muy familiarizado con Reflection, pero pensé que aún necesitaría verificar los tipos para encontrar en qué orden deben ir los argumentos (si va a invocar 'Intersect (Rectangle, Circle)' o 'Intersect (Circle, Rectangle) '). Usted dice que no necesitará 'si's pero creo que necesita' 'si' 'o código duplicado. – josaphatv

Cuestiones relacionadas