我目前正在处理一个问题而且我遇到了一个问题,我有多个架构选项,但我不确定哪个是最好的选择。
上下文: 我正在为一款使用瓷砖地图的游戏编写一些代码。瓷砖具有共同的属性,例如,所有地砖都是可步行的,而墙不是(连同其他属性)。因此,有一种参考,每个瓷砖可以指向一个共同的参考,以辨别其属性是什么。
我已经提出了一些解决方案,但不确定哪种解决方案最有效,或者提供最大的灵活性。因此,我很好奇哪个会被认为是“最好的”,无论是一般情况还是我的具体情况。同样,如果有一个更好的方法,我没有列出,请告诉我。
(顺便说一句,随着磁贴类型数量的增长,我也可能会遇到硬编码这些值并且某种序列化或文件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
编辑:设计人员在编译之前确定了tile的类型,即不允许任何类创建新的TileData类型(例如,在示例2和3中,实例)。
答案 0 :(得分:1)
方法2对设计人员友好,并且比方法3稍微有效。如果你想逐个系统而不是逐个进行某种推理,它也可以通过方法1的扩展方法来补充。 -tile。
考虑使用静态工厂补充构造函数:
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)。你可以将它传递给构造函数(和Tweak,作为另一个参数,因为否则调整后的tile将克隆其父级的ID!)。有一些东西(单元测试没问题)确保这个TileData类的所有字段都有不同的ID会很好。最后,为了避免必须将这些ID重新输入Tiled,您可以制作一些东西,将此类中的数据导出到Tiled TSX or TMX file(或者您最终使用的任何地图编辑器的类似文件)。
编辑:最后一个提示。如果您的一致ID是连续的int,您可以&#34;编译&#34;将您的切片数据分成静态数组,按属性拆分。这对于性能很重要的系统非常有用(例如,寻路需要大量查找可步行性)。
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>
用于相同的目的,并使用相同的语法访问它,但它也不会执行。
答案 1 :(得分: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继承该类并覆盖需要具有特定值而不是默认值的属性。
只需调用通用静态工厂方法Get&lt;&gt;(),即只接受派生类型的Tile,即可通过基类来制作一个tile:
Tile wallLeft = Tile.Get<Wall>();
Tile floor = Tile.Get<Floor>();
所以一切都是平铺的,代表一组不同的已定义属性值。可以通过它们的类型或属性值来识别它们。更重要的是,你可以看到我们摆脱了所有If..Else
,Switch case
,Constructor overloads
。听起来怎么样?
使用新属性扩展平铺
因此,例如Tiles需要新的属性/属性,例如Color simple将虚拟属性添加到名为Color的Tile类中。在构造函数中给它一个默认值。 Optinally(非强制)如果您的磁贴应为特殊颜色,则覆盖子类中的属性。
引入新的瓷砖类型
只需使用Tile
类派生New Tile类型并覆盖必需的属性。
答案 2 :(得分: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
}
}
答案 3 :(得分: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
}
}
答案 4 :(得分: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;
答案 5 :(得分: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";