代码之家  ›  专栏  ›  技术社区  ›  Adamski

面向对象的游戏设计

  •  21
  • Adamski  · 技术社区  · 15 年前

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

    do {
      for (GameEntity entity : entities) {
        entity.update(gameContext);
      }
    
      for (Drawable drawable : drawables) {
        drawable.draw(graphics2d);
      }
    } while (gameRunning);
    

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

    我的问题 :以面向对象的方式实现这一点的最佳方法是什么?到目前为止,我看到的所有例子都将游戏循环合并到一个叫做 Game ,执行以下步骤: 检测碰撞,检查坏人是否被杀,检查玩家是否被杀,重新喷漆 等,并封装所有游戏状态(剩余生命等)。换句话说,这是非常 程序的 所有的逻辑都在游戏课上 . 有人能推荐更好的方法吗?

    以下是迄今为止我想到的选择:

    • 通过A GameContext 如果需要,实体可以从中移除自身或更新游戏状态的每个实体(例如,如果玩家被杀,则为“不运行”)。
    • 注册每个 GameEntity 作为中央的听众 游戏 类并采用面向事件的方法;例如,碰撞将导致 CollisionEvent 向碰撞中的两名参与者开火。
    6 回复  |  直到 14 年前
        1
  •  14
  •   Evan Rogers    15 年前

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

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

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

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

    这种方法的优点如下:

    • 您可以自由组合功能 没有为每一个 组合或使用复杂继承 树。

    • 几乎没有任何功能 你可以假设所有的游戏 可以放在游戏中的实体 实体基类(什么是灯光 与赛车或 天空盒子?)

    • 组件查找的ID似乎 很贵,但服务很好 大部分密集的工作 遍历所有组件 属于特定类型。在这些情况下, 最好是存储所有的数据 你需要一张整洁的单子。

        2
  •  6
  •   plinth    15 年前

    在我所研究的一个特定的引擎中,我们将逻辑与图形表示分离,然后拥有对象,这些对象将发送消息,以满足他们的需要。我们这样做是为了让游戏存在于本地机器或网络上,从代码的角度看,它们是不可区分的。(命令模式)

    我们还在一个单独的对象中进行了实际的物理建模,该对象可以随时更改。这让我们很容易与重力等混淆。

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

    例如,我们有一个对象的基类,该对象是可相交的,可以监听碰撞事件。我们把它分为健康盒子。在碰撞时,如果它被一个玩家实体击中,它会向碰撞器发送一个命令,让碰撞器恢复健康,发送一条消息,向所有能听到的人广播一个声音,解除碰撞,激活动画,从场景图中删除图形,并设置一个计时器,稍后重新进行自我验证。听起来很复杂,但事实并非如此。

    如果我记得(已经12年了),我们对场景有着抽象的概念,所以游戏就是一系列场景。当一个场景完成时,一个事件被激发,它通常会发送一个命令,取下当前场景并启动另一个场景。

        3
  •  3
  •   SyntaxT3rr0r    15 年前

    我不同意,因为你有一个主要的游戏类,所有的逻辑都必须发生在那个类。

    为了表达我的观点,这里过于简单地模仿你的例子:

    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 implements Drawable {
    
    handle( Needle needle ) {
        if ( needle collide with us ) {
            exploded = true;
        }
    }
    
    draw (...) {
       if (!exploded) {
          draw();
         }
      }
    }
    

    当然,有一个主循环负责在实体之间传递消息,但是与气泡和指针碰撞相关的逻辑肯定不在主游戏类中。

    我很确定,即使在你的例子中,所有与运动相关的逻辑都不会发生在主类中。

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

    这是完全不正确的。

    至于好的设计:如果你能很容易地为你的游戏提供另一个“视图”(比如说,一个迷你地图),如果你能很容易地为“完美的帧回放器”编写代码,那么你的设计可能并不差(也就是说,通过只记录输入和它们发生的时间,你应该能够准确地重新创建游戏。这就是帝国时代、魔兽争霸3等等如何重放的:只有用户输入和他们发生的时间才会被记录下来[这也是重放文件通常很小的原因]。

        4
  •  2
  •   pestilence669    15 年前

    我自己写的引擎(原始引擎和脏引擎),但是一个预建的引擎有一个体面的OO模型,是 Ogre . 我建议您看看它(它是对象模型/API)。节点分配有点奇怪,但是你看得越多就越有意义。它还非常好地记录了大量的工作游戏示例。

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

        5
  •  2
  •   Max Dohme    14 年前

    我只是把这个作为答案发布,因为我还不能对其他帖子发表评论。

    作为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/

        6
  •  1
  •   trashgod    15 年前

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

    附录:你需要将游戏的模型分开,但是你可以根据需要将模型重新考虑到尽可能多的类中。