【VRChat】所有権を理解する:いま値を書ける人は誰か

作成: 2026-09-08最終更新: 2026-09-09

所有権はオブジェクトごとに1人だけ。Masterとの違い、自動で移る場面、所有者が退出したときの引き継ぎを、操作権のボタンと照明のボタンで確かめます。

同期入門で作った照明スイッチには、こんな1行がありました。

Networking.SetOwner(Networking.LocalPlayer, gameObject);

「押した人が所有権を取る」処理です。この行がないと、押しても他の人には伝わりません。しかも エラーは出ません。 自分の画面だけ変わって、原因が分からないまま止まります。

この記事では、所有権が誰にあるかを画面で確かめながら、いつ移り、退出するとどうなるか を見ていきます。

2人が同じランプを、マイクを渡し合いながら操作する

この記事でわかること

  • 所有権はオブジェクトごとに1人だけであること
  • MasterとOwnerの違い
  • 所有権が自動で移る場面
  • 所有者が退出したときの引き継ぎ

ネットワーク同期入門 を試した状態から始めます。

Sponsored


持てるマイクは、物ごとに1本

所有権(Owner)は、そのオブジェクトの同期変数を書ける権利 です。カラオケのマイクにたとえると分かりやすくなります。

オブジェクトごとに、いま操作できる人が違う

大事なのは、マイクは物ごとに1本ずつある ことです。

  • Aさんが照明スイッチのOwner
  • Bさんがコップ(Pickup)のOwner
  • 扉は誰も触っていないので、最初に入った人のまま

同時に成り立ちます。「Aさんがワールド全体のOwner」という状態はありません。

ここでもう1つ、Master という役割があります。インスタンス全体の進行役で、部屋を開いた人が担当することが多いものです。

MasterとOwnerは別です。 店長がたまたまマイクを持っていることはありますが、店長だから歌えるわけではありません。

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

所有権が移るのは、次の場面です。

場面移り方
Networking.SetOwner() を呼んだ自分で移す
Pickupを掴んだ自動で移る
Ownerが退出したVRChatが別の人へ割り当てる

Pickupが自動で移るのは便利ですが、「掴んだ人がOwnerになる」ことを前提にした作り は覚えておいてください。

Sponsored

実践:所有権を目に見えるようにする

操作権を取るボタンと、照明を切り替えるボタンを分けて置きます。所有者の名前を掲示板に出して、誰が持っているかを見えるようにします。

1. オブジェクトを置く

床のあるシーンに、次を置きます。

名前作り方と設定
OwnerLightPoint Light。(0, 2.5, 0)、Range 6、Intensity 2、Mode「Realtime」
ClaimButtonCube。(-1, 1, 1)、Scale (0.4, 0.4, 0.4)
LightButtonCube。(1, 1, 1)、Scale (0.4, 0.4, 0.4)
OwnerCanvasUI Canvas(World Space)。(0, 2, 2.8)、Scale (0.005, 0.005, 0.005)
ライト・2つのボタン・掲示板の配置

OwnerCanvas の子に TextMeshPro を作り、OwnerText と名付けます。所有者の名前をここに出します。

ボタンを2つに分けているのは、「所有権を取ること」と「値を変えること」を別々に体験する ためです。

2. コードを書く

「Create Empty」で OwnerDemo を作り、(0, 0, 0) に置きます。

Assets/ScriptsOwnershipDemo を作ります。

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を割り当てます。

ClaimButtonLightButton にも 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 にして起動します。

順序操作期待する結果
1AとBが入る掲示板に、最初に入った人の名前が出る
2Bが照明のボタンだけ押す何も起きない。Consoleに「所有権がありません」
3Bが操作権のボタンを押す掲示板の名前がBに変わる。Aの画面でも変わる
4Bが照明のボタンを押す両方の画面で照明が点く
5Bが退出するAの画面で、所有者がAに変わる

2番目が、この記事のいちばん大事な体験です。 コードは正しいのに、所有権がないだけで何も起きません。エラーも出ません。

同期がうまくいかないとき、ここを疑えるかどうかで解決の速さが変わります。

所有者が退出すると、残った人へ自動的に引き継がれる

5番目も確かめてください。所有者がいなくなっても、ワールドは壊れません。 VRChatが自動で別の人に割り当てます。

うまくいかないときは

  • 押しても何も起きない → 所有権を持っていません。先に取ります。実用のコードでは、自動で取りに行く形にします
  • 名前が出ない → Owner Textが None か、Canvasの設定を確認します
  • 所有者の表示が更新されないOnOwnershipTransferredOnDeserialization の両方から更新しているか確認します
  • 自分の画面だけ変わる → 所有権を取っていないか、RequestSerialization() を呼んでいません
Sponsored

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

  • 実用のコードでは自動で取る: この記事では体験のために分けましたが、実際は「押されたら、まず所有権を取る」と書くのが普通です。ネットワーク同期入門 のコードがその形です
どのオブジェクトの所有権を取るのかを間違えない
  • どのオブジェクトの所有権かを間違えない: 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 へ進んでください。

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