「対戦・協力プレイを付けたい」——マルチプレイヤーは魅力的ですが、シングルプレイとは根本的に違う難しさがあります。プレイヤーごとに別のマシンでゲームが動いており、誰かが動くたびに全員の画面で同じ世界が見えるように同期し続けなければなりません。
この複雑な通信と同期を引き受けてくれるのがネットワーキングソリューションで、Unity公式の現行版が Netcode for GameObjects です(旧UNetの後継。ほかにPhotonなどサードパーティ製も有名です)。GameObjectとコンポーネントの普段のワークフローに統合されているため、比較的スムーズに始められます。
この記事でわかること
- マルチプレイヤーの土台——サーバーオーソリティという考え方
- NetworkManager・NetworkObject・NetworkTransformの役割
- 変数の同期——NetworkVariable<T>
- 処理の依頼——RPC(ServerRpc / ClientRpc)
- 実践:2人で動き回れる最小構成を組む
サーバーとクライアント
マルチプレイヤーでまず必要なのは、「誰の言い分を正とするか」の決定者です。もし各プレイヤーの端末が勝手に「攻撃が当たった」「自分は無傷だ」と主張したら、世界の整合性は崩壊します。
そこで、ゲーム状態の決定権(権威)を サーバー(Server) が一手に持ち、各プレイヤーは クライアント(Client) としてサーバーに接続して決定に従う——これが サーバーオーソリティ(Server Authority) です。クライアントは「攻撃したい」とお願いを送るだけで、当たったかどうかを判定するのはサーバー。チート防止と一貫性のための標準アーキテクチャです。

Netcode for GameObjectsの起動モードは3つです。
- Server: サーバー専用。ゲーム管理に専念します(専用サーバー構成)。
- Client: サーバーに接続して遊ぶ側。
- Host: サーバー+クライアントの一人二役。プレイヤーの1人がサーバーを兼ねる構成で、小規模ゲームや開発中のテストで最もよく使います。
NetworkManagerとトランスポート層
中心となるコンポーネントがNetworkManagerです。セッションの管理・接続・ネットワークオブジェクトの生成を担うシングルトンで、シーンに1つ配置します。
実際のデータ送受信はトランスポート(Transport) 層が行います。標準のUnity Transportでローカル開発からリレー接続まで対応できます。
NetworkObjectとNetworkTransform
- NetworkObject: ネットワーク上で同期したいオブジェクト(プレイヤー・敵・動く床)に付けます。ネットワーク全体で一意なIDが割り当てられ、同期の管理対象になります。
- NetworkTransform: 位置・回転・スケールを自動同期したい場合に追加します。標準は サーバー権限(Authority Mode: Server) で、サーバー側で動かした結果が全クライアントに配られます。プレイヤーのように 所有クライアントが直接動かしたいオブジェクトは、InspectorでAuthority Modeを
Ownerに 変更します(後述の実践で使います)。
「プレハブにNetworkObject+NetworkTransformを付けて、NetworkManagerのPrefabsリストに登録する」が同期オブジェクトの基本セットアップです。
NetworkVariable:変数の同期
体力・スコア・弾薬数のような値の同期にはNetworkVariable<T>を使います。
using Unity.Netcode;
using UnityEngine;
public class PlayerHealth : NetworkBehaviour
{
// サーバーだけが書き込める体力変数(全員が読める)
public NetworkVariable<int> CurrentHealth = new NetworkVariable<int>(
100, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server);
public override void OnNetworkSpawn()
{
// 値が変わった瞬間に呼ばれる(クライアント側のUI更新などに)
CurrentHealth.OnValueChanged += OnHealthChanged;
}
private void OnHealthChanged(int previousValue, int newValue)
{
Debug.Log($"Health: {previousValue} -> {newValue}");
// 体力バーUIの更新など
}
// サーバー側でのみ実行するダメージ処理
public void TakeDamage(int amount)
{
if (!IsServer) return; // サーバー以外では何もしない
CurrentHealth.Value -= amount; // 書き換えれば自動で全クライアントに同期される
}
}
サーバーが.Valueを書き換えるだけで全クライアントに自動同期され、受け取り側はOnValueChangedで変更を検知します。「サーバーが書き、クライアントは購読する」 がNetworkVariableの基本形です。
RPC(Remote Procedure Call)
値の同期ではなく、「この処理を向こうのマシンで実行してほしい」という依頼がRPCです。
[ServerRpc]: クライアント→サーバーへの依頼。「攻撃ボタンを押した」などの入力をサーバーに伝えます。[ClientRpc]: サーバー→全クライアントへの命令。エフェクト再生・サウンドなど「全員の画面で同時に起きてほしい」ことに使います。
using Unity.Netcode;
using UnityEngine;
public class PlayerActions : NetworkBehaviour
{
// クライアント側: 攻撃ボタンで呼ばれる
public void RequestAttack()
{
AttackServerRpc(); // サーバーに実行を依頼
}
[ServerRpc] // クライアント → サーバー
private void AttackServerRpc()
{
// ここはサーバーでのみ実行される。当たり判定などの「判定」はここで行う
bool hit = CheckHit();
if (hit)
{
PlayHitEffectClientRpc(); // 結果の「見た目」を全員に配る
}
}
[ClientRpc] // サーバー → 全クライアント
private void PlayHitEffectClientRpc()
{
// ここは全クライアントで実行される。エフェクトやSEの再生など
}
private bool CheckHit() => true; // 実際はRaycast等で判定
}

