在 Node.js 中編寫多人文本冒險引擎(第 1 部分)

已發表: 2022-03-10
快速總結↬聽說過文字冒險嗎? 在本系列文章中,Fernando Doglio 解釋瞭如何創建一個能夠讓您和您的朋友玩任何文本冒險的完整引擎的過程。 沒錯,我們將通過在文本冒險類型中添加多人遊戲來增加它的趣味性!

文字冒險是最早的數字角色扮演遊戲之一,當時遊戲沒有圖形,你所擁有的只是你自己的想像力和你在 CRT 顯示器的黑屏上閱讀的描述。

如果我們想懷舊,也許 Colossal Cave Adventure(或者只是 Adventure,因為它最初的名字)這個名字敲響了警鐘。 那是有史以來第一款文字冒險遊戲。

一張當年真實文字冒險的照片
一張當年真實文字冒險的照片。 (大預覽)

上圖是您實際看到的遊戲,與我們當前的頂級 AAA 冒險遊戲相去甚遠。 話雖如此,它們玩起來很有趣,並且會佔用你數百小時的時間,因為你獨自坐在文本前,試圖弄清楚如何擊敗它。

可以理解的是,這些年來,文字冒險已經被呈現更好視覺效​​果的遊戲所取代(儘管有人可能會說,其中很多遊戲為了圖形而犧牲了故事),尤其是在過去的幾年裡,與其他人合作的能力越來越強朋友一起玩。 這個特殊的功能是原始文本冒險所缺乏的,也是我想在本文中帶回的一個。

本系列的其他部分

  • 第 2 部分:遊戲引擎服務器設計
  • 第 3 部分:創建終端客戶端
  • 第 4 部分:將聊天添加到我們的遊戲中

跳躍後更多! 繼續往下看↓

我們的目標

