[VRChat] Per-Player State with PlayerObject: Your Own Stamp Card

Created: 2026-09-08Last updated: 2026-09-09

Handing every visitor their own object with VRCPlayerObject. Covers the template-and-copy relationship, what VRCEnablePersistence saves, and how to find your own copy.

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.

Two people each holding their own stamp card, with different counts

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.

Sponsored


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.

Store a slip, or hand out a whole desk

Here's the guidance for choosing.

What you want to holdWhat suits it
Volume, subtitles on or offPlayerData
Whether they've seen the tutorialPlayerData
Each person's score, and the logic computing itPlayerObject
Each person's inventory, and its displayPlayerObject

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.

Sponsored

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.

Per-player copies made from one template

The flow goes like this.

  1. Place the stamp card object in the scene
  2. Add VRC Player Object to it
  3. When a player enters, a copy is made for them
  4. The template itself is disabled at runtime
  5. 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.

ComponentWhat it doesWithout it
VRC Player ObjectMakes a copy per visitorIt stays one shared object
VRC Enable PersistenceKeeps that copy's synced variables until next timeValues 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.

Only the presser's card gains a stamp

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."

  1. VRC Player Object
  2. 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.

OrderActionExpected result
1A and B enterBoth screens show "0 stamps"
2A presses three timesA's screen shows "3 stamps"
3B presses onceB's screen shows "1 stamps." A's screen still shows 3
4Look at Unity's HierarchyCopies have appeared near the template, one per person
5Upload and re-enterIt 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.

Sponsored

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 GetPlayerObjects and 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.

VRChat Notes in this section63