简体   繁体   English

以OO方式进行游戏设计

[英]Game design in an OO manner

I'm designing a simple game, which uses Java 2D and Newtonian physics. 我正在设计一个简单的游戏,它使用Java 2D和牛顿物理。 Currently my main "game loop" looks something like: 目前我的主要“游戏循环”看起来像:

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

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

When an entity is instructed to update itself it will adjust its velocity and position based on the current forces applied to it. 当指示实体自我更新时,它将根据施加于其上的当前力来调整其速度和位置。 However, I need entities to exhibit other behaviour; 但是,我需要实体展示其他行为; eg if a "bad guy" is shot by a player the entity should be destroyed and removed from the game world. 例如,如果玩家射杀了“坏人”,则该实体应该被摧毁并从游戏世界中移除。

My question : What is the best way to achieve this in an object oriented manner? 我的问题 :以面向对象的方式实现这一目标的最佳方法是什么? All examples I've seen so far incorporate the game loop into a God class called something like Game , which performs the steps: detect collisions, check-if-bad-guy-killed, check-if-player-killed, repaint , etc and encapsulates all the game state (lives remaining, etc). 到目前为止,我所看到的所有例子都将游戏循环合并到一个名为Game的God类中,它执行以下步骤: 检测碰撞,检查是否坏人杀死,检查是否被玩家杀死,重新绘制等等并封装所有游戏状态(剩余生命等)。 In other words, it's very procedural and all the logic is in the Game class . 换句话说,它是非常程序化的所有逻辑都在Game类中 Can anyone recommend a better approach? 有谁能推荐更好的方法?

Here are the options I've thought of so far: 以下是我到目前为止所考虑的选项:

  • Pass a GameContext to each entity from which the entity can remove itself if required or update the game state (eg to "not running" if the player is killed). GameContext传递给每个实体,如果需要,实体可以从中移除自身或更新游戏状态(例如,如果玩家被杀,则“不运行”)。
  • Register each GameEntity as a listener to the central Game class and take an event oriented approach; 将每个GameEntity注册为中心Game类的监听器,并采用面向事件的方法; eg a collision would result in a CollisionEvent being fired to the two participants in the collision. 例如,碰撞会导致CollisionEvent被发射到CollisionEvent中的两个参与者。

I have worked closely with two commercial game engines and they follow a similar pattern: 我与两个商业游戏引擎密切合作,他们遵循类似的模式:

  • Objects represent components or aspects of a game entity (like physical, renderable, whatever), rather than the whole entity. 对象代表游戏实体的组件或方面(如物理,可渲染,无论如何),而不是整个实体。 For each type of component there is a giant list of components, one for each entity instance that has the component. 对于每种类型的组件,都有一个巨大的组件列表,每个组件对应一个具有该组件的实体实例。

  • The 'game entity' type itself is just a unique ID. “游戏实体”类型本身只是一个唯一的ID。 Each giant list of components has a map to look up the component (if any exists) that corresponds to an entity ID. 每个巨大的组件列表都有一个映射,用于查找与实体ID对应的组件(如果存在)。

  • If a component requires an update it is called by a service or system object. 如果组件需要更新,则由服务或系统对象调用。 Each service is updated directly from the game loop. 每个服务都直接从游戏循环更新。 Alternatively, you could call services from a scheduler object which determines update order from a dependency graph. 或者,您可以从调度程序对象调用服务,该对象从依赖关系图确定更新顺序。

Here are the advantages of this approach: 以下是此方法的优点:

  • You can freely combine functionality without writing new classes for every combination or using complex inheritance trees. 您可以自由组合功能,而无需为每个组合编写新类或使用复杂的继承树。

  • There is almost no functionality that you can assume about all game entities that you could put in a game entity base class (what does a light have in common with a race car or a sky-box?) 几乎没有任何功能可以假设您可以放入游戏实体基类中的所有游戏实体(灯光与赛车或天空盒有什么共同之处?)

  • The ID-to-component look-ups might seem expensive, but services are doing most of the intensive work by iterating through all the components of a particular type. ID到组件的查找可能看起来很昂贵,但是服务通过迭代特定类型的所有组件来完成大部分密集工作。 In these cases, it works better to store all the data you need in a single tidy list. 在这些情况下,最好将所需的所有数据存储在一个整洁的列表中。

In one particular engine that I worked on, we decoupled the logic from the graphic representation and then had objects that would send messages for what they wanted to do. 在我工作的一个特定引擎中,我们将逻辑与图形表示分离,然后有对象发送消息以实现他们想要做的事情。 We did this so that we could have games exist on a local machine or networked and they were indistinguishable from each other from a code standpoint. 我们这样做是为了让我们可以在本地机器或网络上存在游戏,从代码的角度来看它们彼此无法区分。 (Command pattern) (命令模式)

We also had the actual physics modeling done in a separate object that could be changed on the fly. 我们还在一个可以即时更改的单独对象中完成了实际的物理建模。 This let us easily mess with gravity, etc. 这让我们很容易搞砸重力等。

