【VRChat】ネットワーク同期入門:所有権とUdonSyncedを理解する

作成: 2025-12-19

VRChatマルチプレイヤーの核心となるネットワーク同期の基本。オブジェクト所有権、[UdonSynced]変数、RequestSerializationによるデータ共有の仕組み。

概要

VRChatの最大の魅力は、複数のプレイヤーが同じ空間で同じ体験を共有できることです。あるプレイヤーがボタンを押したら、他の全員の視界でもドアが開く。これが実現できるのは、ネットワーク同期という仕組みがあるからです。

UdonSharpにおけるネットワーク同期は、一見複雑に見えますが、いくつかの基本的な概念を理解すれば、その仕組みは非常に明快です。本記事では、マルチプレイヤー対応ギミックを作成する上で不可欠な以下の3つの核心的要素について解説します。

  1. 所有権 (Ownership): 誰がそのオブジェクトを操作する権利を持っているか。
  2. 同期変数 ([UdonSynced]): プレイヤー間で共有される変数。
  3. シリアライゼーション: データが実際にネットワークを介して同期されるプロセス。

1. 所有権 (Ownership) - 「このオブジェクトは誰のもの?」

VRChatのワールドに存在する全てのGameObjectには、必ず一人の所有者 (Owner) がいます。所有者とは、そのオブジェクトの状態を自由に変更し、その変更を他のプレイヤーに通知する権限を持つプレイヤーのことです。

  • デフォルトの所有者: ワールドに最初から配置されているオブジェクトの所有者は、そのワールドのインスタンスを立てた人(Master)です。
  • 所有権のルール: 同期変数の値を変更できるのは、そのオブジェクトの所有者だけです。所有者でないプレイヤーが値を変更しようとしても、その変更はローカル(自分だけ)のものであり、他のプレイヤーには伝わりません。

この「所有者だけが変更できる」というルールが、複数のプレイヤーが同時に同じオブジェクトを操作しようとしたときに、状態が混乱するのを防ぐための基本的な仕組みです。

所有権の取得方法

プレイヤーがオブジェクトを操作するギミックを作る場合、まずそのプレイヤーがオブジェクトの所有者になる必要があります。所有権を取得するには、Networking.SetOwner()メソッドを使用します。

Networking.SetOwner(VRCPlayerApi player, GameObject obj);

  • player: 新しい所有者となるプレイヤー。通常は自分自身、つまりNetworking.LocalPlayerを指定します。
  • obj: 所有権を取得したいGameObject。通常はスクリプトがアタッチされているオブジェクト自身、つまりgameObjectを指定します。

このメソッドは、プレイヤーがオブジェクトに対して何らかのアクションを起こしたとき(例: Interact)に呼び出すのが一般的です。

public override void Interact()
{
    // このオブジェクトの所有権を自分(ローカルプレイヤー)に設定する
    Networking.SetOwner(Networking.LocalPlayer, gameObject);

    // ... この後に同期変数を変更する処理を記述 ...
}

2. 同期変数 ([UdonSynced]) - 全員で共有するデータ

プレイヤー間で共有したい変数は、宣言の直前に[UdonSynced]という属性を付けます。これにより、その変数は「ネットワーク同期の対象である」とUdonに認識されます。

using UdonSharp;
using UnityEngine;

public class SyncedVariableExample : UdonSharpBehaviour
{
    // このbool値は、インスタンス内の全プレイヤーで同期される
    [UdonSynced]
    private bool isLightOn = false;
}

[UdonSynced]を付けられる変数の型には制限がありますが、int, float, bool, string, Vector3, Quaternion, Colorなど、基本的なデータ型の多くがサポートされています。

3. シリアライゼーション - データ同期のプロセス

[UdonSynced]属性を付けた変数の値は、以下のプロセスを経て全プレイヤーに同期されます。この一連の流れをシリアライゼーション (Serialization) と呼びます。

  1. 所有権の取得: プレイヤーAがオブジェクトを操作し、Networking.SetOwner()で所有者になります。
  2. 同期変数の変更: プレイヤーA(所有者)が、[UdonSynced]変数の値を変更します。(例: isLightOn = true;
  3. シリアライゼーションの要求: プレイヤーAがRequestSerialization()を呼び出します。これは「変数の値が変更されたので、このデータを他の全員に送ってください」というリクエストです。
  4. データの送信: VRChatのサーバーが、プレイヤーAから送られてきた新しい変数のデータ(シリアライズされたデータ)を、インスタンス内の他の全プレイヤー(プレイヤーB、C、...)に送信します。
  5. デシリアライゼーション: データを受け取った他のプレイヤー(B、C、...)のUdon VMが、OnDeserialization()イベントを自動的に呼び出します。このイベントが発生した時点で、彼らの持つ[UdonSynced]変数の値は、プレイヤーAが設定した新しい値に更新されています。
  6. 状態の反映: OnDeserialization()メソッド内で、更新された変数の値に基づいてワールドの状態(例: ライトを点灯させる)を更新する処理を記述します。

実装例:同期するスイッチ

このプロセスを、クリックするとライトがON/OFFする同期スイッチで見てみましょう。

using UdonSharp;
using UnityEngine;
using VRC.SDKBase;

public class SyncedSwitch : UdonSharpBehaviour
{
    public GameObject targetLight;

    [UdonSynced]
    private bool isLightOn = false;

    void Start()
    {
        // 初期状態を反映
        UpdateLightState();
    }

    public override void Interact()
    {
        // 1. 所有権を取得
        Networking.SetOwner(Networking.LocalPlayer, gameObject);

        // 2. 同期変数を変更
        isLightOn = !isLightOn;

        // 3. 変更を全プレイヤーに通知するよう要求
        RequestSerialization();

        // 自分自身の見た目も即座に更新
        UpdateLightState();
    }

    // 5. 他のプレイヤーでデータが更新されたときに呼び出される
    public override void OnDeserialization()
    {
        // 6. 更新された状態を反映
        UpdateLightState();
    }

    private void UpdateLightState()
    {
        if (targetLight != null)
        {
            targetLight.SetActive(isLightOn);
        }
    }
}

このコードでは、Interactしたプレイヤーが値を変更し、RequestSerialization()で通知します。他のプレイヤーはOnDeserializationで通知を受け取り、UpdateLightState()を呼び出すことで、全員のライトの状態が一致します。

まとめ

  • ネットワーク同期の基本は所有権 (Ownership) です。同期変数を変更できるのは所有者だけです。
  • プレイヤーがオブジェクトを操作する際は、まずNetworking.SetOwner()所有権を取得します。
  • プレイヤー間で共有したい変数の前には[UdonSynced]属性を付けます。
  • 所有者が同期変数の値を変更したら、RequestSerialization()を呼び出して変更を通知します。
  • 他のプレイヤーはOnDeserialization()イベントで変更を受け取り、ワールドの状態を更新します。

この「所有権取得 → 値の変更 → 通知 → 受信して更新」という流れが、UdonSharpにおけるネットワーク同期の最も基本的なパターンです。このパターンを理解することが、マルチプレイヤー対応ギミックを制作するための第一歩となります。

次の記事では、スクリプト同士で連携するための「メソッドとカスタムイベント」について解説します。