21

我正在设计一个简单的游戏,它使用 Java 2D 和牛顿物理学。目前我的主要“游戏循环”看起来像:

do {
  for (GameEntity entity : entities) {
    entity.update(gameContext);
  }

  for (Drawable drawable : drawables) {
    drawable.draw(graphics2d);
  }
} while (gameRunning);

当一个实体被指示更新自身时,它将根据施加在它身上的当前力调整其速度和位置。但是,我需要实体表现出其他行为;例如,如果玩家射杀了“坏人”,则该实体应该被摧毁并从游戏世界中移除。

我的问题:以面向对象的方式实现这一目标的最佳方法是什么?到目前为止,我看到的所有示例都将游戏循环合并到一个名为 like 的 God 类Game中,该类执行以下步骤:检测碰撞、检查如果坏人杀死、检查如果玩家杀死、重绘等封装所有游戏状态(剩余生命等)。换句话说,它是非常程序化的,所有的逻辑都在 Game 类中。谁能推荐一个更好的方法?

以下是我到目前为止想到的选项:

  • 将 a 传递GameContext给每个实体,如果需要,该实体可以从中移除自己或更新游戏状态(例如,如果玩家被杀死,则“未运行”)。
  • 将每个注册GameEntity为中心Game类的侦听器并采用面向事件的方法;例如,碰撞将导致CollisionEvent向碰撞中的两个参与者开枪。
4

6 回答 6

14

我与两个商业游戏引擎密切合作,它们遵循类似的模式:

  • 对象代表游戏实体的组件或方面(如物理、可渲染等),而不是整个实体。对于每种类型的组件,都有一个巨大的组件列表,每个实体实例都有一个组件列表。

  • “游戏实体”类型本身只是一个唯一的 ID。每个庞大的组件列表都有一个映射来查找与实体 ID 对应的组件(如果存在)。

  • 如果组件需要更新,则由服务或系统对象调用。每个服务都直接从游戏循环中更新。或者,您可以从调度程序对象调用服务,该对象根据依赖关系图确定更新顺序。

以下是这种方法的优点:

  • 您可以自由组合功能,而无需为每个组合编写新类或使用复杂的继承树。

  • 对于可以放入游戏实体基类的所有游戏实体,几乎没有任何功能可以假设(灯光与赛车或天空盒有什么共同点?)

  • ID 到组件的查找可能看起来很昂贵,但服务通过迭代特定类型的所有组件来完成大部分密集工作。在这些情况下,最好将所需的所有数据存储在一个整洁的列表中。

于 2010-02-10T16:29:56.453 回答
6

在我使用的一个特定引擎中,我们将逻辑与图形表示分离,然后让对象为他们想要做的事情发送消息。我们这样做是为了让游戏存在于本地机器或网络上,并且从代码的角度来看它们彼此无法区分。(命令模式)

我们还在一个单独的对象中完成了实际的物理建模,该对象可以动态更改。这让我们很容易搞砸重力等。

我们大量使用了事件驱动代码(侦听器模式)和大量计时器。

例如,我们有一个可相交对象的基类,它可以监听碰撞事件。我们将其细分为健康箱。在碰撞时,如果它被玩家实体击中,它会向对撞机发送一条命令,让它恢复生命值,发送一条消息以向所有能听到它的人广播声音,停用碰撞,激活动画以移除图形场景图,并设置一个计时器以稍后重新实例化。听起来很复杂,但事实并非如此。

如果我记得(已经有 12 年了),我们有场景的抽象概念,所以游戏就是一系列场景。当一个场景完成时,会触发一个事件,该事件通常会发送一个命令取消当前场景并开始另一个场景。

于 2010-02-09T19:02:20.637 回答
3

我不同意,因为你有一个主 Game 类,所有逻辑都必须在该类中发生。

这里过度简化模仿你的例子只是为了说明我的观点:

mainloop:
  moveEntities()
  resolveCollisions()   [objects may "disappear"/explode here]
  drawEntities()        [drawing before or after cleanEntitites() ain't an issue, a dead entity won't draw itself]
  cleanDeadEntities()

现在你有了一个 Bubble 类:

Bubble implements Drawable {

handle( Needle needle ) {
    if ( needle collide with us ) {
        exploded = true;
    }
}

draw (...) {
   if (!exploded) {
      draw();
     }
  }
}

所以,当然,有一个主循环负责在实体之间传递消息,但与 Bubble 和 Needle 之间的碰撞相关的逻辑绝对不在主 Game 类中。

我很确定即使在您的情况下,与运动相关的所有逻辑都不会发生在主类中。

所以我不同意你用粗体写的“所有逻辑都发生在主类中”的说法。

这根本不正确。

至于好的设计:如果您可以轻松地提供游戏的另一个“视图”(例如,迷你地图)并且如果您可以轻松编写“逐帧完美播放器”,那么您的设计可能不是太糟糕了(也就是说:通过只记录输入和它们发生的时间,你应该能够完全按照它的播放方式重新创建游戏。这就是帝国时代、魔兽争霸 3 等重播的方式:它只是用户输入和它们发生的时间被记录下来[这也是回放文件通常很小的原因])。

于 2010-02-09T19:05:47.760 回答
2

我编写自己的引擎(原始和肮脏),但是具有不错的 OO 模型的预构建引擎是Ogre。我建议看一下它(它是对象模型/ API)。节点分配有点古怪,但越看越明白。它还包含大量有效的游戏示例,记录得非常好。

我自己也从中学到了一些技巧。

于 2010-02-09T19:10:06.977 回答
2

我只是将其发布为答案,因为我还无法评论其他帖子。

作为 Evan Rogers 出色答案的附录,您可能对这些文章感兴趣:

http://www.gamasutra.com/blogs/MeganFox/20101208/6590/Game_Engines_101_The_EntityComponent_Model.php

http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/

于 2011-02-23T00:27:19.110 回答
1

这个游戏是一个保持模型和视图分离的实验。它使用观察者模式来通知视图游戏状态的变化,但事件可能会提供更丰富的上下文。最初,该模型是由键盘输入驱动的,但这种分离使得添加计时器驱动的动画变得很容易。

附录:您需要将游戏的模型分开,但您可以根据需要将该模型重新分解为多个类。

于 2010-02-09T19:07:29.533 回答