来場するたびにス タンプが1つ増えるカードを作りたい。10個たまったら特別な部屋が開く、といった仕掛けです。
数字を残すだけなら PlayerData でできます。ただ、人ごとに スクリプトも変数も表示もひとそろい 欲しくなると、キーを何本も並べる形は苦しくなります。
そこで使うのが VRCPlayerObject です。この記事では、入室した人ごとに専用のスタンプカードが配られる ようにします。
この記事でわかること
- テンプレートとコピーの関係
- VRCEnablePersistenceが保存する範囲
- 自分のコピーを探す方法
- PlayerDataとの使い分け
PlayerDataで音量を保存する を試した状態から始めます。
紙をしまうか、机ごと配るか
PlayerDataは、ロッカーに紙をしまう仕組みでした。「スタンプは7個」と書いた紙が1枚あるだけで、ワールドに物は増えません。
PlayerObjectは、ロッカーそのものを1台ずつ配ります。 入室した人ごとに、専用のオブジェクトがワールドに現れます。数字だけでなく、スクリプト、表示、同期変数まで、まるごとその人のものになります。

使い分けの目安はこうです。
| 持ちたいもの | 向いているもの |
|---|---|
| 音量、字幕のオンオフ | PlayerData |
| チュートリアルを見たか | PlayerData |
| 各自のスコアと、その計算をする処理 | PlayerObject |
| 各自の持ち物リストと、その表示 | PlayerObject |
値が数個ならPlayerDataで足ります。「その人専用の仕掛けを1式そろえたい」ときがPlayerObjectの出番 です。
Networking.SetOwner() で所有権を取り合う必要がないのも大きい点です。自分のコピーは最初から自分のものなので、書き込みの順番を心配せずに済みます。
型は1つ、コピーは人数分
シーンに置くのは 型(テンプレート) です。実行時にプレハブを複製する話ではなく、シーン上に1つ置いたオブジェクトが型になります。

流れはこうです。
- スタンプカードのオブジェクトをシーンに置く
- それに VRC Player Object を追加する
- プレイヤーが入室すると、その人用のコピーが作られる
- 型のほうは実行中は無効になる
- 退室すると、その人のコピーは消える
型を直接いじっても、誰のデータにもなりません。 読み書きするのはコピーのほうです。ここを取り違えると、「動いているように見えるのに何も保存されない」という状態になります。
もう1つ、VRC Enable Persistence という部品があります。役割は名前のとおりです。
| 部品 | すること | ないとどうなるか |
|---|---|---|
| VRC Player Object | 入室した人ごとにコピーを作る | 共有の1個のまま |
| VRC Enable Persistence | そのコピーの同期変数を次回まで残す | いる間だけの値になる |
保存されるのは同期変数だけ です。ふつうのフィールドや、テキストに表示されている文字そのものは残りません。数字を [UdonSynced] で持って、表示はその数字から作り直す形にします。
実践:自分だけのスタンプカードを作る
ボタンを押すと自分のスタンプが1つ増えて、退室しても数が残る仕掛けを作ります。

