使用 Socket.io 設定多人遊戲 | hyperPad Documentation
Logo
Documentation教學使用 Socket.io 設定多人遊戲

使用 Socket.io 設定多人遊戲

第 1 部分:簡介

建議先備知識

本教學將說明如何架設伺服器並將遊戲連線至伺服器,讓你透過 Socket.io 行為為專案啟用多人遊戲功能。我們會介紹 hyperPad 的進階功能與行為,因此強烈建議你先閱讀其他一些教學,並熟悉這套軟體,再繼續進行。

本教學假設你熟悉 hyperPad 的核心功能,並對程式撰寫有基本認識,因為我們將著重於使用 Javascript 指令碼建立遊戲伺服器。本教學也假設你對網路概念,以及伺服器與用戶端之間的差異有基本了解。

本教學的主要目標是教你如何為遊戲加入多人遊戲功能,而不是建立遊戲本身。我們將探討 Socket.io 用戶端與伺服器之間的關係,以及它們如何透過 Socket.io 行為互相通訊。

需求

若要建立並架設伺服器,你需要一台能執行 Node.JS 的電腦(Mac、Windows 等)。本教學會說明如何安裝 Node.JS 並為此設定。如果你要在家用網路中架設伺服器,若要讓外部網路連入,通常需要變更閘道器的連接埠轉送設定。

本教學使用的簡易示範遊戲已經建好。遊戲中有兩名玩家在小型迷宮裡玩鬼抓人。我們會探討範例專案如何使用 Socket.io 行為,因此建議你下載專案並在 hyperPad 中開啟。

完成版的 hyperPad 範例專案可在此下載:Multiplayer tag tutorial.tap

概覽

本教學會介紹為遊戲建立伺服器的基本知識。伺服器會處理遊戲中的大部分細節,例如計分、遊戲大廳等。接著,我們會建立行為,在遊戲與伺服器之間互相傳送資訊。

以下是遊戲的大致流程:

1. 在主選單中,我們會連線至伺服器,並讓使用者選擇建立或加入遊戲。

a. 若要建立遊戲,則載入等候室。

b. 若要加入遊戲,則載入可用遊戲房間清單。

c. 當兩名玩家進入等候室後,遊戲便會開始。

2. 在遊戲中,伺服器會將玩家隨機放置在四個區域之一,並指定其中一人為「鬼」。

a. 抓到另一名玩家時,雙方位置會重新隨機配置,鬼的身分會交換,抓到人的玩家會獲得一分。

b. 若在指定時間內沒有人被抓到,伺服器會扣除目前鬼玩家一分,交換鬼的身分,然後再次將玩家隨機放到出生區域。

3. 玩家達到指定分數後,遊戲會載入一個宣告勝利者的覆蓋畫面。接著,玩家會離開房間並返回主選單。

第 2 部分:設定伺服器

建立 Socket.io 伺服器需要執行使用 Socket.io 函式庫的 Javascript 應用程式,並監聽玩家連線。Socket.io 是一套 Javascript 網路函式庫,可為我們簡化建立網路應用程式時許多底層細節。後續會進一步說明。

下載並安裝 Node.JS

首先,前往 Node.JS,並在你要用來架設伺服器的電腦(Mac、Windows 等)上下載。下載完成後,執行安裝程式並依照指示操作。安裝過程中的選項都可以維持預設值。Node.JS 讓我們能夠在不使用網頁瀏覽器的情況下,直接執行 Javascript 應用程式。

下載並執行範例伺服器

接著,從 GitHub 下載本教學的多人遊戲伺服器範例:

https://github.com/hyperPad/multiplayerServerExample

按一下「Clone or download」按鈕,並選擇「Download ZIP」。這會下載一份伺服器範例程式碼。

使用 Socket.io 設定多人遊戲

解壓縮下載的 ZIP 檔。裡面會有幾個小型檔案,其中最重要的是「index.js」檔案,也就是伺服器的程式碼。接著,在伺服器範例資料夾中開啟命令列/終端機。

輸入「npm install」並按下 Enter,然後等待執行完成。npm 是套件管理工具,會讀取 package.json 檔案並下載伺服器所需的套件(包括 Socket.io!)。

使用 Socket.io 設定多人遊戲

指令執行完成後,執行伺服器所需的一切都已準備就緒。在終端機中輸入「node .」,伺服器便會啟動。

使用 Socket.io 設定多人遊戲

完成了!伺服器現在正在連接埠 3000 上監聽 Socket.io 連線。

「node .」會在目前目錄中啟動 Node.JS,並尋找該目錄的索引檔案來執行。預設情況下,這個檔案是「index.js」Javascript 檔案,伺服器的大部分程式碼都位於其中,本教學將逐步分析這些程式碼。

