Evennia 圖解

這篇文章試著從高層次介紹 Evennia 伺服器,以及它由哪些主要部分組成。希望能幫助你更快理解整體架構是如何串在一起的。

Evennia 的兩大組件

Evennia 的 Portal 與 Server

這張圖裡顯示的是你從 Evennia 下載下來的主體部分。它本身_還不能_單獨啟動一個完整遊戲。接下來我們很快就會補上那塊缺少的「拼圖」,不過在那之前,先看看目前手上有哪些東西。

首先你會發現,Evennia 主要由兩個部分組成,也就是 Portal 與 Server。它們是彼此分開的程序。

Portal 負責追蹤所有對外連線,也理解 Telnet protocol、websocket、SSH 等連線方式。它完全不碰資料庫,也不知道遊戲狀態。Portal 和 Server 之間傳遞的資料與連線協定無關,意思是無論玩家透過哪種方式連進來,Server 收發的都是同一種資料。因為有 Portal 作為外層保護,所以就算 Server 完整重開,玩家也不會因此斷線。

Server 則是主要的「mud driver」,負責處理與遊戲世界及其資料庫有關的一切。它是非同步的,底層使用 Twisted

在 Server 的同一個程序裡,也包含了 Evennia 的 Web Server。它負責提供遊戲網站。

初始化遊戲目錄

建立遊戲目錄

當你安裝好 Evennia 之後,就會有 evennia 這個指令可以使用。你會用它建立一個遊戲目錄(這裡假設叫做 mygame)。圖中較深灰色的那一塊就是它,也是剛剛還缺著的部分。你的夢想遊戲,就是在這裡誕生。

在初始化過程中,Evennia 會在 mygame/ 裡建立 Python module 範本,並串好所有必要設定,讓 mygame 變成一個已經完整可運作、雖然還是空白、但可以立刻開始擴充的遊戲。

初始化的一部分也包含建立資料庫,然後啟動伺服器。從這一刻起,你的新遊戲就已經運轉起來了。你可以用 telnet 連到 localhost:4000,或直接在瀏覽器打開 http://localhost:4001 來連進遊戲。

現在,這個新的 mygame 世界還需要 Characters、地點、物品,以及更多內容。

資料庫

image3

Evennia 是完全持久化的系統,並透過 Django 在 Python 中抽象化它的資料庫。資料表數量不多,而且設計得很通用;每一張表都由一個 Python class 代表。就像圖中所示,範例裡的 ObjectDB Python class 對應一張資料表。class 上的屬性就是那張表的欄位(fields),而每一列資料都是這個 class 的一個 instance,也就是遊戲中的一個實體。

圖中示範的欄位裡,包含了這個 ObjectDB 實體的 key(名稱),以及它目前「location」的 Foreign key 關聯。

從圖上可以看出,Trigger 正待在 Dungeon 裡,身上還帶著他信賴的十字弓 Old Betsy

db_typeclass_path 是個很重要的欄位。它是一個 Python 風格的路徑,用來告訴 Evennia:實際上是哪個 ObjectDB subclass 正在代表這個實體。這正是 Evennia Typeclass system 的核心,讓你能用一般 Python 方式操作資料庫中的遊戲實體。

從資料庫到 Python

image4

在這張圖裡,你可以看到一棵稍微簡化過的 Python class inheritance tree,也就是身為 Evennia 開發者時會接觸到的結構,旁邊還搭配了三個實際存在的實體。

Objects 代表的是你在遊戲裡實際會看到的東西,而它的子類別則實作了 Evennia 會用到的各種 handler、helper code 與 hook method。在你的 mygame/ 目錄裡,你只要匯入它們,再 override 你想修改的部分即可。於是 Crossbow 就能被改造成只會十字弓才有的行為,而 CastleRoom 也能加入城堡房間特有的功能。

當你在遊戲裡建立一個新實體時,資料庫表中就會自動新增一列,接著 Trigger 就會在遊戲中出現!如果我們在程式裡查詢資料庫中的 Trigger,得到的會是一個 Character class 的 instance,也就是一個可以正常操作的 Python 物件。

看到這裡,你可能會以為自己得為遊戲中的每一種物件都建立一個 class。實際結構當然由你決定,但 Evennia 也提供了其他方法,讓你可以針對單一物件做客製化。繼續看下去。

