游戏设计的技术方面。如何为引擎结构化游戏内容的数据
如何将所有游戏内容录入到引擎可接受的格式中,这一点由康斯坦丁·萨赫诺夫(Vengeance Games工作室创始人及高等经济学院游戏项目管理课程的科学主管)进行了阐述。
康斯坦丁·萨赫诺夫
初学者和经验丰富的游戏设计师面临的一项常见而复杂的任务是,为了将大量内容的参数传递给引擎而进行描述。
在这篇文章中,我将以我正在开发的游戏为例,介绍整个过程。这是一款以剧情为主、具有连续任务集的行星地形重塑模拟器。
未来游戏的关键机制包括:
- 在行星上建造建筑;
- 地形重塑行星;
- 发展技能树。
游戏中将有大量的建筑。每个建筑都有许多条件、功能和参数。
为了理解:建造任何建筑都需要资源。每座建筑所需的资源数量和类型各不相同。建筑的功能同样不同。有些建筑生产资源,有些提高其他建筑的效率,有些甚至影响完全不同的游戏实体。
而游戏设计师的任务是构建数据处理流程,使得这些数据对同事们可理解,能够被引擎调用并且易于修改。
如何做到这一点?
分阶段进行。
1. 确定数据格式
第一步是与程序员达成一致,确定向引擎传递内容信息的格式。
这个问题可以用多种方法解决。
例如,我们可以使用ScriptableObject机制,或者直接在Unity / UE中编写数据脚本。然而,这种方法仅在某种特定类型的内容数量较少(例如,仅涉及三到五个建筑物)或者完全是独特的场景对象时才适用。
因此,如果您的游戏只有十行数据,您不需要任何“Google 表格”、JSON或其他解决方案。
而在我们的情况下,建筑数量可能多达一百。每个建筑都会对行星、周围环境、邻近建筑产生不同的影响,资源生产也会因此受到影响。此外,每座建筑有着多种发展等级。
所有这些在代码中是无法完全编写的。此外,在这种情况下,我们所需要的灵活性将无从谈起。添加、删除或平衡内容将不仅仅是不方便,而是相当棘手。
这对于任何游戏来说都是合理的,尤其是在描述内容时需要填充数百个配置文件,而单个游戏单位的平衡调整涉及多个不同的表格。
因此,我建议使用数据标记脚本语言的格式来传递信息,使用JSON或XML(老旧的Lua和更先进的YAML也可以)。
在具体项目中,我选择了JSON。
有人指出它的优势在于来自JavaScript的熟悉语法。我认为其主要优点是——有现成的免费模块可在资产商店中用于数据的序列化,适用于JSON/XML。
此外,我的学生们的经验表明,学习编写JSON只需几天。这与学习引擎或编程语言是完全不同的。
所以,与程序员就信息传递格式达成一致之后,游戏设计师必须形成每种类型对象的完整参数列表,标明数据类型和可能的限制。
图1. Confluence页面片段,包含JSON脚本参数列表
所得列表需要与程序员确认。游戏设计师所创建的文档将转化为游戏代码中的数据结构描述。
图2. JSON配置中建筑升级成本数据结构的截图
2. 数据关系与结构
通常,游戏中没有孤立的对象。每个对象与某些数据相互关联。
为记录这些相互连接并形成数据结构,使用SQL术语是很方便的,同时将特定类型的内容列表(在我们的情况下是建筑)视为表格。
关键和关联类型
在形成数据结构时,必须牢记以下概念:
外键
标识符(通常是自然数),用于从一个表中获取另一个表中的信息。
关联类型
内部和外部键的使用逻辑。通常分为:
- 一对一:两个表的信息之间的关联,每个记录仅在每个表中使用一次。例如,某个表中的建筑标识符完全等同于另一个表中的标识符。
- 一对多:这种关系中,表A中的一行可以对应表B中的多行。但表B中的一行只能对应表A中的一行。例如,某个建筑可能有多个升级等级,每个等级具有不同的属性。
- 多对多:这种关系中,表A中的多个记录可以对应表B中的多个记录。例如,“四维工厂”建筑在第5级时开启了一个额外的建筑槽位,而“天空邮轮”属性也对建筑施加效果,在所在区域内增加一个建筑槽位。
如果有技术可能性,尽量创建带有唯一ID的效果和其他字段,优先采用一对多的关联。
正常化数据库是一个规则集合,旨在防止数据冗余。关于这一点的资料在网络上有很多。
形成项目配置文件的数据结构更容易用实例来展示。让我们简要回顾建筑的构建机制。
- 玩家的行星被划分为不同区域,每个区域由六边形组成。
- 每个六边形只能容纳一座建筑。
- 除了建筑外,区域内还可能有资源和异常现象。
资源是建筑的构建基础。例如,某个石英矿将允许在该单元中仅建造特定的建筑来提取石头。
而异常情况则是一个虚拟容器,包含在研究后可激活的效果和/或可以在研究异常后收集的资源。
建筑可以拥有不同的等级。每个等级具有:
- 升级到该等级所需支付的费用;
- 该等级建筑所提供的效果集;
- 此等级下生产的资源。
一座建筑可以有多个二级。这意味着,在将建筑升级到二级时,玩家可以选择如何升级建筑。
图3. Confluence页面片段,展示建筑发展的描述
从截图中可以看到,“行星撕裂者”建筑(是的,我是死亡空间的粉丝)在建造时需要金属。它建成后每回合生产1个石头。玩家有机会将其升级两次。在升级到二级时,玩家可以选择增加每回合石头产量1或2个。
选择第二个选项将使玩家成本增加100% + 30%(升级的通货膨胀系数)。在将建筑升级到三级时,除了每回合生产石头之外,该建筑还可以选择额外的效果:以50%的概率额外获得1个金属或1个石头。
让我们设计一个数据模型,以描述这些建筑发展的可能性。
图4. 配置文件中建筑内容列表(表格)之间的关联
当前模型的主要表格是Buildings。该表格中描述了所有游戏中可用的建筑。每座建筑都有一个唯一的数字ID(自然数)。我们将根据该ID在所有表格中识别建筑。
假设我们需要获取某个具体建筑的文本描述或资产(例如3D模型)。在这种情况下,程序代码将在Buildings表中查找具有给定唯一ID的条目。接着,它将从该条目中获取所需的信息。
图5. Google 表格中Buildings表的内容片段截图
除了Buildings表外,还有Building_Levels表。该表中列出了所有建筑的所有等级。特别是,对于“行星撕裂者”建筑,将有五条记录:一条用于建筑的第一级,两个用于第二级和第三级。所有这些条目的ID不同,但building_id相同。它是一个外键,可以唯一地找到Buildings表中的建筑。
这样的数据结构允许产品具有不同数量的等级,并为每座建筑提供不同的奖励。游戏设计师可以设计提取资源的建筑,它们可以简单地升级十次。或者复杂的宇宙港,只能升级几次,但有五种不同的方式。这演示了一对多关系的运作。
图6. Google 表格中Building_Levels表的内容片段截图
将建筑升级到新等级并选择其中一个升级选项可能有不同的成本。显而易见,扩展生产+3的费用应该比扩展+1的费用更高。否则我们就会犯下“无用或过于有用”不平衡的错误,如加菲尔德所称:为什么要花高价去购买比更便宜的替代品更差的东西。
为了设计每个等级升级的不同成本,方便引入一个补充表格Building_Upgrade_Costs。其结构类似于等级表。但现在引用该表的外键不再是建筑,而是具体的等级。对于每个等级,都列出升级所需的资源清单。
图7. Google 表格中Building_Upgrade_Costs表的内容片段截图
类似地,还描述了Building_Effects和Building_Production表,分别负责“以50%概率产生1个金属或1个石头”的特殊效果和生产资源的数量。
3. 内容生成管道
游戏的主要场景是行星,玩家可以在其六边形上放置建筑,方法是将相应的卡片拖放到那里。每回合,玩家获得一张卡(建筑预制件或法术)。从这段简短的描述中,已经形成了对过程的初步理解:
- 开发游戏机制的设计文档(建筑和升级);
- 创建建筑能力列表:
a) 资源生产。例如,每回合+1能量;
b) 随机掉落。例如,75%的概率每回合获得一单位随机资源;
c) 经济修改器(系数)。例如,人口增长速度+5%;
d) 带选择器和条件的复杂效果。例如,所有区域内的石头源每升级一级消耗的能量减1。 - 创建内容列表(建筑);
- 形成Excel / Google(或其他)表格的结构,以记录数据;
- 将内容填充到表格中,并进行平衡;
- 在项目的特定格式或通用数据传输格式(例如,JSON、YAML、LUA等)中形成并验证数据。
- 将数据转移到引擎中并构建版本;
- 测试数据的有效性和游戏的平衡。
图8. Confluence页面关于行星和建筑物的建设描述的片段截图
在上面的截图中,可以看到,为了描述建筑的构建机制,我们准备了多个文档,讲述了什么是行星、区域和它们的六边形、如何分发卡牌来创建建筑物、它们如何在场景中放置等等。
更方便的是将庞大的文档划分成小的章节,以揭示机制的单个元素,并通过贯穿整个叙述的链接体系将其连接起来。幸好Confluence、Notion等相似的项目和产品文档服务非常适合这个目的。
此外,内容与机制描述的分离也是良好的做法。不应该将建筑的功能、背后的经济及这些建筑的列表合并到一篇大文章中。然而,这项规则并非通用的。它只是出于便利信息消费的简单需求。如果您的机制是有限的,并不需要成百上千的单位、法术和等级,那么在一页上详细描述所有内容也是方便的。
4. 内容表的形成与填充
在拥有经过确认的产品文档和内容之后,游戏设计师便面临如何用技术语言描述这些内容的问题。
要开始处理建筑,必须预先描述所有与其相关的内容:资源、卡片和经济。
图9. Google 表格中资源表的截图
注意,在建筑表中没有任何关于特定建筑的功能、建造费用和升级费用的信息。
图10. Google 表格中的建筑物清单截图
值得指出的是,“名称”字段中的文本仅用于方便游戏设计师使用,真正的艺术描述文本、标题和效果工作说明被转移到本地化文件中。它的工作逻辑基于与其他表格相同的原则。
图11. 本地化文件的截图
借此例子,我们可以讨论自动化的优点。
1. 自动检查
“Google 表格”可以保护游戏设计师避免错误输入内容。至少它会在输入不正确的数据时发出警告。例如,当选择与本地化文本相关的组时,子组字段会自动加载适用于该组的选项。
例如,如果我们选了“行星”组,则无法指定与行星无关的键子组。同样,所有其他表格也是如此:在指定资源表中资源值的可能范围(min_value, max_value)时,只能输入从- maxint到maxint的合理数值。输入其他文本将引发错误,警告游戏设计师所输入的数据必须是数字。结果,程序员将从游戏设计师那里获得经过严格检查和有效的配置。
2. 单元格高亮
这有助于理解尚未填写哪些数据。例如,在本地化文件的截图中,我们看到第二行输入了一个待定本地化键的预置。我们选择了“行星”组和“建筑”子组,但尚未描述该键下将要记录的文本。因此,文件将“项目”字段高亮为红色。在下面的一行中,“项目”字段已填充,但未输入翻译,因此“key”(本地化键)后面的“ru”字段被高亮。如果游戏的原始语言是俄语,则可以设置表格,以便它高亮显示所有在俄罗斯本地化中变化的字段,包括英文、西班牙文和其他语言的字段。这样,我们就永远不会忘记提交需要重新翻译的文本,其原文已发生变化。
自动化输入内容的另一个重要部分是平衡计算。
在不断添加新内容(在我们的情况下是建筑物)并从零开始计算生产、购买成本、升级成本及多项其他参数的情况是正常的。这不仅浪费时间,而且增加了出错的机会。
我参与的所有游戏项目的一个共同原则是:自动化所有工作,而不是自己编写代码。任何手动工作都有错漏的机会。
图12. 截图,显示建筑物升级到特定等级的费用表
在上面的截图中,可以看出,一些参数是自动计算的。
举个例子,我们知道某个建筑没有任何效果,除了简单的资源生产。因此,假设“行星撕裂者”在第一级每回合生产1个石头。
我们的任务是计算建造这座建筑的成本。
游戏设计师决定,在第一级的情况下,它只用金属建造。因此,我们需要计算建造一座生产1块石头的建筑所需的金属量。为此,我们需要知道以下两件事情:
- 金属和石头的价值之间的关系;
- 该建筑应在多少个回合之内回本。
第一个参数在资源表中由“weight”系数定义(请参见图9)。第二个参数是单独页面上固定的常量。
知道这些,表格会计算出用能量(最便宜的资源)建造建筑的成本,然后将其转化为指定的资源——金属。
表格还会考虑其他许多细节和系数。例如,随着建筑等级或区域内建筑数量的增加,带来的通货膨胀。
我保留对平衡过程的简单描述,因为对其的讨论将是一个独立的小型三小时讲座。我们当前的目标是理解并确保平衡和数学同样可以并且应该实现自动化。
5. 数据验证与导入引擎
下一步是将表格中的信息编码,以便它可以传递给引擎。
正如我之前提到的,我们使用JSON格式,然而没有任何障碍阻止您选择其他任何信息表示方法。
请注意Google 表格中配置文件的任何截图(图9、10、12)。最后一列总是包含一个用于JSON脚本的单元格。
图13. 生成JSON脚本的公式截图
图14. 通过公式生成的最终JSON脚本截图
每行数据在最后都会用包含该行信息的脚本单元结束。
下一步是检查生成的脚本是否包含有效的无错误代码。为此,可以方便地使用各种在线验证器。通过输入“JSON online validator”在搜索引擎中轻松找到它们。
图15. 在线解析器中JSON代码的错误检查截图
在确认数据没有错误后,应将脚本中的所有行数据合并为一个单元,以方便将最终文本拷贝到编辑器中。
如果您是使用自动构建工具,而不是手动构建,可以添加功能自动提取指定单元格中的文本,从而覆盖特定的配置文件。
最终的内容处理自动化算法可以简要地描述如下:
图16. 内容处理流程图
6. 总结
感谢您耐心阅读至此。我希望这篇文章对您有所帮助。
我知道阅读包含大量表格的技术文本可能会显得乏味,但对于团队生产文化的发展,这是非常重要的。
无论您的游戏是拥有百万预算的大型项目还是独立团队的小型冒险,如果您专注于商业成功,开发过程中的技术自动化是创建游戏的重要元素,没有它,生产地狱的风险将大大增加。