如何组织游戏代码以适应 MVC 模式?

Posted

技术标签:

【中文标题】如何组织游戏代码以适应 MVC 模式?【英文标题】:How can you organize the code for a game to fit the MVC pattern? 【发布时间】:2010-10-08 00:05:51 【问题描述】:

我是一名大学新生,正在攻读计算机科学学位...过去几年我编写了很多程序,但最近我对组织代码、设计模式、语言差异的理论思想有了更多的了解等。

我有一个 Java 课程,因此我放弃了 C++ 研究/开发,转而使用 Java 和 JOGL(Java OpenGL)。太棒了!但这不是重点。

我想做一个小型角色扮演游戏,但这个问题确实适用于任何类型的游戏。您如何以结构化的方式组织游戏对象,例如模型-视图-控制器模式?它看起来是一个了不起的模式,被广泛使用并且很有意义,但是我在弄清楚如何实现它时遇到了麻烦。

例如,我需要跟踪一个 GL 对象以绘制到屏幕上。我必须有实现 MouseListener、MouseMotionListener、MouseWheelListener 和 KeyListener 的类(或一个类,一个多合一的输入管理器)。而且我必须将我的游戏数据放在所有这些不同的类都可以访问和修改它的地方;如果有人按下键盘上的按钮,输入管理类需要以某种方式执行该键映射到的操作;当需要绘制一个框架时,图形类需要找到一种方法来循环遍历所有不同的“事物”并将它们全部绘制出来。

还有我最大的问题,GUI;它与这一切有什么关系?它有点像输入,但不完全是,它需要从实际游戏模拟中设置和获取数据片段......而且更复杂的是,如果我决定尝试添加网络,它(类似于 GUI ) 还需要访问大量数据以进行修改和读取...

哦,我只是一头雾水。我不知道如何让所有这些以面向对象的方式协同工作......编写明显符合模式的东西很容易,但是当你有大量发生的事情都与一个游戏循环相关时,相互修改还有游戏数据等等,……我什至都不知道了。也许我只是在做一个比实际更大的交易。

有没有其他人有这种感觉?请说明我的情况,这样我就可以少花时间担心和不知道从哪里开始!

编辑:找到了一个很好的图表,可以帮助我弄清楚这一切... 来源:(当心,PS 文件!)http://www.tucs.fi/publications/attachment.php?fname=TR553.ps.gz

Edit2:我也喜欢这个人对他如何计划他的 MVC 游戏的解释:http://interactivesection.wordpress.com/2007/11/19/dum-de-dum-drum-my-first-mvc-game-development/

Edit3:另一篇很棒的文章! http://dewitters.koonsolo.com/gamemvc.html

【问题讨论】:

【参考方案1】:

将模型视为一种游戏 API 可能会对您有所帮助。如果游戏一开始就没有 UI,你的游戏会沦落到什么地步?你提到你想到的是一个角色扮演游戏,所以在这种情况下,你可以想象让玩家角色、他/她的库存、法术、能力、NPC,甚至地图和战斗规则之类的东西都成为模型的一部分.这就像大富翁的规则和片段,没有最终游戏如何显示或用户将如何与之交互的细节。它就像 Quake 一样,是一组抽象的 3D 对象在关卡中移动,计算了交叉点和碰撞等内容,但没有渲染、阴影或声音效果。

通过将所有这些都放入模型中,游戏本身现在与 UI 无关。它可以连接到像 Rogue 游戏那样的 ASCII 文本界面,或类似于 Zork 的命令行 UI,或基于 Web 或 3D UI。根据游戏机制,其中一些 UI 可能非常适合,但它们都是可能的。

View 层是 UI 依赖层。它反映了您所使用的 UI 的特定选择,并将非常专注于该技术。它可能负责读取模型的状态并以 3D、ASCII 或图像和网页的 HTML 格式绘制模型。它还负责显示玩家与游戏互动所需的任何控制机制。

Controller 层是两者之间的粘合剂。它不应该包含任何实际的游戏逻辑,也不应该负责驱动 View 层。相反,它应该将在视图层中执行的操作(单击按钮、单击屏幕区域、操纵杆操作等)转换为对模型执行的操作。例如,掉落物品、攻击 NPC 等等。它还负责收集数据并进行任何转换或处理,以使 View 层更容易显示它。

现在,我在上面描述的方式好像有一个非常独特的事件序列驱动游戏,这可能只适合网页游戏。那是因为这就是我最近花费的时间。在一个不受用户请求和服务器响应驱动的游戏中(例如,在用户机器上运行的游戏),您可能希望确保模型层很好地实现了观察者模式。例如,如果由于时间流逝而在模型中发生操作,那么您可能不希望视图层不断轮询模型以获取更新。相反,通过使用观察者模式,模型可以在模型对象发生更改时通知任何观察者。这又可以用来提示立即更新视图以反映更改。

然后,如果 60 秒过去导致玩家的基地发生一些维修,基地可以进行维修并立即通知任何附属于它的观察者基地已更新。视图可以作为观察者附加,并注意它需要重新显示基础,因为它的状态已经改变。通知本身可能包含足够的信息来更新视图,或者它可能必须转身并咨询模型才能更新,但结果将是相同的。

【讨论】:

【参考方案2】:

你们相处得很好。基本上,问自己一个问题“如果我必须更改程序的某些部分,哪些代码会更改?”

