You want a card that gains a stamp on every visit. Collect ten and a special room opens.
Keeping just a number works with PlayerData. But once you want a whole set per person — script, variables, and display — lining up key after key gets painful.
That's what VRCPlayerObject is for. This article makes every visitor get their own stamp card.
What You'll Learn
- The template-and-copy relationship
- What VRCEnablePersistence saves
- How to find your own copy
- When to use this versus PlayerData
Start from having tried saving volume with PlayerData.
Store a slip, or hand out a whole desk
PlayerData was a mechanism for storing a slip in a locker. One slip reading "7 stamps," with nothing added to the world.
PlayerObject hands out a locker itself, one per person. Every visitor gets a dedicated object appearing in the world. Not just numbers — scripts, displays, and synced variables all become theirs.

Here's the guidance for choosing.
| What you want to hold | What suits it |
|---|---|
| Volume, subtitles on or off | PlayerData |
| Whether they've seen the tutorial | PlayerData |
| Each person's score, and the logic computing it | PlayerObject |
| Each person's inventory, and its display | PlayerObject |
A handful of values is fine with PlayerData. PlayerObject's turn comes when you want a whole set of machinery dedicated to that person.
Not having to contend for ownership with Networking.SetOwner() is a big deal too. Your copy is yours from the start, so you needn't worry about write ordering.
One template, one copy per person
What you place in the scene is a template. This isn't about instantiating a prefab at runtime — one object placed in the scene becomes the template.

The flow goes like this.
- Place the stamp card object in the scene
- Add VRC Player Object to it
- When a player enters, a copy is made for them
- The template itself is disabled at runtime
- When they leave, their copy disappears
Touching the template directly affects nobody's data. What you read and write is the copy. Get that wrong and you end up with "it looks like it's working and nothing gets saved."
There's another component, VRC Enable Persistence. Its role matches its name.
| Component | What it does | Without it |
|---|---|---|
| VRC Player Object | Makes a copy per visitor | It stays one shared object |
| VRC Enable Persistence | Keeps that copy's synced variables until next time | Values last only while present |
Only synced variables get saved. Ordinary fields, and the text currently shown in a label, don't persist. Hold the number with [UdonSynced] and rebuild the display from that number.
Hands-On: Build your own stamp card
Build a mechanism where pressing a button adds a stamp to your card, and the count survives leaving.

1. Enable persistence
First, enable saving for the world as a whole.
Select VRCWorld in the Hierarchy and enable Persistence in the VRC Scene Descriptor settings. With that off, nothing save-related works.
2. Build the template
Create StampCardTemplate with "Create Empty" at (0, 0, 0).
Add these two via "Add Component."
- VRC Player Object
- VRC Enable Persistence
Don't give it any visuals. Do the display on a shared board and let the copy hold only the number. Copies are all made at the same position, so anything visible would overlap.
Create StampCard in Assets/Scripts and attach it to StampCardTemplate.
using UdonSharp;
using VRC.SDKBase;
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class StampCard : UdonSharpBehaviour
{
// Kept until next time by VRC Enable Persistence
[UdonSynced] public int stamps;
public void AddStamp()
{
// Touch only your own copy
if (!Networking.IsOwner(gameObject)) return;
stamps++;
RequestSerialization();
}
}
This script has no finding logic and no display logic. All it holds is a number.
3. Build the desk
Create StampDesk as a "3D Object → Cube" at (0, 1, 2). Put a World Space Canvas and a TextMeshPro above it, named StampText.
Create StampDesk in Assets/Scripts.
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();
}
// Rebuild the display once the saved data comes back
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 + " stamps";
}
// Find the copy made for me
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() returns the copies handed to that player. Don't search by name. Copy names vary by environment and become unreliable once several people are in.
Add a Udon Behaviour to StampDesk, drag in Stamp Text, and set Interaction Text to Stamp the card.
4. Confirm
Launch Build & Test with Number of Clients set to 2.
| Order | Action | Expected result |
|---|---|---|
| 1 | A and B enter | Both screens show "0 stamps" |
| 2 | A presses three times | A's screen shows "3 stamps" |
| 3 | B presses once | B's screen shows "1 stamps." A's screen still shows 3 |
| 4 | Look at Unity's Hierarchy | Copies have appeared near the template, one per person |
| 5 | Upload and re-enter | It continues from the previous count |
Row 3 is the feel of this mechanism. Pressing the same desk, only your own card increments. Note that you wrote zero lines of ownership contention.
Row 4 is reassuring too. You can see with your own eyes that there really is one copy per person.
Common Pitfalls
- Pressing doesn't increment → You're operating the template directly. Find the copy with
GetPlayerObjects - The count doesn't save → VRC Enable Persistence isn't attached, or the variable isn't
[UdonSynced] - Nothing works at all → Persistence is disabled in the VRC Scene Descriptor
- It shows 0 only right after entering → Check that you rebuild the display in
OnPlayerRestored - Everyone's cards overlap visually → You gave the copy visuals. Do the display on the shared board
- Someone else's count increments → The
Networking.IsOwner()check is missing
Bonus: Good to Know Up Front
- Copies are made at the same position: They all overlap, so anything visible needs logic to offset it. At the introductory level, letting copies hold only numbers is safe
- Only synced variables get saved: A button's pressed depth, or the characters written into a label, don't persist. Rebuilding the display from a number avoids the problem entirely
- You can read other people's cards too: Pass another player to
GetPlayerObjectsand you get their copy. A board listing everyone's stamp counts is built this way - Use it alongside PlayerData: Simple settings like volume go in PlayerData; whole sets like an inventory go in PlayerObject. There's no need to pick one
- It's useful all over: Stamp rallies, per-person inventories, individual scores, doors that open with progress. Anything that differs per person and should persist
Summary
PlayerObject is a mechanism for handing every visitor a dedicated object.
- What you place in the scene is a template. You read and write the copy handed out at runtime
- VRC Player Object makes the copies; VRC Enable Persistence keeps the synced variables
- Find your own copy with
Networking.GetPlayerObjects(). Not by name - A copy's owner is that person. No ownership contention
The question to ask when choosing is: "Do a handful of values suffice, or do I want a whole set of machinery?" A whole set means PlayerObject.
For a mechanism that plays video, go to setting up a video player. To load text from outside, go to loading strings.