1. 保存を有効にする
まず、ワールド全体で保存を使えるようにします。
Hierarchyの VRCWorld を選び、VRC Scene Descriptor の設定にある Persistence の項目を有効にします。これがオフだと、保存に関する仕掛けはすべて動きません。
2. テンプレートを作る
「Create Empty」で StampCardTemplate を作り、(0, 0, 0) に置きます。
「Add Component」で次の2つを追加します。
- VRC Player Object
- VRC Enable Persistence
見た目は付けません。表示は共有の掲示板でやって、コピーには数字だけを持たせます。 コピーは全員分が同じ位置に作られるので、見えるものを入れると重なってしまいます。
Assets/Scripts で StampCard を作り、StampCardTemplate に付けます。
using UdonSharp;
using VRC.SDKBase;
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class StampCard : UdonSharpBehaviour
{
// VRC Enable Persistence によって次回まで残る
[UdonSynced] public int stamps;
public void AddStamp()
{
// 自分のコピーだけを触る
if (!Networking.IsOwner(gameObject)) return;
stamps++;
RequestSerialization();
}
}
このスクリプトには、探す処理も表示する処理もありません。持っているのは数字だけ です。
3. 受付を作る
「3D Object → Cube」で StampDesk を作り、(0, 1, 2) に置きます。その上に World Space の Canvas と TextMeshPro を置いて、StampText と名付けます。
Assets/Scripts で StampDesk を作ります。
using UdonSharp;
using UnityEngine;
using TMPro;
using VRC.SDKBase;
[UdonBehaviourSyncMode(BehaviourSyncMode.None)]
public class StampDesk : UdonSharpBehaviour
{
[SerializeField] private TextMeshProUGUI stampText;
public override void Interact()
{
StampCard card = FindMyCard();
if (card == null) return;
card.AddStamp();
Refresh();
}
// 保存データが戻ってきたら、表示を作り直す
public override void OnPlayerRestored(VRCPlayerApi player)
{
if (player.isLocal) Refresh();
}
private void Refresh()
{
StampCard card = FindMyCard();
if (card == null || stampText == null) return;
stampText.text = "スタンプ " + card.stamps + " 個";
}
// 自分用に作られたコピーを探す
private StampCard FindMyCard()
{
GameObject[] objects = Networking.GetPlayerObjects(Networking.LocalPlayer);
if (objects == null) return null;
for (int i = 0; i < objects.Length; i++)
{
StampCard card = objects[i].GetComponentInChildren<StampCard>();
if (card != null) return card;
}
return null;
}
}
Networking.GetPlayerObjects() が、そのプレイヤーに配られたコピーを返します。名前で探さないでください。 コピーの名前は環境によって変わりますし、複数の人が入ると当てになりません。
StampDesk に Udon Behaviour を追加し、Stamp Textをドラッグして、Interaction Text を スタンプを押す にします。
4. 確かめる
Build & Test で Number of Clients を 2 にして起動します。
| 順序 | 操作 | 期待する結果 |
|---|---|---|
| 1 | AとBが入る | どちらの画面も「スタンプ 0 個」 |
| 2 | Aが3回押す | Aの画面は「スタンプ 3 個」 |
| 3 | Bが1回押す | Bの画面は「スタンプ 1 個」。Aの画面は 3 のまま |
| 4 | Unityの Hierarchy を見る | テンプレートの近くに、人数分のコピーが増えている |
| 5 | アップロードして入り直す | 前回の数から続く |
3番目が、この仕組みの手ざわりです。 同じ受付を押しているのに、増えるのは自分のカードだけです。所有権を取り合う処理を1行も書いていないことに注目してください。
4番目も見ておくと安心できます。コピーが本当に人数分あることが目で確認できます。
うまくいかないときは
- 押しても増えない → テンプレートを直接操作しています。
GetPlayerObjectsでコピーを探します - 数が保存されない → VRC Enable Persistence が付いていないか、変数が
[UdonSynced]になっていません - 何も動かない → VRC Scene Descriptor の Persistence が無効になっています
- 入室直後だけ 0 と出る →
OnPlayerRestoredで表示を作り直しているか確認します - 全員のカードが重なって見える → コピーに見た目を入れています。表示は共有の掲示板側でやります
- 他の人の数が増えてしまう →
Networking.IsOwner()の判定が抜けています
おまけ:先に知っておくと良いこと
- コピーは同じ場所に作られる: 全員分が重なるので、見えるものを入れるなら位置をずらす処理が要ります。入門では、コピーには数字だけを持たせるのが安全です
- 保存されるのは同期変数だけ: ボタンの押し込み具合や、テキストに書かれた文字は残りません。数字から表示を作り直す形にしておけば、これで困りません
- 他の人のカードも読める:
GetPlayerObjectsに他のプレイヤーを渡せば、その人のコピーが取れます。全員のスタンプ数を並べた掲示板は、これで作れます - PlayerDataと併用してよい: 音量のような単純な 設定はPlayerData、持ち物のような一式はPlayerObject。片方に寄せる必要はありません
- 使いどころはいろいろ: スタンプラリー、各自のインベントリ、個人スコア、進行度に応じて開く扉。「人ごとに違っていて、次回も残ってほしいもの」が対象です
まとめ
PlayerObjectは、入室した人ごとに専用のオブジェクトを配る仕組みです。
- シーンに置くのは型。実行中に配られるコピーを読み書きする
- VRC Player Object がコピーを作り、VRC Enable Persistence が同期変数を残す
- 自分のコピーは
Networking.GetPlayerObjects()で探す。名前では探さない - コピーの所有者は本人。所有権を取り合う必要がない
選ぶときの問いかけは、「値が数個で足りるか、仕掛けを1式そろえたいか」 です。1式ならPlayerObjectです。
動画を流す仕掛けなら 動画プレイヤーを設置する、外から文章を読み込むなら 文字列を読み込む へ進んでください。