17

我有一个关于我正在制作的 XNA 游戏的问题,但这也是未来游戏的通用问题。我正在制作 Pong 游戏,但我不知道在哪里更新什么,所以我会更好地解释我的意思。我有一个类 Game, Paddle 和 Ball,例如,我想验证球与屏幕限制或桨之间的碰撞,但我遇到了两种方法来做到这一点:

更高级别的方法- 公开桨和球的属性并在 Game.Update 上检查碰撞?

较低级别的方法- 我将我需要的所有信息(屏幕限制和桨信息)提供给球类(通过参数,或在公共公共静态类中)并在 Ball.Update 上检查碰撞?

我想我的问题以更通用的方式是:

一个对象是否需要知道如何更新和绘制自己,甚至需要以某种方式提供给它们的更高级别的依赖项?

或者

更好地在 Game.Update 或 Game.Draw 中的更高级别处理它,或者使用管理器来简化代码?

我认为这是一个适用于所有游戏的游戏逻辑模型问题。我不知道我的问题是否清楚,如果没有,请随时提问。

4

4 回答 4

47

回答您的问题的困难部分是您同时问:“我现在应该做什么,对于 Pong”和“我以后应该做什么,在一些通用游戏中”。


要制作 Pong,您甚至不需要 Ball 和 Paddle 课程,因为它们基本上只是位置。只需在您的 Game 类中添加这样的内容:

Vector2 ballPosition, ballVelocity;
float leftPaddlePosition, rightPaddlePosition;

Update然后只需在游戏和Draw功能中以适合您的顺序更新和绘制它们。简单的!


但是,假设您要创建多个球,并且球具有许多属性(位置、速度、旋转、颜色等):您可能想要创建一个Ball可以实例化的类或结构(桨也是如此)。您甚至可以将一些函数移动到它们自包含的类中(Draw函数就是一个很好的例子)。

但是保持设计概念相同——所有对象到对象的交互处理(即:游戏玩法)都发生在你的Game类中。

如果您有两个或三个不同的游戏元素(或类),这一切都很好。


然而,让我们假设一个更复杂的游戏。让我们以基本的乒乓球游戏为例,添加一些弹球元素,如多人球和玩家控制的脚蹼。让我们添加一些来自 Snake 的元素,假设我们有一条 AI 控制的“蛇”以及一些球或蛇可以击中的拾取物体。为了更好地衡量,假设桨也可以像太空侵略者中那样发射激光,并且激光螺栓根据它们击中的东西做不同的事情。

天哪,这是一个巨大的互动混乱!我们将如何应对?我们不能把它全部放在游戏中!

简单的!我们创建了一个接口(或抽象类或虚拟类),我们游戏世界中的每个“事物”(或“演员”)都将从中派生。这是一个例子:

interface IActor
{
    void LoadContent(ContentManager content);
    void UnloadContent();

    void Think(float seconds);
    void UpdatePhysics(float seconds);

    void Draw(SpriteBatch spriteBatch);

    void Touched(IActor by);

    Vector2 Position { get; }
    Rectangle BoundingBox { get; }
}

(这只是一个例子。没有适用于所有游戏的“一个真正的演员界面”,你需要自己设计。这就是我不喜欢的原因DrawableGameComponent。)

拥有一个通用界面允许 Game 只讨论 Actors - 而不是需要了解游戏中的每一种类型。只剩下要做每种类型通用的事情了——碰撞检测、绘图、更新、加载、卸载等。

一旦你演员中,你可以开始担心特定类型的演员。例如,这可能是一个方法Paddle

void Touched(IActor by)
{
    if(by is Ball)
         ((Ball)by).BounceOff(this.BoundingBox);
    if(by is Snake)
         ((Snake)by).Kill();
}

现在,我喜欢让球被 Paddle 弹起,但这真的是一个品味问题。你可以反过来做。

最后,您应该能够将所有演员粘贴在一个大列表中,您可以在 Game 中简单地遍历该列表。

