游戏架构

18

我有一个关于XNA游戏的问题,但也是未来游戏的通用问题。我正在制作一款乒乓球游戏,但不确定应该在哪里更新内容,所以我会更好地解释我的意思。我有一个类游戏、球拍和球,例如,我想要验证球与屏幕边界或挡板之间的碰撞,但我遇到了两种方法:

高级方法 - 使球拍和球属性公共,在游戏更新时检查碰撞?

低级方法 - 我将我需要的所有信息(屏幕限制和球拍信息)提供给球类(通过参数或公共静态类),并在球更新时检查碰撞?

我想我的问题更普遍的方式是:

一个对象需要知道如何更新和绘制自己,即使从某种程度上依赖于更高级别的依赖项也是这样吗?

还是

更好的方法是在Game.Update或Game.Draw中在更高的级别进行处理,或使用管理器简化代码?

我认为这是适用于每个游戏的游戏逻辑模型问题。如果我没有表达清楚,请随时提问。


1
我认为你所说的“逻辑”实际上是指“架构”。 - Andrew Russell
2
我一直在引用这个问题,所以我打算自己修正标题... - Andrew Russell
2
这更适合于http://gamedev.stackexchange.com/。 - Alex
4个回答

48

回答你的问题有难度,因为你同时在问:“我现在应该为乒乓球做什么”和“将来在一般游戏中应该做什么”。


要制作乒乓球游戏,甚至不需要Ball和Paddle这样的类,因为它们基本上只是位置。只需将以下代码放入您的Game类中:

Vector2 ballPosition, ballVelocity;
float leftPaddlePosition, rightPaddlePosition;

然后只需在游戏的 UpdateDraw 函数中以任何适合您的顺序更新和绘制它们。简单!


但是,假设您想创建多个球,并且球具有许多属性(位置、速度、旋转、颜色等):您可能希望创建一个Ball类或结构体,使其可以实例化(对于挡板也是一样)。甚至可以将某些函数移动到该类中,使它们成为自包含的函数(Draw函数就是一个很好的例子)。

但要保持设计概念相同-所有对象之间的交互处理(即游戏玩法)都发生在Game类中。

如果您只有两个或三个不同的游戏元素(或类),那么这很好。


然而,让我们假设有一个更复杂的游戏。我们拿基本的乒乓球游戏来举例,加入一些弹球的元素,例如多球和玩家控制的翻板。让我们从贪吃蛇中添加一些元素,例如一个由AI控制的“蛇”,以及一些可以被球或蛇撞击的捡起物品。为了增加趣味,让我们说挡板也可以像太空侵略者中那样发射激光,并且激光雷达根据撞击的物体不同有不同的效果。

天哪,这是一堆复杂的交互!我们该如何应对?我们无法将所有内容都放在Game类中!

简单!我们创建一个接口(或抽象类或虚拟类),每个游戏世界中的“物体”(或“角色”)都将从中派生。以下是一个示例:

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 的原因。)

拥有一个共同的界面使得游戏只需要与角色交流 - 而不需要了解游戏中的每个类型。它只需处理每个类型都具有的常见事项 - 碰撞检测,绘制,更新,加载,卸载等。

一旦你进入角色,就可以开始关注特定类型的角色。例如,这可能是 Paddle 中的一个方法:

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

现在,我想让球通过挡板弹起来,但这纯粹是个人口味问题。你可以反过来做。

最终你应该能够将所有的演员都放在一个大列表中,在游戏中简单迭代它们。

实际上,出于性能或代码简洁性等原因,您可能会拥有多个不同类型的演员列表。这没问题-但总的来说,尽量坚持游戏只知道通用演员的原则。

演员还可能希望查询存在哪些其他演员,出于各种原因。因此,给每个演员一个对Game的引用,并使演员列表在Game上公开(当您编写游戏代码并且它是您自己的内部代码时,没有必要非常严格关注public/private)。


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

这在这篇文章中有详细描述。我在这个答案中有一个简单的例子。如果您发现自己的单个演员接口开始变成更多的抽象类“树”,那么这是一个适当的下一步。


很棒的回复 - “不过让我们假设一个更复杂的游戏。” 这听起来是个很酷的游戏... 我们正在开发类似的东西 :) - Harag

2
您还可以选择开始思考游戏中不同组件需要如何相互交流。
球和球拍都是游戏中的对象,在这种情况下,它们是可渲染、可移动的对象。球拍具有以下标准:
1. 只能上下移动 2. 球拍固定在屏幕的一侧或底部 3. 球拍可能由用户控制(1对电脑或1对1) 4. 球拍可以被渲染 5. 球拍只能移到屏幕的底部或顶部,不能越过其边界
球的标准如下:
1. 不能离开屏幕的边界 2. 可以被渲染 3. 根据球拍上的击打位置,您可以间接控制它(一些简单的物理学) 4. 如果球落在球拍后面,游戏该回合结束 5. 游戏开始时,通常将球附加到输掉的人的球拍上。
识别共同标准,您可以提取一个接口。
public interface IRenderableGameObject
{
   Vector3 Position { get; set; }
   Color Color { get; set; }
   float Speed { get; set; }
   float Angle { get; set; }
}

你还需要一些游戏物理。
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(...);
}

这并不是全部逻辑,但它应该让你了解要寻找什么,因为你正在构建一组库和函数,可以用来确定会发生什么、可能发生什么以及在这些事情发生时你需要如何行动。

另外,通过查看这个内容,你可以对其进行改进和重构,使其成为更好的设计。

了解你的领域并将你的想法写下来。 没有计划就是计划失败。


1

你可以把球和挡板看作游戏的组件,而XNA提供了基类GameComponent,它具有可覆盖的Update(GameTime gameTime)方法来执行逻辑。此外,还有DrawableGameComponent类,它带有自己的可覆盖Draw方法。每个GameComponent类也都有一个Game属性,它保存创建它们的游戏对象。在那里,你可以添加一些服务,让你的组件可以自行获取信息。

你想采用哪种方法,是使用一个“主”对象来处理每个交互,还是将信息提供给组件并让它们自行反应,完全取决于你。在较大的项目中,后一种方法更受欢迎。此外,这也是处理事物的面向对象方式,为每个实体提供其自己的更新和绘制方法。

0

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接