We made heavy use of the event driven code (listener pattern), and lots and lots of timers. 我们大量使用事件驱动代码(监听器模式)和大量定时器。

For example, we had a base class for an object that was intersectable that could listened to the collision event. 例如,我们有一个可以交叉的对象的基类,可以监听碰撞事件。 We subclassed it into a health box. 我们将其分为健康箱。 On collision, if it was hit by a player entity, it sent a Command to the collider that it should gain health, sent a message to broadcast a sound to all that could hear it, deactivated collisions, activated the animation to remove the graphics from the scene graph, and set a timer to reinstantiate itself later. 在碰撞时,如果它被玩家实体击中,它会向碰撞器发送一个命令,它应该获得健康,发送消息向所有可以听到它的人广播声音,停用碰撞,激活动画以从中删除图形场景图,并设置一个计时器以便以后重新实例化。 It sounds complicated, but it really wasn't. 这听起来很复杂,但事实并非如此。

If I recall (and it's been 12 years), we had the abstract notion of scenes, so a game was a sequence of scenes. 如果我记得(已经12年了),我们就有了抽象的场景概念,所以游戏就是一系列场景。 When a scene was finished, an event was fired that would typically send a command take down the current scene and start another one. 当一个场景结束时,会触发一个事件,该事件通常会发送命令,取消当前场景并启动另一个场景。

I disagree that because you have a main Game class all the logic has to happen in that class. 我不同意,因为你有一个主要的Game类,所有的逻辑都必须在那个类中发生。

Over-simplification here mimicking your example just to make my point: 这里的过度简化模仿你的例子只是为了说明我的观点:

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()

Now you have a Bubble class: 现在你有一个Bubble类:

Bubble implements Drawable {

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

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

So, sure, there's a main loop that takes care of passing the messages around between the entities but the logic related to the collision between a Bubble and a Needle is definitely not in the main Game class. 所以,当然,有一个主循环负责传递实体之间的消息,但与Bubble和Needle之间的冲突相关的逻辑肯定不在主Game类中。

I'm pretty sure that even in your case all the logic related to the movement is not happening in the main class. 我很确定即使在你的情况下,所有与运动相关的逻辑都不会发生在主类中。

So I disagree with your statement, that you written in bold, that "all the logic happens in the main class". 所以我不同意你的陈述,你用粗体写的,“所有的逻辑都发生在主类中”。

This is simply not correct. 这根本不正确。

As for good design: if you can easily provide another "view" of your game (like, say, a mini-map) and if you can easily code a "frame-for-frame perfect replayer", then your design is probably not that bad (that is: by recording only the inputs and the time at which they happened, you should be able to recreate the game exactly as it was played. That's how Age of Empires, Warcraft 3, etc. make their replay: it's only the user inputs and the time at which they happened that is recorded [that's also why the replay files are typically so tiny]). 至于好的设计:如果你可以轻松提供你的游戏的另一个“视图”(比如说,迷你地图),如果你可以轻松编写“逐帧完美的重播”,那么你的设计可能不是那个糟糕的(也就是说:通过仅记录输入和它们发生的时间,你应该能够完全按照它的方式重新创建游戏。这就是帝国时代,魔兽争霸3等等的重播:它只是用户输入和记录它们发生的时间[这也是重播文件通常如此微小的原因])。

I write my own engines (raw & dirty), but a pre-built engine that has a decent OO model, is Ogre . 我编写自己的引擎(原始和肮脏),但是预制的引擎有一个像样的OO模型,是Ogre I'd recommend taking a look at it (it's object model / API). 我建议看看它(它的对象模型/ API)。 The node assignment is a tad funky, but it makes perfect sense the more you look at it. 节点分配有点时髦,但是你看的越多就越有意义。 It's also extremely well documented with a ton of working game examples. 大量的工作游戏示例也记录得非常好。

I've learned a couple tricks from it myself. 我自己从中学到了一些技巧。

I'm only posting this as an answer because I'm not able to comment on other posts yet. 我只是将此作为答案发布,因为我还无法对其他帖子发表评论。

As an addendum to Evan Rogers excellent answer you might be interested in these articles: 作为Evan Rogers的补遗,您可能会对这些文章感兴趣:

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

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

This game was an experiment in keeping the model and view separate. 这个游戏是一个实验,保持模型和视图分开。 It uses the observer pattern to notify the view(s) of changes in the game's state, but events would perhaps offer a richer context. 它使用观察者模式来通知视图中游戏状态的变化,但事件可能会提供更丰富的上下文。 Originally, the model was driven by keyboard input, but the separation made it easy to add timer-driven animation. 最初,该模型由键盘输入驱动,但分离使得添加定时器驱动的动画变得容易。

Addendum: You need to keep the game's model separate, but you can re-factor that model into as many classes as required. 附录:您需要将游戏模型分开,但您可以根据需要将该模型重新分解为多个类。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM