【问题标题】:C++/OOP game designC++/OOP 游戏设计
【发布时间】:2012-11-20 06:26:26
【问题描述】:

我正在通过使用 SFML 和 OpenGL 制作一个小游戏来练习我的新手 c++ 技能。编程部分大部分都进​​行得很顺利,但我对实际代码/类设计有疑问。

我有一个类 MainLoop,它包含游戏循环并拥有以下每个类的一个实例:Events、Graphics、Commands、Game 和 UI。我最初希望所有这些都是一个类(函数在不同的 .cpp 文件中分隔),但被告知这是 OOP/C++ 的错误方法。然而,虽然我可以看到分离它们的好处(封装、模块化、调试),但我似乎也遇到了很多不好的事情。让我以用户按下 UI 按钮为例。

首先,MainLoop 从 SFML 的窗口类中获取事件。 MainLoop 将它发送到我自己的 Event 类,它解释事件并将其发送到 UI 类以检查它是否“点击”任何按钮。如果为真,则 UI 类将其发送到解释按钮命令的 Command 类。然后,最后,命令类将其发送到 Game 类或它需要去的任何其他地方。

这一切对我来说似乎都非常严厉,而且,至少我目前的做法是,需要大量的前向声明(在我了解这些之前,我最终得到了大量的循环依赖)。我怀疑它对性能也有多大好处。

无论如何,这里有什么我遗漏的技巧吗?这些类应该如何连接,它们应该如何通信?我应该如何将命令从 Event 类转发到 UI 类?我真的应该在任何地方都有前向声明、包含和东西,这不会破坏模块化吗?我应该让它全部通过 MainLoop 类并使用不需要声明的整数/浮点数/字符转发返回吗?我有点不知所措。

【问题讨论】:

    标签: c++ sfml


    【解决方案1】:

    我可以想象它看起来很重,但这是正确的做法。请注意,函数调用一点也不繁重,它确实使整个事情更容易阅读。或者至少应该。 ;-)

    每个类都应该有一个包含类定义的头文件,但不包含其成员函数的实现。任何文件都应该能够包含任何类头文件。仅当您使用模板(实现必须在头文件中)时,才可能存在循环依赖关系,但根据您的描述,我认为您没有它们。标题不需要相互包含。如果您需要在函数参数中传递指向其他类的指针或引用,则可以在标题的开头前向声明其他类。您应该能够在源文件的顶部包含任何内容。如果不是,请提供更多信息,说明您认为在您的情况下需要这样做的原因。

    【讨论】:

    • 我同意.. 我默认在可能的情况下前向声明.. 在考虑类及其公共函数时请记住,OO 的目的是管理复杂性(为您自己和任何其他人类工作在代码上)通过创建抽象层。对于性能,通常您选择的算法/数据结构比调用函数具有更大的影响。尽管使用游戏和其他实时软件,您有时不得不为性能做出妥协。
    • 在任何地方都使用前向声明对我来说似乎有点奇怪。最好的情况下,每个班级不应该能够“独立存在”吗?为了模块化,我的意思是。 IE。如果我的 Graphics 类需要我的 UI 类(例如,获取用于绘制的 UI 元素的坐标),那么我将无法在另一个应用程序中使用我的 Graphics 类而不包括 UI 类......类似的东西。同样,在同样的方面,命令应该直接从一个子类(例如事件)到另一个子类(例如 UI),还是应该通过我的 MainLoop 类进行中继,从而至少保留一些模块化?
    • 你的 Graphics 类需要“一个”UI 类,它不需要是你的。它只必须定义相同的接口(或至少您使用的部分)。至于通过主循环,这一切都取决于哪个类管理什么。如果您的图形类需要在屏幕上绘制一些东西,我认为让它直接调用 UI 没有问题。但是,如果按下按钮应该启动一些命令,我​​认为 UI(检测按钮按下)不需要知道其按钮的含义。所以让 MainLoop 决定调用哪个命令是有意义的。
    • 谢谢!在任何地方都使用这么多指针、引用和前向声明并不是很舒服,但我想随着我越来越多地练习做像这样的“更大的项目”,这一切都会开始变得更自然。
    【解决方案2】:

    我可以向您推荐一些我在开发游戏时使用的设计,虽然它们肯定不是什么好东西,但我从来没有遇到过很多问题。我会保持简短,只是为了给你一个想法。

    首先你需要一个视图管理器,这个管理器应该管理你游戏的当前视图,这可以实现为视图堆栈或其他任何东西。因此,您将拥有一个 ViewManager 类,它知道您游戏的所有视图并能够将内容分派到当前视图。

    那么你需要一个抽象类GameView,它应该提供来自外部的基本接口,例如:

    • drawMe(),绘制视图
    • receivedMouseEvent(Event e),将接收鼠标事件
    • activate()deactivate() 执行在视图管理器中推送或弹出视图时应执行的操作

    现在使用这个抽象类,您应该实现游戏的任何特定视图或部分视图,以便您可以在视图堆栈中推送和弹出它们。

    一个好办法是有子类来管理 UI 元素,例如类ActiveArea 响应点击,Button 继承自ActiveArea,它还能够提供两种状态的图形。这些元素应包含在存储在抽象视图中的可点击元素列表中,以便每个具体视图都可以将其按钮添加到通用实现中而无需担心。这样你就可以拥有类似(元代码)的东西

    void AbstractView::receiveEvent(Event e) {
      for (ActiveArea *area in areas)
        if (area.isInside(e)) {
          area->action();
          return;
        }
    
      innerReceiveEvent(e); //which should be a pure virtual function that will call a method specified in concrete views
    }
    

    通过这种方式,您将让每个视图管理自己的状态,并让视图管理器负责绘制和管理事件,例如

    void ViewManager::draw() {
      for (AbstractView *view in views) // from top to bottom of the stack
        view.draw();
    }
    

    【讨论】:

    • 我没有考虑过这种方法,但听起来确实很有用。到目前为止,我一直在使用一个简单的全局代理“GameState”,我在 Events/UI/Commands 中实现了它来决定显示什么以及如何对输入做出反应。 IE。如果 GameState==MainMenu,则绘制这些 UI 对象,如果在主游戏视图中绘制这些,等等。您的方法可能会更干净,更有用。但你根本不摧毁它们吗?你只是让他们活跃/不活跃?
    • 这个主要看你的具体问题,如果浏览量大就销毁掉,否则直接关闭即可。当然,前一种方法要求您拥有视图的持久状态(可能是内部对象),以便您可以在需要时恢复它。
    • 我也使用了你建议的方法,但我觉得有必要切换到更类似于我解释过的方法,一旦整个事情的复杂性超过某个阈值,代码就会变得一团糟值得重构它
    • 我想我会尝试实现这样的东西,因为我可以看到我目前的方法最终会变得混乱。听起来需要一些计划才能让一切正常工作。感谢您的提示!
    猜你喜欢
    • 2010-12-30
    • 1970-01-01
    • 2023-03-14
    • 2013-11-02
    • 1970-01-01
    • 2013-09-08
    • 2012-01-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多