Attributes

image5

除了前面那些之外,Attribute 也是另一個在幕後直接連到資料庫的 class。每個 Attribute 基本上都有一個 key、一個 value,以及一個指向其他 ObjectDB 的 ForeignKey 關聯。

Attribute 會把 Python 結構序列化後存進資料庫,意思是你幾乎可以存任何合法的 Python 內容,例如圖中那份技能 dictionary。此後,「strength」與「skills」這些 Attributes 就能直接從 Trigger 物件上取得。這個機制(以及其他幾種資源)讓你可以打造高度個別化的實體,同時只為那些行為本質上真的不同的東西建立 class。

控制遊戲行為

image6

Trigger 多半是由一位真人玩家控制。這位玩家會透過一個或多個 Sessions 連上遊戲,每開一個 client,就會對應一個 Session。

他在 mygame 裡的帳號,會由一個 Account 實體表示。AccountDB 會保存密碼與其他帳號資訊,但它本身不直接存在於遊戲世界中。透過 Account 實體,Sessions 就能控制(也就是「puppet」)一個或多個遊戲內的 Object 實體。

在這張圖裡,某位使用者同時用三個 Session 連進遊戲。他登入的是名為 Richard 的玩家 Account。透過這些 Session,他同時在操作遊戲內的 TriggerSir Hiss 兩個實體。Evennia 可以設定是否允許這類不同形式的 Connection Styles

Commands

image7

要讓玩家真的能控制自己的遊戲角色並開始玩,他們就必須能送出 Commands

Command 可以代表玩家主動輸入到遊戲裡的任何內容,例如 lookgetquitemote 等等。

每個 Command 都同時負責參數 parsing 與實際執行。由於每個 Command 都是一般 Python class,所以你可以只實作一次 parsing 邏輯,再讓其他指令去繼承成果。上圖中的 DIKUCommand 父類別就實作了所有 DIKU-style 指令共用的語法解析,於是 CmdLook 等其他指令就不必重寫。

Command Sets

image8

所有 Evennia Commands 都一定會被收納進 CommandSet 裡。CommandSet 是能容納多個 Command instance 的容器。某個特定的 Command class 可以把 instance 提供給任意多個 CommandSet,而這些 set 會永遠綁定在遊戲實體上。

在這張圖裡,Trigger 取得了一個包含許多實用指令的 CommandSet,所以他本人(以及背後控制他的 Account/Player)現在都可以使用這些指令。

image9

TriggerCommandSet 只對他自己可用。在這張圖裡,我們又把一個含有三個指令的 CommandSet 放到了 Dungeon 房間上。房間本身其實不需要用指令,但我們可以把這組 set 設定成影響_房間裡面的人_。請注意,這裡的指令是這些命令的_另一個版本_(所以顏色不同)!下面會解釋原因。

合併 Command Sets

image10

多個 CommandSet 可以像 Set Theory 那樣被動態地、暫時地合併,只是 Evennia 允許你自訂合併優先權。在這張圖裡,我們看到的是一種 Union 型的合併:來自 Dungeon、名稱相同的 Commands 會暫時覆蓋 Trigger 原本的指令。當 Trigger 待在 Dungeon 裡時,他使用的就是這一版指令;等他離開後,自己的 CommandSet 就會完整恢復。

為什麼要這樣做?例如,假設地城一片漆黑,那我們就可以讓 Dungeon 版本的 look 指令只在 Trigger 身上有光源時,才顯示房間內容。沒有光時,你甚至可能沒辦法順利撿起地上的東西,還可能在背包裡亂翻一通!

任意數量的 Command Sets 都可以在執行時即時合併。這讓你能實作多種彼此重疊的狀態(例如在昏暗房間裡戰鬥,而且角色還喝醉了),卻不必為每一種可能組合都寫出巨大的 if 判斷。這種合併是非破壞性的,所以你只要移除 cmdset,就能在需要時回到先前的狀態。

現在就去探索吧!

這當然遠遠不是 Evennia 功能的完整清單,但它應該已經提供你一批值得繼續往下深挖的有趣概念。

你可以在本手冊的 Core ComponentsCore Concepts 章節中找到更多細節。如果你還沒讀過,也很建議先看看 Evennia 簡介