Photon Server SDK (Part 2) — Game Server

一般如果要寫大型的伺服器端的遊戲應用或是商業邏輯(Server-Side Game Logic), 像是 MMO 這種, 只有單一的而且巨大世界的遊戲類別, 可以直接參考 Photon Server SDK 中的 MMO Application 的架構寫法進行擴展, 瞭解它的框架後, 甚至可以自己開始寫新個完全新的 MMO Server.

如果想開發的是屬於 Room-Based 面向的, 而且基於安全或是須要統一管理或是其它理由, 希望遊戲應用或是商業邏輯可以跑在 Server-Side 的, 那麼, 在目前新版的 Photon Server 4 中, 有下面二種方式可以做到:

  • 參考並繼承 SDK 提供的 Application 做擴展. (Photon 3 僅有這種方式)
  • Photon Plugins 的方式, 直接在 Game/Room 裡設計並提供我們特別客製化的功能.

這二種方式各有相對的優缺點, 我們先來瞭解這二者跟 Game Server 之間有什麼樣的相關性, 之後就可以依照實際的專案須求來選擇適合的運用方式.


Lite(Hive) App 與 Game Server 的相關性

在 Photon SDK 中, Lite(Hive) Application 這個範例設計得很輕巧, 而它主要就是實作出 Room-Based 類型遊戲的應用基礎架構; 在 LoadBalancing Application 裡面的 Game Server 亦是由 Lite(Hive) app 變化擴展而來的, 它主要提供的功能如下:

  • 在每個 Room 中, 能讓一小部份的玩家能夠在同一個地方互動, 而且不會受到其它 Room 的玩家或是一般事件所影響.
  • 每個 Room 會對應到一個 Game, 並且以它的名字作為房間的識別標誌.
  • 每個人都可以加入或是離開這些空間裡(Rooms)的某一個(Room).
  • 連上的 Client 都會有個 ID 做為玩家的識別名稱
  • 可以針對每個 Room 跟玩家做一些設定參數(Properties), 讓新加入的玩家取得後, 方便做些剛進入 Room 時的初始化設定.

要達到上面提供的功能, LiteApp 有定義幾個概念, 參考如下:

Peers

Peer 即是對一個已連上 Server 的玩家的參考. 在 LitePeer Class 中有對 Peer 的封裝與擴展的用法.

當有個 Client 連線連到 Lite App, 那麼 LiteApplication.CreatePeer 會建立對應的 LitePeer, 之後從該 Client 傳來的要求(operations) 都會是由相對應的這個 LitePeer 來處理.

在 Lite App 中, 每個 LitePeer 會有個 RoomReference 做為狀態指示. 每個玩家同一時間只能在任一個 Room 之中. 所以在 Join Room 的時侯, 這個狀態指示會被建立. 當 peer 斷線時, LitePeer 會呼叫 OnDisconnect; 若 peer 仍在某個空間內 (Room), 它就可以移除掉這個 peer.

Rooms

在 Lite App 中, 每個 peer 進入 room 後就一起在裡面互相傳資料互動, 即是開始遊戲. 每個遊戲有唯一的名稱, 而且是完全的獨立於其它的遊戲. 每個從玩家來的要求(request), 都會依序被處理. 而因為所有的多個的遊戲空間是平行存在, 每個請求也都幾乎是即時(ticks)即處理完, 所以這種方式很方便以後的架構擴展.

每個遊戲空間 room 裡面會記錄著在該空間裡的玩家們的列表, 而且會在 Join / Leave / Disconnect 時更新資訊, 並且玩家們互相間也會傳送這些 Events. 在 Room 中, 玩家們是以 Actor 來表示, 每個 Actor 有個數值 ActorNumber, 所以事件(event)的傳送時, 會用這個數值來識別是誰發送的.

Operations & Events

