基於Java的2D mmorpg開源引擎Threerings系列之一(概述篇)

Threerings是Three Rings Design公司旗下的一款基於Java並完全具備開發出商業品質的2D mmorpg遊戲的開源引擎。Three Rings Design是一家位於美國總部在加州舊金山的網遊開發商,但又不同於一般網遊開發商的是,該公司旗下的所有遊戲都拋棄了砍怪升級,打裝備pk的老套路,而是着重於遊戲性本身,從非常與衆不同的視角來開發它的遊戲,讓人有完全耳目一新的感覺,大家有興趣可以去該公司主頁http://www.threerings.net下載下來玩一下,不過都是E文哦。旗下的一款Puzzle Pirates更是從2003年12月開始一直穩定運營到現在,這款遊戲就是在此引擎基礎上搭建的(或者說此引擎脫胎於該款遊戲)。但是由於文檔資料相當缺乏,再加上該公司又比較低調,所以即使開源,瞭解的人也並不多,國內對之瞭解的更是少之又少,筆者到目前爲止還沒有發現有關這款引擎的中文介紹資料。所以,筆者在此願拋磚引玉,把它介紹給大家,希望能有更多的人能瞭解它。

 

 OK,閒話少敘,下面筆者就帶大家來具體瞭解一番。首先,我們去http://www.threerings.net/code/下載它的源代碼。所謂three rings,主要就是分別指narya, nenya, vilya這三個核心框架,在字面上應該是指在魔戒中除了The Ring之外最強大的三個ring了,呵呵,美國佬還挺有意思的,給取了這麼個名。我們可以看到,在http://www.threerings.net/code/上除了三個核心框架外,還有很多其他的工具類庫,我們即可以在此頁面上單獨下載,也可以從subversion源碼庫中籤出完整的開發包,當然還可以點此下載編譯好的開發包,不過筆者還是決定從svn://code.threerings.net/gardens/trunk 簽出完整的源碼開發包,爲了論述的方便起見,筆者假設你把源碼開發包簽出到threerings/gardens目錄下,整個源碼開發包下載下來大概有100多M,需要花不少時間,所以,在此期間,你可以先去泡杯茶什麼的,呵呵。

 

總算全部下載下來了,不過先別急,在正式開始嘗試前,我們還要配置好我們的運行環境,首先,請確保JDK版本在1.5之上,其次,請確保你的ant版本在1.6以上,如果沒有安裝,可以去這裏http://ant.apache.org/下載。好了,在環境配置好之後,我們就可以正式開始了,如果你用的是Linux操作系統,那恭喜你,你只需要進入threerings/gardens目錄,在命令行下運行ant distall。如果成功的話,會在threerings/gardens目錄下生成一個dist目錄,在這個目錄下包含了所有運行你自己的應用的所有jar包。

 

如果你用的是windows,跟筆者一樣,我想大多數人都是,如果直接運行ant distall會發現很多無法打開壓縮包的錯誤,這是因爲在threerings內部,他們用的都是Linux作爲開發環境,並且使用了symbolic link作爲管理jar包的方式(其實他們應該嘗試下maven作爲jar包管理工具),而symbolic link對於windows來說是不支持的,所以我們需要用真正的jar包來覆蓋這些symbolic link。打開threerings/gardens/lib以及threerings/gardens/build/lib/java目錄,可以發現在這兩個目錄下有許多長度只有1k的jar包,如果你用文本編輯器打開的話就會發現它只是一個鏈接而已,那我們只需要有點耐心,使用帶版本號的jar包來一一覆蓋不帶版本號的1k的jar文件。

 

另外,在運行ant distall命令前,還需要在threerings/gardens目錄下把gardens.conf.dist文件複製成相同目下的gardens.conf文件。

 

好了,現在在成功運行ant distall之後,進入threerings/gardens/projects/narya/tests目錄,在這個目錄下有一個server跟client的例子,但是在運行他們之前,爲了方便起見,我們編輯一下threerings/gardens/projects/narya/tests/build.xml這個文件,在此文件的末尾,加入下面的代碼。

 

<target name="runserver">     
	<java classname="com.threerings.presents.server.TestServer" fork="true">          
		<classpath refid="classpath"/>     
	</java>
</target>
<target name="runclient">     
	<java classname="com.threerings.presents.client.TestClient" fork="true">          
		<classpath refid="classpath"/>     
	</java>
</target>

 

在此,我們就加入了兩個ant target,這樣就不需要手工來運行java命令了。下面我們就來運行一下這個server和client的例子。打開一個命令行,在threerings/gardens/projects/narya/tests下,運行ant runserver;再打開一個命令行,在同樣的目錄下運行ant runclient。如果運行成功,在衆多的輸出裏面,你在server端可以看到其中一行有test request...字樣,在client端可以看到其中一行有test response...字樣,這樣就基本能說明服務端和客戶端能夠正常通訊了。

 

 那麼我們現在就來看看在client端的具體的代碼。打開threerings/gardens/projects/narya/tests/src/java/com/threerings/presents/client/TestClient.java,在main函數中我們創建了一個UsernamePasswordCreds對象,這個是在client登錄的時候發送給服務端的驗證信息,我們把這個對象傳遞給client對象,而client對象則包含了所有的連接信息。最後我們調用client.logon(), 在這個時候,client對象把登錄的驗證信息發送給服務器,服務器驗證通過後發回一些客戶端用於自舉的數據,然後客戶端會再次連接到服務端。

 

    public static void main (String[] args)
    {
        TestClient tclient = new TestClient();
        UsernamePasswordCreds creds =
            new UsernamePasswordCreds(new Name("test"), "test");
...
        Client client = new Client(creds, rqueue);
        tclient.setClient(client);
...
        client.logon();
...
    }

 

