Skip to content

Message

第 3 章 訊息處理

3-1 場景與訊息

當一群玩家連線到遊戲即時通訊伺服器時,位在同一個遊戲場景內的玩家可以「看見」彼此的行為。此處所謂的「看見」指的是網路訊息的通透性,當玩家傳遞訊息到他所身處的遊戲場景時,此場景中的所有玩家都會收到,不在此場景中的其它玩家則不會收到,也就是說,「遊戲場景」具有分隔網路空間的效用。 就遊戲開發實務來說,玩家在遊戲中可能位於一個場景,也可能同時存在於多個不同的場景,這點和真實世界的人們一時間只能出現在一個場景有所不同。

上圖顯示四種不同用途的遊戲場景,依照遊戲的需求設計而建立的。
例如一開始可以建立一個名為主大廳的場景,玩家進入遊戲時候都會進入主大廳,可用於用於收發全域訊息,例如伺服器10分鐘後維修、攻城戰今天延後,玩家使用廣播器...等等。

接下來建立兩個靜態場景名為草原、火山,A玩家在草原走動,在火山的B玩家不會收到訊息,A玩家使用廣播器後,B玩家可以收到資料,因此如上說明,一個玩家可以身處在多個「網路場景」。

此處是 Online 觀念中的「場景」,與 Unity3D 的「場景」都具有空間分的概念,雖然兩者的名詞相同,但是開發者在程式邏輯中不必將兩者視為一體,可以依照遊戲機制與需求做更有彈性的規劃。

3-2 建立遊戲場景

開發者可以依照需要,另外定義個別的場景,在 SGC API 中,CloudScene 共有三個建構子:

(1) CloudScene(CloudGame game, string sguid)

如果 sguid 代表的是靜態場景,則以 Launch()進入場景。
以下程式是在 CloudGame 呼叫 Launch()之後,等到觸發 OnCompletion 事件後,如果 Launch 成功,表示該靜態場景已被實例化,系統會賦與該實例場景識別代號(sid),存放於 CloudScene 物件的 sid 屬性中:

void OnCompletion(int code, CloudGame game)
{
    if (code == 0)
    {
        string sguid = "....."; // 靜態場景的sguid
        CloudScene ars = new CloudScene(game,sguid);
        ars.onCompletion += SceneLaunchCompletion;
        ars.onMessageIn += SceneMessageIn;
        ars.Launch();
    }
}
void SceneLaunchCompletion(int code, CloudScene scene)
{
    if (code == 0)
    {
        Debug.Log(scene.sid); // 場景實例的sid
    }
}
void SceneMessageIn(string m, int d, CloudScene scene)
{
    // 收到場景廣播訊息
}

如果 sguid 所代表的是動態場景,有以下三種方法建立場景實例:

  • 呼叫 Launch()建立一個新的場景副本,並進入該場景中。 請注意,每次呼叫 Launch 函式所產生的場景副本都是獨一無二的,若要和其它玩家一同在場景中互動,線上其他玩家必須知道這個場景的 sid 識別碼,因此開發者必須透過主大廳或是私人場景將 sid 傳遞給其他玩家。
CloudScene ars = new CloudScene(game,sguid);
ars.onCompletion += SceneLaunchCompletion;
ars.onMessageIn += SceneMessageIn;
ars.Launch();
  • 呼叫 Match(),由 SGC 伺服器配對進入一個已經建立的場景副本,配對順序以先建立的場景副本為優先,直到該場景的玩家數已到開發者設定的上限後才會再配對至下一個。
CloudScene ars = new CloudScene(game,sguid);
ars.onCompletion += SceneLaunchCompletion;
ars.onMessageIn += SceneMessageIn;
ars.Match();

請注意:若場景的玩家數沒有上限,則 SGC 伺服器會自動改採 FairMatch() 方法配對。

  • 呼叫 FairMatch(),依序一個接一個將玩家配對至已建立的場景副本,使用此函式可讓各個場景副本都能公平的配對到玩家。
CloudScene ars = new CloudScene(game,sguid);
ars.onCompletion += SceneLaunchCompletion;
ars.onMessageIn += SceneMessageIn;
ars.FairMatch();

