如何组织游戏代码以符合MVC模式?

73

我是一名大学新生,正在攻读计算机科学学位......过去几年里我已经编写了很多代码,但最近我开始更深入地研究有关代码组织、设计模式、语言差异等方面的理论知识。

我正在上Java课程,所以我已经放弃了C++的研究和开发,并转而学习Java和JOGL(Java OpenGL)。这很好!但这不是重点。

我想制作一个小型角色扮演游戏,但这个问题适用于任何类型的游戏。如何将游戏对象组织成结构化的形式,例如Model-View-Controller模式?它看起来是一个非常棒、被广泛使用并且非常合理的模式,但我很难想出如何实现它。

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

而我的最大问题是GUI;它在哪里与其连接?它有点像输入,但又不完全一样,并且它需要从实际的游戏模拟中设置和获取数据......更加复杂的是,如果我决定尝试添加网络功能,那么(与GUI类似)它也需要访问大量数据以进行修改和读取......

哦,我真的很困惑。我不知道如何以面向对象的方式使所有这些东西协同工作......编写符合模式的代码还是相对容易的,但是当你有大量事情发生,所有这些事情都绑定在一个游戏循环中,相互修改和修改游戏数据等等......我甚至不知道了。也许我把这件事情想得太复杂了。

还有其他人有这种感觉吗?请给我一些明确的建议,让我能够花更少的时间担心,知道从哪里开始!

编辑:我找到了一张不错的图表,可能会帮助我弄清所有的内容...来源:(注意,PS文件!)http://www.tucs.fi/publications/attachment.php?fname=TR553.ps.gz

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

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

6个回答

57

把模型视为一种游戏API可能会有所帮助。如果从一开始就没有为游戏指定用户界面,那么你的游戏将被简化成什么样子?假设你想要制作RPG游戏,那么在这种情况下,你可以将玩家角色、物品清单、技能、NPC甚至是地图和战斗规则都视为模型的一部分。这就像是《大富翁》的规则和棋子,但没有最终游戏的具体显示方式或用户与之交互的形式。它就像是Quake游戏中的一组抽象的3D对象,在级别中移动并计算交叉和碰撞等,但没有渲染、阴影或声音效果。

通过将所有这些内容放入模型中,游戏本身现在不再依赖于用户界面。它可以连接到类似于Rogue游戏的ASCII文本界面、类似于Zork的命令行界面、基于Web的界面或3D界面。其中一些界面可能根据游戏机制完全不合适,但它们都是可能的。

视图层是依赖于用户界面的层。它反映了你选择的具体界面技术,并且非常专注于该技术。它可能负责读取模型的状态并以3D、ASCII或图像和HTML显示在Web页面上。它还负责显示玩家需要使用的任何控制机制以与游戏交互。

控制器层是连接这两个层的粘合剂。它不应该有实际的游戏逻辑,也不应该负责驱动视图层。相反,它应该将在视图层(单击按钮、单击屏幕区域、摇杆操作等)执行的操作转换为对模型的操作。例如,丢弃物品、攻击NPC等。它还负责收集数据并进行任何转换或处理,以使视图层更容易地显示它。

现在,我所描述的方式好像是游戏中有一个非常明显的事件序列,这可能只适用于 Web 游戏。这是因为最近我花了时间在这方面上。在不像 Web 那样由用户请求和服务器响应驱动的游戏中(例如运行在用户机器上的游戏),您可能需要确保模型层良好地实现了观察者模式。例如,如果 Model 中发生了动作,因为时间正在流逝,那么您可能不想让 View 层不断地轮询 Model 获取更新。相反,使用观察者模式,Model 可以在对象更改时通知任何观察者。这可以用来立即促使 View 更新以反映更改。

然后,如果经过 60 秒钟,玩家基地进行了一些修复,基地可以执行修复并立即通知附加到它的任何观察者基地已更新。View 可以作为观察者附加,并指出它需要重新显示基地,因为其状态已更改。通知本身可能已经包含足够的信息来更新视图,或者它可能需要返回并查询模型以更新,但结果将是相同的。


19

你已经掌握了一些技巧。基本上,你需要问自己一个问题:“如果我要更改程序的某个部分,哪些代码会发生变化?”

如果更改只是影响外观而不改变基本数据,那么它属于视图。如果数据可以以多种方式查看,则属于模型。如果涉及到操作方式,则属于控制。

因此,如果你想画一个有两个刀片或一个刀片的“斧头”,那么它属于视图。如果你想知道用斧头造成多少伤害,那么它属于模型。如果你想通过键入“s”或右键单击来挥动斧头,那么它属于控制。


8

我理解你的感受。我还记得第一次接触MVC时,我试图将所有内容都塞进去。我确实制作了一个使用MVC模式的游戏。但后来我发现,我做的太过头了。我试图将每个类都强行塞进MVC的一个类别中。

我的建议是阅读《设计模式》(Gang of Four所著)。除了MVC外,还有很多有用的模式。有时候根本不需要使用MVC,特别是对于游戏来说,我不确定MVC是否是个好主意。原因在于你不想以多种不同方式(视图)显示游戏对象,而是想要为许多不同类型的游戏对象重用绘制代码。

对于我自己的2D游戏引擎,我非常活跃地使用了策略模式。像玩家和怪物这样的游戏对象,我称之为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 的独立对象。这是为了在执行路径搜索算法时可以构建可能位置和方向的搜索树。然后我不想携带关于如何在每次更新时行为或者如何绘制精灵的信息。


1
将这个想法进一步发展,你最终会得到一个基于组件的系统。 - pek

3
MVC模式将用户交互逻辑集中化是游戏开发的良好模型。
我曾经做过Flash游戏开发。这里是一篇关于Flash对象池的文章。该概念跨平台,并可能给你一些想法。
你对同时进行的所有事情感到担忧是正确的。根据您的游戏设计,游戏循环可能有很多处理。这就是您将学习所有肮脏的优化技巧的地方,通常是通过艰难的方式:)
有许多组织代码的方法-其中一个选项可能是编写GameManager类作为Singleton。所有游戏对象都与其相关联以进行管理和用户交互。GameManager处理所有用户输入并将消息分派给其对象池。您可以使用接口来定义游戏对象和GameManager之间的常见通信模式。
就性能优化而言,线程非常强大。异步操作可以确保您不会浪费那些宝贵的周期。

你的链接已经失效了。自从Java 1.4版本以来,对象池在大多数情况下都是无用的。我不确定使用单例是否绝对必要,我见过很多游戏项目滥用管理器,而一个设计不良的管理器可能会成为内存泄漏的主要来源。控制对象的生命周期比把沙拉和胡萝卜放进一个单一的管理器更有用。我的意思是避免保留无用对象的引用,这样垃圾收集器就可以完成其工作,对于托管对象来说这已经足够了,而你必须自己销毁未托管的对象。 - gouessej

1

我对MVC的思考方式是MDUC
Model(模型)
Display(显示)
User-input Controller(用户输入控制器)

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

这是完全相同的模式,但名称略微更具描述性,因此我发现每个模式部分的职责更清晰,因此模式的含义更易记忆。

一旦您看到您正在将数据和模型操作从显示屏和用户输入中分离出来,就更容易看到在自己的代码中如何对其进行分组。


1

所有的监听器和处理程序都需要放在控制器类中,屏幕上对象的状态(例如位置、颜色等)应该是您的模型类的一部分,而任何绘制屏幕上物体的代码将成为视图的一部分。


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