使用Node.js實(shí)現(xiàn)一個(gè)多人游戲服務(wù)器引擎
摘要
聽(tīng)說(shuō)過(guò)文字冒險(xiǎn)游戲嗎? 如果你的年齡足夠大的話(huà)(就像我一樣),那么你可能聽(tīng)說(shuō)過(guò)、甚至玩過(guò)“back in the day”。在本文中,我將向你展示編寫(xiě)的整個(gè)過(guò)程。這不僅僅是一個(gè)文本冒險(xiǎn)游戲,而是一個(gè)能讓你和你的朋友們一起玩的,可以進(jìn)行任何劇情的文本冒險(xiǎn)游戲引擎。 沒(méi)錯(cuò),我們將通過(guò)在添加多人游戲功能來(lái)增加它的趣味性。
文字冒險(xiǎn)是最早的 RPG 形式的游戲之一,回到還沒(méi)有圖形畫(huà)面的時(shí)代,你只能通過(guò)閱讀 CRT 顯示器上黑色背景下的描述,并且依賴(lài)自己的想象力來(lái)推動(dòng)游戲劇情的發(fā)展。
如果要懷舊的話(huà),可能世界上第一個(gè)文字冒險(xiǎn)游戲名叫 Colossal Cave Adventure(也許是叫 Adventure)。
文字冒險(xiǎn)游戲 back in the day 的畫(huà)面
上圖是你實(shí)際看到的游戲畫(huà)面,這與我們現(xiàn)在的頂級(jí) AAA 冒險(xiǎn)游戲相差甚遠(yuǎn)。 盡管如此,但是他們玩起來(lái)卻很有趣,并會(huì)很容易的消磨你幾百個(gè)小時(shí)的時(shí)間,因?yàn)橹挥心阕约鹤约鹤陲@示器前,試圖找到打穿它的途徑。
可以理解的是,多年以來(lái),文字冒險(xiǎn)已經(jīng)被更好的視覺(jué)效果所取代,特別是在過(guò)去幾年里,游戲的協(xié)作性越強(qiáng),你可以和朋友們一起玩。 這是原始的文字冒險(xiǎn)游戲所缺少的,同時(shí)也是我想在本文中提到的功能。
我們的目標(biāo)
可能你已經(jīng)從標(biāo)題中猜到了,本文的重點(diǎn)在于創(chuàng)建一個(gè)文字冒險(xiǎn)引擎,并且讓你和朋友們一起玩,使你能夠與他們進(jìn)行協(xié)作,就像在玩“龍與地下城”這個(gè)游戲一樣。
在創(chuàng)建引擎時(shí),聊天服務(wù)器和客戶(hù)端的工作了相當(dāng)大。 在本文中,我將向你展示設(shè)計(jì)思路、解釋引擎背后的架構(gòu)、客戶(hù)端如何與服務(wù)器交互以及這個(gè)游戲的規(guī)則。
為了讓你對(duì)我的目標(biāo)又一個(gè)直觀的感受,先上一張圖:
游戲客戶(hù)端的 UI 設(shè)計(jì)
這就是我們的目標(biāo)。 一旦達(dá)成這個(gè)目標(biāo),將會(huì)得到截圖而不是簡(jiǎn)單和骯臟的模型。 所以,需要了解這個(gè)過(guò)程。首先要介紹的就是整體設(shè)計(jì);然后介紹我將用來(lái)編碼的相關(guān)工具;最后我將向你展示一些核心代碼(當(dāng)然,還有指向完整代碼庫(kù)的鏈接)。
希望到最后,你能夠自己創(chuàng)造一個(gè)新的文字冒險(xiǎn)游戲,并與朋友一起樂(lè)在其中!
設(shè)計(jì)階段
在設(shè)計(jì)階段,我將描述這個(gè)游戲的整體藍(lán)圖。 我會(huì)盡力不讓你覺(jué)得無(wú)聊,不過(guò)我認(rèn)為在給你展示第一行代碼之前,很有必要先搞清楚幕后的一些工作。
我想接下來(lái)介紹的這四個(gè)組件能夠提供相當(dāng)多的細(xì)節(jié):
- 引擎
- 這將成為游戲的主服務(wù)器。游戲規(guī)則會(huì)在這里實(shí)現(xiàn),它將為任何類(lèi)型的客戶(hù)端提供技術(shù)無(wú)關(guān)接口。本項(xiàng)目中我們將實(shí)現(xiàn)終端類(lèi)型的客戶(hù)端,但是你可以用Web客戶(hù)端或者你喜歡的任何其他類(lèi)型。
- 聊天服務(wù)器
- 因?yàn)樗膹?fù)雜性足以再寫(xiě)一篇文章了,所以這項(xiàng)服務(wù)也會(huì)擁有自己的模塊。聊天服務(wù)器負(fù)責(zé)讓玩家在游戲的過(guò)程中彼此通信。
- 客戶(hù)端
- 如前文所述,這將是一個(gè)終端類(lèi)型的客戶(hù)端,在理想情況下,它看起來(lái)與之前的模型類(lèi)似。它將利用引擎和聊天服務(wù)器所提供的服務(wù)。
- 游戲( JSON文件 )
- 最后,我將介紹實(shí)際游戲的定義。這部分的重點(diǎn)是創(chuàng)建一個(gè)可以運(yùn)行任何游戲的引擎,只要你的游戲文件符合引擎的要求即可。所以,即使這不需要編碼,我也將解釋如何構(gòu)建冒險(xiǎn)文件以便將來(lái)編寫(xiě)我們自己的冒險(xiǎn)規(guī)則。
引擎
游戲引擎或游戲服務(wù)器將會(huì)是REST API,并提供所有必需的功能。
我選擇REST API只是因?yàn)椋▽?duì)于這種類(lèi)型的游戲)HTTP造成的延遲以及他的異步特性不會(huì)造成任何麻煩。 但是,我們必須為聊天服務(wù)器采用不同的路線。 在開(kāi)始定義 API 之前,先需要定義引擎的功能。 所以,讓我們來(lái)看看吧。
特性 | 描述 |
---|---|
加入游戲 | 玩家可以通過(guò)指定的游戲ID來(lái)加入游戲。 |
創(chuàng)建一個(gè)新游戲 | 玩家還可以創(chuàng)建新的游戲?qū)嵗?引擎應(yīng)該返回一個(gè)ID,以便其他人可以使它來(lái)加入游戲。 |
返回場(chǎng)景 | 此功能應(yīng)返回玩家所在的當(dāng)前場(chǎng)景。 基本上,它將返回描述,包含所有相關(guān)信息(可能的操作、其中的對(duì)象等)。 |
與場(chǎng)景互動(dòng) | 這將是最復(fù)雜的一個(gè),因?yàn)樗鼘目蛻?hù)端獲取命令并執(zhí)行該操作——例如移動(dòng),攻擊,獲取,查看,讀取等等。 |
檢查庫(kù)存 | 雖然這是與游戲互動(dòng)的一種方式,但它與場(chǎng)景并沒(méi)有直接關(guān)系。 因此,檢查每個(gè)玩家的庫(kù)存將被視為不同的操作。 |
關(guān)于移動(dòng)
我們需要一種用來(lái)測(cè)量游戲中距離的方法,因?yàn)樵谟螒蛑型婕铱梢圆扇〉暮诵男袆?dòng)之一就是移動(dòng)。 我們需要用這個(gè)數(shù)字作為時(shí)間的衡量標(biāo)準(zhǔn),來(lái)簡(jiǎn)化游戲的玩法。 考慮到這一類(lèi)型的游戲具有基于回合的動(dòng)作,例如戰(zhàn)斗,使用實(shí)際時(shí)鐘對(duì)時(shí)間進(jìn)行測(cè)量可能不是最好的。 所以我們將使用距離來(lái)測(cè)量時(shí)間(意味著距離為 8 比距離為 2 將需要更多的時(shí)間,從而允許我們做一些事情,例如為持續(xù)一定數(shù)量的“距離點(diǎn)”的玩家添加效果)。
考慮運(yùn)動(dòng)的另一個(gè)原因是不是一個(gè)人在玩這個(gè)游戲。 為簡(jiǎn)單起見(jiàn),引擎不會(huì)讓玩家隨意組隊(duì)(雖然這對(duì)未來(lái)可能是一個(gè)有趣的改進(jìn))。 該模塊的初始版本只允許個(gè)人朝著大多數(shù)參與者決定的地方移動(dòng)。因此,必須以協(xié)商一致的方式進(jìn)行移動(dòng),這意味著每一步行動(dòng)都將等待大多數(shù)人在行動(dòng)之前提出請(qǐng)求。
戰(zhàn)斗
戰(zhàn)斗是這種游戲另一個(gè)非常重要的方面,我們不得不考慮將它添加到引擎中,否則我們最終會(huì)失去一些樂(lè)趣。
說(shuō)實(shí)話(huà),這并不需要重新發(fā)明輪子。基于回合制的組隊(duì)對(duì)戰(zhàn)已經(jīng)存在了幾十年,所以在這里只實(shí)現(xiàn)這個(gè)機(jī)制的一個(gè)簡(jiǎn)單版本。我們將把它與“龍與地下城”中的“主動(dòng)性”這個(gè)概念混合起來(lái),產(chǎn)生一個(gè)隨機(jī)數(shù)使戰(zhàn)斗更有活力。
換句話(huà)說(shuō),就是參與戰(zhàn)斗的每個(gè)人的行動(dòng)順序?qū)?huì)被隨機(jī)化,其中包括敵人。
最后(雖然我將在下面詳細(xì)介紹這一點(diǎn)),你可以用設(shè)置的“攻擊力”值的物品。這些是你在戰(zhàn)斗中可以使用的道具;如果一個(gè)道具沒(méi)有這個(gè)屬性的話(huà)只能對(duì)敵人造成 0 點(diǎn)傷害。當(dāng)你試圖用這樣的道具進(jìn)行戰(zhàn)斗時(shí),我們可能會(huì)添加一條消息,這樣你就能知道自己要做的事情是毫無(wú)意義的。
客戶(hù)端 - 服務(wù)器交互
現(xiàn)在來(lái)看看客戶(hù)端怎樣基于前面定義的功能與服務(wù)器進(jìn)行交互(目前還沒(méi)考慮端點(diǎn),不過(guò)馬上就會(huì)講到這個(gè)):
客戶(hù)端與服務(wù)器之間的交互
客戶(hù)端和服務(wù)器之間的初始交互(從服務(wù)器的角度來(lái)看)是一個(gè)新游戲的開(kāi)始,其步驟如下:
- 創(chuàng)建一個(gè)新游戲。
- 客戶(hù)端請(qǐng)求向服務(wù)器創(chuàng)建新游戲。
- 創(chuàng)建聊天室。
- 雖然沒(méi)有明確說(shuō)明,但是服務(wù)器不只是在聊天服務(wù)器中創(chuàng)建聊天室,而且還設(shè)置好了所需的一切,可以允許一組玩家進(jìn)行游戲。
- 返回游戲的元數(shù)據(jù)。
- 一旦服務(wù)器為玩家創(chuàng)建好了游戲和聊天室,那么客戶(hù)端會(huì)在后續(xù)請(qǐng)求用到這個(gè)信息。這是客戶(hù)端可以用來(lái)標(biāo)識(shí)自己和將要加入的游戲?qū)嵗囊唤MID。
- 手動(dòng)分享游戲ID 。
- 這一步必須由玩家自己完成。我們可以提出某種共享機(jī)制,但我會(huì)將它留在愿望清單上等待將來(lái)改進(jìn)。
- 加入游戲。
- 這個(gè)非常簡(jiǎn)單。每個(gè)人都有一個(gè) ID,客戶(hù)端通過(guò)這個(gè) ID 加入游戲。
- 加入聊天室。
- 最后,玩家的客戶(hù)端程序?qū)⑼ㄟ^(guò)游戲的元數(shù)據(jù)加入對(duì)應(yīng)的聊天室。這是游戲開(kāi)始前的最后一步。一旦完成所有操作,玩家就可以開(kāi)始在游戲中冒險(xiǎn)了!
游戲的動(dòng)作指令
一旦滿(mǎn)足了先決條件,玩家就可以開(kāi)始游戲,通過(guò)聊天室分享他們的想法,并推動(dòng)故事的發(fā)展。上圖顯示了所需的四個(gè)步驟。
以下步驟將作為游戲循環(huán)的一部分來(lái)運(yùn)行,這意味著它們將會(huì)不斷重復(fù),一直到游戲結(jié)束。
- 請(qǐng)求場(chǎng)景。
- 客戶(hù)端程序?qū)⒄?qǐng)求當(dāng)前場(chǎng)景的元數(shù)據(jù)。這是循環(huán)每次迭代的第一步。
- 返回元數(shù)據(jù)。
- 服務(wù)器將發(fā)回當(dāng)前場(chǎng)景的元數(shù)據(jù)。這些信息中包括一般描述,從中可以找到的對(duì)象以及它們彼此之間的關(guān)系。
- 發(fā)送命令。
- 好戲開(kāi)始。這是玩家的主要輸入方式。它包括玩家想要執(zhí)行的操作,以及可選的操作目標(biāo)(例如吹蠟燭、抓住巖石等)。
- 對(duì)發(fā)來(lái)的命令做出響應(yīng)。
- 這應(yīng)該屬于第二步,但為了清楚起見(jiàn),我把它作為額外步驟。主要區(qū)別在于第二步可以被認(rèn)為是這個(gè)循環(huán)的開(kāi)始,而這一步考慮到你已經(jīng)開(kāi)始進(jìn)行游戲了,因此,服務(wù)器需要了解這個(gè)動(dòng)作將影響誰(shuí)(單個(gè)或所有玩家)。
作為額外步驟,雖然不是流程的一部分,但服務(wù)器將通知客戶(hù)端與它們相關(guān)的狀態(tài)的更新情況。
存在這個(gè)額外重復(fù)步驟的原因是玩家可以從其他玩家的動(dòng)作中獲得更新。回想從一個(gè)地方移動(dòng)另一個(gè)地方的需求;正如我之前所說(shuō)那樣,一旦大多數(shù)玩家選擇了方向,那么所有玩家都會(huì)移動(dòng)(不需要所有球員的輸入)。
不過(guò) HTTP(前面已經(jīng)提到服務(wù)器為REST API)不允許這種類(lèi)型的行為。所以,我們的選擇是:
- 每隔 X 秒從客戶(hù)端輪詢(xún),
- 使用某種與客戶(hù)端-服務(wù)器連接通信機(jī)制并行工作的通知系統(tǒng)。
根據(jù)我的經(jīng)驗(yàn),我傾向于選擇選項(xiàng) 2。實(shí)際上,我會(huì)(在本文中)使用Redis來(lái)實(shí)現(xiàn)這種行為。
下圖演示了服務(wù)之間的依賴(lài)關(guān)系。
客戶(hù)端應(yīng)用程序與游戲引擎之間的交互
聊天服務(wù)器
我將把這個(gè)模塊的設(shè)計(jì)細(xì)節(jié)留給開(kāi)發(fā)階段(本文不涉及這一部分)。話(huà)雖如此,我們?nèi)钥梢詻Q定一些事情。
我們可以確定的一件事是服務(wù)器的限制集合,這將簡(jiǎn)化我們的工作。如果我們正確地玩牌,最終可能會(huì)有一個(gè)提供強(qiáng)大界面的服務(wù),從而允許我們?nèi)ミM(jìn)行擴(kuò)展甚至修改實(shí)現(xiàn),以提供更少的限制,而不會(huì)影響到游戲。
- 每個(gè)組隊(duì)只有一個(gè)房間。
- 我們不會(huì)創(chuàng)建子組隊(duì)。這和不讓組隊(duì)分裂是相輔相成的。也許一旦以后我們實(shí)現(xiàn)了這個(gè)增強(qiáng)功能,允許創(chuàng)建子組和自定義聊天室或許是一個(gè)好主意。
- 沒(méi)有私信功能。
- 這純粹是為了簡(jiǎn)化,但是只有群聊并不夠好。目前我們不需要私信。請(qǐng)記住,任何時(shí)候只研究你的最小化可行產(chǎn)品,盡量避免掉進(jìn)不必要功能的陷阱;這是一條危險(xiǎn)的道路,很難從困境中擺脫出來(lái)。
- 不會(huì)保存留言。
- 換句話(huà)說(shuō),如果你離開(kāi)組隊(duì),將會(huì)丟失這些信息。這將極大地簡(jiǎn)化我們的任務(wù),因?yàn)槲覀儾槐靥幚砣魏晤?lèi)型的數(shù)據(jù)存儲(chǔ),也不必浪費(fèi)時(shí)間來(lái)優(yōu)化存儲(chǔ)和恢復(fù)舊消息的數(shù)據(jù)結(jié)構(gòu)。它們都存在于內(nèi)存中,只要聊天室處于活動(dòng)狀態(tài),就會(huì)一直存在。一旦關(guān)閉,就會(huì)簡(jiǎn)單地對(duì)它們說(shuō)Goodbye!
- 通過(guò)網(wǎng)絡(luò)套接字進(jìn)行通信。
- 可悲的是,我們的客戶(hù)將不得不處理雙重溝通渠道:游戲引擎的 RESTful 和聊天服務(wù)器的套接字。這可能會(huì)增加客戶(hù)端的復(fù)雜性,但與此同時(shí),它將為每個(gè)模塊使用最佳通信方法。 (在聊天服務(wù)器上強(qiáng)制 REST 或在游戲服務(wù)器上強(qiáng)制使用套接字沒(méi)有任何意義。這種方法會(huì)增加服務(wù)器端代碼的復(fù)雜性,這也是處理業(yè)務(wù)邏輯的代碼,所以讓我們關(guān)注目前的問(wèn)題。)
這就是聊天服務(wù)器。畢竟,它不會(huì)很復(fù)雜。在開(kāi)始編碼之前還有很多工作要做,但是對(duì)于本文來(lái)說(shuō)已經(jīng)足夠了。
客戶(hù)端
這是最后一個(gè)需要編碼的模塊,它將是最笨重的一個(gè)模塊。根據(jù)經(jīng)驗(yàn)來(lái)看,我更喜歡讓客戶(hù)端笨重,使服務(wù)器輕巧。這樣為服務(wù)器開(kāi)發(fā)新的客戶(hù)端會(huì)更加容易。
這是我們最終應(yīng)該采用的架構(gòu)。
最終架構(gòu)
我們要實(shí)現(xiàn)的ClI客戶(hù)端很簡(jiǎn)單,不會(huì)實(shí)現(xiàn)任何非常復(fù)雜的東西。實(shí)際上,必須要解決的最復(fù)雜的部分是 UI,因?yàn)樗且粋€(gè)基于文本的界面。
客戶(hù)端應(yīng)用程序必須實(shí)現(xiàn)的功能如下:
- 創(chuàng)建一個(gè)新游戲。
- 因?yàn)槲蚁MM可能保持簡(jiǎn)單,所以這只能通過(guò) CLI 界面完成。實(shí)際用戶(hù)界面只會(huì)在加入游戲后被用到,這把我們帶到下一個(gè)問(wèn)題。
- 加入現(xiàn)有游戲。
- 玩家可以根據(jù)由上一條返回的游戲編號(hào)來(lái)加入游戲。另外,這件事應(yīng)該能夠在沒(méi)有 UI 的情況下完成,因此這個(gè)功能將成為開(kāi)始使用文本 UI 所需的過(guò)程的一部分。
- 解析游戲定義文件。
- 我們將對(duì)這點(diǎn)進(jìn)行的討論,客戶(hù)端應(yīng)該能夠理解這些文件,以便能夠理解要顯示的內(nèi)容,并知道應(yīng)該如何使用這個(gè)數(shù)據(jù)。
- 與冒險(xiǎn)互動(dòng)。
- 基本上,這使玩家能夠在任何時(shí)間與給出描述的環(huán)境進(jìn)行交互。
- 為每位玩家維護(hù)背包內(nèi)容。
- 客戶(hù)端的每個(gè)實(shí)例都將在內(nèi)存中包含一份道具列表。此列表將被備份。
- 支持聊天。
- 客戶(hù)端程序還需要連接到聊天服務(wù)器,并使用戶(hù)登錄到組隊(duì)的聊天室。
稍后將詳細(xì)介紹客戶(hù)端的內(nèi)部結(jié)構(gòu)和設(shè)計(jì)。與此同時(shí),讓我們完成設(shè)計(jì)階段的最后一部分:游戲文件。
游戲:JSON文件
這是它變得有趣的地方,因?yàn)榈酱螢橹?,我已?jīng)涵蓋了基本的微服務(wù)定義。其中一些可能會(huì)基于 REST,而另外一些可能會(huì)使用套接字,但本質(zhì)上它們都是一樣的:你定義并對(duì)它們編碼,然后它們提供服務(wù)。
我不打算對(duì)這個(gè)特定的組件做任何編碼,但我們?nèi)匀恍枰O(shè)計(jì)它?;旧衔覀兪窃趯?shí)現(xiàn)一種協(xié)議來(lái)定義游戲、它內(nèi)部的場(chǎng)景以及一切。
如果你想一想,文本冒險(xiǎn)的核心基本上是一組相互連接的房間,里面是你可以與之互動(dòng)的“事物”,所有這些都與一個(gè)引人入勝的故事聯(lián)系在一起。現(xiàn)在我們的引擎不會(huì)處理最后一部分,這部分將取決于你。
現(xiàn)在回到相互連接的房間,對(duì)我來(lái)說(shuō)這就像一個(gè)圖結(jié)構(gòu),如果我們還添加了前面提到的距離或移動(dòng)速度的概念,還需要一個(gè)加權(quán)圖。這只是一組節(jié)點(diǎn),它們具有權(quán)重(或只是一個(gè)數(shù)字 —— 不要糾結(jié)它的名稱(chēng)),代表了它們之間的路徑。下面是一個(gè)示意圖(我喜歡通過(guò)觀察進(jìn)行學(xué)習(xí),所以只看圖,好嗎?):
這是一個(gè)加權(quán)圖 —— 就是這樣。我相信你已經(jīng)弄明白了,但為了完整起見(jiàn),讓我告訴你一旦我們的引擎準(zhǔn)備就緒,你將會(huì)做些什么。
一旦開(kāi)始設(shè)置游戲,你將創(chuàng)建地圖(就像你在下圖中左側(cè)看到的那樣)。然后將其轉(zhuǎn)換為加權(quán)圖,如圖所示。引擎將能夠接收它并讓你按正確的順序進(jìn)行瀏覽。
一個(gè)地牢的示例圖
通過(guò)上面的加權(quán)圖,可以確保玩家不能從入口一下子走到左翼。他們必須通過(guò)這兩者之間的節(jié)點(diǎn),這樣做會(huì)消耗時(shí)間,可以用連接的權(quán)重來(lái)測(cè)量。
現(xiàn)在,進(jìn)入“有趣”的部分。來(lái)看看地圖在 JSON 格式中的樣子。這個(gè)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 } } ] } } }
它看起來(lái)有很多內(nèi)容,但是如果你把它視為一個(gè)簡(jiǎn)單的游戲描述,就會(huì)明白這是一個(gè)含有六個(gè)房間的地牢,每個(gè)房間都與其他房間相互連接,如上圖所示。
你的任務(wù)是穿越并探索它。你會(huì)發(fā)現(xiàn)有兩個(gè)地方可以找到武器(無(wú)論是在廚房還是在黑暗的房間,只要破壞掉椅子就能得到)。你也將面對(duì)一扇上鎖的門(mén),所以,一旦找到鑰匙(位于類(lèi)似辦公室的房間內(nèi)),就可以打開(kāi)并用你收集到的武器和BOSS展開(kāi)一場(chǎng)大戰(zhàn)。
你可以干掉它而獲勝,也可以被它殺死而輸?shù)簟?/p>
現(xiàn)在讓我們更詳細(xì)地了解整個(gè) JSON 結(jié)構(gòu)及其中的三個(gè)部分。
Graph
這里包含節(jié)點(diǎn)之間的關(guān)系?;旧线@一部分會(huì)直接轉(zhuǎn)換為我們之前看到的圖。
這部分的結(jié)構(gòu)非常簡(jiǎn)單。它是一個(gè)節(jié)點(diǎn)列表,其中每個(gè)節(jié)點(diǎn)都包含以下屬性:
- 一個(gè)標(biāo)識(shí)游戲中所有其他節(jié)點(diǎn)的唯一 ID;
- 一個(gè)名稱(chēng),實(shí)際上是給玩家看到的 ID 版本;
- 一組指向其他節(jié)點(diǎn)的鏈接。這可以通過(guò)四個(gè)可能的 key 來(lái)描述:north, south, east 和 west.。我們可以通過(guò)添加這四個(gè)組合來(lái)增加更多方向。每個(gè)鏈接都包含相關(guān)節(jié)點(diǎn)的 ID 以及該關(guān)系的距離(或權(quán)重)。
Game
本節(jié)包含常規(guī)設(shè)置和條件。特別是在上面的示例中,此部分包含輸贏條件。換句話(huà)說(shuō),在這兩個(gè)條件下,我們會(huì)讓游戲知道什么時(shí)候結(jié)束。
為了簡(jiǎn)單起見(jiàn),我添加了兩個(gè)條件:
- 要么通過(guò)殺死 BOSS 獲勝,
- 或者因?yàn)楸粴⒍數(shù)簟?/li>
Rooms
這一部分占了 JSON 文件很大的篇幅,也是最復(fù)雜的部分。在這里描述冒險(xiǎn)中所有區(qū)域及其內(nèi)部所有房間。
每個(gè)房間都有一把鑰匙,使用我們之前定義的 ID。每個(gè)房間都有一個(gè)描述,一個(gè)物品列表,一個(gè)出口(或門(mén))列表和一個(gè)非玩家角色(NPC)列表。在這些屬性中,唯一應(yīng)該被強(qiáng)制定義的屬性是描述,因?yàn)橐嫘枰@個(gè)屬性才能讓你明白所看到的內(nèi)容。如果有什么東西需要展示,它們只能在那里。
讓我們來(lái)看看這些屬性能為游戲做些什么。
description
這一項(xiàng)并不像想象的那么簡(jiǎn)單,因?yàn)槟憧吹降姆块g可能會(huì)根據(jù)不同的情況而變化。例如:如果你查看第一個(gè)房間的描述,就會(huì)注意到在默認(rèn)情況下,你將看不到任何東西,除非你有一個(gè)點(diǎn)亮的火炬。
因此,拾取物品并使用它們,可能會(huì)觸發(fā)影響游戲中其他部分的全局條件。
items
這些代表了你可以在房間內(nèi)找到的所有東西。每個(gè)項(xiàng)目都會(huì)共享與 graph 節(jié)點(diǎn)相同的 ID 和名稱(chēng)。
它們還有“目標(biāo)”屬性,該屬性指示一旦拾取該道具應(yīng)放在哪里。這是有意義的,因?yàn)槟闶稚现荒苎b備一個(gè)道具,而在背包中可以存放很多的道具。
最后,其中一些道具可能會(huì)觸發(fā)其他操作或者狀態(tài)更新,具體取決于玩家決定用它們做什么。其中一個(gè)例子就是從入口處點(diǎn)燃的火把。如果你拿著一個(gè),將在游戲中觸發(fā)狀態(tài)更新,這反過(guò)來(lái)將使游戲向你顯示下一個(gè)房間的不同描述。
道具也可以有“子道具”,一旦原始道具被銷(xiāo)毀(例如通過(guò)“分解”操作)就會(huì)發(fā)揮作用。一個(gè)道具可以被分解為多個(gè),并在“subitems”元素中定義。
本質(zhì)上,此元素只是一個(gè)新道具的數(shù)組,其中還包含可以觸發(fā)其創(chuàng)建的一組操作?;旧峡梢愿鶕?jù)你對(duì)原始道具執(zhí)行的操作創(chuàng)建不同的子道具。
最后,有些物品會(huì)有“傷害”屬性。所以如果你用某個(gè)道具擊中 NPC,該值用于從中減去生命。
exits
出口是與道具分開(kāi)的實(shí)體,因?yàn)橐嫘枰滥闶欠衲軌蚋鶕?jù)其狀態(tài)去遍歷它們。否則被鎖定的出口無(wú)法讓你通過(guò),除非你把它的狀態(tài)改為已解鎖。
NPC
最后,NPC 將成為另一個(gè)列表的一部分。它們是有狀態(tài)信息的項(xiàng)目,引擎將使用這些狀態(tài)信息來(lái)了解每個(gè)項(xiàng)目的行為方式。在我們的例子中定義的是 “hp”,它代表健康狀態(tài),還有“damage”,就像武器一樣,每次命中將從玩家的健康狀況中減去相應(yīng)的值。
這就是我創(chuàng)造的地牢。內(nèi)容很多,將來(lái)我可能會(huì)考慮寫(xiě)一個(gè)編輯器,來(lái)簡(jiǎn)化 JSON 文件的創(chuàng)建。但就目前而言還沒(méi)有必要。
你可能還沒(méi)有意識(shí)到,這樣在文件中定義游戲是有很大好處的,能夠像超級(jí)任天堂時(shí)代那樣切換 JSON 文件。只需加載一個(gè)新文件就能開(kāi)始另一個(gè)游戲。非常簡(jiǎn)單!
總結(jié)
感謝你能讀到這里。希望你能喜歡我所經(jīng)歷的設(shè)計(jì)過(guò)程,并將想法變?yōu)楝F(xiàn)實(shí)。我正在努力實(shí)現(xiàn)這一目標(biāo)。我們以后可能會(huì)意識(shí)到,今天定義的內(nèi)容可能會(huì)不起作用,出現(xiàn)這種情況時(shí),我們將不得不回溯并修復(fù)它。
我敢肯定,有很多方法可以對(duì)這里提出的想法進(jìn)行改善,并創(chuàng)建一個(gè)地獄的引擎。但是這需要在本文中添加的更多的內(nèi)容,為了不讓讀者感到無(wú)聊,所以就先這樣吧。
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
Nodejs 發(fā)布自己的npm包并制作成命令行工具的實(shí)例講解
今天小編就為大家分享一篇Nodejs 發(fā)布自己的npm包并制作成命令行工具的實(shí)例講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-05-05Nodejs爬蟲(chóng)進(jìn)階教程之異步并發(fā)控制
這篇文章主要介紹了Nodejs爬蟲(chóng)進(jìn)階教程之異步并發(fā)控制的相關(guān)資料,需要的朋友可以參考下2016-02-02使用?Node.js和Express搭建服務(wù)器的過(guò)程步驟詳解
Node.js?是一個(gè)開(kāi)源、跨平臺(tái)的?JavaScript?運(yùn)行時(shí)環(huán)境,這篇文章主要介紹了如何使用?Node.js和Express搭建服務(wù)器,需要的朋友可以參考下2023-09-09node.js實(shí)現(xiàn)多圖片上傳實(shí)例
這篇文章主要介紹了node.js實(shí)現(xiàn)多圖片上傳實(shí)例,包括路由、控制器和視圖的源碼,重點(diǎn)在圖片上傳處理程序,需要的朋友可以參考下2014-06-06使用?Node-RED對(duì)?MQTT?數(shù)據(jù)流處理
本文將介紹使用 Node-RED 連接到 MQTT 服務(wù)器,并對(duì) MQTT 數(shù)據(jù)進(jìn)行過(guò)濾和處理后再將其發(fā)送至 MQTT 服務(wù)器的完整操作流程。讀者可以快速了解如何使用 Node-RED 對(duì) MQTT 數(shù)據(jù)進(jìn)行簡(jiǎn)單的流處理2022-05-05