agentsclimarketplace

Unity multiplayer

Skill IdoCohen560/claude-unity-game-studio/examples/my-first-game/.claude/skills/unity-multiplayer

AI-powered Unity game development studio for Claude Code - 49 agents, 120+ skills, and a full studio hierarchy that turns a single AI session into a coordinated game dev team. View more about me in my portfolio.

Install
npx -y skills add IdoCohen560/claude-unity-game-studio --skill unity-multiplayer

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 8 stars8 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Unity 6 multiplayer and networking guide. Use when building multiplayer games, working with Netcode for GameObjects, NetworkManager, NetworkObject, NetworkBehaviour, RPCs (ServerRpc, ClientRpc), NetworkVariables, or Unity multiplayer services (Relay, Lobby). Covers client-server architecture, state synchronization, and scene management. Based on Unity 6.3 LTS documentation.

SKILL.md

17.3 KB, as published. Nobody here has run it

Unity Multiplayer & Networking

Multiplayer Architecture Overview

Unity's multiplayer ecosystem comprises several layers:

LayerPackage/ServicePurpose
High-LevelNetcode for GameObjectsGameObject-based networking logic
High-LevelNetcode for EntitiesDOTS-based networking
Low-LevelUnity Transport (com.unity.transport)UDP/WebSocket communication with optional reliability, ordering, fragmentation
ServicesRelayNAT traversal via cloud relay servers
ServicesLobbyMatchmaking and session discovery
ServicesSessions SDKPlayer group management
ToolsMultiplayer Play ModeSimulate up to 4 players in-editor
ToolsMultiplayer ToolsAnalysis, debugging, testing utilities

Topology options:

  • Client-Server (Dedicated): Server has authority; clients send inputs, server validates
  • Client-Server (Listen/Host): One player acts as both server and client
  • Distributed Authority: Ownership-based authority distributed among clients

Use the Multiplayer Center (Window > Multiplayer > Multiplayer Center) to get package recommendations based on your game's needs.

Netcode for GameObjects Setup

Installation

Install com.unity.netcode.gameobjects (v2.10+) via Package Manager. This automatically pulls in com.unity.transport.

NetworkManager Configuration

Add a NetworkManager component to a GameObject in your scene. It is the singleton entry point for all networking.

using Unity.Netcode;

public class GameLauncher : MonoBehaviour
{
    public void StartAsHost()
    {
        NetworkManager.Singleton.StartHost();
    }

    public void StartAsServer()
    {
        NetworkManager.Singleton.StartServer();
    }

    public void StartAsClient()
    {
        NetworkManager.Singleton.StartClient();
    }

    public void Shutdown()
    {
        NetworkManager.Singleton.Shutdown();
    }
}

Key NetworkManager properties:

  • IsServer, IsClient, IsHost -- execution context
  • ConnectedClients -- dictionary of connected clients
  • ConnectedClientsIds -- read-only list of client IDs
  • LocalClientId -- local client's ID
  • SceneManager -- NetworkSceneManager instance
  • SpawnManager -- NetworkSpawnManager instance
  • NetworkConfig -- project network configuration

Key NetworkManager events: OnClientConnectedCallback, OnClientDisconnectCallback, OnConnectionEvent, OnServerStarted/OnServerStopped, OnClientStarted/OnClientStopped, OnTransportFailure

NetworkObject and NetworkBehaviour

NetworkObject

Every networked GameObject needs a NetworkObject component. It provides identity, ownership, and visibility.

Key properties:

  • NetworkObjectId (ulong) -- unique ID synchronized across network
  • IsSpawned -- whether spawned on the network
  • OwnerClientId -- client ID of current owner
  • IsOwner -- true if local player owns this object
  • HasAuthority -- true if local instance has authority

Spawning (server-side only):

// Basic spawn
NetworkObject netObj = Instantiate(prefab).GetComponent<NetworkObject>();
netObj.Spawn();

// Spawn with specific owner
netObj.SpawnWithOwnership(clientId);

// Spawn as player object
netObj.SpawnAsPlayerObject(clientId);

// Despawn
netObj.Despawn(destroy: true);

Ownership (server-side only):

netObj.ChangeOwnership(newClientId);
netObj.RemoveOwnership();

Visibility:

netObj.NetworkShow(clientId);
netObj.NetworkHide(clientId);
bool visible = netObj.IsNetworkVisibleTo(clientId);