請保持終端機開啟,因為關閉它也會關閉伺服器。當玩家連線或中斷連線、房間建立或銷毀,以及發生其他事件時,你會看到終端機列出相關訊息。

**注意:**若要允許來自家用網路外部的連線,你通常需要在家用閘道器上開啟連接埠 3000。各網路的設定方式不盡相同,但通常可以透過網路搜尋家用數據機/路由器的連接埠轉送指南找到說明。變更連接埠轉送設定後,你可能需要關閉並重新啟動 Node.JS 伺服器。

第 3 部分:連線至伺服器

伺服器基本設定完成後,就可以在 hyperPad 遊戲中連線至伺服器。無論目前在哪個場景,都應將這個設定設為專案啟動時首先執行的動作(因為所有內容都需要與伺服器通訊)。最好將此行為附加到全域圖層上的物件,讓它在所有場景中都能生效。

使用 Socket.io 設定多人遊戲

在範例專案中,全域圖層上的「Server」標籤包含上述內容。(專案下載連結位於本教學第 1 部分的「需求」一節。)

只需這兩個行為,就能連線至伺服器。首先,切換到自訂分頁,找到 Socket.io Client 行為並拖曳至畫布。在它的屬性視窗中,切換到 URL 分頁並輸入伺服器網址。上圖中的伺服器網址是「http://192.168.0.191:3000」,包括通訊協定和連接埠。請將它改成你的伺服器網址,否則啟動遊戲時很可能無法連線。

現在已設定好伺服器資訊,但還需要連線至伺服器。因此,只要加入 Connect to Socket 行為,開啟其屬性,在空白欄位中選取用戶端,並將 Function 屬性設為「Connect」即可。

如此一來,專案載入時就會自動連線至伺服器。

使用 Socket.io 設定多人遊戲

第 4 部分:建立與加入房間

接下來要建立遊戲大廳,讓玩家加入並一起遊玩。

使用 Socket.io 設定多人遊戲

這是簡單的主選單畫面。玩家只要點選其中一個按鈕,就能建立房間或尋找可加入的房間。

建立房間

來看看如何與伺服器通訊並建立房間。

使用 Socket.io 設定多人遊戲

很簡單吧?簡單來說,按下按鈕後,系統會提示我們輸入房間名稱。輸入完成後,房間名稱會傳送至伺服器,由伺服器建立房間,接著我們會載入等候室場景。

接下來我們會經常使用 Emit to Socket,因為它是將資料傳送至伺服器的主要方式。你可以把它簡單想成「線上版」的 Broadcast Message 和 Receive Message(Emit to Socket 會同時執行這兩項操作)。

現在,我們需要在伺服器中加入一些資訊,讓伺服器收到 Emit 行為傳來的訊息後建立房間。

使用 Socket.io 設定多人遊戲

socket.on('createRoom', (roomName, callback) => {

這一行會在伺服器上建立 Socket 事件(以 lambda 運算式表示),監聽事件名稱為「createRoom」的 Emit。

第一個參數是透過 Emit to Socket 行為傳入的值,在此例中就是使用者輸入的房間名稱。這裡將參數命名為「roomName」。

第二個參數是稍後會在 Socket 事件中呼叫的函式,用來通知用戶端。只有在伺服器上呼叫 callback 函式後,Emit to Socket 行為才會繼續執行。這裡將此參數命名為「callback」。

const room = {
id: uuid(),
name: roomName,
sockets: []
};

這會建立一個結構實例,包含房間的重要資訊。

「id」是由工具函式「uuid()」產生的唯一識別碼。稍後可以用它在眾多已建立的房間中明確識別這個房間。

「name」會設定為使用者稍早輸入的名稱。

「sockets」會初始化為空陣列。之後會用它記錄連線至該房間的玩家 Socket。

rooms[room.id] = room;

「rooms」是目前啟用中房間的全域清單。由於我們正在建立新房間,因此會以房間 ID 為索引,將房間存入清單。

joinRoom(socket, room);

這會呼叫全域函式「joinRoom」(第 29 行),將玩家的 Socket 加入房間的「sockets」陣列。由於建立房間的玩家也要加入該房間,因此這個呼叫也會讓玩家加入房間。

callback();
});

最後,我們呼叫 callback 函式,通知用戶端房間已建立。這會讓 Emit to Socket 繼續執行,接著載入等候室場景。這也代表「createRoom」Socket 事件結束。

加入房間

加入房間的方式稍有不同,因為我們需要從按鈕所在的場景存取另一個場景中的資訊。

使用 Socket.io 設定多人遊戲

加入房間時,我們將行為的起始部分放在全域圖層上,與連線至伺服器的行為放在一起。如此一來,就能在任何場景中存取資訊;在此例中,我們需要存取房間清單。至於按鈕,只要點選它,就會將玩家載入房間清單場景。

