Answers:
数据驱动编码
您提到的每件事都是可以在数据中指定的。你为什么要装载aspecificmap
?因为游戏配置说这是玩家开始新游戏时的第一级,或者那是他们刚刚加载的玩家保存文件中当前保存点的名称,等等。
您如何找到aspecificmap
?因为它在列出映射ID及其磁盘资源的数据文件中。
仅需要一小组特别少的“核心”资源,这些资源在合理程度上难以避免或无法避免进行硬编码。通过一些工作,可以将其限制为单个硬编码的默认资产名称,例如main.wad
或类似名称。通过将命令行参数传递给游戏aka,可以在运行时更改此文件game.exe -wad mymain.wad
。
编写数据驱动的代码还依赖其他一些原则。例如,可以避免让系统或模块请求特定的资源,而取而代之的是反转那些依赖关系。也就是说,不要在其初始化代码中DebugDrawer
增加负载debug.font
;而是DebugDrawer
在其初始化代码中使用资源句柄。该手柄可能是从主游戏配置文件加载的。
作为代码库中的一个具体示例,我们有一个从资源数据库中加载的“全局数据”对象(该对象本身默认为./resources
文件夹,但可以通过命令行参数重载)。全局数据的资源数据库ID是代码库中唯一必需的硬编码资源名称(我们还有其他名称,因为有时程序员会变得懒惰,但最终最终还是要修复/删除它们)。该全局数据对象充满了其唯一目的是提供配置数据的组件。组件之一是UI全局数据组件,它包含许多其他配置项中所有主要UI资源(字体,Flash文件,图标,本地化数据等)的资源句柄。当UI开发人员决定主界面的资产从命名/ui/mainmenu.swf
到/ui/lobby.swf
他们只是更新该全局数据引用;根本不需要更改引擎代码。
我们将此全局数据用于所有内容。所有可玩角色,所有级别,UI,音频,核心资产,网络配置等所有内容。(嗯,不是所有的东西,但是其他的东西都是需要修复的错误。)
这种方法还有很多其他优点。首先,它使资源打包和捆绑成为整个过程的组成部分。引擎中的硬编码路径也往往意味着必须用任何脚本或工具将这些相同的路径硬编码到打包游戏资产中,然后这些路径可能会不同步。取而代之的是,我们依靠单个核心资产和参考链来构建资产捆绑,例如,bundle.exe -root config.data -out main.wad
并且知道它将包含我们需要的所有资产。此外,由于捆绑程序将仅遵循资源引用,因此我们知道它将仅包含我们需要的资产,并跳过在项目生命周期中不可避免地积累的所有剩余绒毛(加上我们可以自动生成该列表)修剪的绒毛)。
整个过程的棘手案例是脚本。从概念上讲,使引擎由数据驱动很容易,但是我看到了很多项目(爱好AAA),在这些项目中,脚本被视为数据,因此“被允许”随意使用资源路径。不要那样做 如果Lua文件需要资源并且只是调用一个函数,例如textures.lua("/path/to/texture.png")
资产管道,那么知道脚本需要/path/to/texture.png
正确操作并且可能会认为该纹理是未使用的和不必要的,就会遇到很多麻烦。脚本应像其他任何代码一样对待:它们需要的任何数据(包括资源或表)都应在配置条目中指定,引擎和资源管道可以检查其依赖性。foo.lua
改为“加载脚本” 的数据应改为“foo.lua
并提供这些参数”,其中参数包括任何必要的资源。例如,如果脚本随机产生了敌人,则将可能的敌人列表从该配置文件传递到脚本中。然后,引擎可以向敌人预先加载等级(因为它知道可能产生的完整列表)并且资源管道知道将所有敌人与游戏捆绑在一起(因为它们已被配置数据明确引用)。如果脚本生成路径名字符串并仅调用load
函数,则不会引擎或资源管道都可以通过任何方式专门了解脚本可能尝试加载的资产。
避免在常规函数中进行硬编码的方式相同。
您传递参数,并将信息保留在配置文件中。
在这种情况下,编写引擎和编写类在软件工程上绝对没有区别。
MgrAssets
public:
errorCode loadAssetFromDisk( filePath )
errorCode getMap( mapName, map& )
private:
maps[name, map]
然后,您的客户端代码读取一个“主”配置文件(该文件是硬编码的或作为命令行参数传递的),其中包含指示资产文件在何处以及它们所包含的映射的信息。
从那里,一切都由“主”配置文件驱动。
我喜欢其他答案,所以我会有点相反。;)
您无法避免将有关数据的知识编码到引擎中。无论信息来自何处,引擎都必须知道如何寻找它。但是,您可以避免将实际信息本身编码到引擎中。
一种“纯”数据驱动的方法是让您使用加载初始配置所需的命令行参数来启动可执行文件,但是必须对引擎进行编码才能知道如何解释该信息。例如,如果您的配置文件是JSON,你需要硬编码你看,例如变量引擎必须知道寻找"intro_movies"
和"level_list"
等等。
但是,“结构良好”的引擎只需换出配置数据及其引用的数据,便可以为许多不同的游戏工作。
因此,我们的口头禅不是要避免硬编码,而是要确保您可以用最少的精力进行更改。
与数据文件方法(我全心全意支持)形成对比,可能是您可以将数据编译到引擎中。如果这样做的“成本”较低,那么就没有真正的危害。如果您是唯一从事此工作的人,则可以将文件处理推迟到以后再进行,而不必搞砸自己。我的前几个游戏项目都将大数据表硬编码到游戏本身中,例如武器列表及其分类数据:
struct Weapon
{
enum IconID icon;
enum ModelID model;
int damage;
int rateOfFire;
// etc...
};
const struct Weapon g_weapons[] =
{
{ ICON_PISTOL, MODEL_PISTOL, 5, 6 },
{ ICON_RIFLE, MODEL_RIFLE, 10, 20 },
// etc...
};
因此,您可以将这些数据放在易于引用的位置,并且可以根据需要轻松进行编辑。理想的做法是将这些内容放入某种配置文件中,但随后您需要进行解析和转换,所有这些爵士乐,加上连接结构间引用可能会成为您确实不想承受的额外痛苦。处理。