NetworkBehaviour

All networked scripts inherit from NetworkBehaviour instead of MonoBehaviour.

Lifecycle methods (in order):

  1. OnNetworkPreSpawn(ref NetworkManager) -- before any spawning
  2. OnNetworkSpawn() -- after NetworkObject spawns; register handlers here
  3. OnNetworkPostSpawn() -- after all sibling NetworkBehaviours spawn
  4. OnNetworkPreDespawn() -- before despawn
  5. OnNetworkDespawn() -- on despawn

Ownership callbacks:

  • OnGainedOwnership() / OnLostOwnership()
  • OnOwnershipChanged(ulong previous, ulong current) -- fires on all clients
using Unity.Netcode;

public class PlayerController : NetworkBehaviour
{
    public override void OnNetworkSpawn()
    {
        if (IsOwner)
        {
            // Initialize local player controls
            EnableInput();
        }
    }

    void Update()
    {
        if (!IsOwner) return;
        // Only the owner processes input
        HandleMovement();
    }

    public override void OnNetworkDespawn()
    {
        // Cleanup
    }
}

Status checks: IsServer, IsClient, IsHost, IsOwner, IsSpawned, IsLocalPlayer, HasAuthority

NetworkVariables

NetworkVariable<T> synchronizes state from server to all clients automatically. Type T must be unmanaged (primitives, unmanaged structs).

public class PlayerHealth : NetworkBehaviour
{
    public NetworkVariable<int> Health = new NetworkVariable<int>(
        value: 100,
        readPerm: NetworkVariableReadPermission.Everyone,
        writePerm: NetworkVariableWritePermission.Server
    );

    public override void OnNetworkSpawn()
    {
        Health.OnValueChanged += OnHealthChanged;
    }

    public override void OnNetworkDespawn()
    {
        Health.OnValueChanged -= OnHealthChanged;
    }

    private void OnHealthChanged(int oldValue, int newValue)
    {
        Debug.Log($"Health changed: {oldValue} -> {newValue}");
        UpdateHealthUI(newValue);
    }

    // Server-side only (due to WritePerm.Server)
    public void TakeDamage(int amount)
    {
        if (!IsServer) return;
        Health.Value -= amount;
    }
}

Write permissions:

  • NetworkVariableWritePermission.Server (default) -- only server can write
  • NetworkVariableWritePermission.Owner -- only owner can write

NetworkList<T> -- synchronized list (T must be unmanaged + IEquatable<T>):

public class Inventory : NetworkBehaviour
{
    public NetworkList<int> Items;

    void Awake()
    {
        Items = new NetworkList<int>();
    }

    public override void OnNetworkSpawn()
    {
        Items.OnListChanged += OnItemsChanged;
    }

    private void OnItemsChanged(NetworkListEvent<int> changeEvent)
    {
        Debug.Log($"List changed: {changeEvent.Type}");
    }
}

RPCs (ServerRpc, ClientRpc)

RPCs are remote procedure calls between server and clients. Methods must be in a NetworkBehaviour and use the [Rpc] attribute with a SendTo target.

Unified Rpc Attribute (v2.x)

public class CombatSystem : NetworkBehaviour
{
    // Server executes this when any client calls it
    [Rpc(SendTo.Server)]
    void AttackRpc(int targetId, RpcParams rpcParams = default)
    {
        ulong senderId = rpcParams.Receive.SenderClientId;
        ProcessAttack(senderId, targetId);
    }

    // All clients (and host) execute this
    [Rpc(SendTo.ClientsAndHost)]
    void ShowDamageEffectRpc(Vector3 position, int damage)
    {
        SpawnDamagePopup(position, damage);
    }

    // Only the owner executes this
    [Rpc(SendTo.Owner)]
    void NotifyOwnerRpc(string message)
    {
        Debug.Log(message);
    }

    // Everyone including sender
    [Rpc(SendTo.Everyone)]
    void PlaySoundRpc(int soundId)
    {
        AudioManager.Play(soundId);
    }
}

SendTo Targets

TargetDescription
ServerExecutes on server; locally if called on server
NotServerAll clients except server (excludes host)
OwnerObject's owner only
NotOwnerEveryone except owner
AuthorityServer in client-server; owner in distributed authority
NotAuthorityAll non-authority instances
ClientsAndHostAll clients including host
EveryoneAll instances on the observer list
MeLocal execution only
NotMeEveryone except sender
SpecifiedInParamsTarget set at runtime via RpcSendParams

