在我正在进行的项目中,我有一组构成基于3D体素的环境(如Minecraft)。这些世界存储在外部数据文件中。
此文件包含以下数据: 每个街区, 它的位置, 和它的类型。
当调用LoadLevel方法时,我希望它迭代文件中每个块的数据,为每个块创建一个新的Block对象实例。传递像位置这样的东西没问题。它就像
一样简单CreateBlock(Vector3 position)
问题在于类型。所有类型都是子类(想想抽象块,然后像GrassBlock或WaterBlock那样继承抽象Block的属性。)假设有一个像#34; GrassBlock"我想要创建,而不是通用块,我如何通过该方法实现这一点?我所知道的唯一方法是通过反思,我被建议远离。我可以通过泛型打字或其他方式来做到这一点吗?
这在游戏设计中似乎是一个非常重要的问题,但我所问过的任何人似乎都没有任何想法。有什么帮助吗?
答案 0 :(得分:2)
通用打字仍需要反思。
首先:您正在寻找的是工厂模式。它为您创建对象,而无需在任何地方自己明确地执行此操作。
基本上有两种选择:
这确实会对其产生影响,但如果您尚未确定它是一个问题,请不要忽视它。它将是可读和可维护的。
对每个选项进行硬编码,并根据您传入的某种元数据创建新实例(这将标识每个块的类型)。这样做的好处是不使用反射,因此不会导致性能下降,但也会降低可扩展性,如果你有500个不同的块,你可以猜出代码的样子。
答案 1 :(得分:1)
如果没有反射,您可以使用switch
的工厂方法。假设BlockType
是一个枚举。
public static Block CreateBlock(BlockType type, Vector3 position)
{
switch (BlockType type)
{
case BlockType.Grass:
return new GrassBlock(position);
case BlockType.Water:
return new WaterBlock(position);
default:
throw new InvalidOperationException();
}
}
但是为了拥有更可维护的东西,你仍然可以使用反射,直到它被证明是一个瓶颈。在这种情况下,您可以切换到运行时代码生成。
private static readonly Dictionary<Type, Func<Vector3, Block>> _activators = new Dictionary<Type, Func<Vector3, Block>>();
public static Block CreateBlock(Type blockType, Vector3 position)
{
Func<Vector3, Block> factory;
if (!_activators.TryGetValue(blockType, out factory))
{
if (!typeof(Block).IsAssignableFrom(blockType))
throw new ArgumentException();
var posParam = Expression.Parameter(typeof(Vector3));
factory = Expression.Lambda<Func<Vector3, Block>>(
Expression.New(
blockType.GetConstructor(new[] { typeof(Vector3) }),
new[] { posParam }
),
posParam
).Compile();
_activators.Add(blockType, factory);
}
return factory(position);
}
此代码将在运行时生成工厂函数,这是第一次请求给定类型的块。如果需要,您可以使用ConcurrentDictionary
来使此功能成为线程安全的。
但可能对你的目的来说有点矫枉过正;)
答案 2 :(得分:1)
当然,您可以创建没有任何反射的对象。 简单地为每个类分配整数索引:
Func<Vector3, Block>[] factories =
{
(v) => new GrassBlock(v), // index 0
(v) => new WaterBlock(v), // index 1
. . .
}
将此索引保存在外部数据中。在反序列化时读取Vector3 v
和索引i
,然后调用var block = factories[i](v);
答案 3 :(得分:0)
你为什么要避免反思?如果你只能在启动时执行此代码(听起来你可以在阅读文件时这样做),那么我个人在使用反射方面没有太大的问题。
另一种方法是存储完全限定的类型名称(例如My.System.Blocks.GrassBlock)并使用
加载该类型var typeName = readStringTypeFromFile(file);
Block type = Activator.CreateInstance(typeName, location);
正如我所说,在启动时运行这样的东西对我很好,如果需要你可以测试它的性能。
快速而肮脏的小提琴:https://dotnetfiddle.net/BDmlyi