构建一个基于状态的游戏引擎,以及Makefile结构帮助

Structuring a state based game engine, and Makefile structure help?

本文关键字:引擎 游戏 以及 Makefile 结构 帮助 一个 构建 于状态 状态      更新时间:2023-10-16

我正在开发一个基于状态的游戏引擎,首先我对它感到满意。

有一个抽象类GameState.hpp,里面有我使用的虚拟方法(init、run、pause等)

有一个GameEngine.cpp/hpp,它是一个包含GameState对象堆栈的类,并通过运行当前状态的相关方法来设置游戏循环。

我的测试游戏TestGame.cpp创建了一个GameEngine对象,并推送了一个TestState实例并运行,等等。所有这些都如我所期望的那样工作。

我想构建我的源代码树,而不是从同一个目录编译所有内容,并且考虑了以下内容,因为每个游戏将有多个状态:

src/
  +Engine/
    +GameEngine.cpp
    +GameEngine.hpp
    +GameState.hpp
  +TestGame/
    +States/
      +TestState.cpp
      +TestState.hpp
    +TestGame.cpp

在构建时,我不确定是否了解需要用什么编译什么,以及编译后的对象应该放在哪里。

到目前为止,我最初的想法是:

  1. 用GameState.hpp编译GameEngine.cpp/hpp,给出GameEngine.o

  2. 编译每个游戏状态,例如用GameState.hpp编译TestState.cpp/hpp,得到TestState.o

  3. 使用GameEngine.o/hpp、GameState.hpp、TestState.o/hpp(以及任何其他状态)编译TestGame.cpp/hpp,可获得TestGame.bin

我走对了吗?GameEngine应该构建一个lib,还是一个常规的.o?我仍然有点不清楚是否需要在每次编译中包含头。此外,每次编译的输出文件是在与源文件相同的目录中,还是应该在结构化的bin/目录中?

我会玩一玩,发布一些Makefile尝试和输出。如果我应该发布代码,请告诉我,总共可能有200行。没有实际的图形实现等等…目前只是一个框架。

谢谢大家,非常感谢你们的帮助!

编辑:感谢大家的快速回复。我已经阅读了下面的评论,想更新并澄清一些事情。

首先,我正在研究自动工具,但这在很大程度上是一个学习项目,其中一部分是开发makefile。此外,我有Java背景,所以我真的很想确切地了解我正在构建什么,以及我的项目是如何组合在一起的。如上所述,我甚至不知道某个东西应该是一个库,还是只是在编译时指向它。

我的目标概述:我认为引擎几乎是一个子项目。即使它很简单(<10个文件),它也会加载我认为的"状态",并循环状态定义的[处理事件、更新、显示]方法。我有一种算法,它的目标是每秒更新次数恒定,同时根据需要改变帧速率。

例如,statesdisplay方法将使用引擎的SDL支持的显示功能(可能是平铺引擎,状态可以说"加载这些资源,显示这些平铺",或者小部件库,等等)来呈现游戏世界模型的表示。handle events方法可以使用抽象的键盘接口。我希望能够在引擎中添加这样的功能。更新将管理一个代表游戏世界的模型。它将跟踪世界上的物体(场景、玩家和np),应用基于物理的运动和碰撞检测等

我想我想能够构建这个,并在我正在开发的游戏中包含对象(和标题?)。这是否是一个问题'将引擎源目录复制到游戏项目的根目录,并将其添加到主Makefile中,一起构建',或者"每当引擎发生变化时,都在引擎上运行make,用最新引擎对象(共享对象,其他什么?)的副本和标题构建游戏项目",对我来说,使用一个引擎处理多个小型游戏项目最容易的是什么?我有点想让自己做好准备,这样我就可以用我所拥有的随机游戏创意,比如2周的编码comps,等等,脚踏实地

状态表示任何不同的视图和交互集。引擎有一个LIFO状态堆栈,因此一个简单的游戏可以在堆栈上有以下状态:行驶模式-暂停菜单-设置实际的游戏有一个GameEngine的实例,并且会添加DrivingMode来启动游戏。用户暂停,这将暂停DrivingMode状态并推送PauseMenu状态(在推送时运行其init、run等)。在这里,用户选择暂停暂停菜单状态的设置,并加载设置。。。当它们退出时,会弹出状态(清理等……首先运行),然后恢复顶部状态。