(2) CloudScene(CloudGame game, string sguid, bool isLocked)

用在動態場景,且只能呼叫 Launch()建立場景副本,參數 isLocked 為 true 時,表示這個場景副本不接受伺服器的自動配對,如果有其他玩家使用 Match()FairMatch()進行配對時,這個場景副本會被排除在外,不會被列入配對之中。 如果參數 isLocked 為 false,功能則和第一個建構子完全一樣。

(3) CloudScene(CloudGame game, string sguid, uint sid)

這個建構函式用在動態場景,當場景副本已經建立,只要多一個參數 sid,就可以進入同一場景, 當然,sid 通常不會是自己所建立的場景副本,而是遠端的玩家建立之後,將場景的 sid 傳遞過來後,本地玩家程式再依此 sid 進入同一個場景。

CloudScene ars = new CloudScene(game,sguid,sid);
ars.onCompletion += SceneLaunchCompletion;
ars.onMessageIn += SceneMessageIn;
ars.FairMatch();

3-3 訊息系統

訊息系統是用來進行遊戲控制之用,訊息分為三種:

(1) 私人訊息

SGC 提供連線中的每一個使用者專用的私人場景,指定傳送至此私人場景中的訊息,只有這個私人場景的擁有者可以收到。當訊息傳送至私人場景時,系統將會觸發 CloudGame 物件的 OnPrivteMessageIn 事件,事件函式定義如下:

void OnPrivteMessageIn(string Msg, int delayMilliSecond, CloudGame game)
參數定義和主大廳的 OnMessageIn 事件函式一樣。
傳送私人訊息則是由 CloudGame 物件的 PrivacySend()函式負責,呼叫時必須指定接收者的poid, PrivacySend()的定義如下:
void PrivacySend(string data, uint poid)
由於線上玩家的 poid 只有玩家自己知道,它並不是公開的資料,為了讓線上其它玩家有機會傳送私人訊息給自己,開發者可以將程式設計成當玩家登入後馬上透過主大廳傳送 poid 讓大家知道,也就是廣播登入者資訊。

(2) 特定場景訊息

開發者可以依照遊戲需求在遊戲管理後台新增場景,SGC 也將指派一組SGUID 給新增的場景,然後程式設計師再以此 SGUID 建立 CloudScene 物件。 CloudScene 物件共有三個建構子,稍後的章節裡將會詳細介紹它們使用時機與方法。

CloudScene 物件建立後呼叫 Launch()Match()FairMatch() 等函式,等到觸發OnCompletion()事件後成功進入場景,CloudScene 物件便可開始收發訊息。

傳送訊息由 CloudScene 物件的 Send()函式負責,Send()函式的定義如下:

void Send(string Msg)

另外,接受訊息則是由 OnMessageIn 事件負責,事件函式定義如下:

void OnMessageIn(string Msg, int delayMilliSecond, CloudScene scene)

參數 Msg 是要傳送或接收的訊息;若收發兩端都已打開「封包延遲偵測雷達」,參數 delayMilliSecond會指示訊息可能在多少毫秒前發出的。

不過要特別注意的是 OnMessageIn 事件的第三個參數,它的型別是 CloudScene,這個參數非常重要,如果程式不是採用繼承的方式另外定義自訂的 CloudScene 類別, 當應用程式中需要建立多個場景時,為了讓這些場景物件共用OnMessageIn()函式程式碼,所以必須在事件觸發時,透過這個參數辨識訊息是從那一個場景傳過來的,然後才能進行對應的動作。

(4) 離線廣播訊息

傳送「離線廣播訊息」是透過 CloudGame 物件的 SendOnClose()函式,定義如下:

void SendOnClose(string msg, CloudScene scene)

訊息送出後會儲存在 SGC 伺服器中,等到連線中斷後,訊息才會由伺服器傳送特定場景,讓線上的每個玩家收到。

訊息格式是由程式設計師自行定義,只要能達到讓每個玩家都能知道是誰離線,並進行必要的處理就可以了。

