【VRChat】ネットワーク同期入門:全員で同じライトを見る仕組みを作る

作成: 2025-12-19最終更新: 2026-09-09

状態は黒板、合図は叫び、所有権はマイク。Manual同期のboolで照明スイッチを作り、2人での確認と途中参加までを一通り通します。同期でつまずく順番も整理します。

自分の画面ではライトが点いたのに、隣の人の画面は暗いまま。ローカルで動くスイッチを作った直後に、必ず出会う問題です。

同じインスタンスにいても、実際には各自のPCがそれぞれワールドを動かしています。自分の手元で Light.enabled を変えても、その変更は他の人には届きません。

この記事では、「ライトが点いているか」という1つの値を全員へ伝えて、それぞれの画面でライトを点ける 仕組みを作ります。

2つの部屋で同じライトが点き、あいだを光の粒が流れている

この記事でわかること

  • 「残る状態」と「その場かぎりの合図」の使い分け
  • 所有権(Owner)が「いま値を書ける人」であること
  • 送信を頼む処理と、受け取って反映する処理
  • 2人で確認する手順と、途中参加の確かめ方

最初のUdonSharp でスクリプトを作れる状態から始めます。

Sponsored


状態は黒板、合図は叫び

VRChatで情報を全員に伝える方法は、大きく2つあります。役割がはっきり違います。

同期変数は黒板に書いて残る、Network Eventはその場かぎりの叫び

同期変数 は、部屋の黒板です。いまの状態を書いておくと、みんながそれを読めます。あとから入ってきた人も、黒板を見れば現在の状態が分かります。

Network Event は、その場での叫びです。「いま花火を撃て」「いま効果音を鳴らせ」と伝えます。声は残らないので、あとから入ってきた人には届きません。

使い分けは、これだけです。

伝えたいもの使うもの理由
ライトが点いているか同期変数途中参加の人も現在の状態を知る必要がある
扉が開いているか同期変数同上
いま鳴らす短い効果音Network Event過去の音を鳴らし直す必要はない
ゲームの得点同期変数途中から見た人にも今の点数を見せたい

この記事で作るライトは、黒板のほうです。

もう1つ、先に知っておくことがあります。同期するのは「値」であって、ライトそのものではありません。 送るのは isOn という真偽の値だけで、その値を見てライトを点けるのは、各自のPCの仕事です。

操作した人が点灯状態のboolを送り、受信した人が自分のライトへ反映する

書けるのは、マイクを持っている人だけ

黒板には、誰でも書けるわけではありません。所有権(Owner) を持っている人だけが書けます。

マイクを持っている人だけが黒板に書ける

カラオケで考えると分かりやすくなります。

  • 曲名を変えられるのは、いま マイクを持っている人 だけ(Owner)
  • 他の人は画面の曲名を見られるが、書き換えはできない
  • 歌いたい人は、まずマイクを受け取ってからボタンを押す

コードでは、こういう順番になります。

1. マイクを受け取る   → Networking.SetOwner(自分, このオブジェクト)
2. 黒板に書く         → isOn = !isOn
3. 送ってと頼む       → RequestSerialization()

ここで Master という言葉も出てきますが、別のものです。Masterはインスタンス全体の進行役、いわばカラオケ店の店長です。店長であることと、いまマイクを持っていることは別 です。

最初はMasterがOwnerになっていることが多いので、「Masterなら同期できる」と誤解しがちです。でもそれは偶然そうなっているだけで、仕組みではありません。押した人がマイクを受け取る、と書いておけば誰が押しても動きます。

Sponsored

実践:全員で切り替わる照明を作る

押すと全員の画面でライトが切り替わるスイッチを作ります。あとから入った人にも、そのときの点灯状態が伝わります。

1. スイッチとライトを置く

歩けるシーンに2つ置きます。変化を見やすくするため、Directional LightのIntensityを 0.2 くらいまで下げておきます。

名前作り方と設定
SharedSwitchCube。Position (0, 1, 1)、Scale (0.4, 0.4, 0.4)
RoomLightLight → Point Light。Position (0, 2, 1)、Range 5、Intensity 2、Mode「Realtime」
スイッチとライトの配置

ライトのModeは必ず Realtime にします。Bakedだと焼き込んだ光が残るので、実行中に切り替えても見た目が変わりません。

2つは親子にせず、別々のオブジェクトとして置きます。

2. コードを書く

Assets/Scripts で「Create → U# Script」を選び、SharedLightSwitch という名前で作ります。

using UdonSharp;
using UnityEngine;
using VRC.SDKBase;

[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]  // 変わったときだけ送る
public class SharedLightSwitch : UdonSharpBehaviour
{
    [SerializeField] private Light targetLight;

    [UdonSynced] private bool isOn;  // これが「黒板」。全員で共有する値

    private void Start()
    {
        if (targetLight == null)
        {
            Debug.LogWarning("[SharedLightSwitch] Target Lightが未設定です。");
        }
        ApplyState();  // 手元の値を、まず見た目へ反映する
    }

    public override void Interact()
    {
        if (!Utilities.IsValid(Networking.LocalPlayer)) return;

        // 1. マイクを受け取る
        if (!Networking.IsOwner(gameObject))
        {
            Networking.SetOwner(Networking.LocalPlayer, gameObject);
        }
        if (!Networking.IsOwner(gameObject)) return;

        // 2. 黒板に書く
        isOn = !isOn;
        ApplyState();

        // 3. 送ってと頼む
        RequestSerialization();
    }

    // 値が届いたときに呼ばれる(押した本人では呼ばれない)
    public override void OnDeserialization()
    {
        ApplyState();
    }

