注意: 雖然 JavaScript 對於本網站不是必需的,但您與內容的互動將受到限制。請開啟 JavaScript 以獲得完整的體驗。

為自閉症兒童定製的影像瀏覽遊戲

引言

我的兒子納特患有自閉症。他喜歡看熟悉的地方、事物和人物的照片。我們買了一臺數碼相機,這樣我們就可以隨心所欲地拍攝儘可能多的普通事物,而不必擔心膠片和沖洗的成本和麻煩。納特的品味很難預測,所以我們可能會拍攝一些肯定會讓他滿意的主題,結果卻發現他對此毫無興趣。我們希望在拍照時能夠撒一張大網,以確保捕捉到能引起他興趣的影像。

有了相機之後,我們拍了幾百張照片,並教納特如何使用一款名為 ThumbsPlus 的現成影像檢視器在電腦上瀏覽它們。然而,看著納特翻閱照片,我注意到一件事:一旦他熟悉了照片的順序,他就會點選照片,就好像他點選的位置很重要一樣。影像檢視器並不在意:在圖片上的任何點選都會顯示下一張圖片。但如果納特知道下一張圖片是當前圖片左側的一個東西,他就會點選圖片的左側。如果他知道下一張圖片是透過圖片右側的一扇門,他就會點選那扇門。他玩過許多兒童冒險遊戲,例如《睡衣山姆》,並且知道這種導航方式。即使在一個不使用這種導航風格的程式中,他也開始使用它,就好像它確實使用了。

所以我想到,為什麼不製作一個按照他的方式工作的影像檢視器呢?為什麼不編寫一個程式,讓我可以從我們的生活中構建圖片環境,並讓納特以他想要的方式在其中導航呢?

Natworld Screen Shot

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。