Block not found: "hero"
Programming/Technical Mar 19, 2025

Game Networking - From Local to Multiplayer

Master game networking from local multiplayer to online multiplayer. Learn client-server architecture, synchronization, lag compensation, and networking best practices for Unity and other game engines.

By GamineAI Team

Why Game Networking Matters

Multiplayer games dominate the industry. From battle royales to cooperative adventures, players crave social gaming experiences. Understanding networking fundamentals opens doors to creating games that bring people together.

The Challenge: Networked games must handle latency, synchronization, and connection issues while maintaining smooth gameplay. A poorly implemented networking system can ruin even the best game design.

The Opportunity: Well-implemented multiplayer creates replayability, community, and long-term engagement that single-player games struggle to match.

Understanding Network Architectures

Client-Server Architecture

Most modern multiplayer games use a client-server model where one authoritative server manages game state and clients send input to the server.

How It Works:

  • Server: Authoritative game state, validates all actions
  • Clients: Send input, receive state updates
  • Benefits: Prevents cheating, easier to manage, scalable
  • Drawbacks: Requires server infrastructure, latency concerns

Unity Netcode Example:

using Unity.Netcode;
using UnityEngine;

public class NetworkPlayer : NetworkBehaviour
{
    private NetworkVariable<Vector3> position = new NetworkVariable<Vector3>();
    private NetworkVariable<float> health = new NetworkVariable<float>(100f);

    public override void OnNetworkSpawn()
    {
        if (IsOwner)
        {
            // This client owns this player
            SetupLocalPlayer();
        }
        else
        {
            // This is a remote player
            SetupRemotePlayer();
        }
    }

    [ServerRpc]
    public void MovePlayerServerRpc(Vector3 newPosition)
    {
        // Server validates and updates position
        if (IsValidPosition(newPosition))
        {
            position.Value = newPosition;
        }
    }

    [ClientRpc]
    public void TakeDamageClientRpc(float damage)
    {
        // All clients receive damage update
        health.Value -= damage;
        UpdateHealthUI();
    }

    private void SetupLocalPlayer()
    {
        // Enable input, camera, etc.
        GetComponent<PlayerController>().enabled = true;
        Camera.main.transform.SetParent(transform);
    }

    private void SetupRemotePlayer()
    {
        // Disable local input, use network position
        GetComponent<PlayerController>().enabled = false;
    }
}

Peer-to-Peer Architecture

In peer-to-peer, all players connect directly to each other without a central server.

How It Works:

  • Peers: Each player hosts their own game state
  • Synchronization: Players exchange state directly
  • Benefits: No server costs, lower latency for small groups
  • Drawbacks: Vulnerable to cheating, harder to scale, connection issues

When to Use:

  • Small player counts (2-4 players)
  • Local multiplayer games
  • Prototyping and testing
  • Games where cheating isn't critical

Local Multiplayer Implementation

Split-Screen Setup

Split-screen is the simplest form of multiplayer, perfect for couch co-op games.

Unity Split-Screen Example:

using UnityEngine;

public class SplitScreenManager : MonoBehaviour
{
    public Camera player1Camera;
    public Camera player2Camera;
    public int playerCount = 2;

    void Start()
    {
        SetupSplitScreen();
    }

    void SetupSplitScreen()
    {
        if (playerCount == 2)
        {
            // Two-player split screen
            player1Camera.rect = new Rect(0f, 0.5f, 1f, 0.5f); // Top half
            player2Camera.rect = new Rect(0f, 0f, 1f, 0.5f);   // Bottom half
        }
        else if (playerCount == 4)
        {
            // Four-player split screen
            player1Camera.rect = new Rect(0f, 0.5f, 0.5f, 0.5f);     // Top-left
            player2Camera.rect = new Rect(0.5f, 0.5f, 0.5f, 0.5f);   // Top-right
            player3Camera.rect = new Rect(0f, 0f, 0.5f, 0.5f);       // Bottom-left
            player4Camera.rect = new Rect(0.5f, 0f, 0.5f, 0.5f);     // Bottom-right
        }
    }
}

Input Management for Multiple Players

Unity Input System:

using UnityEngine;
using UnityEngine.InputSystem;

public class MultiplayerInput : MonoBehaviour
{
    public PlayerInput[] players;

    void Start()
    {
        // Create input for each player
        for (int i = 0; i < players.Length; i++)
        {
            players[i] = PlayerInput.Instantiate(
                playerPrefab, 
                playerIndex: i, 
                controlScheme: "Gamepad",
                pairWithDevice: Gamepad.all[i]
            );
        }
    }
}

Online Multiplayer Fundamentals

