为自闭症儿童定制的图像浏览游戏
引言
我的儿子纳特患有自闭症。他喜欢看熟悉的地方、事物和人物的照片。我们买了一台数码相机,这样我们就可以随心所欲地拍摄尽可能多的普通事物,而不必担心胶片和冲洗的成本和麻烦。纳特的品味很难预测,所以我们可能会拍摄一些肯定会让他满意的主题,结果却发现他对此毫无兴趣。我们希望在拍照时能够撒一张大网,以确保捕捉到能引起他兴趣的图像。
有了相机之后,我们拍了几百张照片,并教纳特如何使用一款名为 ThumbsPlus 的现成图像查看器在电脑上浏览它们。然而,看着纳特翻阅照片,我注意到一件事:一旦他熟悉了照片的顺序,他就会点击照片,就好像他点击的位置很重要一样。图像查看器并不在意:在图片上的任何点击都会显示下一张图片。但如果纳特知道下一张图片是当前图片左侧的一个东西,他就会点击图片的左侧。如果他知道下一张图片是通过图片右侧的一扇门,他就会点击那扇门。他玩过许多儿童冒险游戏,例如《睡衣山姆》,并且知道这种导航方式。即使在一个不使用这种导航风格的程序中,他也开始使用它,就好像它确实使用了。
所以我想到,为什么不制作一个按照他的方式工作的图像查看器呢?为什么不编写一个程序,让我可以从我们的生活中构建图片环境,并让纳特以他想要的方式在其中导航呢?
Natsworld 中超过 1500 张图像之一。用户界面只是一组方向光标(靠近中心) 放大
选择 Python
我以前用 Python 做过小型脚本项目,喜欢它快速上手的特点。它将为我提供一个可塑的环境,让我可以尝试各种功能,看看纳特会觉得什么有趣。尽管这是一个供我儿子在家使用的项目,但它与更大、看似更严肃的项目有着共同的问题:需求不明确、客户不可预测、开发人员资源有限。在选择开发工具时,对我来说最重要的考虑是我的生产力。由于这是“下班后”的工作,我的时间和精力会很紧张,我希望能够专注于用户体验,而不是构建环境、内存管理等等。
我寻找现有的软件,并在 pygame (www.pygame.org) 上找到了许多有趣的例子。Pygame 提供了我进行图像处理和显示管理所需的所有功能,而且那里有一些现有的项目提供了有用的例子。Pyzzle 几乎提供了我需要的东西,但内部组织不如我希望的那样好,无法快速尝试新功能。
实施
我使用现有的例子来指导我使用 pygame,为我的程序创建了一个新的框架。我的程序,我简单地命名为“纳特的世界”,将提供一个可供探索的虚拟世界。它将按照纳特预期的方式工作,提供一个类似于 Myst 等游戏的简单探索环境;点击屏幕左侧会让你向左转,点击中央会让你向前移动,等等。
最简单地说,整个环境只是一组节点,每个节点都有一张要显示的图像,以及一个可以导航到的其他连接节点的列表。世界中的一个物理位置由许多节点表示,每个方向一个。这些节点统称为一个“地点”。随着时间的推移,这个模型被扩展以向环境中添加功能,但这个简单的模型让我开始了工作。Python 为我构建我的 Node 类提供了一个干净的面向对象基础,而 pygame 则提供了显示图像、创建光标和收集输入的原语。
最大的挑战之一是决定如何组织世界描述的方式。一方面,我可以为世界的结构创建一个描述性语言,并编写一个解释器来读取描述并构建游戏运行的内存中结构。这将是一项相当大的工作,并且需要我将世界中可能出现的所有结构形式化。这也意味着在我拥有纳特实际可以使用的东西之前,会有更长的延迟。另一方面,我可以直接使用 Python 语句来构造这些结构。这将是乏味且容易出错的。
作为一个方便的折中方案,我使用 Python 语句来构建世界,但大量使用了实用函数和类来为常见结构提供快捷方式。例如,我的所有图像都存储在按日期命名的目录中,我不得不为每个节点输入一个图像文件名。ImgShortcut 类使这变得简单。定义__call__方法让我可以玩转 Python 语法以达到我想要的效果。
class ImgShortcut:
def __init__(self, fmt):
self.fmt = fmt
def __call__(self, arg):
return self.fmt % (arg)
nov4 = ImgShortcut(r'C:\img\vol3\20011104\dscf%04d.jpg')
nov10 = ImgShortcut(r'C:\img\vol3\20011110\dscf%04d.jpg')
>>> print nov4(17)
c:\img\vol3\20011104\dscf0017.jpg
再举一个例子,世界上的许多地点都共享相同的四节点结构:一个用于北方视图,一个用于西方视图,等等。使用 Python 的关键字参数语法,我能够编写一个函数来构建这种常见的地点类型。
def nesw(name, **data):
"""
Make four nodes for n, e, w, s from a location.
Keys:
images: ni, ei, wi, si.
destinations: n, e, w, s.
"""
# (code omitted)
第一个参数是新节点的基本名称(节点名称是通过附加“:n”、“:w”等来创建的)。其余参数由关键字指定:ni 是北方节点的图像名称,n 是北方节点;wi 是西方节点的图像,w 是西方节点,等等。现在我可以用一个简单的简写语法创建一个地点。
nesw('front43',
ni = nov4(17),
ei = nov4(18), e = 'allerton_hawthorne:e',
si = nov10(381), s = 'hall:s',
wi = nov4(16), w = 'markliesl:w'
)
由于关键字语法,我可以省略不适用于特定地点的参数。现在我有一个几乎是声明性的语法,方便创建常见结构,而无需为一种新的小语言编写解释器。如果我遇到需要完整 Python 的特殊情况,它也可用。
世界及其支持程序并行发展。每当我想到新的地点结构时,我都会编写新的实用函数,如 `nesw()`,以便轻松创建它们。我可以看着纳特玩游戏,了解他想要它做什么,并迅速添加功能。Python 的许多功能(解释型代码、面向对象、垃圾回收、列表和字典)都支持这种快速迭代。
Node 类得到了扩展和子类化,以创建新的节点类型。例如,我添加了一个 MenuNode 子类来处理屏幕上的目的地菜单。这允许点击汽车,然后选择您想开往哪里。节点之间的过渡也增加了,使效果更令人愉悦。点击立体声会弹出一个歌曲菜单供播放(当然是通过 pygame)。
结果
目前,源代码总大小约为 1400 行代码,加上描述世界的 1300 行代码,目前共有超过 1500 个节点。
当我向朋友展示《纳特的世界》时,他们总是谈论如果我能为其他人也制作出类似的东西会有多棒。我曾考虑过编写一个 WorldBuilder 应用程序。它将允许非技术人员使用 GUI 浏览图像,并将它们连接到一个节点世界中。这将是一个不错的项目,如果我能用它来扩展《纳特的世界》就更好了。我不知道我是否有时间和精力在业余时间构建这样的东西,但如果我真的做了,我知道 Python 将是这项工作的工具。
关于作者
内德·巴切尔德是来自马萨诸塞州布鲁克莱恩的软件工程师。他的妻子和三个儿子都喜欢玩《纳特的世界》。他的网站是 https://www.nedbatchelder.com。