Lite Application 中已定義的一些 Operation:

  • Join : 依指定的名稱可以進入任何的 Room. 若指定的名稱不存在則會直接建立個新的 Room. 若 Peer 之前已加入過其它的房間, 那新的 Join 會先自行做離開之前所在的 Room 的動作.
  • Leave : 離開 Room. 但仍與 Server 保有連線.
  • RaiseEvent : 告之 Room 要傳送 Event 給其它的使用者. 事件可以帶有 EventCode 事件代碼, 以及一些資料.
  • GetProperties : 在 Lite App 中, Room / Player 可以設定的一些參數, 用它來取出.
  • SetProperties : 在 Lite App 中, Room / Player 可以設定的一些參數, 用它來存入. Server-Side 裡面不會使用這些 Room / Player 的參數資料.

Lite Application 中已定義的一些 Event:

  • Join : 當 Join Room 發生時, Room 裡的玩者表單會被更新, 所有玩家都會收到這個事件(包含剛加入的玩家也會收到).
  • Leave : 當某個玩家離開 Room, 其它在同個 room 裡的人都會通知.
  • Properties update: 當某個參數( Actor/Room) 被改了, 所有的人都會接收到此事件; 亦即所有的 Client 都是靠它來更新某些參數的新資料.

RaiseEvent / Custom Events

在 Lite app 設計中, 同一空間 Room 的任一玩家可以對其它玩家傳送帶有任意資料的 Event. 當然, 實作時最好都是只傳送小量的變動資料.

像下面, 這個在 client-side 的程式碼, 會把該對應的 player 的位置資訊傳送出去. 直接叫用 RaiseEvent 就會幫忙處理並送出這個事件與資料.

// Raises an event with the position data of this player.
internal void SendEvMove(LitePeer peer)
{
if
(peer == null)
{
return;
}

Hashtable evInfo = new
Hashtable();
evInfo.Add((byte)STATUS_PLAYER_POS_X, (byte)this.posX);
evInfo.Add((byte)STATUS_PLAYER_POS_Y, (byte)this.posY);

//OpRaiseEvent(byte eventCode, Hashtable evData, bool sendReliable, byte channelId, bool encrypt)

peer.OpRaiseEvent(EV_MOVE, evInfo, isSendReliable, (byte)0, Game.RaiseEncrypted);
}

在 LiteApp 中, Server-Side 並不會在傳送的過程中做其它處理, 所以我們只管送出我們想送的任何資料就可以. 上面程式的 EV_MOVE 在 LiteApp 中並不知道其意, 它只是 Client-Side 之間互相知道就好.

在接收端, 上面的程式碼會引起 EventAction 呼叫, 處理事件跟資料如下:

//in Game.cs:
public void EventAction(byte eventCode, Hashtable photonEvent)
{
int actorNr = 0;
if (photonEvent.ContainsKey((byte)LiteEventKey.ActorNr))
{
actorNr = (int) photonEvent[(byte) LiteEventKey.ActorNr];
}

// get the player that raised this event
Player p;
this.Players.TryGetValue(actorNr, out p);

switch (eventCode)
{
case Player.EV_MOVE:
p.SetPosition((Hashtable)photonEvent[(byte)LiteEventKey.Data]);
break;
//[...]
}
}

//in Player.cs:
internal void SetPosition(Hashtable evData)
{
this.posX = (byte)evData[(byte)STATUS_PLAYER_POS_X];
this.posY = (byte)evData[(byte)STATUS_PLAYER_POS_Y];
}

除了 Client 可以互相傳送事件資料外, Server 當然也可以依須要傳送任何事件, 有興趣的話, 還可以參考 HandleRaiseEvent 的用法~

Properties

在 Lite App 中, properties 也是一種以 HashTable 代表的結構, 方便玩家做些資料的設定跟提取. 它通常是附在 Actor 或是 Room 上的. 所以可以用來設定 room 的一些資料, 玩家的名字, 或是其它可以被 Client 取得的資訊.