Legacy Attributes (still supported)

[ServerRpc]
void RequestSpawnServerRpc(ServerRpcParams rpcParams = default)
{
    // Runs on server; only owner can call by default
}

[ClientRpc]
void UpdateUIClientRpc(int score)
{
    // Runs on all clients
}

Connection Approval

Use ConnectionApprovalCallback on NetworkManager to validate connecting clients.

// Server-side: register approval callback
NetworkManager.Singleton.ConnectionApprovalCallback = (request, response) =>
{
    string password = System.Text.Encoding.UTF8.GetString(request.Payload);
    response.Approved = (password == "secret");
    response.CreatePlayerObject = response.Approved;
    // Optionally set: response.PlayerPrefabHash, response.Position, response.Rotation
    if (!response.Approved) response.Reason = "Invalid password";
    response.Pending = false; // Signal decision is made
};

// Client-side: set payload before connecting
NetworkManager.Singleton.NetworkConfig.ConnectionData =
    System.Text.Encoding.UTF8.GetBytes("secret");
NetworkManager.Singleton.StartClient();

Scene Management

NetworkSceneManager (accessed via NetworkManager.Singleton.SceneManager) handles synchronized scene loading.

public class GameSceneManager : NetworkBehaviour
{
    public void LoadGameScene()
    {
        if (!IsServer) return;
        // Server-only: loads scene on all clients
        NetworkManager.Singleton.SceneManager.LoadScene("GameScene", LoadSceneMode.Single);
    }

    public void LoadAdditiveScene()
    {
        if (!IsServer) return;
        NetworkManager.Singleton.SceneManager.LoadScene("Arena", LoadSceneMode.Additive);
    }

    public override void OnNetworkSpawn()
    {
        NetworkManager.Singleton.SceneManager.OnSceneEvent += OnSceneEvent;
    }

    void OnSceneEvent(SceneEvent sceneEvent)
    {
        switch (sceneEvent.SceneEventType)
        {
            case SceneEventType.LoadComplete:
                Debug.Log($"Client {sceneEvent.ClientId} loaded {sceneEvent.SceneName}");
                break;
            case SceneEventType.LoadEventCompleted:
                Debug.Log($"All clients loaded {sceneEvent.SceneName}");
                break;
            case SceneEventType.SynchronizeComplete:
                Debug.Log($"Client {sceneEvent.ClientId} fully synchronized");
                break;
        }
    }
}

Scene event types: OnLoad, OnUnload, OnSynchronize, OnLoadComplete, OnUnloadComplete, OnLoadEventCompleted, OnUnloadEventCompleted, OnSynchronizeComplete

Important: Do NOT start new scene events within scene event callbacks.

Unity Multiplayer Services (Relay, Lobby)

Full integration examples in references/transport-layer.md

Unity Relay

Relay provides NAT punchthrough via cloud relay servers (no port forwarding needed). Requires UGS authentication.

Host flow: Create allocation, get join code, configure transport, start host. Client flow: Join allocation with code, configure transport, start client.

// Host: create relay and start
Allocation allocation = await RelayService.Instance.CreateAllocationAsync(maxPlayers);
string joinCode = await RelayService.Instance.GetJoinCodeAsync(allocation.AllocationId);
transport.SetRelayServerData(allocation.ToRelayServerData("dtls")); // "dtls" = encrypted UDP
NetworkManager.Singleton.StartHost();

// Client: join relay and connect
JoinAllocation join = await RelayService.Instance.JoinAllocationAsync(joinCode);
transport.SetRelayServerData(join.ToRelayServerData("dtls"));
NetworkManager.Singleton.StartClient();

Unity Lobby

Lobby provides session discovery and matchmaking. Store the Relay join code in lobby data.

// Create lobby with relay join code
_lobby = await LobbyService.Instance.CreateLobbyAsync(name, maxPlayers, new CreateLobbyOptions {
    Data = new Dictionary<string, DataObject> {
        { "JoinCode", new DataObject(DataObject.VisibilityOptions.Member, relayJoinCode) }
    }
});

// Query available lobbies
QueryResponse response = await Lobbies.Instance.QueryLobbiesAsync(new QueryLobbiesOptions {
    Filters = new List<QueryFilter> {
        new QueryFilter(QueryFilter.FieldOptions.AvailableSlots, "0", QueryFilter.OpOptions.GT)
    }
});

