前面討論了連線遊戲是如何在伺服器跟玩家之間溝通的方法跟行為,不過遊戲跟 Web 和其他應用相比從初期就很快需要面對複雜的狀態問題。也因為這樣遊戲的除錯跟維護相對不容易的,不過這幾年前端的蓬勃發展之後其實也出現了同樣的狀態管理問題,所以我們可以來看看在 Unlight 使用了大量的 Singleton 是否能跟前端這幾年所提出的解決方案有互相借鏡的地方。
我們在 Unlight 的客戶端會發現大量的使用了 Singleton (單例)來做一個統一的中央管理,不過在這之前可以先看看伺服器是否也有這樣使用?
回想一下我們在前面討論伺服器跟客戶端如何連線時像是 src/authentication.rb
這類伺服器的啟動程式,基本上呼叫了 AuthServer.setup
之後,基本上就交由 EventMachine 來管理。
嚴格上要說有使用 Singleton 似乎不太適合,不過我們可以確定有盡可能保持著一個玩家對應一個實例的狀態:
如果考慮到資料庫保存的狀態,我們可以視為每次玩家連上線之後都是是維持一個相同且唯一的狀態。
至於在客戶端部分,就確實有使用 ActionScript 語言特性來嘗試達到 Singleton 的實現(跟 JavaScript 類似的方法)
以 src/net/server/AuthServer.as
為例子,我們會看到像這樣的建構子:
public function AuthServer(caller:Function = null)
{
if( caller != createInstance ) throw new ArgumentError("Cannot user access constructor.");
_command = new AuthCommand(this);
player.addEventListener(Player.AUTH_START,loginHandler); // 接続時
player.addEventListener(Player.REGIST_START,registHandler); // 接続時
}
簡單來說就是檢查如果不是由 createInstance
去呼叫的話,就會直接拋出錯誤來終止執行。
至於 createInstance
做的事情就很簡單,只是單純的新增物件實例。
private static function createInstance():AuthServer
{
return new AuthServer(arguments.callee);
}
大多數 Singleton 物件都會透過定義 instance
這個靜態方法來提供唯一的存取介面,因此 AuthServer
以及其他 Server 類型物件也都做了這樣的定義。
public static function get instance():AuthServer
{
if( __instance == null ){
__instance = createInstance();
}
return __instance;
}
在客戶端除了 Server 系列之外,各類 Controller 和一些 UI 元件也都會定義成 Singleton 的形式。
透過這樣的方式,我們就可以確保玩家在同一個客戶端連線的狀況下只會跟伺服器建立一次連線,除非斷線不然都會使用同一個連線來處理所有相關的操作。
舉例來說玩家連上大廳(Lobby)伺服器的情況,不應該出現玩家同時連上兩個大廳如果沒有使用 Singleton 的話就可能因為程式執行錯誤同時連上,進而引發一些預期外的狀況(像是 Race Condition 等)造成遊戲上的異常。
這算是很基本的 Singleton 應用情境,不過 Singleton 在遊戲中經常會出現要跨場景、物件需要管理狀態的情況,在這種時候就能夠體現出 Singleton 物件的好處。
通常這類物件都會是 Manager (管理)的角色,像是 PlayerManager (玩家管理器)可以負責管理玩家的狀態,而遊戲中則是透過管理器提取玩家的數值、狀態等資訊,這樣的設計在這幾年 Unity 提到的 ECS 架構上也可以看到類似的思考方式。
我的個人部落格是弦而時習之平常會把自己發現的一些新技巧紀錄在上面,也歡迎大家來逛逛。