1. 程式人生 > >遊戲開發筆記(六)——服務端架構設計

遊戲開發筆記(六)——服務端架構設計

        上回寫了寫服務端的分層結構,分層是比較巨集觀上的東西,至於層次間具體的互動方式還得通過各個模組的互動方式來體現,姑且把這種模組劃分以及其間的互動關係稱之為架構吧,下面就來談談MMORPG的服務端架構

        對於製作一款遊戲而言,首先要考慮的是做什麼樣的遊戲?要實現哪些功能?而為了進一步簡化問題,我們先考慮一款遊戲的核心玩法——即少了這一部分便會使得整個遊戲失去骨骼,乃至於產生一種“皮之不存毛將焉附”的感覺的部分。

        首先,對於一款MMORPG而言,角色和戰鬥是RPG遊戲中比較強調的一個部分,其他功能會豐富整個遊戲使它增添樂趣,但不會取而代之。所以我們的系統至少應當包含有NPC模組、玩家角色模組,需要有活動的場所因此——場景模組,需要有活動的內容因此——戰鬥模組。

        其次,為了使玩家間能夠相互通訊,我們還需要網路模組;為了記錄玩家資料,我們需要資料庫模組;為了監控和記錄遊戲和程式狀態,我們需要一個日誌模組。好了,再說缺少什麼的話,我覺得是有了更好,但若沒有我們也能好歹應付下來。

        最後,羅列一下:NPC、玩家、場景、戰鬥、網路、資料庫、日誌——完整MMORPG的最小系統版圖~

接下來考慮這些邏輯如何在程式中體現的問題——

《遊戲系統開發筆記(四)——遊戲程式簡介》中曾簡單介紹過服務端的程式結構,那麼這裡再來細化一下,先考慮最簡單的設計

直接上賈程式碼:

int main(int argc, char* argv[])
{
   Init(); //初始化各種資源、環境、資料
    while(g_State != GAME_EXIT)
   {
       CheckToSleep(); // 定一個迴圈間隔,沒跑滿的時間咱們讓CPU放鬆下
        //GameLoop
       NetMessLoop();  // 收發網路訊息,並將收到的訊息分派給各個模組(比如加到各自的訊息佇列中)
        NpcAILoop();    // Npc的AI
       PlayerLoop();   // 處理玩家行為
        //OtherLoop     // 比如幫會、副本、任務等等等
        DBLoop();       // 資料庫操作
        TimeEvent();    // 定時任務
    }
    Release(); // 清理和記錄工作
    return 0;
}


        這裡我們把預期遊戲的功能拆分成網路訊息、AI、玩家行為、定時任務幾塊,通過一個迴圈不斷的去重新整理各種狀態、執行各種請求,從而達到使遊戲運轉起來的目的,迴圈的間隔時間主要影響了遊戲的最小響應時間。其中日誌模組、資料庫一般會設計成一個全域性範圍的功能,各個模組直接對其進行操作。

        這個例子把各個模組功能完成理論上就可以完成任務了,而且看上去是十分清晰簡潔的。按這種方式寫下去,通常就是對每個線上玩家和NPC設定一個唯一ID,放到各個模組的表中,各個Loop維護一個訊息佇列,訊息格式可能設計成訊息ID+引數的形式。接下來實現各種訊息對應的處理函式,然後處理訊息的過程中從引數解析到物件ID,通過ID和一組操作角色行為的函式來進行控制。

        ——這是比較典型的C風格的遊戲服務端做法,過程式的風格使得結構看起來比較扁平化,相對容易快速把握住整體結構。

下面是這個簡單例子的邏輯結構:

--------------------------------------------------------------------------------------------------

        可以從中看出,我們把大部分任務都歸到了邏輯模組,這會帶來兩個顯而易見的問題:1、過於複雜 2、專案程式碼難以重用

        另外,在這個例子中我們也沒有考慮多執行緒,所有操作都在主執行緒內完成,好處當然就是簡單清晰。帶來的問題,首要的倒談不上硬體資源浪費,因為多核的伺服器其實大不了多開幾個遊戲服務端,一個核跑一個服也就沒有浪費不浪費了。更加重要的問題是現在單核的主頻因為技術制約已經很難再有所提高了,依靠單核的計算能力很可能會使得一個遊戲只能承載寥寥幾百人同時線上——多麼杯具!

        所以總的來說,對於現在的商業遊戲專案來說,專案程式碼難以重用和承載人數太少是太難以接受的,一般來說也不願意投入許多精力去做這樣的東西吧。

        所以我們需要首先針對這兩個問題對專案進行優化——

        程式碼重用問題一般而言首先會考慮根據“一般性”和“特殊性”對功能進行分離,設計一組劃分更加合理的模組。而承載人數的問題,雖然優化程式是很重要一方面,但既然結構上具有不合理性,當然先考慮從結構上解決問題——我們需要使程式能夠更加充分的利用硬體資源。具體的措施我會先從第一個問題著手,因為模組設計好了通常也就為多執行緒做好了必要準備,剩下的可能也就是把它們分別裝配到其他執行緒的事情而已了。

        因為網路、資料庫和日誌三個模組功能上非常獨立,為它們各自設計一組邏輯無關的操作介面後就已經具備了重用的條件,這個例子中主要的問題在邏輯模組。

        經過考慮,對於MMORPG來說邏輯模組的只有移動、場景相關的功能是具有比較廣泛的一般性的,其它功能都或多或少的和具體遊戲產生不可分割的聯絡。但因為移動相關的功能比較複雜、執行頻率也非常高,所以可以單獨抽出來做一個模組。說它複雜因為移動不只是簡單的從一點到另一點,還會涉及碰撞、移動路徑、同步等問題。我們把可視、可移動的這種能力——很基本的能力設計成一個獨立物件(實體物件),組合到需要這種能力的其它物件中。

        實體物件的設計包含兩個部分,其一是執行具體移動相關業務的部分,其二是邏輯模組和該模組互動的介面層,上面說的組合物件實際上是指把這裡的作為介面的物件組合進來,這樣我們才能把實體物件需要做的工作給完整的抽取出來。給這個模組賦予一個好聽點的名字,就叫引擎模組吧。

        我們的邏輯模組剩餘的還有角色、NPC、戰鬥、場景以及其它遊戲功能,它們相互之間會有比較頻繁和複雜的相互操作,最多隻能做到把各個子模組劃分的再清晰些,但若是把任何一個部分放其它執行緒去就麻煩啦!複雜的遊戲邏輯裡面還要夾雜各種非同步操作簡直要讓人發瘋。

所以最後關於執行緒劃分的設計也差不多出來啦:

-------------------------------

        這種結構說起來算是一個拆分多執行緒的雛形吧,具體操作過程中可能還要有許多調整。根據效能熱點和功能型別可以考慮進一步拆分細項,但拆分的過程中要考慮好多執行緒帶來的利弊得失,對於遊戲伺服器來說,多執行緒間過多的互動會帶來較大的訊息處理延遲,使得一些細節的遊戲表現很難做到位。另外也要考慮到開發的難度,像邏輯這邊只能說還是更多的要為開發難度著想,非同步的遊戲邏輯下實在是很容易出各種BUG,有的不好查,有的查出來又不好解決 等等等~~

        總體來說,負載問題能得到或多或少的緩解,而程式碼複用的問題,一般來說引擎層的東西寫穩定了就不需要再改了。邏輯上的東西雖然大多都很有個性,但考慮到MMORPG類遊戲總體上都長的差不多,直接拿到一個專案來修改和擴充套件也無妨,而且可以相對節約學習使用引擎的成本。考慮到擴充套件和節約程式碼的問題,我覺得可能(僅僅是可能,這方面毫無經驗)用C++來實現會更適合些。

        結合上一章的分層結構來看,系統層實際上只提供了一些底層操作供上層呼叫,相當於工具庫,沒有結構上的意義,所以它對於這套架構來說是不可見的。而引擎層在上面第二張圖裡也已經有雛形了——有個引擎執行緒,但要注意的是引擎執行緒和引擎層是不等價的,上面也說了,這裡只是“比較隨便的”稱之為引擎模組而已,其實你愛叫什麼“移動層”的也完全隨你。引擎層所指的範圍要大些,包括這裡的“引擎執行緒”在內,還有網路、資料庫、日誌模組都可以歸入引擎執行緒。而作為大頭的邏輯執行緒(主執行緒),同時也可以認為是分層意義上的邏輯層

        很晚了,先寫到這裡。