這裡是第二個用來與伺服器通訊的行為。Socket Event 可以視為 Receive Message,只有在伺服器廣播指定訊息後才會啟動。

使用 Socket Event 和 Emit to Socket 時,最好記住:Socket Event 只會對來自伺服器的資訊做出反應,而 Emit to Socket 則會因本機執行的動作而呼叫。

至於操作邏輯,連線至伺服器後,我們會 Emit「getRoomNames」請求,以取得所有可用房間的名稱。

使用 Socket.io 設定多人遊戲

接著,我們會設定一個玩家可以點選以加入房間的標籤。

使用 Socket.io 設定多人遊戲

這是房間清單場景,可在此載入並顯示所有可用的遊戲房間。只要點選房間名稱,玩家就會進入該房間。由於前面設定好的行為,清單會自動載入所有開放中的房間,並在畫面上產生房間名稱標籤。如果沒有顯示任何房間,可以按「Refresh List」按鈕,重新執行這些行為來載入房間。

使用 Socket.io 設定多人遊戲 使用 Socket.io 設定多人遊戲

這裡是設定清單的邏輯。由於我們只需要了解它如何連線至伺服器,因此不會在此深入說明所有細節。

首先,我們使用 Emit to Socket 行為,要求伺服器傳送可用房間的資訊。接著使用 Get Array Value 行為。伺服器傳來的所有資料都會以陣列形式傳送,而所需資訊會位於前面的元素中。因此,我們將 Get Array Value 設定為取得索引 0 的值。之後,其他行為會擷取資料,並為每個房間建立標籤,將它們產生在畫面上。

接下來,我們要檢查場景中需要點選的物件,也就是名為 Room Name 的標籤。

使用 Socket.io 設定多人遊戲

這段文字產生後會作為按鈕使用,但我們仍需要附上想要加入的房間 ID。為此,首先要取得房間 ID,並使用 Get Attribute 行為將其設為動態值。接著,向伺服器 Emit 加入此房間的請求,並載入等候室。

使用 Socket.io 設定多人遊戲

socket.on('joinRoom', (roomId, callback) => {

這是「joinRoom」Socket 事件的進入點。第一個參數的值就是我們想加入、並已 Emit 至伺服器的房間 ID。這裡將參數命名為「roomId」。

const room = rooms[roomId];
joinRoom(socket, room);

使用用戶端傳來的 roomId,我們可以在伺服器上找到正確的房間實例。接著呼叫全域函式「joinRoom」(第 29 行),並傳入想要連線的玩家 Socket,以及房間實例本身。我們稍後會查看「joinRoom」函式。

callback();
});

最後,我們呼叫 callback 函式,通知用戶端繼續載入等候室場景,並結束「joinRoom」Socket 事件。

那麼,「joinRoom」函式到底在做什麼?一起來看看。

使用 Socket.io 設定多人遊戲

room.socket.push(socket);

如前所述,「room.socket」成員陣列會記錄連線至房間的 Socket。這一行會將 Socket 推入陣列中,完成這項工作。

socket.join(room.id, () => {
socket.roomId = room.id;
console.log(socket.id, "Joined", room.id);
});

這是正式將用戶端連線至房間的呼叫。首先,我們會讓 Socket 依房間 ID 加入房間。完成後,就會呼叫後面的 callback,將房間 ID 附加至 Socket。最後,我們會在主控台記錄玩家已加入房間!

使用 Socket.io 設定多人遊戲

等候室

等候室只是一個場景,玩家會先載入至此,等待其他玩家加入或遊戲開始。

使用 Socket.io 設定多人遊戲

進入房間後,我們會向伺服器 Emit「ready」訊息。伺服器收到兩則訊息後,就會傳送遊戲即將開始的訊息(「initGame」)。我們會透過 Socket Event 接收此訊息,並載入遊戲關卡。行為設定大致就是這些。如果你願意,也可以新增按鈕,讓自己離開房間並返回主選單。

接著來分析伺服器端的程式碼,看看它在做什麼。

使用 Socket.io 設定多人遊戲

這是用戶端加入房間並準備連線時呼叫的「ready」事件。

const room = rooms[socket.roomId];

由於我們已將房間 ID 附加至 Socket,因此可以取得房間,並確認遊戲是否可以開始。

