簡體   English   中英

用於引用公共屬性的靜態實例和枚舉

[英]Static Instances and Enums for referencing common properties

我正在解決一個問題,我遇到了一個問題,我有多個架構選項,但我不確定哪個是最好的選擇。

上下文:我正在為游戲編寫一些代碼,它使用了一個瓦片地圖。 瓷磚具有共同的屬性,例如,所有地磚都是可步行的,而牆不是(連同其他屬性)。 因此,有一種參考,每個圖塊可以指向一個共同的參考,以辨別其屬性是什么。

我提出了一些解決方案,但不確定哪種解決方案效率最高,或者提供最大的靈活性。 因此,我很好奇哪個被認為是“最好的”,無論是一般情況還是我的具體情況。 同樣,如果我沒有列出更好的方法,請告訴我。

(順便說一句,隨着磁貼類型數量的增長,我可能會遇到這樣的問題,即對這些值進行硬編碼可能並不實際,某些序列化或文件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和3中,實例)。

方法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;
}

這允許您使用一種原型繼承來構建您的tile類型(除了在運行時查找原型鏈,而不是在其中進行烘焙)。 這應該是非常有用的,因為在基於圖塊的游戲中通常具有大致相似但具有略微不同的行為或圖形的圖塊。

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 )。 你可以將它傳遞給構造函數(和Tweak,作為另一個參數,因為否則調整后的tile將克隆其父級的ID!)。 有一些東西(單元測試沒問題)確保這個TileData類的所有字段都有不同的ID會很好。 最后,為了避免將這些ID重新輸入Tiled,您可以創建一些將此類中的數據導出到Tiled TSX或TMX文件 (或者您最終使用的任何地圖編輯器的類似文件)。

編輯:最后一個提示。 如果您的一致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不是連續的int,你可以使用Dictionary<MyIdType, MyPropertyType>來實現相同的目的,並使用相同的語法訪問它,但它也不會執行。

讓我們嘗試用更多面向對象的方法來解決您的需求。 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,例如Wall,Floor等。

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的通用靜態工廠方法Get <>()來制作一個tile就可以通過基類來完成:

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

所以Everything都是Tile,代表一組不同的已定義屬性值。 可以通過它們的類型或屬性值來識別它們。 更重要的是,你可以看到我們擺脫了所有的If..ElseSwitch caseConstructor overloads 聽起來怎么樣?

使用新屬性擴展Tile

因此,例如,在Tiles上需要新的屬性/屬性,例如Color simple將虛擬屬性添加到名為Color的Tile類。 在構造函數中給它一個默認值。 Optinally(非強制)如果您的磁貼應為特殊顏色,則覆蓋子類中的屬性。

介紹新型瓷磚

只需使用Tile類派生New Tile類型並覆蓋必需的屬性。

為什么不重載構造函數?

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

如何創建每種類型的圖塊的方法。

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

只要延伸到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;

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

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";

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM