C# 用于引用公共属性的静态实例和枚举

C# 用于引用公共属性的静态实例和枚举,c#,unity3d,enums,reference,static,C#,Unity3d,Enums,Reference,Static,我目前正在解决一个问题,我遇到了一个问题,我有多个架构选项,但不确定哪一个是未来的最佳选项 背景: 我正在为一个使用平铺贴图的游戏编写一些代码。瓷砖具有公共属性,例如,所有地板瓷砖都可行走,而墙则不可行走(以及其他属性)。因此,具有某种引用是有意义的,每个瓷砖都可以指向一个公共引用,以识别其属性 我已经提出了一些解决方案,但是我不确定哪一个是最有效的,或者哪一个将提供最大的灵活性。因此,我很好奇,无论是在一般情况下还是在我的具体情况下,哪一种被认为是“最好的”。同样,如果我没有列出更好的方法,请

我目前正在解决一个问题,我遇到了一个问题,我有多个架构选项,但不确定哪一个是未来的最佳选项

背景: 我正在为一个使用平铺贴图的游戏编写一些代码。瓷砖具有公共属性,例如,所有地板瓷砖都可行走,而墙则不可行走(以及其他属性)。因此,具有某种引用是有意义的,每个瓷砖都可以指向一个公共引用,以识别其属性

我已经提出了一些解决方案,但是我不确定哪一个是最有效的,或者哪一个将提供最大的灵活性。因此,我很好奇,无论是在一般情况下还是在我的具体情况下,哪一种被认为是“最好的”。同样,如果我没有列出更好的方法,请告诉我

(顺便说一句,随着平铺类型数量的增加,我可能还会遇到这样一个问题:硬编码这些值可能不太实际,某种序列化或文件I/O可能更有意义。正如我在C#中所做的那样,如果您在这里看到任何潜在的障碍,如果您能将它们包括在您的ans中,我将不胜感激。)嗯。)

以下是我的三种方法中的每一种,我对其进行了略微简化,使其更具一般性:

方法#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


编辑:平铺类型在编译前由设计器确定,即不允许任何类创建新的平铺数据类型(即,在示例2和3中,实例)。

为什么不让构造函数重载

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

方法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;
}
注意:当您序列化/反序列化磁贴贴图时,您需要为每个磁贴分配某种类型的一致ID(特别是,这会使处理更容易)。您可以将其传递给构造函数(并作为另一个参数进行调整,因为否则调整后的磁贴将克隆其父级的ID!)。这样做很好(单元测试就可以了),可以确保TileData类型的此类的所有字段都有不同的ID。最后,为了避免将这些ID重新输入到Tiled中,您可以制作一些东西,将此类中的数据导出到一个(或最终使用的地图编辑器的类似文件)中

编辑:最后一个技巧。如果一致ID是连续整数,则可以“编译”平铺数据到按属性划分的静态数组中。这对于性能非常重要的系统非常有用(例如,寻路需要经常查找可行走性)

public static TileData[]ById=typeof(TileData)
.GetFields(BindingFlags.Static | BindingFlags.Public)
.其中(f=>f.FieldType==typeof(TileData))
.Select(f=>f.GetValue(null))
.Cast()
.OrderBy(td=>td.Id)
.ToArray();
public static bool[]Walkable=ById.Select(td=>td.IsWalkable.ToArray();
//现在你可以让你的地图只是一个ID数组
//比如说:if(TileData.Walkable[map[y][x]]{etc}

如果您的ID不是连续的整数,那么您可以出于相同的目的使用
字典
,并使用相同的语法访问它,但它的性能不会很好。

仅扩展Diegos answer,这些方法可以只是用于清洁的字段

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;

让我们试着用更面向对象的方法来解决您的需求。
更少的条件更多的多态性
。在我看来,如果您有更多的机会提出除上述类型之外的新类型的瓷砖。这意味着设计应该是可扩展的,并且应该开放,以最小的更改来引入新组件

例如,让我们保持Tile class a基类

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();
    }
}
如果必须创建新类型的磁贴,只需从磁贴继承类,并重写需要具有特定值而不是默认值的属性

制作互动程序将通过基类完成,只需调用仅接受派生类型互动程序的通用静态工厂方法Get():

        Tile wallLeft = Tile.Get<Wall>();
        Tile floor = Tile.Get<Floor>();
Tile wallLeft=Tile.Get();
瓷砖地板=瓷砖。获取();
因此,所有内容都是平铺的,并表示一组不同的已定义属性值。它们可以通过属性的类型或值来识别。更重要的是,正如您所看到的,我们去掉了所有的
If..Else
开关大小写
构造函数重载
。这听起来怎么样

使用新属性扩展磁贴

因此,例如,在平铺上需要一个新的属性/属性,例如“颜色简单”将一个虚拟属性添加到名为“颜色”的平铺类中。在构造函数中为其指定一个默认值。如果平铺应为特殊颜色,则可选(非强制性)覆盖子类中的属性

推出新型瓷砖


简单地导出新的瓦片类型,使用<代码>瓦片类并重写所需属性。

< P>我想从目前为止的许多建议中提出一种完全不同的(以及自认为是疯狂的)方法。如果您愿意完全抛出类型安全性,请考虑如下:

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

因为我不希望构造函数可以公开访问
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 wallLeft = Tile.Get<Wall>();
        Tile floor = Tile.Get<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";