同期入門で作った照明スイッ チには、こんな1行がありました。
Networking.SetOwner(Networking.LocalPlayer, gameObject);
「押した人が所有権を取る」処理です。この行がないと、押しても他の人には伝わりません。しかも エラーは出ません。 自分の画面だけ変わって、原因が分からないまま止まります。
この記事では、所有権が誰にあるかを画面で確かめながら、いつ移り、退出するとどうなるか を見ていきます。
この記事でわかること
- 所有権はオブジェクトごとに1人だけであること
- MasterとOwnerの違い
- 所有権が自動で移る場面
- 所有者が退出したときの引き継ぎ
ネットワーク同期入門 を試した状態から始めます。
持てるマイクは、物ごとに1本
所有権(Owner)は、そのオブジェクトの同期変数を書ける権利 です。カラオケのマイクにたとえると分かりやすくなります。

大事なのは、マイクは物ごとに1本ずつある ことです。
- Aさんが照明スイッチのOwner
- Bさんがコップ(Pickup)のOwner
- 扉は誰も触っていないので、最初に入った人のまま
同時に成り立ちます。「Aさんがワールド全体のOwner」という状態はありません。
ここでもう1つ、Master という役割があります。インスタンス全体の進行役で、部屋を開いた人が担当することが多いものです。
MasterとOwnerは別です。 店長がたまたまマイクを持っていることはありますが、店長だから歌えるわけではありません。
最初はMasterがすべてのオブジェクトのOwnerになっていることが多いので、「Masterなら同期できる」と誤解しがちです。それは偶然そうなっているだけ で、仕組みではありません。押した人がマイクを取る、と書いておけば誰が押しても動きます。
所有権が移るのは、次の場面です。
| 場面 | 移り方 |
|---|---|
Networking.SetOwner() を呼んだ | 自分で移す |
| Pickupを掴んだ | 自動で移る |
| Ownerが退出した | VRChatが別の人へ割り当てる |
Pickupが自動で移るのは便利ですが、「掴んだ人がOwnerになる」ことを前提にした作り は覚えておいてください。
実践:所有権を目に見えるようにする
操作権を取るボタンと、照明を切り替えるボタンを分けて置きます。所有者の名前を掲示板に出して、誰が持っているかを見えるようにします。
1. オブジェクトを置く
床のあるシーンに、次を置きます。
| 名前 | 作り方と設定 |
|---|---|
OwnerLight | Point Light。(0, 2.5, 0)、Range 6、Intensity 2、Mode「Realtime」 |
ClaimButton | Cube。(-1, 1, 1)、Scale (0.4, 0.4, 0.4) |
LightButton | Cube。(1, 1, 1)、Scale (0.4, 0.4, 0.4) |
OwnerCanvas | UI Canvas(World Space)。(0, 2, 2.8)、Scale (0.005, 0.005, 0.005) |

