java – 程序中消息传递的数据结构?

我想写一个简单的RPG。到目前为止,每次我尝试开始它立即变成一团糟,我不知道如何组织任何东西。所以我开始,尝试一个基本上是MVC框架的新结构的原型。我的应用程序在控制器中开始执行,它将创建视图和模型。然后它将进入游戏循环,游戏循环的第一步是收集用户输入。

用户输入将被视图的一部分收集,因为它可以变化(3D视图将直接轮询用户输入,而远程视图将通过telnet连接接收它,或者命令行视图将使用System.in )。输入将被转换为消息,并且每个消息将被给予控制器(通过方法调用),然后可以解释该消息以修改模型数据或通过网络发送数据(因为我希望具有网络选项) 。

这种消息处理技术也可以在网络游戏的情况下用于处理网络消息。我保持MVC的精神到目前为止吗?

无论如何,我的问题是,什么是最好的方式来表示这些消息?

这里是一个用例,每个消息用斜体表示:让我们说用户启动游戏并选择字符2.然后用户移动到坐标(5,2)。然后他说公开聊天,“嗨!”。然后他选择保存并退出。

视图应该如何将这些消息包装成控制器可以理解的东西?或者你认为我应该有单独的控制器方法,如chooseCharacter(),moveCharacterTo(),publicChat()?我不知道这种简单的实现将工作,当我移动到网络游戏。但在极端的另一端,我不想只是发送字符串到控制器。这很困难,因为选择字符动作需要一个整数,移动采用两个整数,聊天需要一个字符串(和范围(公共私有全局),在私人的情况下,目标用户);没有真正的设置数据类型。

也有任何一般建议是非常欢迎;我在正确的时间担心这个吗?我走向正确的路径到一个布局良好的MVC应用程序?有什么我忘记了吗?

谢谢!

(免责声明:我从来没有在Java编程游戏,只有在C。但一般的想法应该也适用于Java。
我提出的想法不是我自己的,但是我发现在书中或“在互联网上”的解决方案的混搭,参见参考部分。
我自己使用所有这些,到目前为止,它导致一个干净的设计,我知道确切地在哪里放置我添加的新功能。)

恐怕这将是一个很长的答案,它可能不清楚,当第一次阅读,因为我不能描述它只是自上而下很好,所以会有来回的引用,这是由于我的缺乏解释技能,不是因为设计有缺陷。事后看来,我超越了,甚至可能是偏离主题。但现在我已经写了这一切,我不能让自己只是把它丢掉。只是问一些东西不清楚。

在开始设计任何包和类之前,先从分析开始。你想在游戏中有什么功能。不要计划一个“也许我会添加以后”,因为几乎肯定的设计决定之前,你开始添加这个功能,认真地,你计划的存根是不够的。

而为了动机,我从这里的经验,不要认为你的任务,如写一个游戏引擎,写一个游戏!无论你在想什么对未来的项目会很酷,除非你把它放在你现在写的游戏中,否则拒绝它。没有未经测试的死代码,没有动机问题,由于不能解决一个问题,甚至不是一个问题,直接项目提前。没有完美的设计,但有一个够好。值得铭记这一点。

如上所述,我不相信MVC在设计游戏时有任何用处。模型/视图分离不是一个问题,控制器的东西是相当复杂,太多,所以只是被称为“控制器”。
如果你想有名为模型,视图,控制的子包,继续。以下可以被集成到这个包装方案中,尽管其他的至少是同样敏感的。

很难找到我的解决方案的起点,所以我只是开始最顶层:

在主程序中,我只是创建了Application对象,init并启动它。应用程序的init()将创建功能服务器(见下文)并在其中。此外,第一个游戏状态被创建并推到顶部。 (也见下文)

特征服务器封装正交的游戏特征。这些可以独立实现,并通过消息松散耦合。示例特性:声音,视觉表示,碰撞检测,人工智能/决策,物理学等。特征本身的组织方式如下所述。

输入,控制流和游戏循环

游戏状态提供了一种组织输入控制的方法。我通常有一个类收集输入事件或捕获输入状态和以后轮询(InputServer / InputManager)。如果使用基于事件的方法,则事件被给予单个一个注册的活动游戏状态。

当开始游戏时,这将是主菜单游戏状态。游戏状态有init / destroy和resume / suspend函数。 Init()将初始化游戏状态,在主菜单的情况下,它将显示最顶层的菜单级。 Resume()将控制到这个状态,它现在从InputServer接收输入。 Suspend()将从屏幕上清除菜单视图,destroy()将释放主菜单需要的任何资源。

GameStates可以堆叠,当用户使用“新游戏”选项启动游戏时,MainMenu游戏状态将被暂停,PlayerControlGameState将被放入堆栈,现在接收输入事件。这样,您可以根据游戏的状态处理输入。在任何给定时间只有一个控制器处于活动状态,您可以极大地简化控制流程。

输入集合由游戏循环触发。游戏循环基本上确定当前循环的帧时间,更新特征服务器,收集输入并更新游戏状态。帧时间或者被赋予这些的每一个的更新函数或者由Timer单元提供。这是用于确定自上次更新调用以来的持续时间的规范时间。

游戏对象和功能

这个设计的核心是游戏对象和功能的交互。
如上所示,在这个意义上的特征是可以彼此独立地实现的一块游戏功能。游戏对象是以任何方式与玩家或任何其他游戏对象交互的任何东西。示例:玩家头像本身是一个游戏对象。火炬是游戏对象,NPC是游戏对象,如照明区和声源或这些的任何组合。