Network Synchronization

Synchronizing game state across the network is critical. You need to decide what to sync and how often.

Synchronization Strategies:

1. State Synchronization

  • Server sends complete state to clients
  • Simple but bandwidth-intensive
  • Good for small games or turn-based

2. Delta Compression

  • Only send changes since last update
  • Reduces bandwidth significantly
  • More complex implementation

3. Input Synchronization

  • Clients send input, server simulates
  • Deterministic simulation required
  • Most common for action games

Unity Netcode State Sync:

using Unity.Netcode;
using UnityEngine;

public class NetworkedObject : NetworkBehaviour
{
    // Network variables automatically sync
    private NetworkVariable<Vector3> networkPosition = 
        new NetworkVariable<Vector3>(default, 
            NetworkVariableReadPermission.Everyone, 
            NetworkVariableWritePermission.Server);

    private NetworkVariable<Quaternion> networkRotation = 
        new NetworkVariable<Quaternion>(default,
            NetworkVariableReadPermission.Everyone,
            NetworkVariableWritePermission.Server);

    void Update()
    {
        if (IsServer)
        {
            // Server updates position
            networkPosition.Value = transform.position;
            networkRotation.Value = transform.rotation;
        }
        else
        {
            // Clients interpolate to network position
            transform.position = Vector3.Lerp(
                transform.position, 
                networkPosition.Value, 
                Time.deltaTime * 10f
            );
            transform.rotation = Quaternion.Lerp(
                transform.rotation, 
                networkRotation.Value, 
                Time.deltaTime * 10f
            );
        }
    }
}

Lag Compensation

Network latency causes delays between player actions and their effects. Lag compensation techniques make games feel responsive despite network delays.

Client-Side Prediction:

using Unity.Netcode;
using UnityEngine;
using System.Collections.Generic;

public class LagCompensatedPlayer : NetworkBehaviour
{
    private Vector3 serverPosition;
    private Vector3 predictedPosition;
    private Queue<InputState> inputHistory = new Queue<InputState>();

    void Update()
    {
        if (IsOwner)
        {
            // Client predicts movement immediately
            Vector3 input = GetInput();
            predictedPosition += input * Time.deltaTime;
            transform.position = predictedPosition;

            // Store input for reconciliation
            inputHistory.Enqueue(new InputState
            {
                position = predictedPosition,
                timestamp = Time.time,
                input = input
            });

            // Send input to server
            MoveServerRpc(input);
        }
    }

    [ServerRpc]
    void MoveServerRpc(Vector3 input)
    {
        // Server validates and updates
        serverPosition += input * Time.deltaTime;
        UpdatePositionClientRpc(serverPosition);
    }

    [ClientRpc]
    void UpdatePositionClientRpc(Vector3 serverPos)
    {
        if (IsOwner)
        {
            // Reconcile: adjust if server position differs
            float error = Vector3.Distance(predictedPosition, serverPos);
            if (error > 0.1f)
            {
                // Snap to server position and replay inputs
                predictedPosition = serverPos;
                ReplayInputs();
            }
        }
        else
        {
            // Remote players just use server position
            transform.position = serverPos;
        }
    }

    void ReplayInputs()
    {
        // Replay recent inputs from corrected position
        foreach (var inputState in inputHistory)
        {
            predictedPosition += inputState.input * Time.fixedDeltaTime;
        }
    }
}

Server Rewind (Hit Detection):

using Unity.Netcode;
using UnityEngine;
using System.Collections.Generic;

public class ServerRewind : NetworkBehaviour
{
    private Dictionary<float, Vector3> positionHistory = new Dictionary<float, Vector3>();

    void Update()
    {
        if (IsServer)
        {
            // Store position history
            positionHistory[Time.time] = transform.position;

            // Clean old history (keep last 1 second)
            float cutoff = Time.time - 1f;
            var keysToRemove = new List<float>();
            foreach (var key in positionHistory.Keys)
            {
                if (key < cutoff)
                    keysToRemove.Add(key);
            }
            foreach (var key in keysToRemove)
                positionHistory.Remove(key);
        }
    }

    [ServerRpc]
    public void ShootServerRpc(Vector3 shootPosition, Vector3 direction, float clientTime)
    {
        // Rewind to when client shot
        float serverTime = Time.time;
        float latency = serverTime - clientTime;
        float rewindTime = serverTime - latency;

        // Find closest historical position
        Vector3 rewindPosition = GetPositionAtTime(rewindTime);

        // Perform hit detection at rewind position
        RaycastHit hit;
        if (Physics.Raycast(rewindPosition, direction, out hit))
        {
            // Hit detected, apply damage
            if (hit.collider.CompareTag("Player"))
            {
                hit.collider.GetComponent<Health>().TakeDamage(10f);
            }
        }
    }