OwnerCanvas の子に TextMeshPro を作り、OwnerText と名付けます。所有者の名前をここに出します。
ボタンを2つに分けているのは、「所有権を取ること」と「値を変えること」を別々に体験する ためです。
2. コードを書く
「Create Empty」で OwnerDemo を作り、(0, 0, 0) に置きます。
Assets/Scripts で OwnershipDemo を作ります。
using UdonSharp;
using UnityEngine;
using TMPro;
using VRC.SDKBase;
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class OwnershipDemo : UdonSharpBehaviour
{
[SerializeField] private Light targetLight;
[SerializeField] private TextMeshProUGUI ownerText;
[UdonSynced] private bool isOn;
private void Start()
{
ApplyState();
UpdateOwnerText();
}
// 「操作権を取る」ボタンから呼ぶ
public void ClaimOwnership()
{
if (!Networking.IsOwner(gameObject))
{
Networking.SetOwner(Networking.LocalPlayer, gameObject);
}
UpdateOwnerText();
}
// 「照明を切り替える」ボタンから呼ぶ
public void ToggleLight()
{
// 所有権がなければ、送っても届かない
if (!Networking.IsOwner(gameObject))
{
Debug.LogWarning("[Ownership] 所有権がありません。先に操作権を取ってください。");
return;
}
isOn = !isOn;
ApplyState();
RequestSerialization();
}
public override void OnDeserialization()
{
ApplyState();
UpdateOwnerText();
}
// 所有権が移ったときに呼ばれる
public override void OnOwnershipTransferred(VRCPlayerApi player)
{
UpdateOwnerText();
}
private void ApplyState()
{
if (targetLight != null) targetLight.enabled = isOn;
}
private void UpdateOwnerText()
{
if (ownerText == null) return;
VRCPlayerApi owner = Networking.GetOwner(gameObject);
string name = Utilities.IsValid(owner) ? owner.displayName : "(不明)";
bool mine = Networking.IsOwner(gameObject);
ownerText.text = "所有者: " + name + (mine ? "\n(あなた)" : "");
}
}
ToggleLight() で、所有権がないときにあえて弾いています。 実際のワールドでは自動的に取りに行くほうが親切ですが、ここでは「持っていないと届かない」ことを体験するために、あえて分けています。
OnOwnershipTransferred() は、所有権が移ったときに呼ばれるイベントです。表示を更新するのに使っています。
3. ボタンから呼ぶ
OwnerDemo に Udon Behaviour を追加し、OwnershipDemo を指定して、Target LightとOwner Textを割り当てます。
ClaimButton と LightButton にも Udon Behaviour を追加して、それぞれ簡単な中継のスクリプトを書きます。
using UdonSharp;
using UnityEngine;
[UdonBehaviourSyncMode(BehaviourSyncMode.None)]
public class OwnerDemoButton : UdonSharpBehaviour
{
[SerializeField] private OwnershipDemo demo;
[SerializeField] private bool claimMode; // true なら所有権を取る、false なら照明
public override void Interact()
{
if (demo == null) return;
if (claimMode) demo.ClaimOwnership();
else demo.ToggleLight();
}
}
ClaimButton は Claim Mode をオン、LightButton はオフにします。
2人で確かめる
Build & Test で Number of Clients を 2 にして起動します。
| 順序 | 操作 | 期待する結果 |
|---|---|---|
| 1 | AとBが入る | 掲示板に、最初に入った人の名前が出る |
| 2 | Bが照明のボタンだけ押す | 何も起きない。Consoleに「所有権がありません」 |
| 3 | Bが操作権のボタンを押す | 掲示板の名前がBに変わる。Aの画面でも変わる |
| 4 | Bが照明のボタンを押す | 両方の画面で照明が点く |
| 5 | Bが退出する | Aの画面で、所有者がAに変わる |
2番目が、この記事のいちばん大事な体験です。 コードは正しいのに、所有権がないだけで何も起きません。エラーも出ません。
同期がうまくいかないとき、ここを疑えるかどうかで解決の速さが変わります。

5番目も確かめてください。所有者がいなくなっても、ワールドは壊れません。 VRChatが自動で別の人に割り当てます。
うまくいかないときは
- 押しても何も起きない → 所有権を持っていません。先に取ります。実用のコードでは、自動で取りに行く形にします
- 名前が出ない → Owner Textが
Noneか、Canvasの設定を確認します - 所有者の表示が更新されない →
OnOwnershipTransferredとOnDeserializationの両方から更新しているか確認します - 自分の画面だけ変わる → 所有権を取っていないか、
RequestSerialization()を呼んでいません
おまけ:先に知っておくと良いこと
- 実用のコードでは自動で取る: この記事では体験のために分けましたが、実際は「押されたら、まず所有権を取る」と書くのが普通です。ネットワーク同期入門 のコードがその形です

- どのオブジェクトの所有権かを間違えない:
Networking.SetOwner(player, gameObject)のgameObjectは、そのスクリプトが付いているオブジェクトです。別のオブジェクトの同期変数を触りたいなら、そちらの所有権が必要です - Pickupは自動で移る: 掴んだ人がOwnerになります。持ち物の位置を同期する仕組みは、これを前提にしています。VRC Object Sync で扱います
- 所有権の要求を拒否できる:
OnOwnershipRequestを使うと、「いま使用中だから渡さない」という制御ができます。ゲームの進行役を固定したいときなどに使いますが、入門では不要です - Master依存の設計にしない: 「Masterだ けが操作できる」という作りは、Masterが退出したときに困ります。誰がOwnerでも動く形にしておきます
まとめ
所有権は、オブジェクトごとの「いま書ける人」です。
- マイクは物ごとに1本。ワールド全体のOwnerという概念はない
- MasterとOwnerは別。Masterだから書けるわけではない
- 所有権がないと、書き換えても送られない。エラーは出ない
- 所有者が退出しても、自動で引き継がれる
同期が届かないときの問いかけは、「いま自分は、そのオブジェクトのOwnerか」 です。ここを確かめるだけで、原因の半分は見つかります。
一瞬の演出を伝えるなら Network Events、持ち物の位置を共有するなら VRC Object Sync へ進んでください。