mcpField
U
unity-multiplayer

unity-multiplayer

github.com/nice-wolf-studio開発4070 回インストール6 週間前に更新
リポジトリ

unity-multiplayer について

unity-multiplayerは、Unityでネットワーク対応ゲームを開発するためのリファレンススキルです。Netcode for GameObjects、ネットワーク構成、状態同期やRPCに加え、Unityのマルチプレイヤーサービスと実装上の注意点を扱います。

主な機能

  1. 01Netcode for GameObjectsの設定
  2. 02ネットワークオブジェクトと所有権管理
  3. 03NetworkVariablesとRPC
  4. 04接続承認とシーン同期
  5. 05RelayとLobbyのワークフロー
  6. 06マルチプレイヤーのアンチパターン指針

活用シーン

  1. 01ホストまたはクライアント・サーバー型ネットワークの設定
  2. 02プレイヤーとゲーム状態の同期
  3. 03ネットワークオブジェクトの所有権とスポーン管理
  4. 04RelayとLobbyを介したプレイヤー接続

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/nice-wolf-studio/unity-claude-skills --skill unity-multiplayer

インストール後、Claude Code で/unity-multiplayerを入力して読み込まれたか確認します。

同梱ファイルreferences/netcode-gameobjects.mdreferences/transport-layer.md

詳細

Unity マルチプレイヤーとネットワーキング

マルチプレイヤーアーキテクチャの概要

Unity のマルチプレイヤーエコシステムはいくつかのレイヤーで構成されています。

レイヤーパッケージ/サービス目的
ハイレベルNetcode for GameObjectsGameObject ベースのネットワーキングロジック
ハイレベルNetcode for EntitiesDOTS ベースのネットワーキング
ローレベルUnity Transport (com.unity.transport)信頼性、順序制御、フラグメンテーションをオプションで利用できる UDP/WebSocket 通信
サービスRelayクラウドリレーサーバー経由の NAT トラバーサル
サービスLobbyマッチメイキングとセッションの検出
サービスSessions SDKプレイヤーグループの管理
ツールMultiplayer Play Modeエディター内で最大 4 人のプレイヤーをシミュレート
ツールMultiplayer Tools分析、デバッグ、テスト用ユーティリティ

トポロジーの選択肢:

  • クライアント/サーバー(専用): サーバーが権限を持ち、クライアントは入力を送信し、サーバーが検証します
  • クライアント/サーバー(リッスン/ホスト): 1 人のプレイヤーがサーバーとクライアントの両方として動作します
  • 分散権限: 所有権ベースの権限をクライアント間で分散します

ゲームのニーズに基づくパッケージの推奨を確認するには、Multiplayer Center(Window > Multiplayer > Multiplayer Center)を使用します。

Netcode for GameObjects のセットアップ

インストール

Package Manager から com.unity.netcode.gameobjects(v2.10+)をインストールします。これにより com.unity.transport も自動的にインストールされます。

NetworkManager の設定

シーン内の GameObject に NetworkManager コンポーネントを追加します。これはすべてのネットワーキングにおけるシングルトンのエントリーポイントです。

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();
    }
}

NetworkManager の主なプロパティ:

  • IsServer、IsClient、IsHost -- 実行コンテキスト
  • ConnectedClients -- 接続中のクライアントのディクショナリ
  • ConnectedClientsIds -- クライアント ID の読み取り専用リスト
  • LocalClientId -- ローカルクライアントの ID
  • SceneManager -- NetworkSceneManager インスタンス
  • SpawnManager -- NetworkSpawnManager インスタンス
  • NetworkConfig -- プロジェクトのネットワーク設定

NetworkManager の主なイベント: OnClientConnectedCallback、OnClientDisconnectCallback、OnConnectionEvent、OnServerStarted/OnServerStopped、OnClientStarted/OnClientStopped、OnTransportFailure

NetworkObject と NetworkBehaviour

NetworkObject

ネットワーク化されたすべての GameObject に NetworkObject コンポーネントが必要です。ID、所有権、可視性を提供します。

主なプロパティ:

  • NetworkObjectId(ulong)-- ネットワーク全体で同期される一意の ID
  • IsSpawned -- ネットワーク上でスポーン済みかどうか
  • OwnerClientId -- 現在の所有者のクライアント ID
  • IsOwner -- ローカルプレイヤーがこのオブジェクトを所有している場合は true
  • HasAuthority -- ローカルインスタンスが権限を持つ場合は true

スポーン(サーバー側のみ):

// 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);

所有権(サーバー側のみ):

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

可視性:

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

ネットワーク化されたすべてのスクリプトは、MonoBehaviour ではなく NetworkBehaviour を継承します。

ライフサイクルメソッド(順序どおり):

  1. OnNetworkPreSpawn(ref NetworkManager) -- スポーン処理の前
  2. OnNetworkSpawn() -- NetworkObject のスポーン後。ここでハンドラーを登録します
  3. OnNetworkPostSpawn() -- すべての兄弟 NetworkBehaviour のスポーン後
  4. OnNetworkPreDespawn() -- デスポーン処理の前
  5. OnNetworkDespawn() -- デスポーン時

所有権コールバック:

  • OnGainedOwnership()/OnLostOwnership()
  • OnOwnershipChanged(ulong previous, ulong current) -- すべてのクライアントで発生します
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
    }
}

状態チェック: IsServer、IsClient、IsHost、IsOwner、IsSpawned、IsLocalPlayer、HasAuthority

NetworkVariables