每個玩家只能傳送一個「離線廣播訊息」,如果呼叫 SendOnClose()多次,後面傳送的訊息將會覆寫先前所傳送的,也就是說,只有最後一次呼叫時所傳送的訊息才會在玩家斷線時送出。

如果玩家的離線訊息不想廣播,也可以透過另一個多載函式:

void SendOnClose(string msg, uint poid)

此處的 poid 是特定線上使用者的 poid,例如 DP 的 poid。

3-4 發送遊戲控制訊息

發送訊息的 Send()SendPrivacySend()SendOnClose()等都是非阻塞式函式,呼叫之後會立刻返回,由背景程式將訊息傳送至伺服器。

SGC 訊息的目的是用來控制多人互動遊戲的進行,訊息格式由開發者自行定義,程式設計師可以依照遊戲的需求設計成包含「控制指令」與「指令參數」的字串格式。例如:
"newuser:userid,poid","newuser"代表的是「控制指令」,"userid,poid"則是「指令參數」,「控制指令」與「指令參數」之間以 ' : ' 字元分隔。
「指令參數」中有多個參數,以 ' , ' 字元分隔,此例則是有兩個參數:useridpoid

由於 SGC 訊息封包的限制,請勿傳送超過 650 字元的訊息字串,以免訊息被截斷。另外,訊息越長,轉送時間也就越長,接收方收到後也會花費較多的時間處理,最終會影響多人連線遊戲的控制流暢度,因此開發者在設計訊息格式時盡量簡短。

3-5 訊息接收與處理

CloudGame 物件的 OnMessageIn 與 OnPrivateMessageIn 事件,以及 CloudScene 的 OnMessageIn 事件都是用來接收訊息的事件函式,如果訊息格式像上一節那樣是由「控制指令」與「指令參數」組成,中間以 ' : ' 字元分開,那麼我們可以把初級訊息處理程式寫成這個樣子:

public void OnMessageIn(string msg, int delayMilliSecond, CloudScene Scene)
{
    string[] s = msg.Split(':');
    try
    {
        string cmd = s[0];
        string param = s[1];
        switch (cmd)
        {
            case "cmd1":
                // 處理cmd1的程式碼在此
                break;
            case "cmd2":
                // 處理cmd2的程式碼在此
                break;
            case "cmd3":
                // 處理cmd3的程式碼在此
                break;
        }
    }
    catch (Exception)
    {}
}

由於 SGC API 保證在主執行緒觸發訊息接收事件,因此開發者在設計訊息處理程式時必需注意不可耗時,否則將造成遊戲程式畫面與操作凍結。
耗時作業如阻塞式 I/O 動作等,請務必使用執行緒處理,但是為了避免大量的執行緒生成與消滅, 可以改用執行緒集區(Thread Pool)。

3-6 離開特定場景

特定場景建立後經過一段時間,如果遊戲已經不需要讓本地玩家接收到任何來自此特定場景時,我們可以呼叫 CloudScne.Leave(),此函式的定義如下:
void Leave(OnCallCompletion cb,Object token);

參數 cb 是 callback 函式,開發者必須自行撰寫,它的定義如下:

cb(int code,Object token)
code 為零表示離開場景的作業成功完成,非零則是作業失敗;程式設計師可以依照傳進來的 code參數決定後續的工作。

參數 token用來Leave()與 callback 函式,系統會將呼叫 Leave()時所傳入的 token 參數原封不動的傳給 callback 函式。

由於特定場景的 Send() 函式是在系統的背景中處理,所以如果 Send() 之後馬上呼叫 Leave() ,可能會導致訊息 還沒送出就被刪除,最好的做法是在最後一個 Send() 所送出的訊息被自己收到之後再進行 Leave() 的動作。

一旦呼叫了 CloudScene.Leave()後,CloudScene.sid 就會被系統設定為 0,如果開發者需要用到這個屬性,也請務必事先將它存放到別的變數。 離開特定場景後,如果還有建立新的特定場景的需要,請重新 new CloudScene(),也就是說, 已經執行過 Leave()CloudScene 物件是不能重複使用的。