if (room.sockets.length == 2) {

這裡會檢查房間中是否已有兩名玩家等待。假設條件成立,我們就繼續開始遊戲。

for (const client of room.sockets) {
client.emit('initGame');
}

現在房間裡有兩名玩家,我們會逐一走訪每個 Socket,並 Emit「initGame」事件,讓每個用戶端載入前面提到的 Level 場景。

第 5 部分:遊戲玩法

現在來談談核心內容。這裡會完成我們 90% 的工作。以下是為本教學設計的遊戲關卡。

使用 Socket.io 設定多人遊戲

不過,在開始之前,我們先看看名為「Game Logic」的標籤。開啟它來查看內容。

使用 Socket.io 設定多人遊戲

哇,行為真不少!不必擔心,這些只是用來將玩家產生到遊戲中的設定。我們仔細看看:

使用 Socket.io 設定多人遊戲

我們先使用 Emit to Socket 行為,透過「startGame」事件告知伺服器遊戲已開始。接著取得伺服器回傳的陣列,取出第一個值,再使用 Get Dictionary Value 擷取物件所需的各項屬性。另一組獨立的行為樹會執行相同的操作,但對象是對手玩家。最後,我們會廣播「init」訊息給玩家物件,讓遊戲開始。

接下來看看 Emit「startGame」事件時,伺服器端會發生什麼事。

使用 Socket.io 設定多人遊戲

你在這裡看到的 Socket 事件前半段會為每個用戶端設定一些初始值,接著將所有非發送者用戶端加入本機「others」陣列。

使用 Socket.io 設定多人遊戲

後半段會建立一個名為「ack」的本機字典清單,其中包含自己的資訊,以及其他用戶端的資訊。接著,我們會將字典「ack」傳入 callback 函式,把資訊傳回用戶端;它會成為呼叫中的 Emit to Socket 行為的結果值。

之後,系統會呼叫一個 5 秒後執行的計時器,等所有人取得遊玩所需資訊後,才正式開始回合。「beginRound」函式(第 99 行)負責控制此專案的一些遊戲邏輯。我們不會深入說明,但它大致會處理玩家的出生位置、分數檢查,以及通知用戶端誰是鬼。

如前所述,所有內容準備就緒後,「Game Logic」標籤便會呼叫「init」訊息。接下來,我們來查看玩家物件上的行為,以及它如何接收「init」訊息。

使用 Socket.io 設定多人遊戲

你可以看到玩家物件上有幾組行為樹。首先來看左上方的行為樹。

使用 Socket.io 設定多人遊戲

這個行為幾乎負責啟動遊戲中的所有其他功能。

首先,我們接收來自「Game Logic」標籤的「init」訊息。接著取得物件在伺服器上的 ID,啟用其中一個 Socket Event,並設定遊戲畫面,讓鏡頭能準確跟隨角色。

同步移動

使用 Socket.io 設定多人遊戲

這是其中一個最重要的行為。這組小型行為樹會在每次移動搖桿時,更新玩家在伺服器上的位置。你可能也注意到它會參照一些字典值。這些值來自一個獨立的字典行為,其中包含玩家的 X 和 Y 座標。

來看看伺服器上的「moved」事件。

使用 Socket.io 設定多人遊戲

data = JSON.parse(data);

當字典從用戶端傳送至伺服器時,必須先解析,才能輕鬆讀取資料。這是因為 hyperPad 的字典在 Emit 至伺服器時會編碼為 JSON 結構。這一行會解析 JSON 字串結構,並將字典清單存回相同的本機「data」變數中。

socket.x = data.x;
socket.y = data.y;

這裡會使用用戶端傳來的新值,更新儲存在 Socket 上的 X 和 Y 座標。

for (const client of room.sockets) {
if (client == socket) {
continue;
}
client.emit(socket.id, {
x: socket.x,
y: socket.y,
score: socket.score,
isIt: socket.isIt
});
}

接著,我們會逐一走訪所有用戶端,將新的位置及其他資訊傳送給他們,但不包括自己(也就是說,發送訊息的用戶端不需要知道自己的位置)。

使用 Socket.io 設定多人遊戲

這組行為樹負責控制遊戲中的大部分內容。伺服器透過「beginRound」(第 99 行)發出事件,檢查哪位玩家是鬼時,我們會使用 Socket Event 接收訊息。

接著,我們從陣列中取得物件在伺服器上的 ID,並使用 Dictionary Value 將資料拆解成各個部分。然後取得分數、是否為鬼,以及物件的 X 和 Y 座標,並將它們套用至物件屬性。其餘行為則用來設定及控制遊戲中的使用者介面。

結語

本教學涵蓋了不少內容,但如果你讀到這裡,希望你已了解如何運用 Socket.io 行為,為玩家打造多人遊戲體驗。

自己試試看吧!找一款你建立過的遊戲,試著加入線上功能,例如建立一個排行榜場景,連線至伺服器並索取前 10 名分數及玩家名稱清單,再將其顯示出來。

要在一篇文章中教會 Javascript 這類指令碼語言並不容易。所幸,如果你遇到問題,有許多其他資源能協助你撰寫 Node.JS 和 Socket.io 應用程式:

學習 Javascript - https://developer.mozilla.org/bm/docs/Web/JavaScript

學習 Socket.io - https://socket.io/docs/

學習 Node.JS - https://nodejs.org/en/docs/