实际上,出于性能或代码简单性的原因,您最终可能会拥有多个不同类型的参与者列表。这没关系 - 但总的来说,尽量坚持 Game 只知道通用演员的原则。

演员也可能出于各种原因想要查询其他演员的存在。所以给每个actor一个Game的引用,并在Game上公开actor列表(当你编写游戏代码时,不需要对public/private非常严格,它是你自己的内部代码。)


现在,您甚至可以更进一步,拥有多个接口。例如:一种用于渲染,一种用于脚本和AI,一种用于物理等。然后有多种实现可以组合成对象。

这在本文中有详细描述。我在这个答案中有一个简单的例子。如果您开始发现您的单个参与者接口开始变成更多抽象类的“树”,那么这是一个合适的下一步。

于 2011-03-28T13:51:36.147 回答
2

您还可以选择开始考虑游戏的不同组件需要如何相互通信。

Ball 和 Paddle 都是游戏中的对象,在本例中是可渲染、可移动的对象。桨有以下标准

它只能上下移动

  1. 桨叶固定在屏幕的一侧,或底部
  2. Paddle 可能由用户控制(1 对计算机或 1 对 1)
  3. 桨可以渲染
  4. 桨只能移动到屏幕的底部或顶部,它不能越过它的边界

球具有以下标准

它不能离开屏幕的边界

  1. 可以渲染
  2. 根据它在桨上的位置,您可以间接控制它(一些简单的物理学)
  3. 如果它落后于桨,则该轮结束
  4. 比赛开始时,球一般附在输者的球拍上。

确定可以提取接口的通用标准

public interface IRenderableGameObject
{
   Vector3 Position { get; set; }
   Color Color { get; set; }
   float Speed { get; set; }
   float Angle { get; set; }
}

你还有一些 GamePhysics

public interface IPhysics
{
    bool HasHitBoundaries(Window window, Ball ball);
    bool HasHit(Paddle paddle, Ball ball);
    float CalculateNewAngle(Paddle paddleThatWasHit, Ball ball);
}

然后是一些游戏逻辑

public interface IGameLogic
{
   bool HasLostRound(...);
   bool HasLostGame(...);
}

这不是所有的逻辑,但它应该让您了解要查找的内容,因为您正在构建一组库和函数,您可以使用它们来确定将要发生的事情和可能发生的事情以及您需要如何当这些事情发生时采取行动。

此外,查看此内容,您可以对其进行改进和重构,使其成为更好的设计。

了解您的领域并写下您的想法。不计划就是计划失败

于 2011-03-28T14:19:52.417 回答
1

您可以将球和桨视为游戏的一个组件,XNA 为您提供了基类GameComponent,该基类有一个Update(GameTime gameTime)您可以重写以执行逻辑的方法。此外,还有一个DrawableGameComponent类,它带有自己Draw的重写方法。每个GameComponent类还有一个Game属性来保存创建它们的游戏对象。在那里,您可以添加一些服务,您的组件可以使用这些服务自行获取信息。

您想要采用哪种方法,或者拥有一个处理每次交互的“主”对象,或者向组件提供信息并让它们自己做出反应,这完全取决于您。后一种方法在较大的项目中是首选。此外,这将是面向对象的处理方式,为每个实体提供自己的 Update 和 Draw 方法。

于 2011-03-28T12:21:35.003 回答
0

我同意安德鲁所说的。我也在学习 XNA,在我的课上,例如你的球课。我至少有一个 Update(gametime) 方法和一个 Draw() 方法。通常也是 Initialize()、Load()。然后从主游戏类中,我将从它们各自的表亲那里调用这些方法。这是在我了解 GameComponent 之前。这是一篇关于您是否应该使用它的好文章。 http://www.nuclex.org/blog/gamedev/100-to-gamecomponent-or-not-to-gamecomponent

于 2011-03-28T14:17:03.700 回答