哇,现在太多了。随着我的进步,我会添加更多问题或提出新问题。再次感谢您的帮助。

首先,您真的需要状态是模块化的吗?如果您计划为特定目的编写引擎,那么您的状态可能不会有太大变化,因此可能应该作为引擎本身的一部分进行编译。

将状态与引擎分离会增加额外的复杂性,并使实现更加棘手。

下一个问题是,你是否希望你的引擎是一个游戏链接的库,或者引擎的源代码是否被放在游戏的子文件夹中,并与之一起编译。

您的选择是:

  • 将引擎和状态编译为一个库(也许是一个静态库,用于链接游戏)
  • 将引擎编译为静态库,然后将状态编译为可以动态链接的共享对象(这符合模块化状态选项,但会增加很多复杂性)
  • 将整个引擎+游戏编译成一个大的可执行文件,这样就不需要任何库链接(但也使引擎和游戏更加耦合)

在我看来,选项1看起来是最好的

无论哪种选择,我都会将引擎保存在游戏中自己的子目录中。到目前为止,你的文件结构看起来相当稳固,你应该坚持下去

如果你仍然选择允许游戏定义自己的状态,那么你会想选择选项二,在你的引擎中实现一些核心状态(可能是INIT、MAIN_MENU、SHUT_DOWN)以及"插件管理器",在这种情况下,插件管理器将负责向游戏引擎注册游戏的状态。

然后,每个状态都可以单独编译为共享对象(.so),并在加载游戏时动态加载。

至于Makefiles,它实际上取决于你计划使用什么(CMake、GNU自动工具等)

GNU Makefile有一个非常简单的语法,但要想很好地使用它们可能有点棘手。

它们由如下定义的目标组成:

default:
    gcc [...]
all:
    gcc [...]
    gcc [...]
    <...>

当在没有参数的情况下调用make时,第一个目标是默认目标我不会详细描述如何使用makefiles(对于GNUMake:此链接)

您应该知道,makefile很快就会变得非常乏味,而像autotools这样的解决方案通常是首选。

请注意,自动工具是一个由许多程序组成的非常大的系统,学习起来可能非常耗时。如果你想专注于开发你的游戏引擎,我强烈建议你使用像Code::block这样的项目IDE来为你编译和链接代码。

编辑

作为对您编辑的回应,我将添加以下内容:

你似乎非常清楚自己要去哪里,这是最重要的部分。从你所说的来看,如果游戏引擎是你的许多游戏所链接的静态库,你会更好。

然后,你的引擎会提供一个界面来注册状态,游戏会用它来说"这是我的状态,名为title",然后在需要时告诉引擎"切换到名为title的状态"。

你可能想考虑的一件事是,所有游戏都必须注册一些基本状态(由引擎本身强制执行)。

然后引擎将是一个自己的项目,例如,游戏项目将在/lib中有一个引擎库的副本。然后,您将在编译时静态地将引擎链接到您的游戏。

如果你想了解更多关于如何构建状态对象的细节,请告诉我。


你问的很多事情都没有明确的答案——通常是个人偏好。

我从你的问题中得到的印象是,你也编译你的头,你不应该编译头,它们应该只包含在.cpp文件和其他头中。然后将它们作为cpp的一部分进行编译。文件应该只包含它们真正需要的标题。

只要你的项目很小,把所有东西都放在一个目录里通常是最好的,但如果你知道它会增长,那么建立一个好的目录结构是个好主意。像你一样,将潜在的可重复使用的游戏引擎与实际游戏分离是一件好事。

当该部分将在其他项目中重复使用时,或者当您的项目变得太大以至于.o文件的数量变得非常多时,创建库是有用的。因此,如果这适用于游戏引擎,请将其作为一个库。对于像游戏引擎这样的可重用项目,将标题保存在单独的目录中通常也很有用。因为依赖它的项目只需要头和lib,所以它们不需要其余的源。

当项目变得更大时,使用单独的源目录,intermediate(.o)和最终可执行文件有助于保持文件的可管理性,因为使用源存储.o通常会使src目录中的文件数量增加50%。

如果你想把它设置得很好,你可能想看看autoconf和automake。他们有一个陡峭的学习曲线,但他们会从你手中夺走很多Makefile的写作。这些是创建配置脚本的工具,在许多开源软件包的源代码发行版中都可以找到。