Belajar WebSocket - Multiplayer Game Basics
Episode 23 of 34

Belajar WebSocket - Multiplayer Game Basics

Episode ini membangun multiplayer game: arsitektur client-server dengan server otoritatif, sinkronisasi state dengan delta compression, input handling, mitigasi latensi dengan client prediction, dan optimasi khusus game.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

Game multiplayer adalah ujian terberat untuk WebSocket: setiap milidetik berarti, cheat harus dicegah, dan pemain di jaringan berbeda harus melihat dunia yang sama. Kegagalan di sini langsung terasa — karakter melompat-lompat, tembakan tidak mengenai sasaran.

Episode 23 membahas multiplayer game basics: arsitektur dengan server otoritatif, sinkronisasi state, pengumpulan input, mitigasi latensi, dan optimasi yang membuat 60 pemain bermain mulus dalam satu world.

Arsitektur Game

Server Otoritatif

Aturan main dijalankan di server, bukan di klien. Klien hanya mengirim input dan menerima state resmi.

Alur game client-server
client input -> server memvalidasi -> server update state -> broadcast

Server otoritatif mencegah cheat: pemain tidak bisa mengirim "saya pindah ke sini" begitu saja. Klien mengirim "tekan tombol kanan", server menghitung ke mana karakter pindah dan mengumumkannya.

Kenapa Bukan P2P Saja

P2P menghilangkan biaya server, tapi menimbulkan masalah: satu pemain yang curang bisa memengaruhi semua orang, dan sinkronisasi antar N pemain jadi N kuadrat koneksi. Untuk game skala kecil, server otoritatif sederhana adalah keseimbangan terbaik.

Sinkronisasi State

Tick Rate dan Snapshot

Game berjalan dalam langkah waktu tetap yang disebut tick.

JSGame loop dengan tick tetap
const TICK_RATE = 20; // 20 tick per detik
let tick = 0;
 
setInterval(() => {
  tick += 1;
  for (const pemain of pemainList) {
    prosesInput(pemain, tick);
    updateFisika(pemain, tick);
  }
  broadcastState(tick);
}, 1000 / TICK_RATE);

TICK_RATE = 20 berarti state dihitung 20 kali per detik dan disiarkan setiap tick. Semua pemain melihat tick yang sama, sehingga dunia berjalan konsisten untuk semua orang.

Delta Compression

Mengirim posisi semua pemain setiap tick membengkak. Kirim hanya yang berubah.

JSKirim delta state
function broadcastState(tick) {
  const delta = [];
 
  for (const pemain of pemainList) {
    if (pemain.posisi !== pemain.posisiTerakhir) {
      delta.push({
        id: pemain.id,
        x: Math.round(pemain.posisi.x * 10) / 10,
        y: Math.round(pemain.posisi.y * 10) / 10,
      });
      pemain.posisiTerakhir = pemain.posisi;
    }
  }
 
  io.emit("state", { tick, delta });
}

Math.round(... * 10) / 10 membulatkan posisi ke satu desimal — cukup untuk visual, jauh lebih kecil daripada bilangan mengambang penuh. Pemain yang diam tidak dikirim sama sekali.

Input Handling

Input Buffer di Server

Input dari klien datang tidak beraturan; server menyangganya lalu memproses per tick.

JSBuffer input per pemain
const buffer = new Map();
 
socket.on("input", (input) => {
  input.tick = socket.tickTerakhir;
  const list = buffer.get(socket.id) || [];
  list.push(input);
  buffer.set(socket.id, list);
});
 
function prosesInput(pemain, tick) {
  const inputs = (buffer.get(pemain.id) || [])
    .filter((i) => i.tick <= tick);
  for (const input of inputs) {
    terapkan(input);
  }
  buffer.delete(pemain.id);
}

Input yang membawa nomor tick memungkinkan server mengurutkannya dengan benar. Validasi tetap di server: kecepatan maksimal, posisi yang masuk akal, dan aksi yang diizinkan per state game.

Mitigasi Latensi

Client Prediction

Tanpa prediksi, karakter pemain terasa meluncur dengan jeda satu latensi. Client prediction menjalankan gerakan di klien lebih dulu, lalu menyesuaikan saat state server tiba.

JSPrediksi di sisi klien
function inputKiri() {
  // jalankan lokal untuk respons instan
  pemainLokal.x -= KECEPATAN / 60;
  kirimKeServer("input:kiri");
}

Klien menggerakkan karakter seketika untuk memberi respons, lalu server mengirimkan state resmi yang menjadi pembanding. Jika state server berbeda dari prediksi, klien mengoreksi — biasanya tanpa terasa karena perbedaannya kecil.

Interpolasi dan Dead Reckoning

Untuk karakter pemain lain, jangan lompat ke posisi baru — interpolasi di antara dua snapshot membuat gerakan mulus. Dead reckoning mengekstrapolasi posisi dari kecepatan terakhir sehingga lawan tetap terlihat bergerak saat paket hilang sesaat.

Optimasi Khusus Game

Interest Management

Pemain tidak perlu melihat seluruh peta.

JSKirim state hanya ke area terdekat
function tetangga(pemain) {
  return pemainList.filter((p) => {
    const dx = p.x - pemain.x;
    const dy = p.y - pemain.y;
    return dx * dx + dy * dy < 40000; // radius 200 unit
  });
}

tetangga(pemain) membatasi broadcast berdasarkan jarak — pola Area of Interest (AOI). Pemain di sisi kiri peta tidak menerima update dari pemain di sisi kanan, mengurangi beban jaringan secara drastis.

Prioritas Update

Tidak semua update setara: posisi pemain yang berduel lebih penting daripada musuh jauh. Beri prioritas per update dan kirim yang paling mendesak saat bandwidth terbatas.

Penutup

Episode 23 menunjukkan rahasia game multiplayer yang terasa mulus: server otoritatif menegakkan aturan, tick membagi waktu, delta dan AOI menghemat jaringan, serta prediksi dan interpolasi menyembunyikan latensi.

Inti yang harus dibawa pulang:

  • Server otoritatif mencegah cheat dengan mengeksekusi aturan di server.
  • Tick rate tetap menjaga semua pemain pada waktu yang sama.
  • Delta compression dan pembulatan mengecilkan ukuran state.
  • Buffer input memungkinkan pemrosesan terurut per tick.
  • Client prediction memberikan respons instan tanpa menunggu server.
  • Area of Interest memangkas update yang tidak relevan.

Di episode 24 berikutnya kita membahas file transfer over WebSocket: transfer ber-chunk, binary data, progress tracking, serta optimasi ukuran chunk dan kompresi.

Belajar WebSocket - Multiplayer Game Basics | Belajar WebSocket