當客戶端登錄成功以後,在客戶端的偵聽器回調函數clientDidLogon會被框架自動調用。這是通過實現一個叫做SessionObserver的特殊的觀察者接口來完成的。在實現這個接口的同時還需要調用client.addClientObserver(tclient)方法使我們的TestClient註冊成爲一個logon事件的偵聽者。很多用到這種介於服務端和客戶端通訊的功能,包括後面會談到的DObjects,都非常多的使用了觀察者模式,即Observer Pattern,這是GOF23種設計模式中的一種,具體可以參看筆者的另一篇博文深度探索觀察者模式。回過頭來,我們再來看一下clientDidLogon這個方法,在這個方法中包含有許多代碼,但在這裏,我們只關注其中的兩樣,即請求和調用一個服務。服務是在服務端的一個功能,它可以被客戶端直接調用,就好像遠程方法調用(RPC)一樣。這種基於服務的調用就提供了服務端和客戶端通訊的一種基本方法。代碼client.requireService執行後返回一個service對象,通過這個對象,客戶端就可以用它來調用服務端上的服務了。代碼service.getTestOid就是對服務的一個調用,這個服務用來返回分佈式對象DObject的object id。

 

    // from interface SessionObserver
    public void clientDidLogon (Client client)
    {
...
        TestService service = client.requireService(TestService.class);
...
         // get the test object id
        service.getTestOid(client, this);
    }

 

 下面我們來簡單介紹下這個框架中的分佈式對象概念(筆者會在下一篇中詳細介紹),一個分佈式對象即一個distributed object或者我們叫DObject,它是專門用來在服務端和客戶端共享數據的。當服務端更新了一個DObject對象的狀態時,更新後的狀態會被自動複製到client端。不過當client端需要調用這個DObject時,它首先必須訂閱(subscribe)它,打個比方,就象訂閱我們的雜誌一樣。每次一個DObject在服務端被更新時,更新後的新版本會被髮送到訂閱者手裏。通過這種機制,客戶端能保持手裏的DObject更新狀態。

 

 

那我們又如何來訂閱DObject呢?這就需要先找到它的oid(Object ID),比如你定雜誌的時候需要填寫一個唯一可識別的訂刊號。我們通過調用service.getTestOid方法向服務器(雜誌發行商)請求這個id(雜誌的訂刊號)。當我們調用這個方法的時候服務器(雜誌發行商)處理完我們的請求後會回調我們的偵聽方法gotTestOid,在調用的同時DObject(雜誌)的oid(訂刊號)作爲參數被傳入。

  

    // documentation inherited from interface
    public void gotTestOid (int testOid)
    {
        // subscribe to the test object
        _client.getDObjectManager().subscribeToObject(testOid, this);
    }

 

 當我們得到oid後(雜誌訂刊號)後我們便可以訂閱它了。在gotTestOid方法被調用後,這時我們通過oid(雜誌訂刊號)來調用subscribeToObject方法。服務器(雜誌發行商)在得到我們的訂閱DObject(雜誌)請求後便會返回真正的DObject對象(雜誌)到我們的偵聽方法objectAvailable

 

    // from interface Subscriber
    public void objectAvailable (final TestObject object)
    {
        object.addListener(this);
        log.info("Object available: " + object);
        object.postMessage("lawl!");
...
        }

 

在下一篇中具體介紹DObject之前,這兒簡單說下關於mmorpg網絡通訊的安全性問題。衆所周知,有網遊的地方就會有人zuobi,有外掛,那麼作爲一款專用於mmorpg開發的引擎的底層通訊框架必須提供一種機制來儘可能的遏制這種現象。那麼,在這裏對於DObject來說,在遊戲中DObject經常被用於存儲衆多客戶端與服務端共享的敏感數據,對於這些數據,客戶端是沒有權利直接去修改的,客戶端只能發送請求(通過我們前面介紹的服務)給服務端,服務端在接收到來自客戶端的請求後只有在驗證通過之後纔會對DObject作出修改,而只要訂閱了這個DObject的客戶端都會收到更新後的狀態。遊戲引擎通過使用這種機制來確保玩家無法通過修改客戶端運行數據來zuobi。如果你在運行你的服務器的時候發現這樣的警告信息WARNING: Event failed permission check,這是因爲客戶端試圖在本地修改DObject,而服務器拒絕了客戶端的請求。如果仍舊用雜誌訂閱來做比喻(在這裏不一定恰當),如果一個訂戶想要在雜誌中包含一篇新的專題或其他文章,它首先要發送這個請求到雜誌發行商,雜誌發行商更新他們的雜誌包含此新專題並再次發放到訂戶手裏。

 

 

public static void main (String[] args)
    {
        TestClient tclient = new TestClient();
        UsernamePasswordCreds creds =
            new UsernamePasswordCreds(new Name("test"), "test");
        BasicRunQueue rqueue = new BasicRunQueue();
        Client client = new Client(creds, rqueue);
        tclient.setClient(client);
        client.addClientObserver(tclient);
        client.setServer("localhost", Client.DEFAULT_SERVER_PORTS);
        client.logon();
        // start up our event processing loop
        rqueue.run();
    }

 

下一篇我們會詳細介紹Threerings引擎中的DObject部分。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章