在评论中,我发布了我的答案旧答案的精简版:
使用可以DrawableGameComponent
将您锁定到一组方法签名和特定的保留数据模型中。这些一开始通常是错误的选择,并且锁定严重阻碍了代码的未来发展。
在这里,我将通过发布当前项目中的一些代码来说明为什么会这样。
维护游戏世界状态的根对象具有如下所示的Update
方法:
void Update(UpdateContext updateContext, MultiInputState currentInput)
有多个进入点Update
,具体取决于游戏是否在网络上运行。例如,可以将游戏状态回滚到先前的时间点,然后重新进行模拟。
还有一些其他类似更新的方法,例如:
void PlayerJoin(int playerIndex, string playerName, UpdateContext updateContext)
在游戏状态内,有一个要更新的演员列表。但这不仅仅是一个简单的Update
调用。在处理物理学之前和之后,还有其他的过程。还有一些花哨的东西可以用来延迟演员的产卵/破坏以及关卡转换。
除了游戏状态外,还有一个UI系统需要独立管理。但是,UI中的某些屏幕也需要能够在网络环境中运行。
对于绘画,由于游戏的性质,有一个自定义的排序和剔除系统,它从以下方法中获取输入:
virtual void RegisterToDraw(SortedDrawList sortedDrawList, Definitions definitions)
virtual void RegisterShadow(ShadowCasterList shadowCasterList, Definitions definitions)
然后,一旦确定要绘制的内容,就会自动调用:
virtual void Draw(DrawContext drawContext, int tag)
该tag
参数是必需的,因为某些角色可以保留其他角色-因此需要能够在所保留的角色的上方和下方进行绘制。分拣系统可确保以正确的顺序进行。
还可以绘制菜单系统。还有一个分层系统,用于在HUD和游戏周围绘制UI。
现在将这些要求与DrawableGameComponent
提供的内容进行比较:
public class DrawableGameComponent
{
public virtual void Update(GameTime gameTime);
public virtual void Draw(GameTime gameTime);
public int UpdateOrder { get; set; }
public int DrawOrder { get; set; }
}
没有一种合理的方法可以从一个系统使用DrawableGameComponent
到上述系统。(对于复杂程度不高的游戏,这并不是不寻常的要求)。
同样,非常重要的一点是要注意,这些要求并非只是在项目开始时就出现了。他们经过几个月的开发工作而演变。
人们通常会做的,如果他们沿着这DrawableGameComponent
条路走,那就是开始建立在骇客之上:
他们没有添加方法,而是对其自身的基本类型进行了一些难看的向下转换(为什么不直接使用它呢?)。
他们没有替换用于排序和调用Draw
/ 的代码,而是Update
做一些非常慢的事情来即时更改订购号。
最糟糕的是:它们不是在方法中添加参数,而是在不是堆栈的内存位置开始“传递”“参数”(Services
对此很普遍)。这是令人费解的,容易出错的,缓慢的,易碎的,很少是线程安全的,并且如果添加网络,制作外部工具等可能会成为问题。
这也使代码的重用变得更加困难,因为现在您的依赖项隐藏在“组件”内部,而不是在API边界发布。
或者,他们几乎看到了这一切有多么疯狂,然后开始做“经理” DrawableGameComponent
来解决这些问题。除了他们在“经理”边界仍然存在这些问题。
这些“管理器”始终可以轻松地按所需顺序用一系列方法调用替换。其他任何事情都过于复杂。
当然,一旦您开始使用这些hack,一旦意识到您需要的东西超出了所能提供的范围,那么需要采取真正的工作来撤消这些hack DrawableGameComponent
。您将被“ 锁定 ”。诱惑通常是继续堆积在骇客上-实际上,这会使您陷入困境,并将您可以制作的游戏类型限制为相对简单的游戏。
很多人似乎已经达到了这一点并产生了内在的反应-可能是因为他们使用DrawableGameComponent
并且不想接受“错误”。因此,他们抓住了至少有可能使它“起作用” 这一事实。
当然可以使它 “起作用”!我们是程序员-使事情在非常规约束下工作实际上是我们的工作。但是这种约束不是必要的,有用的或合理的...
什么是特别flabbergasting的是,这是惊人的微不足道的侧步这整个问题。在这里,我什至会给您代码:
public class Actor
{
public virtual void Update(GameTime gameTime);
public virtual void Draw(GameTime gameTime);
}
List<Actor> actors = new List<Actor>();
foreach(var actor in actors)
actor.Update(gameTime);
foreach(var actor in actors)
actor.Draw(gameTime);
(添加按DrawOrder
和排序UpdateOrder
很简单。一种简单的方法是维护两个列表并使用List<T>.Sort()
。处理Load
/ 也很简单UnloadContent
。)
该代码实际上是什么并不重要。重要的是我可以对其进行微不足道的修改。
当我意识到我需要一个与之不同的计时系统gameTime
,或者我需要更多的参数(或者其中UpdateContext
包含约15个东西的整个对象),或者我需要一个完全不同的操作顺序进行绘制,或者我需要一些额外的方法时对于不同的更新通行证,我可以继续进行。
而且我可以立即做到。我不需要费心挑选DrawableGameComponent
代码。或更糟糕的是:以某种方式对其进行破解,这将在下次出现这种需求时变得更加困难。
实际上,只有一种情况是使用它的一个不错的选择DrawableGameComponent
:那就是如果您实际上是为XNA 制作和发布拖放组件式的中间件。这是其最初的目的。我要补充一点- 没有人在做!
(类似地,只有Services
在为制作自定义内容类型时才有意义ContentManager
。)