如果它会在不改变基本数据的情况下改变它的外观,那么它就在视图中。如果是可以通过多种方式查看的数据,那就是模型。如果这是你的演奏方式,那么它就是控制。

所以,如果你是用两把刀片还是一把刀片来画一把“斧头”,那就是视图。如果是你用斧头造成的生命值伤害,那就是模型。如果是通过键入“s”或右键单击来挥动斧头,它就是控制。

【讨论】:

【参考方案3】:

我和你在一起我记得当我第一次发现 MVC 时,我试图把所有东西都塞进去。我确实制作了一个使用 MVC 模式的游戏。但我后来发现,我所做的太过分了。我试图将我创建的几乎每个类都归入 MVC 中的一个类别。

我的建议是阅读四人组的“设计模式”。除了 MVC,还有很多有用的模式。有时使用 MVC 根本没有任何意义。特别是对于游戏,我不确定 MVC 是否是个好主意。原因是您不想以多种不同的方式(视图)显示游戏对象,但您想为许多不同类型的游戏对象重用绘图代码。

对于我自己的 2D 游戏引擎,我非常积极地使用了 strategy 模式。游戏对象,比如玩家和我称之为Sprite的怪物。我让精灵的绘制由策略模式处理。那是当我调用 sprite.draw() 我会做这样的事情:

class Sprite 
  void draw() 
    this.view.draw(this.currentPosition, this.currentOrientation);
  

  Point  currentPosition;    // Current position of this sprite
  double currentOrientation; // Facing angle of sprite
;

这种方法的好处是您可以在多个精灵之间共享一个视图对象。因为通常会有很多例如看起来相同但位置不同并且行为可能不同的怪物。

所以行为我也会使用策略模式,它是一个包含描述行为的代码的对象。这样我就可以将相同的行为应用于不同位置的几个怪物。所以每一帧我都会调用一个 update() 函数来更新位置方向和怪物的作用。

class Sprite 
  void setUpdateAction(Action action) 
    this.updateAction = action;
  

  void update(double start_time, double delta_time)
  
    this.prevPosition = position();  
    advance(delta_time); // Advance to next position based on current speed and orientation

    this.updateAction.execute(this, start_time, delta_time);
  

  Action updateAction;
;

这有很多变体。在我目前的实现中,我什至将 currentPositionspeedorientationadvance() 分离到一个单独的对象中称为 MotionState。这样我就可以在进行路径搜索算法时构建可能位置和方向的搜索树。那么我不想随身携带有关如何进行每次更新或如何绘制精灵的信息。

【讨论】:

我非常喜欢在游戏中使用策略模式的想法。我从未开发过游戏,但从设计的角度来看,策略模式对我来说很有意义。 更进一步,你会得到一个Component-based system。【参考方案4】:

集中用户交互逻辑的 MVC 概念是游戏开发的一个很好的模型。

我在 Flash 游戏开发方面做了一些工作。 Here 是一篇关于 Flash 中的对象池的文章。这个概念是跨平台的,可能会给你一些想法。

您同时关注所有正在发生的事情是正确的。根据您的游戏设计,您的游戏循环可能需要处理很多事情。在这里您将学习所有肮脏的优化技巧,通常是困难的方法:)

有很多方法可以组织您的代码 - 一种选择可能是将 GameManager 类编写为 Singleton。所有游戏对象都与它联系起来以进行管理和用户交互。 GameManager 处理所有用户输入并将消息分派到其对象池。您可以使用接口来定义游戏对象和 GameManager 之间的通用通信模式。

就性能优化而言,线程非常强大。异步操作可以确保您不会浪费那些宝贵的周期。

【讨论】:

您的链接已失效。从 Java 1.4 版本开始,对象池在大多数情况下都没有用。我不确定使用单例是绝对必要的,而且我看到许多游戏项目滥用管理器,而设计糟糕的管理器可能是内存泄漏的良好来源。控制对象的生命周期比将沙拉和胡萝卜放在一个管理器中更有用。我的意思是避免保留对无用对象的引用允许垃圾收集器完成其工作,这对于托管对象来说已经足够了,您必须自己销毁非托管对象。【参考方案5】:

您的所有侦听器和处理程序都需要进入 Controller 类,屏幕上对象的状态(例如位置、颜色等)应该是您的 Model 类的一部分,并且在屏幕上绘制内容的任何代码都将成为视图的一部分。

【讨论】:

【参考方案6】:

我对 MVC 的看法是 MDUC 型号 显示 用户输入控制器

模型包含领域模型对象 显示屏在屏幕上显示域模型对象的当前状态和行为。 用户输入控制器处理所有用户输入。

完全一样的模式,只是名字的描述性稍强一些,所以我发现模式的每个部分的职责更清晰,模式的含义也更容易记住。

一旦您看到您将数据和模型操作从显示中分离出来,从用户的输入中,您就可以更轻松地查看在您自己的代码中对哪些内容进行分组。

【讨论】:

以上是关于如何组织游戏代码以适应 MVC 模式?的主要内容,如果未能解决你的问题,请参考以下文章

易于跨引擎和测试的游戏客户端代码设计方法

团队-游戏《石头,剪刀,布》-代码设计规范

团队-游戏《石头,剪刀,布》-代码设计规范

pygame-KidsCanCode系列jumpy-part1-如何组织复杂游戏的代码

游戏引擎设计:多人游戏和监听服务器

如何将边缘添加到网格窗格