正如您現在可能從本文的標題中猜到的那樣,這項工作的全部意義在於創建一個文本冒險引擎,允許您與朋友分享冒險,使您能夠與他們進行類似的協作龍與地下城遊戲(其中,就像好的 ol' 文字冒險一樣,沒有圖形可看)。

在創建引擎時,聊天服務器和客戶端是相當多的工作。 在本文中,我將向您展示設計階段,解釋諸如引擎背後的架構、客戶端如何與服務器交互以及這個遊戲的規則等內容。

只是為了給你一些視覺幫助,這將是什麼樣子,這是我的目標:

遊戲客戶端最終 UI 的通用線框圖
遊戲客戶端最終 UI 的通用線框圖(大預覽)

這就是我們的目標。 一旦我們到達那裡,您將獲得屏幕截圖,而不是快速而骯髒的模型。 所以,讓我們來看看這個過程。 我們將介紹的第一件事是整個事物的設計。 然後,我們將介紹我將用來編寫代碼的最相關的工具。 最後,我將向您展示一些最相關的代碼(當然還有完整存儲庫的鏈接)。

希望到最後,您會發現自己正在創建新的文字冒險,並與朋友一起嘗試!

設計階段

對於設計階段,我將介紹我們的整體藍圖。 我會盡力不讓你厭煩至死,但同時,我認為在編寫第一行代碼之前展示一些需要發生的幕後工作是很重要的。

我想在這裡詳細介紹的四個組件是:

  • 引擎
    這將是主要的遊戲服務器。 遊戲規則將在此處實施,它將為任何類型的客戶端提供一個與技術無關的界面來使用。 我們將實現一個終端客戶端,但您可以使用 Web 瀏覽器客戶端或您想要的任何其他類型執行相同的操作。
  • 聊天服務器
    因為它足夠複雜,可以擁有自己的文章,所以該服務也將擁有自己的模塊。 聊天服務器將負責讓玩家在遊戲過程中相互交流。
  • 客戶端
    如前所述,這將是一個終端客戶端,理想情況下,它看起來類似於之前的模型。 它將利用引擎和聊天服務器提供的服務。
  • 遊戲(JSON 文件)
    最後,我將討論實際遊戲的定義。 這樣做的重點是創建一個可以運行任何遊戲的引擎,只要您的遊戲文件符合引擎的要求。 因此,即使這不需要編碼,我也會解釋如何構建冒險文件,以便將來編寫我們自己的冒險。

引擎

遊戲引擎或遊戲服務器將是一個 REST API,並將提供所有必需的功能。

我選擇 REST API 只是因為——對於這種類型的遊戲——HTTP 增加的延遲及其異步特性不會造成任何問題。 但是,我們將不得不為聊天服務器採用不同的路線。 但是在我們開始為我們的 API 定義端點之前,我們需要定義引擎的能力。 所以,讓我們開始吧。

特徵描述
加入遊戲玩家可以通過指定遊戲的 ID 來加入遊戲。
創建一個新遊戲玩家也可以創建一個新的遊戲實例。 引擎應該返回一個 ID,以便其他人可以使用它來加入。
返回場景此功能應返回聚會所在的當前場景。 基本上,它將返回描述以及所有相關信息(可能的操作、其中的對像等)。
與場景互動這將是最複雜的之一,因為它將接受來自客戶端的命令並執行該操作——例如移動、推送、獲取、查看、讀取等等。
檢查庫存雖然這是一種與遊戲交互的方式,但它並不直接與場景相關。 因此,檢查每個玩家的庫存將被視為不同的操作。

關於運動的一句話

我們需要一種在遊戲中測量距離的方法,因為在冒險中移動是玩家可以採取的核心行動之一。 我們將使用這個數字來衡量時間,只是為了簡化遊戲玩法。 考慮到此類游戲具有回合製動作(例如戰鬥),使用實際時鐘測量時間可能不是最好的。 相反,我們將使用距離來測量時間(這意味著 8 的距離將需要比 2 之一更多的時間來遍歷,因此我們可以做一些事情,比如為玩家添加持續一定數量的“距離點”的效果)。

關於運動要考慮的另一個重要方面是我們不是一個人玩。 為簡單起見,引擎不會讓玩家分裂隊伍(儘管這可能是未來的一個有趣的改進)。 這個模塊的初始版本只會讓每個人都可以移動到大多數黨派決定的地方。 因此,移動必須以協商一致的方式進行,這意味著每一個移動動作都將等待大多數一方的請求才能發生。

戰鬥

戰鬥是這類游戲的另一個非常重要的方面,我們必須考慮將其添加到我們的引擎中; 否則,我們最終會錯過一些樂趣。

老實說,這不是需要重新發明的東西。 回合製派對戰鬥已經存在了幾十年,所以我們將只實現該機制的一個版本。 我們將把它與龍與地下城的“主動”概念混合在一起,滾動一個隨機數字以保持戰鬥更有活力。

換句話說,參與戰鬥的每個人選擇行動的順序將是隨機的,其中包括敵人。

最後(儘管我將在下面更詳細地討論這一點),您將擁有可以使用設定的“傷害”數字拾取的物品。 這些是您可以在戰鬥中使用的物品; 任何沒有該屬性的東西都會對你的敵人造成0傷害。 當您嘗試使用這些物品進行戰鬥時,我們可能會添加一條消息,以便您知道您嘗試做的事情毫無意義。

客戶端-服務器交互

現在讓我們看看給定的客戶端如何使用之前定義的功能與我們的服務器交互(還沒有考慮端點,但我們將在幾秒鐘內到達那裡):

(大預覽)

客戶端和服務器的初始交互(從服務器的角度來看)是一個新遊戲的開始,其步驟如下:

  1. 創建一個新遊戲
    客戶端向服務器請求創建一個新遊戲。
  2. 創建聊天室
    雖然名稱沒有具體說明,但服務器不僅僅是在聊天服務器中創建一個聊天室,而且還設置了它所需的一切,以便允許一組玩家進行冒險。
  3. 返回游戲的元數據
    一旦服務器創建了遊戲並且為玩家準備了聊天室,客戶端將需要該信息來進行後續請求。 這主要是一組 ID,客戶可以用來識別自己和他們想要加入的當前遊戲(稍後會詳細介紹)。
  4. 手動分享遊戲ID
    這一步必須由玩家自己完成。 我們可以提出某種共享機制,但我會將其留在願望清單上,以備將來改進。
  5. 加入遊戲
    這個很簡單。 由於每個人都有遊戲 ID,他們將使用他們的客戶端應用程序加入冒險。
  6. 加入他們的聊天室
    最後,玩家的客戶端應用程序將使用遊戲的元數據加入他們冒險的聊天室。 這是賽前所需的最後一步。 一旦這一切都完成了,那麼玩家就可以開始冒險了!
現有遊戲的動作順序
現有遊戲的動作順序(大預覽)

一旦滿足所有先決條件,玩家就可以開始冒險,通過聚會聊天分享他們的想法並推進故事。 上圖顯示了為此所需的四個步驟。

以下步驟將作為遊戲循環的一部分運行,這意味著它們將不斷重複直到遊戲結束。

  1. 請求場景
    客戶端應用程序將請求當前場景的元數據。 這是循環每次迭代的第一步。
  2. 返回元數據
    服務器將依次發回當前場景的元數據。 這些信息將包括一般描述、在其中找到的對像以及它們之間的關係等內容。
  3. 發送命令
    這就是樂趣的開始。 這是玩家的主要輸入。 它將包含他們想要執行的動作,以及該動作的目標(例如,吹蠟燭、抓石頭等)(可選)。
  4. 返回對發送的命令的反應
    這可能只是第二步,但為了清楚起見,我將其添加為額外的步驟。 主要區別在於第二步可以被認為是這個循環的開始,而這一步考慮到你已經在玩了,因此,服務器需要了解這個動作將影響誰(或者是單個玩家或所有玩家)。

作為一個額外的步驟,雖然不是真正的流程的一部分,但服務器將通知客戶端與它們相關的狀態更新。

這個額外重複步驟的原因是因為玩家可以從其他玩家的動作中接收到更新。 回想從一個地方搬到另一個地方的要求; 正如我之前所說,一旦大多數玩家選擇了一個方向,那麼所有玩家都會移動(不需要所有玩家的輸入)。

這裡有趣的一點是 HTTP(我們已經提到服務器將成為 REST API)不允許這種類型的行為。 所以,我們的選擇是:

  1. 從客戶端每隔 X 秒執行一次輪詢,
  2. 使用某種與客戶端-服務器連接並行工作的通知系統。

根據我的經驗,我傾向於選擇選項 2。事實上,我會​​(並且將在本文中)將 Redis 用於這種行為。

下圖演示了服務之間的依賴關係。

客戶端應用程序和遊戲引擎之間的交互
客戶端應用程序和遊戲引擎之間的交互(大預覽)

聊天服務器

我將把這個模塊的設計細節留給開發階段(這不是本文的一部分)。 話雖如此,有些事情我們可以決定。

我們可以定義的一件事是服務器的一組限制,這將簡化我們的工作。 如果我們打得好,我們最終可能會得到一個提供強大接口的服務,從而允許我們最終擴展甚至更改實現以提供更少的限製而不會影響遊戲。

  • 每個聚會只有一個房間。
    我們不會創建子組。 這與不讓黨分裂密切相關。 也許一旦我們實施了這種增強,允許創建子組和自定義聊天室將是一個好主意。
  • 不會有私信。
    這純粹是為了簡化目的,但是群聊已經足夠了; 我們現在不需要私人消息。 請記住,每當您在開發最小可行產品時,請盡量避免陷入不必要的功能的陷阱; 這是一條危險的道路,而且很難擺脫。
  • 我們不會持久化消息。
    換句話說,如果您離開聚會,您將丟失消息。 這將極大地簡化我們的任務,因為我們不必處理任何類型的數據存儲,也不必浪費時間決定存儲和恢復舊消息的最佳數據結構。 它都將存在於內存中,只要聊天室處於活動狀態,它就會一直存在。 一旦它關閉,我們將簡單地和他們說再見!
  • 通信將通過套接字完成
    遺憾的是,我們的客戶端將不得不處理雙重通信通道:一個用於遊戲引擎的 RESTful 通道和一個用於聊天服務器的套接字。 這可能會稍微增加客戶端的複雜性,但與此同時,它將為每個模塊使用最佳的通信方法。 (在我們的聊天服務器上強制 REST 或在我們的遊戲服務器上強制套接字沒有真正意義。這種方法會增加服務器端代碼的複雜性,它也是處理業務邏輯的代碼,所以讓我們專注於那一邊目前。)

這就是聊天服務器。 畢竟,它不會很複雜,至少最初不會。 在開始編寫代碼時還有更多工作要做,但對於本文而言,這些信息已經綽綽有餘。

客戶端

這是需要編碼的最後一個模塊,它將成為我們最愚蠢的模塊之一。 根據經驗,我更喜歡讓我的客戶愚蠢而我的服務器聰明。 這樣,為服務器創建新客戶端變得更加容易。

就像我們在同一頁面上一樣,這是我們最終應該得到的高級架構。

整個開發的最終高層架構
整個開發的最終高層架構(大預覽)

我們簡單的 CLI 客戶端不會實現任何非常複雜的東西。 事實上,我們必須處理的最複雜的部分是實際的 UI,因為它是基於文本的界面。

話雖如此,客戶端應用程序必須實現的功能如下:

  1. 創建一個新遊戲
    因為我想讓事情盡可能簡單,所以只能通過 CLI 界面來完成。 實際的 UI 只有在加入遊戲後才會使用,這將我們帶到下一點。
  2. 加入現有遊戲
    鑑於從前一點返回的遊戲代碼,玩家可以使用它來加入。同樣,這是您應該能夠在沒有 UI 的情況下執行的操作,因此此功能將成為開始使用文本 UI 所需過程的一部分。
  3. 解析遊戲定義文件
    我們稍後會討論這些,但客戶端應該能夠理解這些文件,以便知道要顯示什麼並知道如何使用這些數據。
  4. 與冒險互動。
    基本上,這使玩家能夠在任何給定時間與描述的環境進行交互。
  5. 為每個玩家維護一個庫存
    客戶端的每個實例都將包含一個內存中的項目列表。 此列表將被備份。
  6. 支持聊天
    客戶端應用程序還需要連接到聊天服務器並將用戶登錄到聚會的聊天室。

稍後將詳細介紹客戶的內部結構和設計。 同時,讓我們完成設計階段的最後一點準備:遊戲文件。

遊戲:JSON 文件

這就是有趣的地方,因為到目前為止,我已經介紹了基本的微服務定義。 他們中的一些人可能會說 REST,而其他人可能會使用套接字,但本質上,它們都是一樣的:你定義它們,編碼它們,它們提供服務。

對於這個特定的組件,我不打算編寫任何代碼,但我們需要設計它。 基本上,我們正​​在實現一種協議來定義我們的遊戲、遊戲中的場景以及遊戲中的所有內容。

如果你仔細想想,文字冒險的核心基本上是一組相互連接的房間,在它們裡面是你可以與之互動的“東西”,所有這些都與一個希望是體面的故事聯繫在一起。 現在,我們的引擎不會處理最後一部分; 那部分將取決於你。 但對於其他人來說,還有希望。

現在,回到一組相互連接的房間,對我來說這聽起來像一個圖表,如果我們還添加我之前提到的距離或移動速度的概念,我們就有了一個加權圖表。 這只是一組節點,它們有一個權重(或只是一個數字——別擔心它叫什麼),代表它們之間的路徑。 這是一個視覺效果(我喜歡邊看邊學,所以只看圖像,好嗎?):

加權圖示例
加權圖示例(大預覽)

這是一個加權圖——就是這樣。 我相信你已經弄清楚了,但為了完整起見,讓我向你展示一旦我們的引擎準備好你將如何去做。

開始設置冒險後,您將創建地圖(如下圖左側所示)。 然後您將其轉換為加權圖,如圖像右側所示。 我們的引擎將能夠將其拾取並讓您以正確的順序通過它。

給定地牢的示例圖
給定地牢的示例圖表(大預覽)

通過上面的加權圖,我們可以確保球員不能從入口一直走到左翼。 他們必須通過這兩者之間的節點,這樣做會消耗時間,我們可以使用連接的權重來衡量。

現在,進入“有趣”的部分。 讓我們看看 JSON 格式的圖形是什麼樣子的。 在這裡忍受我; 這個 JSON 將包含很多信息,但我會盡可能多地介紹它:

 { "graph": [ { "id": "entrance", "name": "Entrance", "north": { "node": "1stroom", "distance": 1 } }, { "id": "1st room", "name": "1st Room", "south": {"node": "entrance", "distance": 1} , "north": { "node": "bigroom", "distance": 1} } , { "id": "bigroom", "name": "Big room", "south": { "node": "1stroom", "distance": 1}, "north": { "node": "bossroom", "distance": 2}, "east": { "node": "rightwing", "distance": 3} , "west": { "node": "leftwing", "distance": 3} }, { "id": "bossroom", "name": "Boss room", "south": {"node": "bigroom", "distance": 2} } { "id": "leftwing", "name": "Left Wing", "east": {"node": "bigroom", "distance": 3} } { "id": "rightwing", "name": "Right Wing", "west": { "node": "bigroom", "distance": 3 } } ], "game": { "win-condition": { "source": "finalboss", "condition": { "type": "comparison", "left": "hp", "right": "0", "symbol": "<=" } }, "lose-condition": { "source": "player", "condition": { "type": "comparison", "left": "hp", "right": "0", "symbol": "<=" } } }, "rooms": { "entrance": { "description": { "default": "You're at the entrance of the dungeon. There are two lit torches on each wall (one on your right and one on your left). You see only one path: ahead." }, "items": [ { "id": "littorch1", "name": "Lit torch on the right", "triggers": [ { "action": "grab", //grab Lit torch on the right "effect":{ "statusUpdate": "has light", "target": "game", } } ] , "destination": "hand" }, { "id": "littorch2", "name": "Lit torch on the left", "triggers": [ { "action": "grab", //grab Lit torch on the left "effect":{ "statusUpdate": "has light", "target": "game", } } ] , "destination": "hand" } ] }, "1stroom": { "description": { "default": "You're in a very dark room. There are no windows and no source of light, other than the one at the entrance. You get the feeling you're not alone here.", "conditionals": { "has light": "The room you find yourself in appears to be empty, aside from a single chair in the right corner. There appears to be only one way out: deeper into the dungeon." } }, "items": [ { "id": "chair", "name": "Wooden chair", "details": "It's a wooden chair, nothing fancy about it. It appears to have been sitting here, untouched, for a while now.", "subitems": [ { "id": "woodenleg", "name": "Wooden leg", "triggeractions": [ { "action": "break", "target": "chair"}, //break { "action": "throw", "target": "chair"} //throw ], "destination": "inventory", "damage": 2 } ] } ] }, "bigroom": { "description": { "default": "You've reached the big room. On every wall are torches lighting every corner. The walls are painted white, and the ceiling is tall and filled with painted white stars on a black background. There is a gateway on either side and a big, wooden double door in front of you." }, "exits": { "north": { "id": "bossdoor", "name": "Big double door", "status": "locked", "details": "A aig, wooden double door. It seems like something big usually comes through here."} }, "items": [] }, "leftwing": { "description": { "default": "Another dark room. It doesn't look like it's that big, but you can't really tell what's inside. You do, however, smell rotten meat somewhere inside.", "conditionals": { "has light": "You appear to have found the kitchen. There are tables full of meat everywhere, and a big knife sticking out of what appears to be the head of a cow." } }, "items": [ { "id": "bigknife", "name": "Big knife", "destination": "inventory", "damage": 10} ] }, "rightwing": { "description": { "default": "This appear to be some sort of office. There is a wooden desk in the middle, torches lighting every wall, and a single key resting on top of the desk." }, "items": [ { "id": "key", "name": "Golden key", "details": "A small golden key. What use could you have for it?", "destination": "inventory", "triggers": [{ "action": "use", //use on north exit (contextual) "target": { "room": "bigroom", "exit": "north" }, "effect": { "statusUpdate": "unlocked", "target": { "room": "bigroom", "exit": "north" } } } ] } ] }, "bossroom": { "description": { "default": "You appear to have reached the end of the dungeon. There are no exits other than the one you just came in through. The only other thing that bothers you is the hulking giant looking like it's going to kill you, standing about 10 feet from you." }, "npcs": [ { "id": "finalboss", "name": "Hulking Ogre", "details": "A huge, green, muscular giant with a single eye in the middle of his forehead. It doesn't just look bad, it also smells like hell.", "stats": { "hp": 10, "damage": 3 } } ] } } } { "graph": [ { "id": "entrance", "name": "Entrance", "north": { "node": "1stroom", "distance": 1 } }, { "id": "1st room", "name": "1st Room", "south": {"node": "entrance", "distance": 1} , "north": { "node": "bigroom", "distance": 1} } , { "id": "bigroom", "name": "Big room", "south": { "node": "1stroom", "distance": 1}, "north": { "node": "bossroom", "distance": 2}, "east": { "node": "rightwing", "distance": 3} , "west": { "node": "leftwing", "distance": 3} }, { "id": "bossroom", "name": "Boss room", "south": {"node": "bigroom", "distance": 2} } { "id": "leftwing", "name": "Left Wing", "east": {"node": "bigroom", "distance": 3} } { "id": "rightwing", "name": "Right Wing", "west": { "node": "bigroom", "distance": 3 } } ], "game": { "win-condition": { "source": "finalboss", "condition": { "type": "comparison", "left": "hp", "right": "0", "symbol": "<=" } }, "lose-condition": { "source": "player", "condition": { "type": "comparison", "left": "hp", "right": "0", "symbol": "<=" } } }, "rooms": { "entrance": { "description": { "default": "You're at the entrance of the dungeon. There are two lit torches on each wall (one on your right and one on your left). You see only one path: ahead." }, "items": [ { "id": "littorch1", "name": "Lit torch on the right", "triggers": [ { "action": "grab", //grab Lit torch on the right "effect":{ "statusUpdate": "has light", "target": "game", } } ] , "destination": "hand" }, { "id": "littorch2", "name": "Lit torch on the left", "triggers": [ { "action": "grab", //grab Lit torch on the left "effect":{ "statusUpdate": "has light", "target": "game", } } ] , "destination": "hand" } ] }, "1stroom": { "description": { "default": "You're in a very dark room. There are no windows and no source of light, other than the one at the entrance. You get the feeling you're not alone here.", "conditionals": { "has light": "The room you find yourself in appears to be empty, aside from a single chair in the right corner. There appears to be only one way out: deeper into the dungeon." } }, "items": [ { "id": "chair", "name": "Wooden chair", "details": "It's a wooden chair, nothing fancy about it. It appears to have been sitting here, untouched, for a while now.", "subitems": [ { "id": "woodenleg", "name": "Wooden leg", "triggeractions": [ { "action": "break", "target": "chair"}, //break { "action": "throw", "target": "chair"} //throw ], "destination": "inventory", "damage": 2 } ] } ] }, "bigroom": { "description": { "default": "You've reached the big room. On every wall are torches lighting every corner. The walls are painted white, and the ceiling is tall and filled with painted white stars on a black background. There is a gateway on either side and a big, wooden double door in front of you." }, "exits": { "north": { "id": "bossdoor", "name": "Big double door", "status": "locked", "details": "A aig, wooden double door. It seems like something big usually comes through here."} }, "items": [] }, "leftwing": { "description": { "default": "Another dark room. It doesn't look like it's that big, but you can't really tell what's inside. You do, however, smell rotten meat somewhere inside.", "conditionals": { "has light": "You appear to have found the kitchen. There are tables full of meat everywhere, and a big knife sticking out of what appears to be the head of a cow." } }, "items": [ { "id": "bigknife", "name": "Big knife", "destination": "inventory", "damage": 10} ] }, "rightwing": { "description": { "default": "This appear to be some sort of office. There is a wooden desk in the middle, torches lighting every wall, and a single key resting on top of the desk." }, "items": [ { "id": "key", "name": "Golden key", "details": "A small golden key. What use could you have for it?", "destination": "inventory", "triggers": [{ "action": "use", //use on north exit (contextual) "target": { "room": "bigroom", "exit": "north" }, "effect": { "statusUpdate": "unlocked", "target": { "room": "bigroom", "exit": "north" } } } ] } ] }, "bossroom": { "description": { "default": "You appear to have reached the end of the dungeon. There are no exits other than the one you just came in through. The only other thing that bothers you is the hulking giant looking like it's going to kill you, standing about 10 feet from you." }, "npcs": [ { "id": "finalboss", "name": "Hulking Ogre", "details": "A huge, green, muscular giant with a single eye in the middle of his forehead. It doesn't just look bad, it also smells like hell.", "stats": { "hp": 10, "damage": 3 } } ] } } } { "graph": [ { "id": "entrance", "name": "Entrance", "north": { "node": "1stroom", "distance": 1 } }, { "id": "1st room", "name": "1st Room", "south": {"node": "entrance", "distance": 1} , "north": { "node": "bigroom", "distance": 1} } , { "id": "bigroom", "name": "Big room", "south": { "node": "1stroom", "distance": 1}, "north": { "node": "bossroom", "distance": 2}, "east": { "node": "rightwing", "distance": 3} , "west": { "node": "leftwing", "distance": 3} }, { "id": "bossroom", "name": "Boss room", "south": {"node": "bigroom", "distance": 2} } { "id": "leftwing", "name": "Left Wing", "east": {"node": "bigroom", "distance": 3} } { "id": "rightwing", "name": "Right Wing", "west": { "node": "bigroom", "distance": 3 } } ], "game": { "win-condition": { "source": "finalboss", "condition": { "type": "comparison", "left": "hp", "right": "0", "symbol": "<=" } }, "lose-condition": { "source": "player", "condition": { "type": "comparison", "left": "hp", "right": "0", "symbol": "<=" } } }, "rooms": { "entrance": { "description": { "default": "You're at the entrance of the dungeon. There are two lit torches on each wall (one on your right and one on your left). You see only one path: ahead." }, "items": [ { "id": "littorch1", "name": "Lit torch on the right", "triggers": [ { "action": "grab", //grab Lit torch on the right "effect":{ "statusUpdate": "has light", "target": "game", } } ] , "destination": "hand" }, { "id": "littorch2", "name": "Lit torch on the left", "triggers": [ { "action": "grab", //grab Lit torch on the left "effect":{ "statusUpdate": "has light", "target": "game", } } ] , "destination": "hand" } ] }, "1stroom": { "description": { "default": "You're in a very dark room. There are no windows and no source of light, other than the one at the entrance. You get the feeling you're not alone here.", "conditionals": { "has light": "The room you find yourself in appears to be empty, aside from a single chair in the right corner. There appears to be only one way out: deeper into the dungeon." } }, "items": [ { "id": "chair", "name": "Wooden chair", "details": "It's a wooden chair, nothing fancy about it. It appears to have been sitting here, untouched, for a while now.", "subitems": [ { "id": "woodenleg", "name": "Wooden leg", "triggeractions": [ { "action": "break", "target": "chair"}, //break { "action": "throw", "target": "chair"} //throw ], "destination": "inventory", "damage": 2 } ] } ] }, "bigroom": { "description": { "default": "You've reached the big room. On every wall are torches lighting every corner. The walls are painted white, and the ceiling is tall and filled with painted white stars on a black background. There is a gateway on either side and a big, wooden double door in front of you." }, "exits": { "north": { "id": "bossdoor", "name": "Big double door", "status": "locked", "details": "A aig, wooden double door. It seems like something big usually comes through here."} }, "items": [] }, "leftwing": { "description": { "default": "Another dark room. It doesn't look like it's that big, but you can't really tell what's inside. You do, however, smell rotten meat somewhere inside.", "conditionals": { "has light": "You appear to have found the kitchen. There are tables full of meat everywhere, and a big knife sticking out of what appears to be the head of a cow." } }, "items": [ { "id": "bigknife", "name": "Big knife", "destination": "inventory", "damage": 10} ] }, "rightwing": { "description": { "default": "This appear to be some sort of office. There is a wooden desk in the middle, torches lighting every wall, and a single key resting on top of the desk." }, "items": [ { "id": "key", "name": "Golden key", "details": "A small golden key. What use could you have for it?", "destination": "inventory", "triggers": [{ "action": "use", //use on north exit (contextual) "target": { "room": "bigroom", "exit": "north" }, "effect": { "statusUpdate": "unlocked", "target": { "room": "bigroom", "exit": "north" } } } ] } ] }, "bossroom": { "description": { "default": "You appear to have reached the end of the dungeon. There are no exits other than the one you just came in through. The only other thing that bothers you is the hulking giant looking like it's going to kill you, standing about 10 feet from you." }, "npcs": [ { "id": "finalboss", "name": "Hulking Ogre", "details": "A huge, green, muscular giant with a single eye in the middle of his forehead. It doesn't just look bad, it also smells like hell.", "stats": { "hp": 10, "damage": 3 } } ] } } } { "graph": [ { "id": "entrance", "name": "Entrance", "north": { "node": "1stroom", "distance": 1 } }, { "id": "1st room", "name": "1st Room", "south": {"node": "entrance", "distance": 1} , "north": { "node": "bigroom", "distance": 1} } , { "id": "bigroom", "name": "Big room", "south": { "node": "1stroom", "distance": 1}, "north": { "node": "bossroom", "distance": 2}, "east": { "node": "rightwing", "distance": 3} , "west": { "node": "leftwing", "distance": 3} }, { "id": "bossroom", "name": "Boss room", "south": {"node": "bigroom", "distance": 2} } { "id": "leftwing", "name": "Left Wing", "east": {"node": "bigroom", "distance": 3} } { "id": "rightwing", "name": "Right Wing", "west": { "node": "bigroom", "distance": 3 } } ], "game": { "win-condition": { "source": "finalboss", "condition": { "type": "comparison", "left": "hp", "right": "0", "symbol": "<=" } }, "lose-condition": { "source": "player", "condition": { "type": "comparison", "left": "hp", "right": "0", "symbol": "<=" } } }, "rooms": { "entrance": { "description": { "default": "You're at the entrance of the dungeon. There are two lit torches on each wall (one on your right and one on your left). You see only one path: ahead." }, "items": [ { "id": "littorch1", "name": "Lit torch on the right", "triggers": [ { "action": "grab", //grab Lit torch on the right "effect":{ "statusUpdate": "has light", "target": "game", } } ] , "destination": "hand" }, { "id": "littorch2", "name": "Lit torch on the left", "triggers": [ { "action": "grab", //grab Lit torch on the left "effect":{ "statusUpdate": "has light", "target": "game", } } ] , "destination": "hand" } ] }, "1stroom": { "description": { "default": "You're in a very dark room. There are no windows and no source of light, other than the one at the entrance. You get the feeling you're not alone here.", "conditionals": { "has light": "The room you find yourself in appears to be empty, aside from a single chair in the right corner. There appears to be only one way out: deeper into the dungeon." } }, "items": [ { "id": "chair", "name": "Wooden chair", "details": "It's a wooden chair, nothing fancy about it. It appears to have been sitting here, untouched, for a while now.", "subitems": [ { "id": "woodenleg", "name": "Wooden leg", "triggeractions": [ { "action": "break", "target": "chair"}, //break { "action": "throw", "target": "chair"} //throw ], "destination": "inventory", "damage": 2 } ] } ] }, "bigroom": { "description": { "default": "You've reached the big room. On every wall are torches lighting every corner. The walls are painted white, and the ceiling is tall and filled with painted white stars on a black background. There is a gateway on either side and a big, wooden double door in front of you." }, "exits": { "north": { "id": "bossdoor", "name": "Big double door", "status": "locked", "details": "A aig, wooden double door. It seems like something big usually comes through here."} }, "items": [] }, "leftwing": { "description": { "default": "Another dark room. It doesn't look like it's that big, but you can't really tell what's inside. You do, however, smell rotten meat somewhere inside.", "conditionals": { "has light": "You appear to have found the kitchen. There are tables full of meat everywhere, and a big knife sticking out of what appears to be the head of a cow." } }, "items": [ { "id": "bigknife", "name": "Big knife", "destination": "inventory", "damage": 10} ] }, "rightwing": { "description": { "default": "This appear to be some sort of office. There is a wooden desk in the middle, torches lighting every wall, and a single key resting on top of the desk." }, "items": [ { "id": "key", "name": "Golden key", "details": "A small golden key. What use could you have for it?", "destination": "inventory", "triggers": [{ "action": "use", //use on north exit (contextual) "target": { "room": "bigroom", "exit": "north" }, "effect": { "statusUpdate": "unlocked", "target": { "room": "bigroom", "exit": "north" } } } ] } ] }, "bossroom": { "description": { "default": "You appear to have reached the end of the dungeon. There are no exits other than the one you just came in through. The only other thing that bothers you is the hulking giant looking like it's going to kill you, standing about 10 feet from you." }, "npcs": [ { "id": "finalboss", "name": "Hulking Ogre", "details": "A huge, green, muscular giant with a single eye in the middle of his forehead. It doesn't just look bad, it also smells like hell.", "stats": { "hp": 10, "damage": 3 } } ] } } }

我知道它看起來很多,但如果你把它歸結為對遊戲的簡單描述,你有一個由六個房間組成的地牢,每個房間都相互連接,如上圖所示。

你的任務是穿過它並探索它。 你會發現有兩個不同的地方可以找到武器(在廚房或黑暗的房間裡,通過打破椅子)。 您還將面臨一扇鎖著的門; 所以,一旦你找到鑰匙(位於類似辦公室的房間內),你就可以打開它並用你收集的任何武器與老闆戰鬥。

您要么殺死它而獲勝,要么被它殺死而失敗。

現在讓我們更詳細地了解整個 JSON 結構及其三個部分。

圖形

這將包含節點之間的關係。 基本上,這部分直接翻譯成我們之前看過的圖表。

本節的結構非常簡單。 它是一個節點列表,其中每個節點都包含以下屬性:

  • 在遊戲中唯一標識節點的 ID;
  • 名稱,基本上是人類可讀的 ID 版本;
  • 一組到其他節點的鏈接。 存在四個可能的鍵就證明了這一點:“北”、“南”、“東”和“西”。 我們最終可以通過添加這四個的組合來添加更多的方向。 每個鏈接都包含相關節點的 ID 和該關係的距離(或權重)。

遊戲

本節將包含一般設置和條件。 特別是,在上面的示例中,此部分包含輸贏條件。 換句話說,有了這兩個條件,我們會讓引擎知道遊戲什麼時候可以結束。

為了簡單起見,我只添加了兩個條件:

  • 你要么通過殺死老闆而獲勝,
  • 或因被殺而輸。

房間

這是 163 行中大部分的來源,也是最複雜的部分。 在這裡,我們將描述我們冒險中的所有房間以及其中的一切。

每個房間都有一把鑰匙,使用我們之前定義的 ID。 每個房間都有描述、物品清單、出口(或門)清單和不可玩角色(NPC)清單。 在這些屬性中,唯一應該是強制性的就是描述,因為引擎需要該描述才能讓您知道所看到的內容。 他們中的其他人只有在有東西要展示的時候才會在那裡。

讓我們看看這些屬性可以為我們的遊戲做什麼。

說明

這個項目並不像人們想像的那麼簡單,因為您對房間的看法可能會根據不同的情況而改變。 例如,如果您查看第一個房間的描述,您會注意到,默認情況下,您什麼都看不到,當然,除非您隨身攜帶點燃的手電筒。

因此,拿起物品並使用它們可能會觸發影響遊戲其他部分的全局條件。

這幾項

這些代表了您可以在房間內找到的所有東西。 每個項目都與圖形部分中的節點具有相同的 ID 和名稱。

他們還將有一個“目的地”屬性,該屬性指示該物品一旦被拾起應該存放在哪裡。 這是相關的,因為您將只能擁有一件物品,而您可以在庫存中擁有任意數量的物品。

最後,其中一些項目可能會觸發其他操作或狀態更新,具體取決於玩家決定如何處理它們。 其中一個例子是入口處點燃的火把。 如果您抓住其中一個,您將觸發遊戲中的狀態更新,進而使遊戲向您顯示下一個房間的不同描述。

物品也可以有“子物品”,一旦原始物品被破壞(例如通過“破壞”動作),它就會發揮作用。 一個項目可以分解為多個項目,這在“子項目”元素中定義。

本質上,這個元素只是一個新項目的數組,其中還包含一組可以觸發其創建的操作。 這基本上打開了根據您對原始項目執行的操作創建不同子項目的可能性。

最後,一些物品將具有“損壞”屬性。 因此,如果您使用物品擊中 NPC,則該值將用於減去他們的生命。

出口

這只是一組屬性,指示出口的方向和它的屬性(描述,如果你想檢查它,它的名稱,在某些情況下,它的狀態)。

出口是與項目分開的實體,因為引擎需要了解您是否可以根據它們的狀態實際遍歷它們。 除非您弄清楚如何將其狀態更改為未鎖定,否則鎖定的出口不會讓您通過它們。

NPC

最後,NPC 將成為另一個列表的一部分。 它們基本上是帶有統計數據的項目,引擎將使用這些數據來了解每個項目的行為方式。 我們在示例中定義的是“hp”,它代表生命值,而“damage”,就像武器一樣,是每次命中將從玩家生命值中減去的數字。

這就是我創建的地牢。 很多,是的,將來我可能會考慮創建一個關卡編輯器,以簡化 JSON 文件的創建。 但就目前而言,這沒有必要。

如果您還沒有意識到,將我們的遊戲定義在這樣的文件中的主要好處是,我們將能夠像您在超級任天堂時代那樣切換 JSON 文件。 只需加載一個新文件並開始新的冒險。 簡單!

結束的想法

感謝您到目前為止的閱讀。 我希望你喜歡我為實現一個想法而經歷的設計過程。 不過請記住,我正在編造這個,所以我們稍後可能會意識到我們今天定義的某些東西不會起作用,在這種情況下,我們將不得不回溯並修復它。

我敢肯定,有很多方法可以改進這裡提出的想法並製造出一個地獄般的引擎。 但這需要更多的文字,而不是讓每個人都覺得無聊,所以我們暫時擱置它。

本系列的其他部分

  • 第 2 部分:遊戲引擎服務器設計
  • 第 3 部分:創建終端客戶端
  • 第 4 部分:將聊天添加到我們的遊戲中