游戏循环:
正如关于建模世界的文章中所讨论的,大多数游戏具有相同的概念性主循环[1]:
一会儿(游戏还没有结束):
获取用户输入
响应用户输入
向用户显示内容
在我们的案例中,游戏循环以游戏的REPL形式实现,即游戏的Read-Eval-Print循环。
- 兴趣点—使用Xcode Instruments分析Unity
- 因此,您想成为独立开发者吗? 大!
- 玩具引擎开发日志#1
- 开发日记-事情变得越来越奇怪
- “来自开发者”:RichOfPickles保留球员背后的心理
在我们的游戏中,读取,评估和打印是单独的功能,分别称为游戏读取,评估和打印游戏,所有这些功能都包含在一个称为游戏复制的封闭功能中。 因此,game-repl调用游戏打印,该游戏打印将游戏评估的结果作为参数,而游戏评价的结果又将游戏读取作为参数。 我们可以使用Python等效代码这样表示(因此更容易理解):
def game-repl() :
游戏打印(游戏评估(游戏读取(读取用户输入()))
game-repl()
从上面的片段中我们可以推断出什么?
- game-repl函数以递归方式调用自身,从而创建无限循环。
- 表达式以正常顺序求值,即,从最内到外,从右到左调用每个函数。
- 因此,调用这些函数的顺序如下:
1. read-user-input() :获取用户在命令行界面中键入的内容,并将其作为参数传递给下一个游戏读取的函数。
2. game-read() :获取用户键入的文本字符串,并将其转换为下一个功能game-eval可以理解的命令。
3. game-eval() :接收命令,检查是否允许该命令并执行。 该函数返回命令执行返回的所有内容。
4. game-print() :获取命令执行的结果,并以可读格式将其打印给玩家。
5. game-repl() :再次调用REPL,并从1开始重复该过程。
阅读了每个功能的功能后,您可能一直认为游戏的大多数功能都在游戏评估功能中。
你是绝对正确的。 游戏评估执行的“命令”处理游戏中所有已定义的逻辑。 实际上,在程序级别, game-eval() 本身将用户输入作为参数,响应用户输入并返回输出以显示给用户 。 REPL的其他组件仅提供与用户的接口— game-read() 将用户输入转换为程序的语言 ,而game-print() 将程序的语言转换为用户的语言。
文件结构:
游戏评估如何处理用户输入并返回所需的输出? 它使用各种其他功能和变量来使之成为可能。 这些函数和变量按以下方式组织到文件中:
- 游戏界面操作 : (game-uis.lisp)
传递给eval的用户输入是命令,这些命令是此文件中定义的各种功能。 这些功能中的每一个都以某种方式或其他方式改变游戏状态,它们是在下一个文件中定义的变量。 - 游戏状态 : (game-state.lisp)
该文件包含一组可变的数据结构,这些数据结构表示游戏的状态-在我们的例子中,玩家在世界上,角色状态的不同方面(例如,他收集的对象)。 因此,游戏状态包括随着玩家玩游戏而改变的事物。 在软件方面,这种游戏状态仅由变量定义组成。 - 游戏初始化数据 : (game-data.lisp)
该文件包含游戏初始化数据。 不同的地方及其描述,那里的物体和不同的途径。 - 场景描述符 : (scene-descriptors.lisp)
该文件包含处理游戏数据中的变量以及游戏状态的功能,以描述可以向用户展示以供其理解的场景,即重新渲染游戏世界。
这些文件实际上包含什么?
如果您尚未浏览GitHub代码,则可能需要一个更具体的概念,即每个文件包含什么以及如何实现功能。
游戏界面动作:
该文件包含用户可以执行的每个操作的功能。 在我们的简单游戏版本中,这些也是用户可以键入以与游戏世界进行交互的命令。
1.看 :描述当前位置
2.步行 方向 :步行并遵循一条路径(东,西等)
3.拾取 对象 :拾取当前位置的对象
4.库存 :描述玩家携带的物品
5. 焊接对象:允许焊接对象
6.扣篮对象:如果允许扣篮对象。
7.将主题溅到对象上:将主题溅到对象上
它还包含一个名为* allowed-commands *的变量,该变量包含允许用户执行的命令(即上述命令)的列表。 这是出于安全目的,以确保用户不能在REPL中键入其他命令。
游戏状态:
* location :玩家所在的位置 (即哪个节点)
* 库存 :玩家携带的物品(即物品)
游戏数据:
我们使用空间图来表示游戏中的位置,即由节点(位置)和边(路径)组成的图。 对于这些数据,我们使用Lisp a列表(关联列表)。
*节点 :以a-list形式包含地点及其描述
*边 :包含到节点和从节点的路径的描述
* objects :包含可以拾取的对象列表
* object-locations :包含对象及其位置(即哪个节点)
场景描述符:
这些功能以人类可读的格式描述游戏数据和游戏状态中的数据,即作为英语的句子,作为用户控制台中的输出。
* describe-location :以可读形式描述任何给定的节点。
* describe-paths :描述具有输入方向和入口点的给定边(路径)
* describe-objects :在给定节点/位置的情况下描述对象及其所在的位置
*对象
这些组件如何协同工作?
- game-eval调用游戏UI动作功能之一,该功能查看游戏的当前状态 (游戏状态变量)和游戏世界 (游戏数据变量),并以所需的方式对其进行更改 。
- 然后调用场景描述符 ,以人类可读的形式描述游戏状态和游戏世界 ,并将其传递给游戏打印以显示为输出
这是可视化的流程图:

见解和扩展
现在您已经了解了该游戏的结构,让我们看一下可以添加的有趣功能。 拥有良好架构的最好的部分是,可以在不更改整体结构或架构的情况下轻松地对其进行修改或扩展。 实际上,我发现架构解决方案之所以令人兴奋,是因为您可以设计结构,从而可以轻松吸收新功能而不必重新设计结构以适应这些功能。
显然,在游戏中添加新动作与添加新的游戏UI动作以及在游戏状态下(如果需要)相应的变量一样简单。 可以通过更改游戏数据变量来扩展游戏世界。
您还可以做一些其他有趣的事情,使该游戏的软件结构更有趣。
- 差异场景图
在输出包含许多视觉元素的游戏中,这些元素的渲染速度很慢(例如:在DOM中使用SVG渲染图形),然后可以通过仅渲染已更改的事物并保留尚未改变的事物来加快渲染速度。 t原样改变。 发现变化的算法称为差异算法 ,并且必须相当快。 差异是在场景图 (或通过DOM进行SVG的情况下为虚拟DOM)上完成的, 场景图是表示屏幕可视状态的数据结构。 - 功能游戏
正如我在使用FP建模世界的文章中详细描述的那样,您可以轻松地弄清楚如何设计不会改变状态或具有副作用的游戏。 因此,就像游戏数据变量一样,甚至游戏状态变量也将变得不可变。 可以执行多项性能优化,以使其变得切实可行。 - 新形式的用户输入
如果我们想使用箭头键而不是输入向东 走,向北走等等,该怎么办? 我们可以轻松地修改游戏阅读以侦听此类用户输入,并将其转换为game-eval可以理解的这些命令。 - 元编程
在该游戏出现的那本书的原始版本中,作者教您创建一个宏 ,该宏可让您在运行时添加游戏功能! 即,游戏UI操作,焊接,扣篮和飞溅之类的动作是由游戏运行时的“功能”创建的-生成了各种运行时功能。
结束语
在我的编程经验的早期,构建这个基本游戏就教会了我很多关于Lisp和体系结构的知识。
我在使用Conrad L. Barski的《 Lasp of Lisp》学习Lisp的同时开始构建此游戏,该书通过动手使我在CLISP中编写代码游戏,Web服务器和其他应用程序,从而使我了解了CLISP语法以外的各种编程范例。 我发现这本书非常有趣。
因此,该游戏(《巫师的冒险》)概念的权利完全掌握在该书作者的手中,随着时间的流逝,我只是将其扩展为了解文本游戏的软件结构和游戏机制。