    Vector3 GetPositionAtTime(float time)
    {
        // Find closest time in history
        float closestTime = 0f;
        float minDiff = float.MaxValue;

        foreach (var key in positionHistory.Keys)
        {
            float diff = Mathf.Abs(key - time);
            if (diff < minDiff)
            {
                minDiff = diff;
                closestTime = key;
            }
        }

        return positionHistory.ContainsKey(closestTime) 
            ? positionHistory[closestTime] 
            : transform.position;
    }
}

Network Optimization

Bandwidth Optimization

Network bandwidth is limited. Optimize what you send and how often.

Optimization Techniques:

1. Reduce Update Frequency

// Update position every 0.1 seconds instead of every frame
private float lastUpdateTime = 0f;
private float updateInterval = 0.1f;

void Update()
{
    if (Time.time - lastUpdateTime > updateInterval)
    {
        SendPositionUpdate();
        lastUpdateTime = Time.time;
    }
}

2. Only Send Changes

private Vector3 lastSentPosition;
private float positionThreshold = 0.1f;

void Update()
{
    if (Vector3.Distance(transform.position, lastSentPosition) > positionThreshold)
    {
        SendPositionUpdate();
        lastSentPosition = transform.position;
    }
}

3. Compress Data

// Use half precision for positions (saves bandwidth)
public struct CompressedPosition
{
    public ushort x, y, z; // 16-bit instead of 32-bit float

    public Vector3 ToVector3()
    {
        return new Vector3(x / 100f, y / 100f, z / 100f);
    }

    public static CompressedPosition FromVector3(Vector3 pos)
    {
        return new CompressedPosition
        {
            x = (ushort)(pos.x * 100f),
            y = (ushort)(pos.y * 100f),
            z = (ushort)(pos.z * 100f)
        };
    }
}

Interpolation and Extrapolation

Smooth movement despite network delays requires interpolation and extrapolation.

Interpolation:

public class NetworkInterpolation : MonoBehaviour
{
    private Vector3[] positionBuffer = new Vector3[10];
    private float[] timeBuffer = new float[10];
    private int bufferIndex = 0;

    public void ReceivePositionUpdate(Vector3 position, float serverTime)
    {
        positionBuffer[bufferIndex] = position;
        timeBuffer[bufferIndex] = serverTime;
        bufferIndex = (bufferIndex + 1) % positionBuffer.Length;
    }

    void Update()
    {
        // Interpolate to position from 100ms ago (account for latency)
        float targetTime = Time.time - 0.1f;
        Vector3 interpolatedPos = InterpolatePosition(targetTime);
        transform.position = interpolatedPos;
    }

    Vector3 InterpolatePosition(float targetTime)
    {
        // Find two positions to interpolate between
        for (int i = 0; i < timeBuffer.Length - 1; i++)
        {
            if (timeBuffer[i] <= targetTime && timeBuffer[i + 1] >= targetTime)
            {
                float t = (targetTime - timeBuffer[i]) / 
                         (timeBuffer[i + 1] - timeBuffer[i]);
                return Vector3.Lerp(positionBuffer[i], positionBuffer[i + 1], t);
            }
        }

        // Fallback: extrapolate from latest position
        return ExtrapolatePosition(targetTime);
    }

    Vector3 ExtrapolatePosition(float targetTime)
    {
        // Estimate position based on velocity
        if (positionBuffer.Length < 2)
            return transform.position;

        Vector3 velocity = (positionBuffer[1] - positionBuffer[0]) / 
                          (timeBuffer[1] - timeBuffer[0]);
        float timeDiff = targetTime - timeBuffer[0];

        return positionBuffer[0] + velocity * timeDiff;
    }
}

Common Networking Patterns

Authority and Ownership

Understanding who controls what is crucial for multiplayer games.

Ownership Patterns:

Server Authority:

  • Server owns all game objects
  • Clients send input, server decides
  • Prevents cheating, standard for competitive games

Client Authority:

  • Client owns their player object
  • Server validates and broadcasts
  • Lower latency, used in some action games

Hybrid Authority:

  • Server owns important objects (players, projectiles)
  • Clients own cosmetic objects (effects, particles)
  • Balance between performance and security

Replication

Replication determines what data gets sent to which clients.

Replication Strategies:

1. Full Replication

  • All clients receive all data
  • Simple but bandwidth-intensive
  • Good for small player counts