我們可以用 SetProperties / GetProperties 來存取相關資料. 像玩家在進入 room 時, Join 時就可以設定些 properties 進去. 而且其它玩家就可以立即取得. 但為了避免每個 Join 發生時會重設(覆寫)原本設定的 Room 資料, 只有在建立新的 room 時的 Join 才可以做資料的設定, 其後加入的人的設定是無效的.


Photon Plugins 與 Game Server 的相關性

現在, Photon 4 裡提供了一種新的程式設計方法: Photon Plugins, 我們可以稱之為 “外掛”或是”插件”, 它是被設計來讓我們可以用很方便的方式來達成 Server-Side 的 Game / Room 機能追加; 像是為了實現計分(Scoring), 分數排名(LeadersBoard)之類的小型功能, 就很適合用此種方式.

但如果用的是之前的 Photon 3 版本, 那麼要追加或是擴展伺服器的功能應用, 還是必須透過一般的繼承方式才能做到.

Photon Plugins 只能使用在 Enterprise Cloud 或是 自有主機(Self-Hosted)的 Photon Server v4 上面.

概念

想加入客製化的服務邏輯, 我們必須把我們的程式碼掛接(inject)到 Photon Server 預先定義好的接口 (hooks). 目前 Photon Server 支援的 Game Server 外掛種類, 僅有由 Room 事件所觸發的接口 (例如: OpJoinRoom)

從定義上來說, Photon Plugin 有唯一名稱, 並且要實作那些相對應於事件的 callback (例如: OnJoin). 我們所寫的 plugin 會編譯成 dll 檔, 將其安置到我們的 Photon Server 上, 或是上傳到 Enterprise Cloud 裡.

當每次 room 新建立時, Photon Server 會自動的載入這些設定好的 dll. 而觸發這個 Plugins 生成的 Room 會稱之為 Host. 載入完成的 plugin 就可以直接存取這個 Host, 並且它們有著同樣的生命週期(lifecycle).

基本流程

要與 Photon 掛接起來的機制, 順序如下面六個步驟:

  1. Intercept the hook call
    當 callback 被觸發時, Host 會先把控制權轉移到 plugins 上.
  2. [選項] Alter call info
    在真正要執行 hook call 前, 存取並修改由 client/server 所送出的請求資料(request).
  3. [選項] Inject custom code
    在執行 hook call 前, 先與 host 做一些處理(例如, 發出個 HTTP request, 查詢 room/actor 的情況, 設定計數值 等等).
  4. Process hook call
    真正決定要如何處理執行這個請求. 可參考 “ICallInfo Processing Methods
  5. [選項] Inject custom code
    執行後, 由 client/server 所送出的請求會成為 “as read-only”. 但在處理執行後, plugin 依然可以跟 Host 互相聯絡互動.
  6. Return
    Plugin 返還控制權給 Host.

Webhooks 即是一個很好的 Photon Plugins 範例, Webhooks 1.2 的原始碼有包含在 plugins SDK 中, 所以有興趣的話也可以研究研究~


有沒有發覺, 要開發多人連線程式, 是否都不用看網路的技術文件跟名詞, 幾乎全部都是遊戲中要用的結構及自行定義一些呼叫的 Operation / Events 就好了呢? 這也就是 Photon Engine 的強大之處了, 呵呵~ 😀

所以, 瞭解了 Room-Based 的 Hive App 架構, 除了可以由 LoadBalancing 開始擴展, 寫些 Server-Side 的 Application, 更可以用 Photo Plugins 的方式, 直接在 Photon Server 4 提供的 LoadBalancing App 裡, 快速的掛上我們寫的 Game / Room 的特殊功能, 這就更是 Photon Engine 強大的地方了~ 🎯

我們在隨後的系列文章, 就會先以 Photon Plugins 的方式, 來快速上手看看 Server-Side Game Server 的機能追加唷~ Stay Tuned! 👻😁👾