您好,登錄后才能下訂單哦!
摘要
聽說過文字冒險游戲嗎? 如果你的年齡足夠大的話(就像我一樣),那么你可能聽說過、甚至玩過“back in the day”。在本文中,我將向你展示編寫的整個過程。這不僅僅是一個文本冒險游戲,而是一個能讓你和你的朋友們一起玩的,可以進行任何劇情的文本冒險游戲引擎。 沒錯,我們將通過在添加多人游戲功能來增加它的趣味性。
文字冒險是最早的 RPG 形式的游戲之一,回到還沒有圖形畫面的時代,你只能通過閱讀 CRT 顯示器上黑色背景下的描述,并且依賴自己的想象力來推動游戲劇情的發展。
如果要懷舊的話,可能世界上第一個文字冒險游戲名叫 Colossal Cave Adventure(也許是叫 Adventure)。
文字冒險游戲 back in the day 的畫面
上圖是你實際看到的游戲畫面,這與我們現在的頂級 AAA 冒險游戲相差甚遠。 盡管如此,但是他們玩起來卻很有趣,并會很容易的消磨你幾百個小時的時間,因為只有你自己自己坐在顯示器前,試圖找到打穿它的途徑。
可以理解的是,多年以來,文字冒險已經被更好的視覺效果所取代,特別是在過去幾年里,游戲的協作性越強,你可以和朋友們一起玩。 這是原始的文字冒險游戲所缺少的,同時也是我想在本文中提到的功能。
我們的目標
可能你已經從標題中猜到了,本文的重點在于創建一個文字冒險引擎,并且讓你和朋友們一起玩,使你能夠與他們進行協作,就像在玩“龍與地下城”這個游戲一樣。
在創建引擎時,聊天服務器和客戶端的工作了相當大。 在本文中,我將向你展示設計思路、解釋引擎背后的架構、客戶端如何與服務器交互以及這個游戲的規則。
為了讓你對我的目標又一個直觀的感受,先上一張圖:
游戲客戶端的 UI 設計
這就是我們的目標。 一旦達成這個目標,將會得到截圖而不是簡單和骯臟的模型。 所以,需要了解這個過程。首先要介紹的就是整體設計;然后介紹我將用來編碼的相關工具;最后我將向你展示一些核心代碼(當然,還有指向完整代碼庫的鏈接)。
希望到最后,你能夠自己創造一個新的文字冒險游戲,并與朋友一起樂在其中!
設計階段
在設計階段,我將描述這個游戲的整體藍圖。 我會盡力不讓你覺得無聊,不過我認為在給你展示第一行代碼之前,很有必要先搞清楚幕后的一些工作。
我想接下來介紹的這四個組件能夠提供相當多的細節:
引擎
游戲引擎或游戲服務器將會是REST API,并提供所有必需的功能。
我選擇REST API只是因為(對于這種類型的游戲)HTTP造成的延遲以及他的異步特性不會造成任何麻煩。 但是,我們必須為聊天服務器采用不同的路線。 在開始定義 API 之前,先需要定義引擎的功能。 所以,讓我們來看看吧。
特性 | 描述 |
---|---|
加入游戲 | 玩家可以通過指定的游戲ID來加入游戲。 |
創建一個新游戲 | 玩家還可以創建新的游戲實例。 引擎應該返回一個ID,以便其他人可以使它來加入游戲。 |
返回場景 | 此功能應返回玩家所在的當前場景。 基本上,它將返回描述,包含所有相關信息(可能的操作、其中的對象等)。 |
與場景互動 | 這將是最復雜的一個,因為它將從客戶端獲取命令并執行該操作——例如移動,攻擊,獲取,查看,讀取等等。 |
檢查庫存 | 雖然這是與游戲互動的一種方式,但它與場景并沒有直接關系。 因此,檢查每個玩家的庫存將被視為不同的操作。 |
關于移動
我們需要一種用來測量游戲中距離的方法,因為在游戲中玩家可以采取的核心行動之一就是移動。 我們需要用這個數字作為時間的衡量標準,來簡化游戲的玩法。 考慮到這一類型的游戲具有基于回合的動作,例如戰斗,使用實際時鐘對時間進行測量可能不是最好的。 所以我們將使用距離來測量時間(意味著距離為 8 比距離為 2 將需要更多的時間,從而允許我們做一些事情,例如為持續一定數量的“距離點”的玩家添加效果)。
考慮運動的另一個原因是不是一個人在玩這個游戲。 為簡單起見,引擎不會讓玩家隨意組隊(雖然這對未來可能是一個有趣的改進)。 該模塊的初始版本只允許個人朝著大多數參與者決定的地方移動。因此,必須以協商一致的方式進行移動,這意味著每一步行動都將等待大多數人在行動之前提出請求。
戰斗
戰斗是這種游戲另一個非常重要的方面,我們不得不考慮將它添加到引擎中,否則我們最終會失去一些樂趣。
說實話,這并不需要重新發明輪子。基于回合制的組隊對戰已經存在了幾十年,所以在這里只實現這個機制的一個簡單版本。我們將把它與“龍與地下城”中的“主動性”這個概念混合起來,產生一個隨機數使戰斗更有活力。
換句話說,就是參與戰斗的每個人的行動順序將會被隨機化,其中包括敵人。
最后(雖然我將在下面詳細介紹這一點),你可以用設置的“攻擊力”值的物品。這些是你在戰斗中可以使用的道具;如果一個道具沒有這個屬性的話只能對敵人造成 0 點傷害。當你試圖用這樣的道具進行戰斗時,我們可能會添加一條消息,這樣你就能知道自己要做的事情是毫無意義的。
客戶端 - 服務器交互
現在來看看客戶端怎樣基于前面定義的功能與服務器進行交互(目前還沒考慮端點,不過馬上就會講到這個):
客戶端與服務器之間的交互
客戶端和服務器之間的初始交互(從服務器的角度來看)是一個新游戲的開始,其步驟如下:
游戲的動作指令
一旦滿足了先決條件,玩家就可以開始游戲,通過聊天室分享他們的想法,并推動故事的發展。上圖顯示了所需的四個步驟。
以下步驟將作為游戲循環的一部分來運行,這意味著它們將會不斷重復,一直到游戲結束。
作為額外步驟,雖然不是流程的一部分,但服務器將通知客戶端與它們相關的狀態的更新情況。
存在這個額外重復步驟的原因是玩家可以從其他玩家的動作中獲得更新。回想從一個地方移動另一個地方的需求;正如我之前所說那樣,一旦大多數玩家選擇了方向,那么所有玩家都會移動(不需要所有球員的輸入)。
不過 HTTP(前面已經提到服務器為REST API)不允許這種類型的行為。所以,我們的選擇是:
根據我的經驗,我傾向于選擇選項 2。實際上,我會(在本文中)使用Redis來實現這種行為。
下圖演示了服務之間的依賴關系。
客戶端應用程序與游戲引擎之間的交互
聊天服務器
我將把這個模塊的設計細節留給開發階段(本文不涉及這一部分)。話雖如此,我們仍可以決定一些事情。
我們可以確定的一件事是服務器的限制集合,這將簡化我們的工作。如果我們正確地玩牌,最終可能會有一個提供強大界面的服務,從而允許我們去進行擴展甚至修改實現,以提供更少的限制,而不會影響到游戲。
這就是聊天服務器。畢竟,它不會很復雜。在開始編碼之前還有很多工作要做,但是對于本文來說已經足夠了。
客戶端
這是最后一個需要編碼的模塊,它將是最笨重的一個模塊。根據經驗來看,我更喜歡讓客戶端笨重,使服務器輕巧。這樣為服務器開發新的客戶端會更加容易。
這是我們最終應該采用的架構。
最終架構
我們要實現的ClI客戶端很簡單,不會實現任何非常復雜的東西。實際上,必須要解決的最復雜的部分是 UI,因為它是一個基于文本的界面。
客戶端應用程序必須實現的功能如下:
稍后將詳細介紹客戶端的內部結構和設計。與此同時,讓我們完成設計階段的最后一部分:游戲文件。
游戲: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": "littorch2", "name": "Lit torch on the right", "triggers": [ { "action": "grab", //grab Lit torch on the right "effect":{ "statusUpdate": "has light", "target": "game", } } ] , "destination": "hand" }, { "id": "littorch3", "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 } } ] } } }
它看起來有很多內容,但是如果你把它視為一個簡單的游戲描述,就會明白這是一個含有六個房間的地牢,每個房間都與其他房間相互連接,如上圖所示。
你的任務是穿越并探索它。你會發現有兩個地方可以找到武器(無論是在廚房還是在黑暗的房間,只要破壞掉椅子就能得到)。你也將面對一扇上鎖的門,所以,一旦找到鑰匙(位于類似辦公室的房間內),就可以打開并用你收集到的武器和BOSS展開一場大戰。
你可以干掉它而獲勝,也可以被它殺死而輸掉。
現在讓我們更詳細地了解整個 JSON 結構及其中的三個部分。
Graph
這里包含節點之間的關系。基本上這一部分會直接轉換為我們之前看到的圖。
這部分的結構非常簡單。它是一個節點列表,其中每個節點都包含以下屬性:
Game
本節包含常規設置和條件。特別是在上面的示例中,此部分包含輸贏條件。換句話說,在這兩個條件下,我們會讓游戲知道什么時候結束。
為了簡單起見,我添加了兩個條件:
Rooms
這一部分占了 JSON 文件很大的篇幅,也是最復雜的部分。在這里描述冒險中所有區域及其內部所有房間。
每個房間都有一把鑰匙,使用我們之前定義的 ID。每個房間都有一個描述,一個物品列表,一個出口(或門)列表和一個非玩家角色(NPC)列表。在這些屬性中,唯一應該被強制定義的屬性是描述,因為引擎需要這個屬性才能讓你明白所看到的內容。如果有什么東西需要展示,它們只能在那里。
讓我們來看看這些屬性能為游戲做些什么。
description
這一項并不像想象的那么簡單,因為你看到的房間可能會根據不同的情況而變化。例如:如果你查看第一個房間的描述,就會注意到在默認情況下,你將看不到任何東西,除非你有一個點亮的火炬。
因此,拾取物品并使用它們,可能會觸發影響游戲中其他部分的全局條件。
items
這些代表了你可以在房間內找到的所有東西。每個項目都會共享與 graph 節點相同的 ID 和名稱。
它們還有“目標”屬性,該屬性指示一旦拾取該道具應放在哪里。這是有意義的,因為你手上只能裝備一個道具,而在背包中可以存放很多的道具。
最后,其中一些道具可能會觸發其他操作或者狀態更新,具體取決于玩家決定用它們做什么。其中一個例子就是從入口處點燃的火把。如果你拿著一個,將在游戲中觸發狀態更新,這反過來將使游戲向你顯示下一個房間的不同描述。
道具也可以有“子道具”,一旦原始道具被銷毀(例如通過“分解”操作)就會發揮作用。一個道具可以被分解為多個,并在“subitems”元素中定義。
本質上,此元素只是一個新道具的數組,其中還包含可以觸發其創建的一組操作。基本上可以根據你對原始道具執行的操作創建不同的子道具。
最后,有些物品會有“傷害”屬性。所以如果你用某個道具擊中 NPC,該值用于從中減去生命。
exits
出口是與道具分開的實體,因為引擎需要知道你是否能夠根據其狀態去遍歷它們。否則被鎖定的出口無法讓你通過,除非你把它的狀態改為已解鎖。
NPC
最后,NPC 將成為另一個列表的一部分。它們是有狀態信息的項目,引擎將使用這些狀態信息來了解每個項目的行為方式。在我們的例子中定義的是 “hp”,它代表健康狀態,還有“damage”,就像武器一樣,每次命中將從玩家的健康狀況中減去相應的值。
這就是我創造的地牢。內容很多,將來我可能會考慮寫一個編輯器,來簡化 JSON 文件的創建。但就目前而言還沒有必要。
你可能還沒有意識到,這樣在文件中定義游戲是有很大好處的,能夠像超級任天堂時代那樣切換 JSON 文件。只需加載一個新文件就能開始另一個游戲。非常簡單!
總結
感謝你能讀到這里。希望你能喜歡我所經歷的設計過程,并將想法變為現實。我正在努力實現這一目標。我們以后可能會意識到,今天定義的內容可能會不起作用,出現這種情況時,我們將不得不回溯并修復它。
我敢肯定,有很多方法可以對這里提出的想法進行改善,并創建一個地獄的引擎。但是這需要在本文中添加的更多的內容,為了不讓讀者感到無聊,所以就先這樣吧。
好了,以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。