2011-05-28 47 views
12

我使用碰撞檢測(RectangleCircleConeRing等)所有這些形狀都從基抽象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類中沒有幾十個'if'檢查和類型檢查?

編輯:

請採取記住,檢查形狀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 
+0

看到我的建議使用反射。如果使用反射來分派呼叫,則對於類型爲矩形/圓形和圓形/矩形的對象使用相同的方法很容易。 – Achim 2011-05-28 21:51:48

回答

12

你可以使用Visitor Patternhere is a C# example

這將允許您只需擁有每個派生形狀實現的Shape.Intersect(Rectangle),Shape.Intersect(Circle)...方法。它會阻止您以一種額外的方法調用爲代價對類型進行任何反思。

編輯 - 這裏是一個示例實現,它很可能是清潔的,如果沒有共享的功能可能會包含在形狀使用接口IShape的,但我只是停留在一個抽象基類。

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

訪客模式是我正在尋找的東西!無論如何,我必須將您的解決方案與我當前的代碼混合,以避免重複的代碼。 – zgorawski 2011-05-29 06:47:25

5

您可以按照您的Shape類進行碰撞檢查,添加IntersectsWith(Shape other)方法成型。我還建議在您的GameObject中增加一個IntersectsWith(GameObject other),這可以讓您將CollisionShape保密。

1

如果支票無論如何都不得不駐留在某個地方。

您可以添加一個Intersects方法Shape

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

然後讓你Intersect方法IntersectionCheckerpublic 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(); 
    } 
} 
+1

這個'if..else if ... else'基於對象的類型,通常聲音被虛擬函數取代。 – 2011-05-28 18:29:14

+0

@Uwe Keim,是的,在這種情況下,你將有一個無限遞歸,因爲這個虛擬函數將是相同的'Shape.Intersects'實現。或者我弄錯你了嗎? – 2011-05-28 18:30:44

+0

'IntersectionChecker'已經似乎檢查了類型,所以你在這裏做一個複選? – 2011-05-28 18:32:34

1

有一個在你的問題的解決方案不是一件容易的構建。你所需要的就是所謂的「雙重調度」,它只在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) {...} 
} 

的公共方法分析輸入的形狀和調度( - >雙調度)的工作匹配的私有方法。其中包含原始交集邏輯。相交的實現不需要「ifs」。您可以使用反射來查找最佳匹配方法。細節取決於您的具體要求以及您如何重視複雜性與性能。但是開始使用簡單的直接實現很容易。因爲一切都封裝在一個地方,所以稍後優化很容易。我認爲這是一個很好的方法。 ;-)

+0

您的「雙重調度只支持」評論令人難以置信的誤導。雙派遣,雖然沒有真正支持的類似C語言仍然可以模擬。 [wiki double dispatch](http://en.wikipedia.org/wiki/Double_dispatch) 您的解決方案是錯誤的代碼,因爲您必須編寫幾乎兩倍於您真正需要的函數:'Intersect(Rectangle,Circle)'和'相交(圓形,矩形)'。如果將未來的更改保持在最低限度,Visitor模式運行良好。 爲了記錄,「一切都封裝在一個地方」並不是一個很好的範例。 – josaphatv 2013-08-29 15:16:42

+0

你能解釋一下,爲什麼訪問者模式需要更少的功能來改變?我甚至沒有提到你作爲例子給出的功能?! – Achim 2013-08-29 18:33:41

+0

參觀者不需要少量更換。我在說,如果你只有一些支持像Circle,Rect和Triangle的東西,並且你不打算添加更多的東西,那麼訪問者模式就可以正常工作。 我知道你沒有提到我給出的功能作爲例子。我對Reflection並不是很熟悉,但我還是認爲你仍然需要檢查類型以找到參數需要進入的順序(無論你是要調用'Intersect(Rectangle,Circle)'還是'Intersect(圓形,矩形)')。你說你不需要'如果',但我認爲你需要'如果'或重複的代碼。 – josaphatv 2013-08-30 13:49:00