    // 値を見た目へ反映するだけ。何度呼んでも同じ結果になる
    private void ApplyState()
    {
        if (targetLight != null)
        {
            targetLight.enabled = isOn;
        }
    }
}

大事な点が3つあります。

[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)] を付けています。Manualは「変わったときだけ送る」方式です。スイッチや扉のように、たまにしか変わらない状態に向いています。

ApplyState() を独立させています。 起動時、押したとき、受信したときの3か所から呼びます。「値を見て、見た目をそろえる」だけの処理なので、何度呼んでも結果が変わりません。ここに isOn = !isOn のような反転を書くと、受信するたびに勝手に切り替わってしまいます。

OnDeserialization() は、押した本人では呼ばれません。 だから押した側でも ApplyState() を明示的に呼んでいます。ここを忘れると「自分の画面だけ変わらない」という妙な状態になります。

3. 割り当てて保存する

SharedSwitch に Udon Behaviour を追加し、Program Source に SharedLightSwitch を指定します。

指定するもの
Target LightHierarchyの RoomLight
Interaction Text照明を切り替える
ライトを欄へドラッグして割り当てる

Sync Method の欄は表示されないか、Manualになっています。コード側の属性で指定しているためです。

4. まず1人で確かめる

ClientSimでPlayして、スイッチを押してみてください。自分の画面でライトが切り替われば、ここまでは正しく書けています。

ただし、これで同期できたとは言えません。 ClientSimに映るもう1人は本物のプレイヤーではないので、送信も受信も本番と同じにはなりません。同期の確認は、次の手順で行います。

2人と途中参加で確かめる

実際のVRChatを2つ起動して確かめます。

  1. SDKのControl PanelでBuilderを開く
  2. Number of Clients2 にする
  3. Force Non-VR を有効にする(デスクトップで確認する場合)
  4. Build & Test を押す

VRChatが2つ起動して、同じインスタンスに2人入ります。片方をAさん、もう片方をBさんとして、順に確かめてください。

順序操作期待する結果
1AとBが入室する両方とも消灯から始まる
2Aだけが1回押すAが点灯し、少し遅れてBも点灯する
3Bだけが1回押すBが所有権を取り、両方とも消灯する
4Aが点灯させたあと、Bが入り直すBは押していないのに点灯した状態で入る

4番目が 途中参加 の確認です。同期変数を使っているので、あとから来た人にも最新の値が届きます。Aさんが押し直す必要はありません。

確認するときの注意が2つあります。送信側の画面だけを見ても、同期できたか分かりません。 操作する画面と、結果を見る画面を分けてください。また、全部のクライアントを閉じてから作り直すと初期状態に戻るので、途中参加の確認になりません。片方を残したまま、もう片方だけ入り直します。

同期でつまずく順番

同期は、上から順に段階を踏んで壊れます。自分がどこで止まっているかを見つけてください。

  1. 自分の画面も変わらない → 同期ではなく、まだローカルの問題です。Interactが動いているか、Target Lightが入っているか、ライトがRealtimeかを確認します
  2. 自分だけ変わって、相手に届かないManual の指定があるか、RequestSerialization() を呼んでいるか、そもそも同じインスタンスにいるかを見ます
  3. 押した人には効かないが、相手には効くOnDeserialization() にだけ反映処理を書いています。押した側でも ApplyState() を呼びます
  4. その場の人には効くが、途中参加の人が暗い → Network Eventだけで伝えていないか確認します。残る状態は同期変数で持ちます
  5. 相手が押すと元に戻る → 複数のスクリプトが同じライトを書き換えていないかを見ます

[UdonSynced] を付けただけでは送られません。印を付けた箱に値を入れて、送れと頼む。 この2段階だと覚えてください。

Sponsored

おまけ:先に知っておくと良いこと

  • 同期できるのは値だけ: bool、数値、文字列、色、座標などは送れますが、GameObjectやTransformといったシーンの参照は送れません。「どのオブジェクトか」は各自のシーンにあるものを使います
  • Continuousは位置向け: Continuous は所有者の値を定期的に送り続ける方式で、動き続ける物の位置に向いています。スイッチのように「変わった瞬間だけ」の用途はManualです
  • 全部にVRC Object Syncを付けない: 位置を送り続ける部品なので、投げる小物には向きますが、スイッチや椅子には合いません。何を共有したいのかで選びます
  • 同時に押されたときの保証はない: 2人がほぼ同時に押すと、両方が同じ値を書く可能性があります。照明なら最終的にそろえば十分ですが、得点の加算のように取りこぼしが困る場合は、所有者に依頼して1か所で処理する設計を考えます
  • Ownerが退出しても大丈夫: VRChatが新しい所有者を自動で割り当てます。ただし「誰が選ばれるか」に依存した作りにはしないでください

まとめ

同期は、3つの役割を分けると整理できます。

  • 残る状態は同期変数(黒板)、その場かぎりの合図はNetwork Event(叫び)
  • 黒板に書けるのは、いま所有権を持っている人だけ。押す人がまず所有権を取る
  • [UdonSynced] を付けて、値を入れて、RequestSerialization() で送信を頼む
  • 見た目への反映は ApplyState() にまとめ、起動時・押したとき・受信時の3か所から呼ぶ

作るときの問いかけは、「これは、あとから来た人にも見えていてほしいか?」 です。イエスなら同期変数、ノーならイベントです。

次は所有権をもっと詳しく見ます。所有権を理解する で、操作する人が切り替わる様子を確かめてください。一瞬の演出を伝えたいなら Network Events へ進みます。

VRChat このセクションのノート63