在基于组件的游戏设计中共享字段
在使用XNA在C#中完成基于组件的游戏引擎之前,我认为这是最后一次大的逻辑飞跃。 我定义了我的Entity类和抽象组件。 我的问题出现在我的EntityFactory中。
当我想创建一个新实体时,我将一个EntityType枚举传递给工厂中的静态方法,并通过一个switch / case语句查找要组合在一起的组件。 问题是,我正在尝试创建一种方法,组件可以与同一实体中的其他组件共享字段,而无需访问所有内容。 例如,如果两个组件具有表示位置的Vector2字段,则它们都应指向相同的Vector2。
我可以通过初始化实体工厂中的所有字段并要求将它们传递到组件的构造函数(并使用ref作为基元)来做到这一点,但这将非常难以维护,因为任何时候我扩展或更改了组件,我将不得不在工厂中的每个使用组件的地方重写代码。 我真的想避免这个解决方案,但如果我找不到更好的方法,我会忍受它。
我目前的解决方案是创建一个名为Attribute的包装类。 它包含两个字段:
private AttributeType type; private Object data;
属性类型是枚举,表示属性的用途。 所以在位置,旋转,纹理等枚举中有条目。
EntityFactory创建一个空的属性列表,并将其传递给每个组件构造函数。 setField方法将由组件的构造函数调用,而不是初始化字段。 这是Attribute类和setField方法。
public class Attribute { private AttributeType type; private Object data; public AttributeType Type { get { return this.type; } } public Object Data { get { return this.data; } } public Attribute(AttributeType type, Object data) { this.type = type; this.data = data; } public static void setField(List attributeList, AttributeType type, out T field, T defaultValue) { bool attributeFound = false; field = defaultValue; foreach (Attribute attribute in attributeList) { if (attribute.Type == type) { field = (T)attribute.Data; attributeFound = true; break; } } if (!attributeFound) { attributeList.Add(new Attribute(type, field)); } } }
我的问题是当属性包含基本类型的数据时。 我考虑在Attribute类中编写一个方法
public void getData(out T field) { field = this.data; }
但是我似乎无法使用ref将数据传递给Attribute构造函数。 我不能使属性通用,因为它不会进入列表。 我只是想知道是否有办法处理值类型以及引用类型数据,或者我在整个事情中某处出现了逻辑错误。
Snarky版本:恭喜你重新改造了这个变量。 厉害。 或者,充其量是接口上的属性。
实用版本:
我可以看到你的设计有一些问题。
第一个问题就是它很复杂 。 最好避免并发症,除非你有令人信服的和存在的原因(即:不是“可能在将来”需要)。 否则YAGNI 。 您应该总是尝试直接在代码中表达概念,然后再创建系统来在数据中表达这些概念(就像我所说的关于重新发明变量的内容;也考虑到这一点 )。
但是我们假设您确实有充分的理由进行基于组件的设计……
第二个问题是拳击 。 拳击发生在你将值类型(例如: int
, float
, Vector2
,任何struct
)直接存储为引用类型的任何地方(例如: object
, IEquatable
)。 盒装对象是不可变的 – 所以每次你的位置改变时,都会创建一个新的盒装对象。 拳击变量是(相对)慢。 盒装对象存储在堆上 – 因此它们会在垃圾收集过程中被考虑,并可能导致垃圾收集。 所以你在问题中提出的设计将会表现得非常糟糕 。
我假设你对基于组件的设计的想法与本文中解释的类似。 这是一个有用的图表:
http://sofzh.miximages.com/c%23/Fig-2.gif
这让我想到了第三个问题:你不应该拥有多个持有职位的组件 ! (在您的设计中,您似乎比您需要的更精细。)
基本上,基于组件的设计是关于重新发明class
而不是变量 。 在正常设计中,您可能具有这样的“渲染”function:
public void Draw() { spriteBatch.Draw(texture, this.Position, Color.White); }
但在基于组件的设计中,您将在不同的类中拥有Draw
和Position
。 顺便说一句,我会实现以下接口:
interface IRenderComponent { void Draw(); } interface IPositionComponent { Vector2 Position { get; set; } }
那么Draw
访问Position
如何? 好吧,你需要一种表达方式(如果你要重新发明类, 这可能是你需要包含的最重要的概念)。
你会怎么做? 这是一个粗略的设计理念:
我会使每个组件类inheritance自具有属性Self
的Component
类。 我会让Self
返回某种ComposedObject
,其机制可以通过接口访问构成组合对象的任何其他组件。 因此,您的渲染组件可能如下所示:
class SimpleRenderer : Component, IRenderComponent { public void Draw() { sb.Draw(texture, Self.Get().Position, Color.White); } }
(这与GameServiceContainer
类似(即: Game.Services
属性)。这里的想法是没有ComposedObject
应该有每个接口的多个实例。如果你的接口数量很少, ComposedObject
甚至不需要使用列表 – 只需直接存储每个。但是你可以拥有实现多个接口的组件。)
现在,如果这对您来说过于冗长,也许您可以在ComposedObject
上添加一些便利属性(或使用扩展方法)来处理常见的数据,比如Position
,如下所示:
public Vector2 Position { get { return Get().Position; } }
然后你的绘图function可以简单地这样:
spriteBatch.Draw(texture, Self.Position, Color.White);