透過示例在 Unity 和 NodeJS 上的遊戲中建立安全、快速的多人遊戲

aow054發表於2024-09-26
介紹規劃多人遊戲開發方法 - 在整個專案的進一步開發中發揮著最重要的作用之一,因為它包含了我們在建立真正高質量的產品時應該考慮的許多標準。在今天的宣言教程中,我們將看一個方法示例,該方法使我們能夠建立真正快速的遊戲,同時尊重所有安全和反違規規則。所以,讓我們定義我們的主要標準:多人遊戲需要一種特殊的方法來管理網路同步,尤其是在實時情況下。 二進位制協議用於加速客戶端之間的資料同步,反應欄位將有助於以最小的延遲和節省記憶體來更新玩家位置。伺服器許可權是一項重要原則,關鍵資料僅在伺服器上處理,確保遊戲完整性並防止作弊。然而,為了讓我們最大限度地提高效能 - 伺服器只進行關鍵更新,剩下的交給客戶端反作弊。實施客戶端反投訴,以便在伺服器上不增加負載的情況下處理不太關鍵的資料。 架構的主要組成部分客戶端(unity):客戶端負責顯示遊戲狀態,將玩家操作傳送到伺服器並從伺服器接收更新。這裡還使用反應欄位來動態更新玩家位置。伺服器端(node.js):伺服器處理關鍵資料(例如,移動、碰撞和玩家動作)並將更新傳送到所有連線的客戶端。非關鍵資料可以在客戶端上處理並使用伺服器轉發到其他客戶端。二進位制協議:二進位制資料序列化用於減少傳輸的資料量並提高效能。同步:提供客戶端之間資料的快速同步,以最大程度地減少延遲並確保流暢的遊戲體驗。客戶端反作弊:它用於我們可以在客戶端上更改併傳送給其他客戶端的資料。 第 1 步:在 node.js 中實現伺服器首先,您需要在 node.js 上設定一個伺服器。伺服器將負責所有關鍵計算並將更新的資料傳輸給玩家。安裝環境要在 node.js 上建立伺服器,請安裝必要的依賴項:mkdir multiplayer-game-servercd multiplayer-game-servernpm init -ynpm install socket.io登入後複製socket.io可以輕鬆地使用web套接字實現客戶端和伺服器之間的實時雙向通訊。基本伺服器實現讓我們建立一個簡單的伺服器,它將處理客戶端連線、檢索資料、計算關鍵狀態並在所有客戶端之間同步它們。// create a simple socket io serverconst io = require('socket.io')(3000, { cors: { origin: '*' }});// simple example of game stateslet gamestate = {};let playerspeedconfig = { maxx: 1, maxy: 1, maxz: 1};// work with new connectionio.on('connection', (socket) =&gt; { console.log('player connected:', socket.id); // initialize player state for socket id gamestate[socket.id] = { x: 0, y: 0, z: 0 }; // work with simple player command for movement socket.on('playermove', (data) =&gt; { const { id, dx, dy, dz } = parseplayermove(data); // check maximal values if(dx &gt; playerspeedconfig.maxx) dx = playerspeedconfig.maxx; if(dy &gt; playerspeedconfig.maxy) dx = playerspeedconfig.maxy; if(dz &gt; playerspeedconfig.maxz) dx = playerspeedconfig.maxz; // update game state for current player gamestate[id].x += dx; gamestate[id].y += dy; gamestate[id].z += dz; // send new state for all clients const updateddata = serializegamestate(gamestate); io.emit('gamestateupdate', updateddata); }); // work with unsafe data socket.on('dataupdate', (data) =&gt; { const { id, unsafe } = parseplayerunsafe(data); // update game state for current player gamestate[id].unsafevalue += unsafe; // send new state for all clients const updateddata = serializegamestate(gamestate); io.emit('gamestateupdate', updateddata); }); // work with player disconnection socket.on('disconnect', () =&gt; { console.log('player disconnected:', socket.id); delete gamestate[socket.id]; });});// simple parse our binary datafunction parseplayermove(buffer) { const id = buffer.tostring('utf8', 0, 16); // player id (16 bit) const dx = buffer.readfloatle(16); // delta x const dy = buffer.readfloatle(20); // delta y const dz = buffer.readfloatle(24); // delta z return { id, dx, dy, dz };}// simple parse of unsafe datafunction parseplayerunsafe(buffer) { const id = buffer.tostring('utf8', 0, 16); // player id (16 bit) const unsafe = buffer.readfloatle(16); // unsafe float return { id, unsafe };}// simple game state serialization for binary protocolfunction serializegamestate(gamestate) { const buffers = []; for (const [id, data] of object.entries(gamestate)) { // player id const idbuffer = buffer.from(id, 'utf8'); // position (critical) buffer const posbuffer = buffer.alloc(12); posbuffer.writefloatle(data.x, 0); posbuffer.writefloatle(data.y, 4); posbuffer.writefloatle(data.z, 8); // unsafe data buffer const unsafebuffer = buffer.alloc(4); unsafebuffer.writefloatle(data.unsafevalue, 0); // join all buffers buffers.push(buffer.concat([idbuffer, posbuffer, unsafebuffer])); } return buffer.concat(buffers);}登入後複製此伺服器執行以下操作:處理客戶端連線。接收二進位制格式的玩家移動資料,驗證它,更新伺服器上的狀態並將其傳送到所有客戶端。以最小延遲同步遊戲狀態,使用二進位制格式來減少資料量。簡單地轉發來自客戶端的不安全資料。要點:伺服器許可權:所有重要資料均在伺服器上處理和儲存。客戶端僅傳送操作命令(例如,位置變化增量)。二進位制資料傳輸:使用二進位制協議可以節省流量並提高網路效能,特別是對於頻繁的實時資料交換。 第2步:在unity上實現客戶端部分現在讓我們在 unity 上建立一個與伺服器互動的客戶端部分。要將 unity 連線到 socket.io 上的伺服器,您需要連線專為 unity 設計的庫。在這種情況下,我們不受任何特定實現的約束(事實上它們都是相似的),而只是使用一個抽象示例。使用反應欄位進行同步我們將使用反應欄位來更新玩家位置。這將使我們能夠更新狀態,而無需透過 update() 方法檢查每個幀中的資料。當資料狀態發生變化時,反應欄位會自動更新遊戲中物件的視覺表示。要獲得反應性屬性功能,您可以使用 unirx。unity 上的客戶端程式碼讓我們建立一個指令碼來連線到伺服器、傳送資料並透過反應欄位接收更新。using UnityEngine;using SocketIOClient;using UniRx;using System;using System.Text;// Basic Game Client Implementationpublic class GameClient : MonoBehaviour{ // SocketIO Based Client private SocketIO client; // Our Player Reactive Position public ReactiveProperty<vector3> playerPosition = new ReactiveProperty<vector3>(Vector3.zero); // Client Initialization private void Start() { // Connect to our server client = new SocketIO("http://localhost:3000"); // Add Client Events client.OnConnected += OnConnected; // On Connected client.On("gameStateUpdate", OnGameStateUpdate); // On Game State Changed // Connect to Socket Async client.ConnectAsync(); // Subscribe to our player position changed playerPosition.Subscribe(newPosition =&gt; { // Here you can interpolate your position instead // to get smooth movement at large ping transform.position = newPosition; }); // Add Movement Commands Observable.EveryUpdate().Where(_ =&gt; Input.GetKey(KeyCode.W)).Subscribe(_ =&gt; ProcessInput(true)); Observable.EveryUpdate().Where(_ =&gt; Input.GetKey(KeyCode.S)).Subscribe(_ =&gt; ProcessInput(false)); } // On Player Connected private async void OnConnected(object sender, EventArgs e) { Debug.Log("Connected to server!"); } // On Game State Update private void OnGameStateUpdate(SocketIOResponse response) { // Get our binary data byte[] data = response.GetValue<byte>(); // Work with binary data int offset = 0; while (offset <h2> 第 3 步:最佳化同步和效能</h2><p><strong>為了確保流暢的遊戲體驗並最大程度地減少同步期間的延遲,建議:</strong></p><ol><li><strong>使用插值:</strong>客戶端可以使用插值來平滑伺服器更新之間的移動。這可以補償較小的網路延遲。</li><li><strong>批次資料傳送:</strong>不要按每一步傳送資料,而是使用批次傳送。例如,每隔幾毫秒傳送一次更新,這將減少網路負載。 </li><li><strong>降低更新頻率:</strong>將傳送資料的頻率降低到合理的最低限度。例如,對於大多數遊戲來說,每秒更新 20-30 次可能就足夠了。</li></ol><h2> 如何簡化二進位制協議的使用?</h2><p>為了簡化您使用二進位制協議的工作 - 建立資料處理的基本原理以及與其互動的方案。</p><p><strong>對於我們的示例,我們可以採用一個基本協議,其中:</strong><br>1) 前 4 位是使用者發出的請求的最大值(例如 0 - 移動玩家,1 - 射擊等);<br>2) 接下來的 16 位是我們客戶的 id。<br>3) 接下來我們填充透過迴圈傳遞的資料(一些網路變數),其中儲存變數的 id、到下一個變數開頭的偏移量(以位元組為單位)、變數的型別和它的價值。</p><p>為了方便版本和資料控制 - 我們可以以方便的格式(json / xml)建立客戶端-伺服器通訊模式,並從伺服器下載一次,以便根據該模式進一步解析我們的二進位制資料以獲得所需的內容我們的 api 版本。</p><h2> 客戶端反作弊</h2><p>在伺服器上處理所有資料是沒有意義的,其中一些資料更容易在客戶端修改併傳送到其他客戶端。</p><p>為了讓你在這個方案中更加安全 - 你可以使用客戶端防駭客系統來防止記憶體駭客 - 例如,我的 gameshield - 一個免費的開源解決方案。</p><h2> 結論</h2><p>我們舉了一個簡單的例子,在 unity 上使用 node.js 伺服器開發多人遊戲,所有關鍵資料都在伺服器上處理,以確保遊戲的完整性。使用二進位制協議傳輸資料有助於最佳化流量,而 unity 中的反應式程式設計可以輕鬆同步客戶端狀態,而無需使用 update() 方法。</p><p>這種方法不僅可以提高遊戲效能,還可以透過確保所有關鍵計算都在伺服器而不是客戶端上執行來增強對作弊的保護。</p><p><strong>當然,一如既往地感謝您閱讀這篇文章。如果您在組織多人專案架構方面仍有任何疑問或需要幫助 - 我邀請您加入我的 discord</strong></p><hr><p><strong>您還可以在我的困境中為我提供很多幫助,並支援釋出新文章以及為開發人員免費提供的庫和資源:</strong></p><p><strong>我的不和諧</strong> | <strong>我的部落格</strong> | <strong>我的 <a style="color:#f60; text-decoration:underline;" href="https://www.php.cn/zt/15841.html" target="_blank">git</a>hub</strong></p><p><strong>btc:</strong> bc1qef2d34r4xkrm48zknjdjt7c0ea92ay9m2a7q55<br><br><strong>eth:</strong> 0x1112a2ef850711df4de9c432376f255f416ef5d0<br><strong><a style="color:#f60; text-decoration:underline;" href="https://www.php.cn/zt/94398.html" target="_blank">usdt</a> (trc20):</strong>trf7sli6trtnau6k3pvvy61bzqkhxdcrlc</p> </byte></vector3></vector3>登入後複製以上就是透過示例在 Unity 和 NodeJS 上的遊戲中建立安全、快速的多人遊戲的詳細內容,更多請關注我的其它相關文章!

相關文章