传统的RPG游戏对象是一些复杂的类层次结构的顶级类,但真的这种方法是错误的。许多正交方面不能放入层次结构中,即使使用接口,最终还是要有具体的类。一个项目是一个游戏对象,一个可拾取的项目是一个游戏对象,一个胸部是一个容器是一个项目,但使一个胸部可以选择是否是这种方法是一个或决定,因为你必须有一个层次结构。当你想要一个只在谜语回答时才打开的魔术谜的胸部,它变得更复杂。只有没有一个所有适合的层次。

一个更好的方法是只有一个游戏对象类,并将每个正交方面(通常在类层次结构中表示)放入其自己的组件/要素类中。游戏对象可以保存其他项目吗?然后添加ContainerFeature到它,可以谈,添加TalkTargetFeature到它,等等。

在我的设计中,一个GameObject只有一个内在的唯一的id,name和location属性,所有的东西都被添加为一个特征组件。组件可以通过调用addComponent(),removeComponent()在运行时通过GameObject接口添加。所以要使它可见添加一个VisibleComponent,使它发出声音,添加一个AudableComponent,使其成为一个容器,添加一个ContainerComponent。

VisibleComponent对于您的问题很重要,因为这是提供模型和视图之间的链接的类。不是所有的东西都需要经典意义上的观点。触发区域将不可见,环境声音区域也不会显示。只有具有VisibleComponent的游戏对象将可见。
当VisibleFeatureServer更新时,可视化表示在主循环中更新。然后它根据注册到它的VisibleComponents更新视图。它是查询每个队列的状态还是只查询从队列接收的消息,这取决于您的应用程序和基础可视化库。

在我的情况下,我使用Ogre3D。这里,当VisibleComponent附加到游戏对象时,它创建一个附加到场景图形和场景节点的实体(3d网格的表示)的SceneNode。每个TransformMessage(见下文)被立即处理。 VisibleFeatureServer然后使Ogre3d将场景重绘到RenderWindow(实质上,细节更复杂,一如既往)

消息

那么这些功能和游戏状态和游戏对象如何相互通信呢?
通过消息。此设计中的消息仅仅是Message类的任何子类。每个具体的消息可以有自己的接口,方便其任务。

消息可以从一个GameObject发送到其他GameObject,从GameObject到它的组件,从FeatureServers到他们负责的组件。

当FeatureComponent被创建并被添加到游戏对象时,它通过调用myGameObject.registerMessageHandler(this,MessageID)来为游戏对象注册它想要接收的每个消息。它还向其特征服务器注册它希望从那里接收的每个消息。

如果玩家试图与它所关注的角色对话,那么用户将以某种方式触发通话动作。例如:如果焦点的焦点是友好NPC,则通过按鼠标按钮触发标准交互。通过向目标游戏对象标准动作发送GetStandardActionMessage来查询目标游戏对象标准动作。目标游戏对象接收消息,并且从第一注册的游戏对象开始,通知其想要知道消息的特征组件。此消息的第一个组件将标准操作设置为触发自身(TalkTargetComponent将标准操作设置为Talk,它将首先接收它),然后将消息标记为已消耗。 GameObject将测试消费,并确定它确实已消耗并返回调用者。然后评估现在修改的消息,并调用生成的操作

是的,这个例子似乎很复杂,但它已经是一个更复杂的。其他人喜欢TransformMessage用于通知位置和方向的变化更容易处理。 TransformMassage对许多功能服务器很有趣。 VisualisationServer需要它来更新GameObject在屏幕上的可视化表示。 SoundServer更新3d声音位置等。

使用消息而不是调用方法的优点应该是清楚的。组件之间存在较低的耦合。当调用方法时,调用者需要知道被调用者。但通过使用消息,这是完全解耦。如果没有接收器,那么没关系。接收者如何处理消息如果根本不是呼叫者的关注。
也许代理在这里是一个好的选择,但Java错过了一个干净的实施这些和在网络游戏的情况下,你需要使用某种RPC,它有一个相当高的延迟。低延迟是互动游戏的关键。

坚持和编组

这使我们了解如何通过网络传递消息。通过将GameObject / Feature交互封装到消息中,我们只需要担心如何通过网络传递消息。理想情况下,您将消息带入通用形式,并将其放入UDP包并发送。接收器将消息解包到适当类的实例,并将其发送到接收器或者广播它,这取决于消息。
我不知道Java的内置序列化是否是任务。但即使不是,有很多libs可以做到这一点。

GameObject和组件通过属性使它们的持久状态可用(C没有内置的序列化。)
它们具有类似于Java中的PropertyBag的接口,可以检索和恢复它们的状态。

参考文献

> The Brain Dump:专业游戏开发者的博客。作为开源星云引擎的作者,游戏引擎用于商业上成功的游戏。我在这里介绍的大部分设计是从星云的应用层。
> Noteworthy article上面的博客,它展示了引擎的应用层。另一个角度,我试图描述上面。
> A lengthy discussion如何布局游戏架构。大多数Ogre具体,但一般足以对他人有用。
> Another argument for component based designs,底部有有用的参考。

http://stackoverflow.com/questions/1189236/data-structures-for-message-passing-within-a-program

本站文章除注明转载外,均为本站原创或编译
转载请明显位置注明出处:java – 程序中消息传递的数据结构?