2015-09-03 94 views
3

我正在處理一個問題,並遇到了一個問題,我有多個體繫結構選項,但我不確定哪個將成爲最佳選項。用於引用公共屬性的靜態實例和枚舉

上下文: 我正在寫一些遊戲的代碼,它使用了一個貼圖。瓷磚具有共同的屬性,例如,所有的地磚都可以步行,而牆壁不是(以及其他屬性)。因此,有一定的參考意義,每個瓦片可以指向一個共同的參考來辨別其特性。

我已經想出了一些解決方案,但是我不確定哪個是最有效的或者將提供最大的靈活性。因此,我很好奇哪些被認爲是「最好的」,無論是一般的還是我的具體情況。同樣,如果有更好的方法可以做到這一點,我還沒有列出,請讓我知道。 (另外,隨着tile類型數量的增加,我可能會遇到這樣的問題:對這些值進行硬編碼可能不太實際,某種類型的序列化或文件I/O可能更有意義。正如我在C#中所做的一樣,如果你在這裏看到任何潛在的絆腳石,如果你可以將它們包含在你的答案中,將會同樣讚賞。)

下面是我的三種方法,我略微簡化了使他們更普遍的:

方法1:枚舉與擴展方法:

public enum TileData{ 
    WALL, 
    FLOOR, 
    FARMLAND 
    //...etc 
} 

public static class TileDataExtensions{ 

    public static int IsWalkable(this TileData tile){ 
     switch(tile){ 
     case TileData.FLOOR: 
     case TileData.FARMLAND: 
      return true; 
     case TileData.WALL: 
      return false; 
     } 
    } 

    public static int IsBuildable(this TileData tile){ 
     switch(tile){ 
     case TileData.FLOOR: 
      return true; 
     case TileData.WALL: 
     case TileData.FARMLAND: 
      return false; 
     } 
    } 

    public static Zone ZoneType(this TileData tile){ 
     switch(tile){ 
     case TileData.WALL: 
     case TileData.FLOOR: 
      return Zone.None; 
     case TileData.FARMLAND: 
      return Zone.Arable; 
     } 
    } 

    public static int TileGraphicIndex(this TileData tile){ 
     switch(tile){ 
     case TileData.WALL: 
      return 0; 
     case TileData.FLOOR: 
      return 1; 
     case TileData.FARMLAND: 
      return 2; 

     } 
    } 

    public enum Zone{ 
     Shipping, 
     Receiving, 
     Arable, 
     None 
    } 
} 

方法2:巨大的私人構造&靜態實例

public class TileData{ 

    public bool IsWalkable{get;}; 
    public bool IsBuildSpace{get;}; 
    public Zone ZoneType{get;}; 
    public int TileGraphicIndex{get;}; 

    public static TileData FLOOR = new TileData(true, true, Zone.None, 1); 
    public static TileData WALL  = new TileData(false, false, Zone.None, 0); 
    public static TileData FARMLAND = new TileData(true, false, Zone.Arable, 2); 
    //...etc 

    private TileData(bool walkable, bool buildSpace, Zone zone, int grahpicIndex){ 
     IsWalkable = walkable; 
     IsBuildSpace = buildSpace; 
     ZoneType = zone; 
     TileGraphicIndex = grahpicIndex; 
    } 

    public enum Zone{ 
     Shipping, 
     Receiving, 
     Arable, 
     None 
    } 
} 

方法3:私有構造函數和setter,用靜態實例:

public class TileData{ 

    public bool IsWalkable{get; private set;}; 
    public bool IsBuildSpace{get; private set;}; 
    public Zone ZoneType{get; private set;}; 
    public int TileGraphicIndex{get; private set;}; 


    public static TileData FLOOR{ 
     get{ 
      TileData t = new TileData(); 
      t.IsBuildSpace = true; 
      t.TileGraphicIndex = 1; 
      return t; 
     } 
    } 
    public static TileData WALL{ 
     get{ 
      TileData t = new TileData(); 
      t.IsWalkable = false; 
      return t; 
     } 
    } 
    public static TileData FARMLAND{ 
     get{ 
      TileData t = new TileData(); 
      t.ZoneType = Zone.Arable; 
      t.TileGraphicIndex = 2; 
      return t; 
     } 
    } 
    //...etc 

    //Constructor applies the most common values 
    private TileData(){ 
     IsWalkable = true; 
     IsBuildSpace = false; 
     ZoneType = Zone.None; 
     TileGraphicIndex = 0; 
    } 

    public enum Zone{ 
     Shipping, 
     Receiving, 
     Arable, 
     None 
    } 
} 

非常感謝,LR92

編輯:瓷磚的類型由設計者在編譯之前確定,即不應該允許任何類創建新的TileData類型(即,在示例中,實例2)。

+0

多少瓦的類型,你預計有(量級)? –

+0

**關於方法3的附加子問題:**每次調用方法3都會重新創建一個方法(因此效率較低,然後說方法2),還是在編譯時/第一次調用時單獨解決? – LeftRight92

+0

@JerryFederspiel少於100個,最多約50個 – LeftRight92

回答

1

方法2是友好的設計師,如果你想要做一些推理系統,操縱系統,而不是一個區塊接的是稍微比方法3.更高效也可以通過方法1的擴展方法補充-瓦。

考慮使用靜態工廠補充你的構造:

private TileData(bool walkable, bool buildSpace, Zone zone, int grahpicIndex){ 
    IsWalkable = walkable; 
    IsBuildSpace = buildSpace; 
    ZoneType = zone; 
    TileGraphicIndex = grahpicIndex; 
} 

private static TileData Tweak(TileData parent, Action<TileData> tweaks) { 
    var newTile = parent.MemberwiseClone(); 
    tweaks(newTile); 
    return newTile; 
} 

這可以讓你用一種原型繼承的建立你的瓷磚類型(除了沒有查找在運行時原型鏈,這將是烘烤)。這應該是非常有用的,因爲在基於瓦片的遊戲中通常具有大部分相似但具有稍微不同的行爲或圖形的圖塊。

public readonly static TileData GRASS =   new TileData(etc.); 
public readonly static TileData WAVY_GRASS =  Tweak(GRASS, g => g.TileGraphicIndex = 10); 
public readonly static TileData JERKFACE_GRASS = Tweak(GRASS, g => g.IsWalkable = false); 
public readonly static TileData SWAMP_GRASS = Tweak(GRASS, g => {g.TileGraphicIndex = 11; g.IsBuildable = false;}); 

注:當你序列化/反序列化瓷磚的地圖,你會希望有某種形式分配給每個瓷磚的一致ID(尤其是,這使得與Tiled工作更容易)。你可以將它傳遞給構造函數(也可以將其作爲另一個參數調整,因爲否則調整的tile將克隆它父項的ID!)。如果有一些東西(單元測試會很好),確保這類TileData類的所有字段都有不同的ID,那就太好了。最後,爲了避免必須將這些ID重新輸入到Tiled中,可以創建一些將此類中的數據導出爲Tiled TSX or TMX file(或適用於您最終使用的任何地圖編輯器的類似文件)。

編輯:最後一個提示。如果您的一致ID是連續的int,則可以將tile數據「編譯」爲按屬性拆分的靜態數組。這對於性能很重要的系統很有用(例如,尋路需要查找可行性)。

public static TileData[] ById = typeof(TileData) 
           .GetFields(BindingFlags.Static | BindingFlags.Public) 
           .Where(f => f.FieldType == typeof(TileData)) 
           .Select(f => f.GetValue(null)) 
           .Cast<TileData>() 
           .OrderBy(td => td.Id) 
           .ToArray(); 
public static bool[] Walkable = ById.Select(td => td.IsWalkable).ToArray(); 

// now you can have your map just be an array of array of ids 
// and say things like: if(TileData.Walkable[map[y][x]]) {etc.} 

如果你的ID不連續的整數,則可以使用Dictionary<MyIdType, MyPropertyType>爲了同樣的目的,並用相同的語法訪問它,但它不會執行,以及。

+0

我將不得不查找Action <>的定義,但是這看起來很有前途......我想我理解它。 – LeftRight92

+0

此外,我沒有使用枚舉上下文之外的擴展方法。這種方法是否垂直相同? – LeftRight92

+0

擴展方法可以繼續任何類型。您的示例中擴展方法簽名的語法將保持不變。 –

0

爲什麼不只是重載構造函數?

public class TileData{ 

    public bool IsWalkable{get;}; 
    public bool IsBuildSpace{get;}; 
    public Zone ZoneType{get;}; 
    public int TileGraphicIndex{get;}; 

    public static TileData FLOOR = new TileData(true, true, Zone.None, 1); 
    public static TileData WALL  = new TileData(false, false, Zone.None, 0); 
    public static TileData FARMLAND = new TileData(true, false, Zone.Arable, 2); 
    //...etc 

    public TileData(bool walkable, bool buildSpace, Zone zone, int grahpicIndex){ 
     IsWalkable = walkable; 
     IsBuildSpace = buildSpace; 
     ZoneType = zone; 
     TileGraphicIndex = grahpicIndex; 
    } 

    public TileData(){ 
     IsWalkable = true; 
     IsBuildSpace = false; 
     ZoneType = Zone.None; 
     TileGraphicIndex = 0; 
    } 

    public enum Zone{ 
     Shipping, 
     Receiving, 
     Arable, 
     None 
    } 
} 
+0

,因爲我不希望構造函數可公開訪問。可用的圖塊類型是預先確定的(在編譯時)。道歉,應該使我原來的帖子更明顯。 – LeftRight92

+0

重載的私有構造函數沒什麼問題。 –

+0

看不出爲什麼我會一定想超載的構造函數,雖然... – LeftRight92

0

怎麼樣創建每種類型的瓷磚。

public class Tile{ 
    public TileType Type { get; private set; } 
    public bool IsWalkable { get; private set; } 
    public bool IsBuildSpace { get; private set; } 
    public Zone ZoneType { get; private set; } 
    public int TileGraphicIndex { get; private set; } 

    private Tile() { 
    } 

    public static Tile BuildTile(TileType type){ 
     switch (type) { 
      case TileType.WALL: 
       return BuildWallTile(); 
      case TileType.FLOOR: 
       return BuildFloorTile(); 
      case TileType.FARMLAND: 
       return BuildFarmlandTile(); 
      default: 
       throw ArgumentException("type"); 
     } 
    } 

    public static Tile BuildWallTile() 
    { 
     return new Tile { 
      IsWalkable = false, 
      IsBuildSpace = false, 
      ZoneType = Zone.None, 
      TileGraphicIndex = 1, 
      Type = TileType.WALL 
     }; 
    } 

    public static Tile BuildFloorTile() 
    { 
     return new Tile { 
      IsWalkable = true, 
      IsBuildSpace = None, 
      ZoneType = Zone.None, 
      TileGraphicIndex = 1, 
      Type = TileType.FLOOR 
     }; 
    } 

    public static Tile BuildFarmlandTile() 
    { 
     return new Tile { 
      IsWalkable = true, 
      IsBuildSpace = false, 
      ZoneType = Zone.Arable, 
      TileGraphicIndex = 2, 
      Type = TileType.FARMLAND 
     }; 
    } 

    public enum Zone{ 
     Shipping, 
     Receiving, 
     Arable, 
     None 
    } 
    public enum TileType{ 
     WALL, 
     FLOOR, 
     FARMLAND 
     //...etc 
    } 
} 
+0

我是否認爲這是一種工廠模式是正確的? – LeftRight92

+0

這是非常相似的,是的 – Diego

+0

此外,除了從靜態實例切換到方法之外,它與實際的方法3(在原始問題中)有何不同? – LeftRight92

0

剛上延伸Diegos答案,該方法可以只爲清潔領域

public class Tile{ 
    public TileType Type { get; private set; } 
    public bool IsWalkable { get; private set; } 
    public bool IsBuildSpace { get; private set; } 
    public Zone ZoneType { get; private set; } 
    public int TileGraphicIndex { get; private set; } 
    private Tile() { } 

    public static Tile BuildTile(TileType type){ 
     switch (type) { 
     case TileType.WALL: return BuildWallTile(); 
     case TileType.FLOOR: return BuildFloorTile(); 
     case TileType.FARMLAND: return BuildFarmlandTile(); 
     default: throw ArgumentException("type"); 
     } 
    } 
    public static Tile wall { 
     get { 
      return new Tile { 
       IsWalkable = false, 
       IsBuildSpace = false, 
       ZoneType = Zone.None, 
       TileGraphicIndex = 1, 
       Type = TileType.WALL 
      }; 
     } 
    } 
    public static Tile floor { 
      get { 
       return new Tile { 
        IsWalkable = true, 
        IsBuildSpace = None, 
        ZoneType = Zone.None, 
        TileGraphicIndex = 1, 
        Type = TileType.FLOOR 
       }; 
      } 
    } 
    public static Tile farmland { 
     get { 
      return new Tile { 
       IsWalkable = true, 
       IsBuildSpace = false, 
       ZoneType = Zone.Arable, 
       TileGraphicIndex = 2, 
       Type = TileType.FARMLAND 
       }; 
      } 
    } 
    public enum Zone{ 
     Shipping, 
     Receiving, 
     Arable, 
     None 
    } 
    public enum TileType{ WALL, FLOOR, FARMLAND //...etc } 
} 

用法:

Tile myWallTile = Tile.wall; 
Tile myFloorTile = Tile.floor; 
1

讓我們嘗試用更多的面向對象的方法來解決你的需求。 Less conditional more polymorphism。在我看來,如果你有更多的機會提出新的瓷磚類型除了提到的。意味着設計應該是可擴展的,並且應該開放以引入新的組件。

例如,讓我們繼續將Tile類作爲基類。

public abstract class Tile 
{ 
    public Tile() 
    { 
     // Default attributes of a Tile 
     IsWalkable = false; 
     IsBuildSpace = false; 
     ZoneType = Zone.None; 
     GraphicIndex = -1; 
    } 

    public virtual bool IsWalkable { get; private set; } 
    public virtual bool IsBuildSpace { get; private set; } 
    public virtual Zone ZoneType { get; private set; } 
    public virtual int GraphicIndex { get; private set; } 

    /// <summary> 
    /// Factory to build the derived types objects 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <returns></returns> 
    public static T Get<T>() where T : Tile, new() 
    { 
     return new T(); 
    } 
} 

現在我們已經定義了一個具有默認屬性的Tile。如果需要,可以將Tile的更多默認屬性添加爲Vitual屬性。由於這個類是abstract,所以不能簡單地創建對象,因此必須引入Derived類,這將是我們特定類型的Tile例如。牆,地板等

public class Floor : Tile 
{ 
    public override bool IsBuildSpace 
    { 
     get { return true; } 
    } 

    public override bool IsWalkable 
    { 
     get { return true; } 
    } 
    public override int GraphicIndex 
    { 
     get { return 1; } 
    } 
} 

public class Wall : Tile 
{ 
    public override int GraphicIndex 
    { 
     get { return 0; } 
    } 

    public override Zone ZoneType 
    { 
     get { return Zone.Arable; } 
    } 
} 

如果必須創建一種新類型的瓷磚。只需從Tile繼承該類並覆蓋需要具有特定值而不是缺省值的屬性。

 Tile wallLeft = Tile.Get<Wall>(); 
     Tile floor = Tile.Get<Floor>(); 

所以一切都是瓷磚和表示:

手工藝瓷磚將通過基類只是通過調用通用的靜態工廠方法獲得<>(),將只接受瓷磚的派生類型來完成定義屬性的不同組值。它們可以通過它們的類型或屬性值來識別。更重要的是,你可以看到我們擺脫了所有的If..Else,Switch case,Constructor overloads。聽起來怎麼樣?

擴展使用新屬性

所以對於例如平鋪Tiles需要新的屬性/屬性,例如,顏色簡單添加虛擬屬性到名爲Color的Tile類。在構造函數中給它一個默認值。 Optinally(不是強制性的)如果你的tile應該是特殊顏色的,則覆蓋子類中的屬性。

介紹瓷磚

的新型只需使用Tile類派生的新瓦型,並覆蓋所需的屬性。

+1

就像這樣聰明,像純粹的面向對象一樣,我不禁感到爲每種類型的tile創建一個新的(儘管派生的)類是一個很大的開銷。一個很好的迴應,所以+1 – LeftRight92

+1

很高興你喜歡它! :) – vendettamit

0

我想從迄今爲止的許多建議中提出一個完全不同的(並且自我肯定是瘋狂的)方法。如果你願意完全拋型安全窗外,看看這個:

public interface IValueHolder 
{ 
    object Value {get; set;} 
} 

public class IsWalkable : Attribute, IValueHolder 
{ 
    public object Value {get; set;} 
    public IsWalkable(bool value) 
    { 
     Value = value; 
    } 
} 

public class IsBuildSpace : Attribute, IValueHolder 
{ 
    public object Value {get; set;} 
    public IsBuildSpace(bool value) 
    { 
     Value = value; 
    } 
} 

public enum Zone 
{ 
    None, 
    Arable, 
} 

public class ZoneType : Attribute, IValueHolder 
{ 
    public object Value {get; set;} 
    public ZoneType(Zone value) 
    { 
     Value = value; 
    } 
} 

public class TileGraphicIndex : Attribute, IValueHolder 
{ 
    public object Value {get; set;} 
    public TileGraphicIndex(int value) 
    { 
     Value = value; 
    } 
} 

public class TileAttributeCollector 
{ 
    protected readonly Dictionary<string, object> _attrs; 

    public object this[string key] 
    { 
     get 
     { 
      if (_attrs.ContainsKey(key)) return _attrs[key]; 
      else return null; 
     } 

     set 
     { 
      if (_attrs.ContainsKey(key)) _attrs[key] = value; 
      else _attrs.Add(key, value); 
     } 
    } 

    public TileAttributeCollector() 
    { 
     _attrs = new Dictionary<string, object>(); 

     Attribute[] attrs = Attribute.GetCustomAttributes(this.GetType()); 
     foreach (Attribute attr in attrs) 
     { 
      IValueHolder vAttr = attr as IValueHolder; 
      if (vAttr != null) 
      { 
       this[vAttr.ToString()]= vAttr.Value; 
      } 
     } 
    } 
} 

[IsWalkable(true), IsBuildSpace(false), ZoneType(Zone.Arable), TileGraphicIndex(2)] 
public class FarmTile : TileAttributeCollector 
{ 
} 

用法的例子:

FarmTile tile = new FarmTile(); 

// read, can be null. 
var isWalkable = tile["IsWalkable"]; 

// write 
tile["IsWalkable"] = false; 

// add at runtime. 
tile["Mom"]= "Ingrid Carlson of Norway";