NetworkVariable<T> は状態をサーバーからすべてのクライアントへ自動的に同期します。型 T は unmanaged(プリミティブ型、アンマネージド構造体)である必要があります。

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;
    }
}

書き込み権限:

  • NetworkVariableWritePermission.Server(デフォルト)-- サーバーのみ書き込み可能
  • NetworkVariableWritePermission.Owner -- 所有者のみ書き込み可能

NetworkList -- 同期リスト(T は 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}");
    }
}

RPC(ServerRpc、ClientRpc)

RPC は、サーバーとクライアント間のリモートプロシージャコールです。メソッドは NetworkBehaviour 内に定義し、送信先として SendTo ターゲットを指定した [Rpc] 属性を使用する必要があります。

統合 Rpc 属性(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 ターゲット
ターゲット説明
Serverサーバーで実行します。サーバー上で呼び出された場合はローカルで実行します
NotServerサーバーを除くすべてのクライアント(ホストを除く)
Ownerオブジェクトの所有者のみ
NotOwner所有者以外の全員
Authorityクライアント・サーバー方式ではサーバー、分散オーソリティ方式では所有者
NotAuthorityオーソリティを持たないすべてのインスタンス
ClientsAndHostホストを含むすべてのクライアント
Everyoneオブザーバーリスト上のすべてのインスタンス
Meローカルでのみ実行
NotMe送信者以外の全員
SpecifiedInParamsRpcSendParams を介して実行時にターゲットを設定
従来の属性(引き続きサポート)
[ServerRpc]
void RequestSpawnServerRpc(ServerRpcParams rpcParams = default)
{
    // Runs on server; only owner can call by default
}

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

接続承認

NetworkManager の ConnectionApprovalCallback を使用して、接続するクライアントを検証します。

// 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();

シーン管理

NetworkSceneManager(NetworkManager.Singleton.SceneManager 経由でアクセス)は、同期されたシーンの読み込みを処理します。

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;
        }
    }
}

シーンイベントの種類: OnLoad、OnUnload、OnSynchronize、OnLoadComplete、OnUnloadComplete、OnLoadEventCompleted、OnUnloadEventCompleted、OnSynchronizeComplete

重要: シーンイベントのコールバック内で新しいシーンイベントを開始しないでください。

Unity Multiplayer Services(Relay、Lobby)

統合例の全容は references/transport-layer.md を参照してください。

Unity Relay

Relay はクラウドリレーサーバーを介して NAT のホールパンチングを実現します(ポートフォワーディングは不要です)。UGS 認証が必要です。

ホストのフロー: アロケーションを作成し、参加コードを取得して、トランスポートを設定し、ホストを開始します。 クライアントのフロー: コードを使用してアロケーションに参加し、トランスポートを設定し、クライアントを開始します。

// 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 はセッションの検索とマッチメイキングを提供します。Relay の参加コードはロビーのデータに保存します。

// 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);

よくあるパターン

カスタム Prefab を使用したプレイヤーのスポーン(サーバー側)
// In a NetworkBehaviour on the server:
NetworkManager.Singleton.OnClientConnectedCallback += (ulong clientId) => {
    GameObject player = Instantiate(playerPrefab);
    player.GetComponent<NetworkObject>().SpawnAsPlayerObject(clientId);
};
オーナー権限による移動
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;
    }
}
入力 RPC を使用したサーバー権限方式
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
    }
}

アンチパターン

  1. 権限なしで NetworkVariable に書き込む -- 書き込めるのはサーバー(または WritePerm.Owner を持つ所有者)のみです。クライアントからの書き込みは通知なく無視されます。

  2. Update で IsOwner チェックを忘れる -- オーナーのチェックがないと、すべてのクライアントで入力処理が実行され、状態の競合が発生します。

  3. Spawn なしで Instantiate を使用する -- Instantiate のみで作成されたオブジェクトはローカルにしか存在しません。ネットワーク上で見えるようにするには、必ず NetworkObject に対して Spawn() を呼び出してください。

  4. クライアントコードからスポーンする -- NetworkObject をスポーンできるのはサーバーのみです。クライアントはスポーンを要求する RPC を送信する必要があります。

  5. NetworkVariable ではなく RPC に大きなデータを含める -- RPC は送信して終わりのため、後から参加したクライアントには届きません。永続的な状態には NetworkVariable を使用してください。

  6. OnValueChanged の購読解除を忘れる -- OnNetworkSpawn で購読し、リークを防ぐために OnNetworkDespawn で購読を解除します。

  7. シーンイベントのコールバック内でシーンイベントを開始する -- NetworkSceneManager で禁止されており、未定義の動作を引き起こします。

  8. Lobby のハートビートを送信しない -- 15~30 秒ごとに SendHeartbeatPingAsync を呼び出さないと、ロビーは期限切れになります。

  9. 頻繁な小規模更新に NetworkVariable を使用する -- 高頻度のデータ(位置など)には、NetworkTransform またはカスタムシリアライゼーションを優先してください。

  10. OnNetworkSpawn より前に RPC を呼び出す -- RPC には NetworkObject がスポーン済みである必要があります。OnNetworkSpawn まで呼び出しを延期してください。

主要 API クイックリファレンス

クラス主なメンバー
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

関連スキル

  • unity-foundations -- Unity の基本概念、GameObject、コンポーネント、シーンの階層構造
  • unity-scripting -- C# スクリプティング、MonoBehaviour のライフサイクル、コルーチン
  • unity-physics -- 物理システム。同期待ちの物理演算には NetworkRigidbody を使用します

その他のリソース

コメント