C# 用于引用公共属性的静态实例和枚举
我目前正在解决一个问题,我遇到了一个问题,我有多个架构选项,但不确定哪一个是未来的最佳选项 背景: 我正在为一个使用平铺贴图的游戏编写一些代码。瓷砖具有公共属性,例如,所有地板瓷砖都可行走,而墙则不可行走(以及其他属性)。因此,具有某种引用是有意义的,每个瓷砖都可以指向一个公共引用,以识别其属性 我已经提出了一些解决方案,但是我不确定哪一个是最有效的,或者哪一个将提供最大的灵活性。因此,我很好奇,无论是在一般情况下还是在我的具体情况下,哪一种被认为是“最好的”。同样,如果我没有列出更好的方法,请告诉我 (顺便说一句,随着平铺类型数量的增加,我可能还会遇到这样一个问题:硬编码这些值可能不太实际,某种序列化或文件I/O可能更有意义。正如我在C#中所做的那样,如果您在这里看到任何潜在的障碍,如果您能将它们包括在您的ans中,我将不胜感激。)嗯。) 以下是我的三种方法中的每一种,我对其进行了略微简化,使其更具一般性: 方法#1:使用扩展方法枚举:C# 用于引用公共属性的静态实例和枚举,c#,unity3d,enums,reference,static,C#,Unity3d,Enums,Reference,Static,我目前正在解决一个问题,我遇到了一个问题,我有多个架构选项,但不确定哪一个是未来的最佳选项 背景: 我正在为一个使用平铺贴图的游戏编写一些代码。瓷砖具有公共属性,例如,所有地板瓷砖都可行走,而墙则不可行走(以及其他属性)。因此,具有某种引用是有意义的,每个瓷砖都可以指向一个公共引用,以识别其属性 我已经提出了一些解决方案,但是我不确定哪一个是最有效的,或者哪一个将提供最大的灵活性。因此,我很好奇,无论是在一般情况下还是在我的具体情况下,哪一种被认为是“最好的”。同样,如果我没有列出更好的方法,请
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";