有关游戏架构/设计模式的建议


16

我一直在研究2D RPG,有一段时间了,我发现自己做出了一些糟糕的设计决定。特别是有些事情引起了我的问题,所以我想知道其他人过去曾使用或将采用哪种设计。

作为一点背景,我从去年夏天的业余时间开始研究它。我最初使用C#进行游戏,但是大约3个月前,我决定切换到C ++。自从我大量使用C ++以来,我一直想对C ++有所了解,并且认为像这样的有趣项目将是一个很好的动力。我一直在广泛使用boost库,并且一直在将SFML用于图形,将FMOD用于音频。

我写了很多代码,但是我正在考虑废弃它并重新开始。

这是我关注的主要领域,并希望就其他人已解决或将要解决的正确方法获得一些意见。

1.周期性依赖 当我做在C#中的比赛中,我并没有真的要这个担心,因为它不是一个问题存在。转向C ++,这已经成为一个相当大的问题,让我觉得我可能设计不正确。我真的无法想象如何解耦我的班级,仍然让他们做我想要的事情。以下是依赖链的一些示例:

我有一个状态效果课。该类有许多方法(应用/取消应用,刻度线等)将其效果应用于角色。例如,

virtual void TickCharacter(Character::BaseCharacter* character, Battles::BattleField *field, int ticks = 1);

每当角色受到状态影响时,都会调用此函数。它可用于实现诸如Regen,Poison等的效果。但是,它也引入了对BaseCharacter类和BattleField类的依赖。自然,BaseCharacter类需要跟踪当前对它们起作用的状态影响,因此这是周期性的。《战地风云》需要跟踪战斗方,并且该方类别有一个引入了另一个周期性依赖关系的BaseCharacters列表。

2-活动

在C#中,我大量使用了委托来勾勒角色,战场等事件(例如,当角色的健康状况发生变化,状态更改,状态效果被添加/删除时,都有一个委托。 )和战场/图形组成部分将与这些代表挂钩,以增强其效果。在C ++中,我做了类似的事情。显然,没有直接等效于C#委托的东西,所以我创建了如下代码:

typedef boost::function<void(BaseCharacter*, int oldvalue, int newvalue)> StatChangeFunction;

在我的角色课上

std::map<std::string, StatChangeFunction> StatChangeEventHandlers;

每当角色的状态更改时,我都会迭代并调用地图上的每个StatChangeFunction。虽然有效,但我担心这是做事的不好方法。

3-图形

这是大事。它与我正在使用的图形库无关,但更多是概念上的。在C#中,我将图形与很多类结合在一起,我知道这是一个糟糕的主意。这次我想做到这一点,所以尝试了另一种方法。

为了实现我的图形,我将游戏中与图形相关的所有图形想象为一系列屏幕。即有一个标题屏幕,一个角色状态屏幕,一个地图屏幕,一个清单屏幕,一个战斗屏幕,一个战斗GUI屏幕,基本上,我可以根据需要将这些屏幕彼此叠加以创建游戏图形。无论活动屏幕是什么,都拥有游戏输入。

我设计了一个屏幕管理器,可以根据用户输入推送和弹出屏幕。

例如,如果您在地图屏幕上(平铺地图的输入处理程序/可视化器)并按下了“开始”按钮,则会向屏幕管理器发出呼叫,将主菜单屏幕推到地图屏幕上并标记地图屏幕无法绘制/更新。播放器将在菜单中导航,这将酌情向屏幕管理器发出更多命令,以将新屏幕推送到屏幕堆栈上,然后在用户更改屏幕/取消按钮时弹出它们。最后,当播放器退出主菜单时,我将其弹出并返回到地图屏幕,将其标记为要绘制/更新并从那里去。

战斗画面会更加复杂。我将有一个屏幕作为背景,一个屏幕使战斗中的每个参加者形象化,以及一个屏幕来使战斗的UI形象化。UI将挂接到角色事件中,并使用这些事件来确定何时更新/重绘UI组件。最后,每一次具有可用动画脚本的攻击都将在弹出屏幕堆栈之前调用一个附加层来对其自身进行动画处理。在这种情况下,每一层都始终标记为可绘制和可更新,并且我得到了一堆处理我的战斗图形的屏幕。

尽管我还无法使屏幕管理器正常工作,但我认为可以花一些时间。我的问题是,这是否完全值得?如果这是一个糟糕的设计,我想在投入太多时间制作我需要的所有屏幕之前先了解一下。您如何为游戏建立图形?

Answers:


15

总的来说,我不会说您列出的任何内容会导致您报废系统并重新开始。这是每个程序员都希望在他们正在从事的任何项目中完成大约50-75%的工作的方式,但这会导致开发周期永无止境,并且从未完成任何事情。因此,为此,每个部分都会有一些反馈。

  1. 这可能是一个问题,但通常比其他任何事情更令人烦恼。您是一次使用#pragma还是#ifndef MY_HEADER_FILE_H #define MY_HEADER_FILE_H ... #endif分别位于顶部或.h文件周围?这样.h文件在每个范围内仅存在一次?如果是,那么我的建议是删除所有#include语句并进行编译,并根据需要添加这些内容以再次编译游戏。

  2. 我是这些类型的系统的忠实拥护者,没有发现任何问题。在C#中,什么是事件通常被事件系统或消息系统取代(可以在此处搜索问题以查找更多信息)。这里的关键是在需要发生的情况下将这些问题降至最低,这听起来已经听起来像您在做,这不应该使这里的担忧最小。

  3. 对于我来说,这似乎也是正确的方向,这也是我为自己的引擎和专业引擎所做的工作。这使菜单系统成为一个状态系统,该状态系统具有根菜单(在游戏开始之前)或显示HUD作为“根”屏幕,具体取决于您的设置方式。

综上所述,我看不到任何值得重启的东西。您可能需要更正式的Event System更换方式,但这会及时到来。循环包含是所有C / C ++程序员必须不断跳过的障碍,并且使图形脱钩的工作似乎都是逻辑上的“下一步”。

希望这可以帮助!


#ifdef不能帮助通知包含问题。
共产党鸭子

只是覆盖了我的基础,希望在跟踪循环包含之前可以在那里。当您有多个符号定义而不是一个文件需要包含一个包含其自身的文件时,它可以是一整条鱼。(尽管他描述的是如果包含文件位于.CPP文件中,而不是.H文件中,他应该可以通过两个彼此了解的基本对象来接受)
詹姆斯

感谢您的建议:)很高兴知道我
处在

4

只要您要在头文件中声明类的位置,并在#.cpp(或任何其他形式)文件中实际包含这些类,则周期性依赖关系就不会成为问题。

对于事件系统,有两个建议:

1)如果要保留当前使用的模式,请考虑切换到boost :: unordered_map而不是std :: map。使用字符串作为键进行映射的速度很慢,尤其是因为.NET在后台做了一些不错的事情来帮助加快速度。使用unordered_map对字符串进行哈希处理,因此比较通常更快。

2)考虑切换到更强大的功能,例如boost :: signals。如果这样做,您可以做一些不错的事情,例如通过从boost :: signals :: trackable派生出使您的游戏对象可跟踪,并使析构函数负责清理所有内容,而不必手动从事件系统注销。您也可以有指向每个插槽(反之亦然,我不记得确切的术语),所以它非常类似于做多信号,+=delegateC#中。boost :: signals的最大问题是它必须被编译,而不仅仅是标头,因此取决于您的平台,启动和运行它可能很麻烦。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.