2. Relevance Filtering

  • Only send data to nearby clients
  • Reduces bandwidth significantly
  • Essential for large-scale games

3. Interest Management

  • Clients only receive relevant updates
  • Based on distance, visibility, importance
  • Most efficient for large games

Testing and Debugging

Network Testing Tools

Unity Netcode Testing:

using Unity.Netcode;
using UnityEngine;

public class NetworkTestManager : MonoBehaviour
{
    void Start()
    {
        // Simulate network conditions
        NetworkManager.Singleton.NetworkConfig.NetworkTransport = 
            new Unity.Netcode.Transports.UTP.UnityTransport();

        // Add latency simulation
        var transport = NetworkManager.Singleton.NetworkConfig.NetworkTransport 
            as Unity.Netcode.Transports.UTP.UnityTransport;
        if (transport != null)
        {
            // Simulate 100ms latency
            // Note: Actual implementation depends on transport
        }
    }

    void OnGUI()
    {
        if (NetworkManager.Singleton != null)
        {
            GUILayout.Label($"Connected Clients: {NetworkManager.Singleton.ConnectedClients.Count}");
            GUILayout.Label($"Is Server: {NetworkManager.Singleton.IsServer}");
            GUILayout.Label($"Is Client: {NetworkManager.Singleton.IsClient}");
        }
    }
}

Common Networking Issues

Issue 1: Desynchronization

  • Symptoms: Players see different game states
  • Causes: Non-deterministic code, timing differences
  • Solution: Use fixed timestep, deterministic physics, validate on server

Issue 2: Lag Spikes

  • Symptoms: Sudden jumps in player positions
  • Causes: Network congestion, packet loss
  • Solution: Implement interpolation, buffer network updates, handle disconnections

Issue 3: Cheating

  • Symptoms: Impossible player actions, modified game state
  • Causes: Client authority without validation
  • Solution: Server-side validation, authoritative server, anti-cheat systems

Best Practices

Performance Optimization

1. Minimize Network Calls

  • Batch updates together
  • Use delta compression
  • Only send essential data

2. Optimize Update Frequency

  • Adjust based on object importance
  • Use adaptive update rates
  • Reduce frequency for distant objects

3. Use Object Pooling

  • Reuse network objects
  • Reduce allocation overhead
  • Improve garbage collection

Security Considerations

1. Server Validation

  • Always validate on server
  • Never trust client data
  • Check for impossible actions

2. Encryption

  • Encrypt sensitive data
  • Use secure connections (TLS)
  • Protect player information

3. Anti-Cheat

  • Server-side validation
  • Rate limiting
  • Behavior analysis

Putting It All Together

Building multiplayer games requires understanding networking fundamentals, choosing the right architecture, and implementing proper synchronization and lag compensation. Start with local multiplayer to learn the basics, then move to online multiplayer with a client-server architecture.

The key is to start simple and iterate. Begin with basic state synchronization, add interpolation for smoothness, implement client-side prediction for responsiveness, and optimize for bandwidth as you scale.

Remember that networking is complex, but modern game engines provide excellent tools. Unity Netcode, Unreal's networking, and other frameworks handle much of the heavy lifting. Focus on game design and let the frameworks handle the networking details.

Next Steps

Ready to build your first multiplayer game? Start with local split-screen to understand multiplayer concepts, then move to online networking with Unity Netcode or your engine's networking solution.

For more networking tutorials, check out our complete guide to Unity game development or explore our game development resources for additional tools and learning materials.

Want to see networking in action? Try our AI Game Builder to experiment with different networking patterns and see how they impact gameplay.


Frequently Asked Questions

What's the difference between client-server and peer-to-peer?

Client-server uses a central authoritative server that all clients connect to. Peer-to-peer has players connect directly to each other. Client-server is more secure and scalable, while peer-to-peer is simpler for small groups.

How do I handle lag in multiplayer games?

Use client-side prediction for immediate feedback, server-side validation for accuracy, and interpolation/extrapolation for smooth movement. Lag compensation techniques like server rewind help with hit detection.

What's the best networking solution for Unity?

Unity Netcode for GameObjects (formerly MLAPI) is the official solution and works well for most games. Mirror Networking is a popular alternative with more features. Choose based on your specific needs.

How much bandwidth does multiplayer gaming require?

It depends on your game. Simple turn-based games might use 1-5 KB/s per player. Action games with many players can use 50-100 KB/s per player. Optimize by reducing update frequency and only sending essential data.

Can I make multiplayer games without a server?

Yes, for local multiplayer or small peer-to-peer games. For online multiplayer with many players, you'll need a server. Cloud services like Unity Gaming Services, Photon, or custom servers can host your game.