流れを1行でまとめると、 「入力はServerRpcで上げ、判定はサーバーで行い、結果の見た目はClientRpcで配る」。この往復がサーバーオーソリティの基本パターンです。
実践:2人で動き回れる最小構成を組む
協力パズルの2人プレイ、対戦アクションの1vs1、ソーシャルゲームのロビー——どんなマルチプレイヤーも、最初の一歩は同じです。「2つのウィンドウで、お互いのキャラクターが動いて見える」。この記事の部品だけで、その最小構成を組みます。

シーンの準備:
- Package Managerから「Netcode for GameObjects」をインストールし、空のGameObjectに
NetworkManagerを追加(TransportはUnity Transportを選択) - プレイヤーのPrefabを作り、
NetworkObjectとNetworkTransformを追加。 NetworkTransformのAuthority ModeをOwnerに変更(プレイヤーは所有クライアントが直接動かすため) NetworkManagerのPlayer Prefab欄にこのPrefabを設定——これで接続した人数分、自動でプレイヤーが生成されます
移動スクリプト——ここに最重要ポイントのIsOwnerが登場します。
using Unity.Netcode;
using UnityEngine;
public class NetworkPlayerMove : NetworkBehaviour
{
[SerializeField] private float speed = 5f;
void Update()
{
// 自分が操作権を持つキャラクターだけを動かす。
// これが無いと、キー入力で「全員の」キャラが動いてしまう
if (!IsOwner) return;
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
transform.Translate(new Vector3(h, 0, v) * speed * Time.deltaTime);
// ※このコードが動くのは、NetworkTransformのAuthority ModeがOwnerのとき
}
}
Authority Modeを忘れずに:
NetworkTransformの標準は サーバー権限 です。そのままだと、Host以外のクライアントがtransformを動かしても サーバーの値で上書きされて動けない/同期されない という定番のハマりどころになります。プレイヤーPrefabのように所有者が直接動かすものはOwner権限に、敵・NPC・動く床のようにサーバーが動かすものはServer権限のまま、と使い分けます。なおNetcode 1.x系にはAuthority Modeの項目が無いため、NetworkTransformを継承してOnIsServerAuthoritative()でfalseを返すClientNetworkTransformを使うのが同じ役割の定番でした。
動作確認: Unity 6なら「Window > Multiplayer Play Mode」で仮想プレイヤーを1人有効にし、片方のNetworkManagerのインスペクタ(またはデバッグUI)でHostを起動、もう片方でClientを起動します。片方のウィンドウでキャラを動かすと、もう片方の画面でも同じキャラが動く——これがあなたの最初のマルチプレイヤーです。
ポイントは2つです。 「操作の入口では必ずIsOwnerを確認する」(マルチプレイヤーのコードは「このコードは誰のマシンの、誰のキャラで動いているのか」を常に意識します。IsOwner忘れは全員が同時に動く定番バグです)と、 「同期はNetworkTransformに任せて、自分は入力だけ書く」(位置を送る通信コードは1行も書いていないことに注目してください)。ここから体力の同期(NetworkVariable)や攻撃(RPC)へ、本文の部品を足していけます。
おまけ:先に知っておくと良いこと
- Multiplayer Play Mode: Unity 6では、エディタ1つで複数プレイヤーの動作確認ができる公式パッケージ「Multiplayer Play Mode」が使えます。ビルドせずにHost+Clientのテストができ、開発効率が大きく変わります。
- 新しい[Rpc]属性: 近年のNetcodeでは
[Rpc(SendTo.Server)]/[Rpc(SendTo.ClientsAndHost)]という統一形式の属性も導入されています。本文のServerRpc/ClientRpcはそのまま使えますが、新しいドキュメントやサンプルで見かけたら同じ役割のものと読み替えてください。 - インターネット越しの接続: 家庭のルーター越しに直接つなぐのは困難なため、実際のオンライン対戦 にはUnity Gaming ServicesのRelay(中継)とLobby(部屋探し)を組み合わせるのが定番構成です。
- ラグは設計で隠す: 通信には必ず遅延があります。クライアント側で先に動かして見せる「クライアント予測」など、遅延を体感させない技術が本格的なマルチプレイヤーの次のステップです。
まとめ
- マルチプレイヤーの土台はサーバーオーソリティ。決定権はサーバー、クライアントはお願いを送る。
- NetworkManagerがセッション管理、NetworkObjectが同期対象の印、NetworkTransformが位置の自動同期。
- 値の同期は
NetworkVariable<T>(サーバーが書き、クライアントが購読)。 - 処理の依頼はRPC——入力は
[ServerRpc]で上げ、演出は[ClientRpc]で配る。 - 操作コードの入口は必ず
IsOwnerで守る。同期はNetworkTransformに任せる(プレイヤーPrefabは Authority Mode: Owner に)。
ネットワークは奥の深い分野ですが、この4点+実践の最小構成を押さえれば、最初のプロトタイプまでは一直線です。お互いの画面でキャラクターが動いた瞬間の感動——あなたのゲームにその瞬間が来るのは、いつですか?