// Join and extract relay code
Lobby lobby = await LobbyService.Instance.JoinLobbyByIdAsync(lobbyId);
string joinCode = lobby.Data["JoinCode"].Value;

// IMPORTANT: Send heartbeats every 15s or lobby expires
await LobbyService.Instance.SendHeartbeatPingAsync(lobby.Id);

Common Patterns

Player Spawning with Custom Prefab (Server-Side)

// In a NetworkBehaviour on the server:
NetworkManager.Singleton.OnClientConnectedCallback += (ulong clientId) => {
    GameObject player = Instantiate(playerPrefab);
    player.GetComponent<NetworkObject>().SpawnAsPlayerObject(clientId);
};

Owner-Authoritative Movement

public class PlayerMovement : NetworkBehaviour
{
    public NetworkVariable<Vector3> Position = new(writePerm: NetworkVariableWritePermission.Owner);
    void Update()
    {
        if (!IsOwner) return;
        // Note: Uses legacy Input for brevity. See unity-input for the new Input System.
        Vector3 move = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
        transform.position += move * Time.deltaTime * 5f;
        Position.Value = transform.position;
    }
}

Server-Authoritative with Input RPCs

public class ServerAuthMovement : NetworkBehaviour
{
    [Rpc(SendTo.Server)]
    void MoveRpc(Vector3 input) { transform.position += input * Time.deltaTime * 5f; }
    void Update()
    {
        if (!IsOwner) return;
        MoveRpc(new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"))); // legacy Input; see unity-input
    }
}

Anti-Patterns

  1. Writing to NetworkVariable without authority -- Only the server (or owner with WritePerm.Owner) can write. Client writes are silently ignored.

  2. Forgetting IsOwner checks in Update -- Without owner checks, all clients run input logic, causing conflicting state.

  3. Using Instantiate without Spawn -- Objects created with Instantiate alone are local-only. Always call Spawn() on the NetworkObject for network visibility.

  4. Spawning from client code -- Only the server can spawn NetworkObjects. Clients must send an RPC to request spawning.

  5. Heavy data in RPCs instead of NetworkVariables -- RPCs are fire-and-forget; late joiners miss them. Use NetworkVariables for persistent state.

  6. Not unsubscribing from OnValueChanged -- Subscribe in OnNetworkSpawn, unsubscribe in OnNetworkDespawn to prevent leaks.

  7. Starting scene events inside scene event callbacks -- NetworkSceneManager forbids this; causes undefined behavior.

  8. Not sending Lobby heartbeats -- Lobbies expire without periodic SendHeartbeatPingAsync calls (every 15-30 seconds).

  9. Using NetworkVariable for frequent small updates -- For high-frequency data (position), prefer NetworkTransform or custom serialization.

  10. Calling RPCs before OnNetworkSpawn -- RPCs require the NetworkObject to be spawned. Defer to OnNetworkSpawn.

Key API Quick Reference

ClassKey Members
NetworkManagerStartHost(), StartServer(), StartClient(), Shutdown(), Singleton, ConnectedClients, LocalClientId, SceneManager, ConnectionApprovalCallback
NetworkObjectSpawn(), SpawnWithOwnership(), SpawnAsPlayerObject(), Despawn(), ChangeOwnership(), NetworkShow(), NetworkHide(), NetworkObjectId, OwnerClientId, IsOwner, HasAuthority
NetworkBehaviourOnNetworkSpawn(), OnNetworkDespawn(), OnGainedOwnership(), OnLostOwnership(), IsServer, IsClient, IsHost, IsOwner, IsSpawned, HasAuthority, RpcTarget
NetworkVariable<T>.Value, OnValueChanged, ReadPerm, WritePerm, CheckDirtyState()
NetworkList<T>Add(), Remove(), Insert(), Clear(), Count, OnListChanged
NetworkSceneManagerLoadScene(), UnloadScene(), OnSceneEvent
[Rpc(SendTo.X)]Server, Owner, NotOwner, ClientsAndHost, Everyone, NotMe, Authority, SpecifiedInParams

Related Skills

  • unity-foundations -- Core Unity concepts, GameObjects, components, scene hierarchy
  • unity-scripting -- C# scripting, MonoBehaviour lifecycle, coroutines
  • unity-physics -- Physics systems; use NetworkRigidbody for synced physics

Additional Resources

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.