#world-persistence
1 messages · Page 5 of 1
and 8 times as many bits!
confusion might be coming from the fact that PlayerObjects can store whatever, since they just save the network state
Oh the issue I had was me just assigning 60 addreses when it needed 61 :3
that would do it
Would it be hypothetically possible to save your data for one world and use it in another
no
Noted
You can, but you'd need to use a save system that isn't persistence.
you absolutely can, but you'd have to setup a web server and use string loading
You're implying using string get requests is the only alternative way to save data between VRC worlds.
This is misleading because there are many other approaches you could take.
what other approach do you suggest
you could certainly use textures and videos as well but i would group those under the same exact solution
You could
- use NUSaveState https://github.com/Nestorboy/NUSaveState
- generate a string that the user copies to save and pastes to load
- use an external program to read the VRC log file for saving, and then use MIDI or string get requests to load data
- etc.
youre right i shouldnt assume they want it to be fully integrated
i never tried to imply it was the only solution i simply provided one as nobody else had bothered to do it
saving to an avatar is a good solution and having the user store it themselves is fine if the data isnt that important, i wouldnt consider using any other option
password-based saves are meh...people always miscopy or lose those xD
if anyone makes a prefab for a local object toggle with persistence I would love to know
what do you mean? That sounds basic for PlayerData
Ive bearly messed with anything in the udon behaviours and even the basic stuff is very confusing to me. Ive been using this that i followed a step by step tutorial to make but would like to see if theres a way to either turn it persistent or if anyone has a prefab for an alternative version
that sounds overly complicated for a local toggle...Do you modify a lot of objects with a single button instead of having them as child of another one?
(I can be free in, about 10min if you want to work on that)
(still need to finish something right now)
but yeah it's easely doable to make it persistant
I have a second one I also used, I just didn't know which was better
Take as long as you need, i dont mind at all since i have plenty of other things to do atm
Yeah second one is more direct
Let see how new you are to this, do you understand the difference between the two versions you used? (I mean, difference in what they do, not just "there's that node here that isn't there")
(guess i'll have to open Unity if I wanna give examples later.. xD)
I think i understand the end result of what it does. One toggles multiple at once while the other toggles one. Both when toggling turn off everything parented under it aswell. As for the individual nodes i have a very rough understanding solely based on their names alone. Not the greatest
Rip me then lol
it's just that n°1 is an indirect call (so you can wire a string to call other custom events to Send node) and n°2 is what you'd call hard-coded aka this intecact can do this and only this
Yeah id want to use the second one then
usually the sender/button and manager are separate graphs
yeah unless you don't care and just hard code the buttons xD
is that big list of gameobject the lights, by any chance?
(just trying to figure out what you're doing)
Its not, i only had those out while i was editing an area, going to delete them after. Im wanting to make a toggle that turns off a water object have and persistantly keep it off as it cuases some people performance issues
Also for persistance you'll probably need a variation of the first method, not the second, since loading the data should activate the effect like an indirect call
Alrighty
wait a single object? not multiple ones?
I mean an array means you can add more to it later if needed I guess...
ok let not overengeener this xD
So, first of all the persistant part
I also wouldnt mind taking the time to do multiple, would be helpful to know
your version is for multiple since an array is a list of objects
instead of just listing one
so i guess it's future proof
For now the persistant part should be
Interact -> Playerdata "Set bool"
then Playerdata "Get bool" -> unary negation -> wire into "Set Bool" value
(same principe as what you're currently using, but we'll be changing the value that is checked instead of the objects directly)
PlayerData can be a bit of a mess to search, try "Pers" you'll see "SDK3PersistencePlayerData" category
I'll wait to see what you do before sending what the graph look like. Take your time to experiment yourself
@stable slate you still there? ^^"
Still there, messing with it. Im a hint slower at things like this but ill get there, apologies
I know this is wrong but its what I could manage and from what i could understand
couldnt get interact to connect to the get bool any
interact go to Set bool
you're almost there
then you connect the unarynegation to SetBool's value
Like this?
ohhhhh
I forgot to mention it, when you're too used to the system it's easy to overlook it as being common sense xD
I get that alot youre fine
this was supposed to be a local toggle, right?
Yes
yes but we were not at the part to chance networkedevent to a local one yet, we're arriving to it
there's no reason to do a network event, or serialization, then
guy seems new to it so I'm going step by step
you still need to request it for persistence to be saved, no?
no, it's separate, it'll be saved as soon as you SetBool
oh, neat to know...guess i have ton of unnecessary requests in my graphs then...
ok so like this then
@stable slate that will be the new call, separate from the action, you'll see why as we continue.
For the world to auto call the action, you need "OnPlayerRestored" -> SendCustomEvent
Which is a node that means "when player data have finished being initialised and is ready to use"
Don't use "OnStart" because the data may not be ready before OnPlayerRestored.
Make sure to check about local player so it doesn't trigger each time someone else joins ^^"
attempting lol
take your time to find the nodes ^^
Like this?
you don't nees the "send" to be networked, since it's only for a local call
look at the pic I sent just earlier
gotcha
then for now you need a key name, aka the variable's unique name, so the game knows what to call/save
be sure it matches in Get and Set
Ill set them both as cupcake for now
you can put any name just make it something easy to remember
done
ok, then you're t that step: #world-persistence message
that one should be easy to find
search "restored"
want me to also add the getislocal and branch in that image aswell?
yeah like that it only triggers for the local player, and not when the game "restore" anyone else joining
that's be redudant calls
ok, that part is done
now let rewrite the custom event
since the unary negation is done in the interact event, it is not needed in the custom event
instead, the SetActive value will be your new key's value
So you copy your GetBool and wire it there
the rest of the FOR loop stay the same
(except you can remove the request serialisation)
alrighty, attempting
Don't worry it's simpler than how I worded it xD
Im processing it slowly but surely
Uncertain what to do with get active self
fudge
and getactiveself isn't needed (but I guess you'd realise it afterward since it isn't wired to anything anymore)
The only problem with current graph is that "cupcake" is false by default. So players will have your object disable by default and need to activate it themselves. Which is good for those who have performance issue but maybe not for everyone else
Is it ok with you?
you could actually use that default value to your advantage, if the intended effect is to have it disabled by default anyway
yup
I would like it the other way around. have the water on when you first load in and have the option to turn it off
@visual cargo for that we just need the SetActive to be the invert of the bool's value, right?
sounds right to me (dont trust my word for it lol)
Since you don't "set" your actual value doesn't change here, it just reads as "do the invert as the variable's state"
Ahhh ok
that's the thing with playerdata's default state that can't be set in editor, need a lil workaround but it's not too bad
setting the state during OnStart "could" work, but you risk to overwrite your data
So, for now your thing should be working
but we can push it one step further if you want
So what exactly is the difference that the one step further does?
reread I *think i get what you mean, im willing to if youre willing to
making the graph reusable for different keys, without having to write a new one for each button
Ah yeah, definitely a good idea
it's usually a good thing ye xD
ok so you'll create a normal variable on the top-left
a String
and make it public
Then you wire it to the "key" on the nodes
Now your key name is this "string" you can set in the inspector
so you can set a different name per button, to be saved separately
for different settings to be saved as the player see fit
ehh that seems like a kinda confusing way to do it
what's the propper way to set a PlayerData as true by default then? ^^"
if you want to default it as on, you can use TryGetBool to check if the key exists; if it doesn't, then enable it to true
oh, so that's what the "try" are for? ._.
@stable slate see, everyone is learning a thing xD
Always and forever
did we just got hit by an ad...?
oh...a bot then?
I ment a normal variable
So string > normalize???
you're searching for nodes, not a variable
hit the little plus button in the Variables section in the top left
yup
🎉
now your graph should look something like that
you can define the objects and key name in inspector (on the right side)
be sure to use names that make sense to understand what does what when you'll look at the graphs again in 5 years... xD
<@&397642795457970181> #world-persistence message
I think we have an ad bot here...
thanks
Looks like its working, and thank you for taking the time to walkthrough it like that, that and for the patience, lets me really soak in what im seeing
one last question before you leave, now that you understand a bit better
you see why we usually separate the "interact" part and the rest?
you can have a generic graph just calling the key name and event, that's just "button" graph.
Then whatever effect is on the manager graphs
there's also an actual button UI element in Unity but it's a bit confusing to use for a newbie
Vaguely yeah, definately a lot more than before, very important
For now this method will work for you
yeah im sticking to my weird ass clickable cubes for now lol
can't blame you for that, been sticking to weirdass clickable cubes for 5 years... xD
xD
all right if it's all let not flood the channel more, have fun making new graphs with that knowledge ^^
After all you just need to change the custom event's part for other effects to happen
I feel like I don't understand the basic flow of playerObjects.
I have a player object that has the persistence component. In postLateUpdate, I'm getting the players location and saving to a synced vec3, then requesting serialization. According to the docs, I should be able to use OnPlayerRestored, check for local, and then do my functions with the data.
Instead of teleporting to the location that should be loaded, I'm not moving at all.
If I instead do it with OnDeserialization it works. Are the docs not right, or is OnPlayerRestored just mean to be used to flag that the player data has been loaded in the first place?
OnPlayerRestored only runs the first time that a player's persistent data is ready
OnPlayerRestored Is how you can tell that data has been loaded from the server after joining.
If you save new data before receiving this event you'll be overwriting their save data.
also requesting serialization every single frame probably isn't a good idea
may just want to have that continuous sync instead
players location? for what? why not make remote attach to your player location, no network needed
sounds like to save the player's position and restore it later
and putting that functionality into a PlayerObject requires you to network the data
oh
this might be easier to do with PlayerData honestly
was the value overwritten by accident in postlateupdate? before OnPlayerRestored?
No??? They were just asking about the purpose of the OnPlayerRestored event.
Please don't.
You'll be synchronizing the entire thing constantly.
A player object is the sane way to approach this.
yeah, that's why I moved it
I didn't know writing the data before load stopped the load data from applying, that helps
......and a PlayerObject isn't being synced constantly?
you don't need to sync every frame. You can easily have an autosaving interval
and this component should be duplicated for every single player?
and every single object should be running a sync every single frame?
If you have any other big data in the playerData, that also gets network synced to everyone
So doing that at like 10hz would eat all the bandwidth
I'd love to just sync the position on playerLeave, but I don't think that's reliable?
sadly that is a specific time that does not work
Kind of frustrating, because that's exactly when I'd love to save data
just set the PlayerObject to have Continuous sync and you won't really need to worry too much about it
The issue with using PlayerData for stuff that updates often is that it has to send the entire player data even if you only changed one value on it.
If you use a PlayerObject instead then it only sends the data required to sync that specific player object.
If you have multiple systems all using Player Data it will very quickly get expensive on bandwidth to update it regularly.
PlayerData should usually not be used by prefabs, and definitely not for data that changes often.
There is an instance of Player Data per player too?
and are those instances going to be running a check in Update to find a player's position?
this all depends on how fast you want to save this information
I've got a world that syncs the data every 5 seconds with zero issues
if you need it multiple times a second, then a PlayerObject makes more sense
my main issue here is the skull react and implying that my suggestion is insane? Was that really necessary? There's clearly a use case for PlayerData here that makes sense. An implementation can always change significantly depending on the desired outcome
If it was only the player position, then I'd agree that I could probably get away with it. I also have like, multiple race courses worth of record data that's in playerData, which I'm at fault for not mentioning, that makes the playerData direction kinda really bad
... running a check in Update ...
Why are you so hung up on me telling them to do anything inUpdate? I never even said that lmao.
Please point at the message where I tell them to use Update for any of this x)
you didn't say that, this is how VR Floorsign described how their current code is running
In postLateUpdate, I'm getting the players location and saving to a synced vec3, then requesting serialization
Exactly! I didn't say that! :D
I skull reacted you suggestion to use PlayerData for persisting values that update regularly because it's bad practice.
Just because it's bad practice doesn't mean it can never work. It's just simply a bad idea to give that advice to people that won't know better.
There really is no need for persistence player position to be anything faster than once every a few seconds
No game is that critical that you lost a few seconds of progress is end of the world
You can even have one respawn point every room, and only save which room you are in, so you dont even need to sync more than once
Correct! I don't think this should be saved more often than once per a few seconds at most!
And I still think updating player data at that rate is ✨not good✨
yeah serialization wont even handle 15 20 Hz, it certainly cant serialize every frame
can I kindly suggest that you stop doing that then? At least towards me.
It's obvious that certain methods of achieving something won't work under certain conditions, but work perfectly fine under others. As you're even just saying here.
Drilling down and suggesting multiple methods in order to achieve a particular goal is inherently going to produce suggestions that end up not working. To just react with a blanket-statement of denial at a suggestion is just rude and unhelpful.
Noted! Sorry that I made you upset. I will avoid using silly emoji reactions and instead explain my thoughts like a normal person.
but if one really need absolute latest data saved, continuous will be the least impact mode because it is bandwidth managed automatically
playerdata is just like manual request serialization, so it will be impacted by how much data is in your playerdata
Are you implying that continuous sync has an inherently lower bandwidth cost than manual sync?
not necessarily lower, just automatically managed and can't throttle and queue up and lag everything else like manual if you sync too much or too often
or rather it is throttled by itself
so it will be latest possible, whereas manual you can tune your frequency all day to try to get close to the bandwidth limit and not affect gameplay and try to get more frequency but you are going to taking up bandwidth if you try to go fast... it can only be as fast as the bandwidth can handle, and continuous only sends as fast as the bandwidth can handle
for transient data like player position continuous is more suitable because it can drop packet if needed and interpolate... and that is still true for persistence (not the interpolate part but you dont want to lag the game just because you want to save the last possible position for the next game)
so if you want latest, continuous is practically latest possible
whether you need to or not is another matter, but if you want latest, that is the theory
Might change to manual on a two-three second timer
Manual sync allows both lower latency and higher sync rate than Continuous sync.
This is not true.
you can sync with manual faster and use more bandwidth yes, but if you want low bandwidth, continuous is the best low bandwidth it can get because it only takes up as much bandwidth as it needs
So you admit that continuous sync is not the "latest possible" option?
we are talking low bandwidth situation because you asked about lower bandwidth, with the same bandwidth, continuous is better at handling latest than manual (at a certain frequency) because continuous is throttled automatically to be the best frequency it can get
you can use more bandwidth to go fast, at the cost of more bandwidth, but for as low as possible bandwidth but still giving latest data, continuous is practically designed to do that
if you are going to request serializations at 15 Hz for the latest possible option and lag the game then by all means do what you want, but for non-impacting bandwidth, just let continuous does its job
that is practically latest possible
Do you have any evidence to support this claim?
i dont, ask a dev
Lmao
that is what i was told or what i can understand
also playing with manual serializations, it doesnt have any magic power to send more data at the same frequency than continuous, it is only how they are managed by vrchat
you can go high frequency and get throttled and get delayed and actually make everybody use more of their bandwidth too
continuous is just a mode for syncing things that needs to be continuous and it is optimized to do that job
at least in theory
so if it isn't, it is the devs fault
because i don't usually use continuous i was a little curious to measure it; at the same data size (a long in this case) continuous has more overhead than manual at the same send rate. it can step back on the send rate to some extent from what i'm seeing, but doesn't prevent clogging
Oh neat, like a %6~ improvement
in reality if your sends are sparse it's not really good to use continuous (it sends continuously as the name suggests); if they're continuous i think that's up to the user (i don't like continuous personally)
I find I have to bang my head into the same problem a few times to understand the reasoning behind why I'm doing certain things to solve it. The latest things has been "Do I really need continuous on this?", looking at prefabs I've pulled in like six months ago
Afaik Continuous sync is mainly designed for use on pickups / objects with rigidbodies.
For example if there's a sleeping rigidbody it will pause syncing entirely.
It also has special handling for pickups. Continuous sync will match the sync rate to the player's sync rate when held.
This is to better align the pickup with the player's hands.
Desktop players and VR players have different sync rates, so it will behave differently when picked up by a VR player vs a Desktop player.
For high frequency updates, the sync rate (or the interval or the Hz) is dynamic and adaptable and managed by vrchat on continuous mode, but is static on manual mode (and optionally managed by you in Udon which is not practicable)...
So by design continuous should behave better for such purpose for most people for most situations, that is a "feature". If it actually performs worse than manual generally for high frequency updates, that is a "bug".
So I suspect it is only sending more stuff than manual under ideal and highly controlled test conditions, whereas on the road, it should cause less delay and use less bandwidth overall because it is supposed to be adaptable as a feature.
@lyric kraken Just curious how was your test setup, was the continuous interval constant during the entire period (I doubt it because network condition is never constant)? And how did you set the manual to match the interval at 4.26 Hz (auto or by hand)? I suspect the difference may be due to variations in conditions (or vrchat hiccups with its background network stuff)
And at another level, I feel we should leave network balancing to vrchat, if we are manually doing it, we are essentially replicating the same function, and 1. it may throw off or conflict with vrchat's balancing and 2. if vrchat updates in the future and have better balancing, the manual balancing might need to be retuned therefore not future proof
We can make the design decisions to have less updates needed and choose manual update every few seconds, and manage network usage this way, rather than actually doing balancing by hand, which I feel can be too unpredictable because vrchat networking is too much of a black box to be engineerable
Considering the discussion seems to be sidetracked, a lil recap about the actual question:
- OnPlayerRestored
("the data has been loaded and is now safe to use" aka load after joining,
basically persistence's "on start" equivalent, trigger once) - OnPlayerDataUpdated
("do something IF a change in data have been detected"
useful to save "when a value changed" rather than every frame, and do checks without a loop)
Looks like your system could use a slow loop (custom event calling itself every X seconds) that you initialise with OnPlayerRestored to avoid overriding the data early.
For the part saving the Vector3
(don't need a DataUpdated here, just wanted to include it earlier if you're not aware of it)
Then you add a "teleport player to [Vector3] position" to OnPlayerRestored to TP them on join.
But don't overthink it, you don't need to save every frame for a custom respawn.
Addditional notes:
||You could also add the same teleport to "on respawn" if you want a dynamic respawn point I guess? (but you need a check if you exclude situations you may be stuck in a falling loop then. checking if the player is grounded is the simplest one even if not foolproof)
You could however have a bool (a normal one, unsaved) to define if the system is on pause, for areas the players can't save in. No idea if you're making a social or adventure world, but it have its uses (like not saving in a private/VIP room, or in a puzzle room that may accidentally softlock).||
The fact it worked during "OnDeserialisation" is unrelated to being saved. It's simply because you made it read the data after changing the value. Could be a custom event, would have worked as well.
(you guys can go back to the ted talk about continous sync, sorry for interrupting. xD)
I guess it is a matter of use case, and some use cases are what continuous was meant for... transient and or interpolatable states, that needs to be latest possible, but flexible and responsive to variations in network conditions, then continuous should be the choice.
If something is high frequency but needs to be distinct and precise and not flexible to network conditions e.g. real time stock market values, then I can see the merits of high frequency manual sync, because the latest data is important enough and rate of data is constant enough to justify being sent constantly at a certain high frequency.
Thank you for coming to my ted talk, please clap
i'd urge you to measure it yourself if you think averaging isn't sound; for reference this was done with a synced long (int64). the difference should be apparent just from the fact that the total bytes have a gap for ~the same number of sends
the interval is now minus then, with "now" being the current time at the point of send and "then" being the last send time, the middle 3 stats are provided by the new network stats when passed a gameobject, the count is incremented post serialize
at minimum, one can work out the average bytes used per send without relying on the measured interval at all. divide the total bytes by the send count. that's the average byte cost per send. multiply that by some rate if you want the bandwidth use for a given rate
i would agree if someone said using continuous is easier to get something running or for someone who doesn't want to manage their own request serialize calls, but it's kind of irresponsible in terms of bandwidth for something that doesn't require constant updates or use vrc's object sync
saying that continuous is "latest possible" isn't true and i'm not sure how else to put it. continuous does have some level of flexibility for the rate from what i've seen, but it doesn't step back enough to stop clogging if that's the concern
continuous can't use arrays, is limited to 200 bytes and blasts data continuously outside of object sync. i don't think the overhead shown is even much of a factor against continuous, it may vary for different data types, but it using data constantly is the important thing to consider
continuous is okay if you're doing something it's good at and don't need any manual control. i personally don't use continuous very much
btw OnPostSerialization fires for continuous sync?
huh, it does, i thought it is manual only
I'm pretty sure it fires all those events including OnPreSerialization and OnPostSerialization
What kind of rate limits are in place for save frequency of persistent data?
Same as normal Udon Sync.
If I ever do any persistent stuff I really hope it’s just bools. Reading this channel has me slightly worried.
ints and floats are fine too, don't worry too much about it...
Bwahah, I totally understand that.
If you're planning to persist a lot of bools though you should consider bit-packing ;)
What’s that?
It's a technique that allows you to store the bools in a way that takes less space on the computer.
Oh, nice
I want to use the bools to store whether or not somebody’s completed an achievement
So hopefully won’t be having too many of them
it’s really easy to save persistence data for basic variable types like bool, int, float, etc.
it’s practically just save after changing the value, load the value in OnPlayerRestored
Yeah, depends on what you do with them.
I remember it was a hassle to make the "upgrade tree" to building a house because things don't just get turned on, but some stuff also turned off (to be replaced) so to be sure everything was correct I had to basically run the effects in sequence instead of just highest number.
A whole wacky set of custom events ("load_0", "load_1" "load_N") linked past the inventory values changes of the corresponding action event ("upgrade_0", "upgrade_1" "upgrade_N")
So when you rejoin you'd basically see your building be speed-built for free instead of just appearing complete. Which not gonna lie is accidental but would look cool if it wasn't happening behind your back... xD
I've seen a similar effect in Roblox tycoons, probably using the same sort of mechanic
Yeah it's pretty common, if not for the same use case at least to spread the generation throught multiple frames and avoid it being too heavy. Because that can be a lot of objects called at once if you don't delay
I just begun to use persistence and have a question. That graph here works so far...you become owner of a coin, it get's deactivated by bool, that is then shared. Also coin as value is grabbed, 1 is added and it's set to it and then it does 'pling'!
It works...but does it mean if there was no coin data before in playerdata, but I SetInt with the name 'coin', it's just created then? and if it's already there, Unity just grabs it?
If a key does not exist, the default value for that type is returned. For example, calling PlayerData.GetInt() would return 0.
interesting, so it's kinda created in the moment it's GetInted or the moment it's SetInted? I read there is a limit of some KB of how much UserData is available for a world? ^^ Anyone has an example how many Ints that would be?
are you asking how many separate ints you can store or more like the max value for an int?
each world can save 100 KB*, each int is 4 bytes, so that is at least 25600 ints
* or 200 KB if you combine PlayerData and PlayerObject, and before compression
playerdata also has the cost of storing the keys to access those ints. Though you can reduce that by serializing them into a byte array and referencing them by number instead of key
The data is limited after compression, not before.
oh? so the length of the key (string) also takes up quota? interesting, so there is incentive to use a shortest name possible?
Yes
Sounds cool tho, so I can store multiple scores if I'd like in future. I have not found much documentation about persistence. More like what it is, but not much about a tutorial how to use it nicely (and if, I bet it would be no tutorial stuff with graphs then for advanced stuff like persistence x3)
specifically with playerdata, yes since it's all set up at runtime. Playerobjects on the other hand have optimizations to store the keys in metadata instead, which does not need to be synced with the main bundle
so assuming someone uses key names like "GameName.IntName" which is say ~16 chars, so 32 bytes, plus the 4 bytes for the int, that is 36 byte per int saved, that is ~2844 ints which is still quite a lot
yeah, 100KB is a pretty hefty amount for preferences, settings, and state. It's certainly not enough for large-scale data like images, but if you're individually inputting bools and ints and floats, you pretty much never have to worry about it.
If you do want to worry about it and you have large string keys, you could build a little translation layer for your internal systems that converts large strings into small hashes. But I wouldn't bother unless you have hundreds
Does OnPlayerRestored trigger for the owner when they create the instance? Been noticing that when I load up 2 clients in a build & test it only seems to be interested in loading the remote player's data instead
or is this something to do with the remote player hijacking the owner's method that is being called?
Does it output a vrcplayerapi variable?
I havent used it yet, but If it does, then id assume its called globally, so youll have to use an islocal check
Feel free to test it by output logging its displayname or something
well rn it looks like this
public override void OnPlayerRestored(VRCPlayerApi player)
{
Debug.LogWarning($"{player.displayName}");
if (player.isLocal)
{
player.Immobilize(false);
}
else if (Networking.IsOwner(gameObject))
{
playerListLocal = playerData.SetupPlayer(player, playerListLocal);
gameManager.SyncData(playerListLocal);
OnPlayersChanged();
}
}
It does yeah, but basically I'm getting the Owner to load in not just their own persistent data but also everyone else's that loads in to the world through the method SetupPlayer, then after that it sends the data over to request serialization in another script in the method SyncData.
Thats what I suspect, since I made sure only the Owner is running it to add persistent data to Serialize into json. I think it's forcing the owner to interrupt the method every time a new player has restored their data.
I had the thought that maybe it could be placed into a queue system when loading the data one player at a time but idk how that would look or if it would work in this context
🤔 I guess a player queue system wouldnt be a bad idea for this
Usually I just make it point at local player and it sort itself out, because it runs locally when the new player joins.
is GetPlayerObjectComponent supposed to be local only or am I doing something wrong here?
I am constantly getting errors when testing in-game about Object Reference not set to an instance of an object :/
You definitely have to show the code.
Also detail of the error.
from what it seems it knows player exists since I did a isValid check earlier in the code, so my gut feeling tells me _player is not valid because it can't find the object component
mainly what ReturnHasPlayerLoaded is doing is returning a simple bool from the player object
unless there's a different way to get this variable without GetPlayerObjectComponent 😓
that's not like a vrc call, you're using something extra there (persistence utilities), it might search through, but it's not apparent
if it's possible for that to not be instantiated yet (like for a player object) you should be checking against that being null after the get and early returning false prior to trying to call into the script
Yeah. I'll give that a try after the get to see if that works. Originally the code anyway there was originally meant to ensure the player data has actually loaded but I guess it still somehow bypassed that check 😂
that worked! thank you ❤️
How difficult is it to save a string (URL) via persistence and load it as a URL?
You can’t construct and load URLs at runtime so I don’t think that’ll work
But how come just b club can load urls from an external link?
Their patreon lets you setup a playlist with 5+ videos
Uhh I know there’s a difference but I can’t explain it 🙏
VRCurls can be stored as persistent data in playerobjects
i figured as much
I have a very peculiar issue that happens within my PlayerObject.
Here is what happens:
I have a Pickup with an udon script on it. That script calls update each frame. I do that in order to add a value to different script.
BUT it turns out, that method is being called more than once each frame! This only starts to happen after a while of playing, and in PC VR from what I've been told.
It could happen on other platforms. I'm not sure and it's obviously difficult to replicate this issue because it happens basically randomly.
And yes, I have confirmed that this happens for sure. I logged some information, like which instance ID of the component is calling it and even the path of the hierarchy, just in case. We observed multiple calls come from the same object each time. I even saw someone have 3 calls each frame.
Afterwards I started tracking the amount of calls to see if there is a timing issue. It's usually green (1 call each frame) but at some point it starts to be red (2 calls each frame):
A user even managed to get magenta (3 calls) like this:
I'm not sure what I'm even asking here to be honest. This is just such a weird issue I don't expect anyone to have an answer. There is still a chance I overlooked something in my own code, but I suspect something else is going on.
it's definitely Update where it's being called, and not FixedUpdate?
It's update
can you show in more detail how exactly you're measuring the number of calls per frame?
So at the time of the screenshots it was like this:
Script One:
void Update(){
script2._AddValue(value);
}
Script Two:
public void _AddValue(float val){
amountCalled += 1;
}
void Update(){
displayDebugInfo();
amountCalled = 0;
}
so that would be measuring the number of calls script one gets per call of script two, but that's not inherently connected to the number of calls per frame
which means there's got to be something weird in the difference between those two scripts
is it a race conditition with Update? like the order that Update gets called across scripts isn't guaranteed?
could be something like that. Do these two scripts have a defined execution order?
I know it's not exactly the same, but if we consider that both run normally without anything else, then they should run basically around the same time?
I have considered race conditions, which is why I added the screenshots above. If I'm not wrong, some of the frames should be skipped if the ordering is wrong, or am I wrong?
Ah just to clarify, green means 1 call and grey means 0 calls. There are no greys so it means there are no race conditions, unless my mind is getting something wrong 😅
yeah it's definitely something weird, if it was only race conditions you'd see gray
I'd take both scripts and compare them against a more solid baseline, like a render event
I'm also 95% confident it's not the code doing something wrong. I obviously left out some of it, but it's basically just an if statement if the button is held, and then some calculations for the speed. But the debug text definitely showed me that the speed is doubling/trippling. That's how I got to know this issue because users got surprised by the reel speed increase.
The only notable thing is that the pickup (script 1) switches its parent from time to time.
I'm not sure what that means, sorry
well, if your goal is to measure "events per frame" then your baseline reset should be an event connected to rendering a frame, not some other script which is also on update
Do you mean like testing it inside just the script 1 update? Not sure if that's what you mean. But I will try and test if it's the actual update that is being called multiple times, or the event on script 2.
Unfortunately, this takes a long time to even encounter, so this will take some time
Ah, but it should be the event that is being called multiple times. Because the same value is being passed multiple times. In script 1, I do a calculation of movement and track the previous position after all, so it shouldn't pass the same value if it gets called immediately again.
hm, could also attempt to measure weirdness by tracking time since last event
So simply Time.time should be enough, right? From what I've tested so far, I'm fairly confident it will show 2-3 calls with the same timestamp. I've done this to try and see if the same component is calling it (so it's not instantiating another one or something):
But I'll check it anyways just in case. I'm realizing it's not as clearcut.
It's just honestly, I feel like I'm wasting too much time on this when I could just block the call when it already received one that frame.
Time.time is the frametime, so it'll give you a consistent timestamp no matter when it's fetched in the frame. Getting multiple updates with the same Time.time would definitely be weird.
Time.RealTimeSinceStartup is slightly different in that it calculates the time at that very specific moment, and you can use it to measure imperceptibly small units of time. Stopwatches operate similarly. You could use those to see the precise gap of time between updates, even if they're multiple times per frame
Okay, I'll use that instead. I see there is a double version too for that. Let's what happens...
i think you should consider frameCount if i'm understanding what you're trying to test, something like this would automate it decently on the script alone
private DataDictionary framesFired = new DataDictionary();
private void Update()
{
RecordFrame();
}
private void RecordFrame()
{
int thisFrame = Time.frameCount;
int count = 1;
if (framesFired.TryGetValue(thisFrame, out DataToken countToken))
{
count = countToken.Int;
count += 1;
Debug.LogWarning($"[{thisFrame}] has fired {count} times!");
}
// Always record count
framesFired.SetValue(thisFrame, count);
}
private void LateUpdate()
{
// This clears on n Entries
if (framesFired.Count > 64)
framesFired.Clear();
}
So the test gave the following results:
I added a second tracking of calls, but this time inside of script one update. It has the same pattern so we can assume that update is called multiple times here...
The second screenshot shows the time when calls were made. These were tracked in script two. I know it's a bit difficult to read, but basically the arrow means it's the first call. Further calls in that same frame don't have an arrow. I tracked it again by using script two's update method to reset the amount of calls.
As you can see, the difference between script two updates is 0.03, but double calls have a difference of 0.0003.
Yeah, that's a good idea, I probably should've tracked the frame num too just in case. Would've made the output a bit more clear 😅
But yeah, I think this kinda proves that update is being called twice on that pickup?
I don't really want to spend more time hunting this down. I hope this can help VRChat or even other people be aware of potential issues.
you're running at 30fps here?
I don't know, those are from a player. Would that matter though?
i just find it odd that they'd be seeing a ~0.03 interval between update calls, if they were actually running 30 then it's valid. i don't think measuring the interval is definitive that you were getting multiple updates per frame as i'd consider that somewhat unlikely, but i can't say it's impossible in some odd udon situation either
No, I already counted the amount of calls in a frame and identified that there are multiple ones (for whatever mysterious reason).
I'm doing the interval test afterwards, to try and see if there is a timing difference between the calls.
And the result is that there is really a pattern there. Each frame is 0.03 apart, which is pretty reasonable at around 30 fps in PCVR, probably in a full instance. The problem comes from those duplicate calls that are called much much more closely to each other.
if this is printing time.time or the double equivalent, i would expect those numbers to be the same for same frame calls, like time.time doesn't advance between frames
I'm using realtime since startup as was suggested above
Yeah, I should've probably also tracked that or the frame counter just in case. That would've been more clear 😅 But I think the pattern can be seen already even without it.
i don't think it'd be impossible to get a call on the back of update one frame and another on the front of update 300 microseconds later in realtime, like what you're saying makes sense, but i think without seeing like "this update loop prints frame, frame is shown multiple times" it isn't apparent that update is firing more than once
like without a defined execution order it'd be possible for your script 2 to clear prior to an increment call on script 1; i think you said you already accounted for that with gray color prints, but it's not apparent how large your color swaths are for a frame
Of course it's possible, but is exactly what the colored line is for. As I said, gray is 0 calls, green is 1 and red 2. There is also magenta for 3 which definitely happened and should not be possible even if as you say the update execution order flips from time to time. In the end, it should always math up to 1. The problem is that it just turns red, so it always calls 2 times each frame. Sometimes even 3...
Trust me, they are large
Like this, the red one is one frame
Okay, I'll do a final test. This time only in the pickup update. I feel like what I've done so far was to complicated, since I had to slowly feel myself into wtf was even going on.
Okay, so I vastly simplified the test. FYI @wary summit, I got more clear results:
When the bug happens, Update is being called twice permanently! So the debug text just shows a 2 while LateUpdate still shows a 1. FixedUpdate jitters a little so it's not as clear from the screenshot I received if there is also an issue there.
Again, I don't know how to replicate it just seems to happen randomly for PCVR players at some point during gameplay, usually in bigger instances from what I understand. It could happen on other platforms but who knows.
The only remarkable thing about this setup: The script is on a pickup that is being reparented from time to time. It's also attached to a PlayerObject.
that's odd, i wasn't able to replicate it with a player object pickup rapidly changing parents, but i wasn't testing with more than 2 clients; about fixedupdate, it can fire multiple times by design, but i guess the implication is that you suspect it could be doubling?
Hm, that sounds like it's getting subscribed to the dispatcher twice over
Perhaps there's something you are doing to it which causes that. Do you ever disable it and then re enable?
Yeah, this would've been impossible for me to track down if I didn't have help from players. It's very inconsistent and the only commonality is that it happens eventually... And yeah I was implying the opposite, I don't think FixedUpdate is affected (but the screenshot is unreliable).
Not the pickup itself, but I do toggle the parent.
i was able to replicate it somewhat reliably in a test, going to test it a bit more though
i will say that it's not actually the same frame in my case, but an increment in update and a reset in lateupdate sometimes results in a value of 2
here are 2 instances of it happening
as there are also cases of a 0 value as well at the point of lateupdate; i suspect (in my case at least) update is triggered, the gameobject is disabled, lateupdate fails to clear the value or something similar?
(in unity, disabling an object in update will actually fail to fire lateupdate for that frame, i don't know what vrc does in comparison)
i haven't been able to lock the state into consistently firing multiple updates per clear at this point
disabling the parent object (on top of reparenting) happened to be the additional thing that started causing it to happen in my test (and checking for an increment/clear value, checking frame count alone in update never raised flags)
i suppose the practical effect is that update can fire "multiple times in a row" if you rely on a later loop to clear something. i don't have the exact conditions to trigger it yet
this is what prints in mono vs udon typically (the choice of frame 816 is arbitrary)
are synced variables on playerobjects persistent, regardless of whether or not i manually save them with playerdata?
#udon-networking message
in my LeaderboardSlot script, the regular score variable seems to be persistent, even though i never directly tell it to save in playerdata. i want the score to display for all players on the leaderboard, but i don't want it to be persistent
the "score" is supposed to reset every instance, but the "highscore" should stay persistent
synced variables on playerobjects are always persistent, if you add the VRCPersistence component
otherwise they aren't
oh okay, so i should remove EnablePersistence then. would the highscore still save in playerdata if i remove that though?
yes, PlayerData is separate
got it
So the double call happens only once? This does sound a bit different from my issue and might simply come from disabling the object, yeah.
in my case it's happening in single instances, but i'm also rapidly reparenting and periodically disabling the parent object to trigger it at random, i don't have a concise way to make it happen atm
vrc's loops seem to differ from unity in how they're handled (as in, disabling an object in update in vrc does not normally seem to stop it from running lateupdate that frame), not sure if it's the same thing as yours or not
Honestly, it doesn't sound like it. I don't rapidly toggle things for the difference in update behavior while being disabled to matter. This is just an item players can pull out, although the hierarchy is a bit more complex than that. But the item should just be enabled at all times during use.
it being disabled didn't seem to directly affect it being triggered, idk, like in my second picture i don't know that it's related to it being disabled (the disable running every 100ms on the original parent is debug logged, but it wasn't parented to it at that point), the presence of it being disabled at some point did alter it though
I'm a bit confused what you mean by altered. It's not like the object just changed by adding the disable? But either way, this feels like it's going to be rough to debug for sure, I'm already exhausted talking about this. As I said, it just randomly starts calling it twice, independent of any calls or toggling. The reason I discovered this is because people reported the mechanic literally being twice as fast permanently for some reason. And the debugging showed update is being called twice.
stopping the periodic disable no longer prints a double increment in my case, that's all, sorry; i wanted to show that i found something that exhibited similar behaviour and why i felt it could be behaving that way
How exactly are you toggling it then?
Like are you doing it multiple times in one frame?
on the object itself (the player object, pickup), this is the parenting behaviour, in my case it's about 14 scene objects, it happens to include null (base scene/no parent) as well as the original parent and some other unrelated scene objects that are always active
private void FixedUpdate()
{
if (!shuffleParents) return;
if (Random.Range(0f, 1f) > 0.75f) return;
transform.parent = targetParents[Random.Range(0, targetParents.Length)];
Transform parent = transform.parent;
string parentName = parent == null ? "null" : parent.name;
Debug.Log($"[{Time.frameCount}][{nameof(FixedUpdate)}] Reparenting to: {parentName}");
}
on its original parent, the toggling behaviour (i forgot that i'd randomized this within a lower range)
[SerializeField] private GameObject parentToEnableDisable;
[SerializeField] private Vector2 minMaxActivationTime = new Vector2(0f, 0.333f);
private bool currentState;
private void Start()
{
currentState = parentToEnableDisable.activeSelf;
SendCustomEventDelayedSeconds(nameof(_ToggleParentObject), Random.Range(minMaxActivationTime.x, minMaxActivationTime.y));
}
public void _ToggleParentObject()
{
currentState = !currentState;
parentToEnableDisable.SetActive(currentState);
Debug.Log($"[{Time.frameCount}][Disabler] Setting state of original parent to: {currentState}");
SendCustomEventDelayedSeconds(nameof(_ToggleParentObject), Random.Range(minMaxActivationTime.x, minMaxActivationTime.y));
}
Uh, so simply put, you're randomizing the reparenting and toggling.
I guess that makes sense but I don't see how it would double increment it.
it's pretty chaotic, i was attempting to trigger it, i would like to get it down to the exact order of operations required; as vrc doesn't seem to follow the same disable behaviour for loops within a frame i'm not sure how it's happening at this point
and as you said it's not permanently stuck in the same way, i had a version where i tried to keep it in that state after finding a double value, but i wasn't able to
Well again, it sounds different then. Unless you accidentally found a way to reverse the behavior or something lol... A user did get it up to 3 calls an update btw.
Also, did you use Player Objects? Because that's the parent of the parent of the structure I use. The reason why I used this channel is because I was suspecting that there is some faulty registration of the update method related to Player Objects going on.
the pickup itself being checked was a playerobject, the parent and above were not
Have you tried looking at Time.time of each incident to make sure it is actually the same frame and not the result of something else being weird?
whoops, not search bar
Does anyone have any ideas or thoughts on how to save URLs persistently?
I'm basically wanting to be able to Save/Load persistent URLs, so everytime players come back to a world they can load those URLs, but I'm having trouble figuring out a successful way to do this.
Any help would be appreciated! I'll also provide any info you'd like or need additionally!
The only world that immedately came to mind as doing this and it working is one of the Karaoke worlds, where you can save custom URLs.
you can store persistence vrcurl via PlayerObject (not possible with PlayerData)
Does anyone know the caveats of having multiple UdonBehaviours on one GameObject that's using VRC Player Object/VRC Enable Persistence? Yesterday I tried using multiple behaviours for a persistent object but found that the object's state wasn't saving properly with more than one behaviour on it. I've since made a few modifications so I don't remember what the component ordering looked like.
For context, I'm implementing a shop system where players get persistent pickups/cosmetics. I want all items to run the same common script to manage persistent variables (mainly if the player has purchased the item or not) and their active state in the hierarchy, and then a second script that provides item-specific functionality.
Right now I'm using a workaround where each item's root GameObject is a (otherwise) empty GameObject only running the common script, and then a child is present that has the Pickup components + the item-specific script. This seems to work but I'm curious to see if I can just have both scripts on the same object.
I don’t understand your reason for using two scripts in the first place?
What does not saving properly mean in your case? But yeah, multiple scripts on one objects is usually fine. Persistence might affect that though, you should test it yourself if no one here has yet.
Ah, but if you manage synced toggle state then you should probably attach that to another object, not the pickup you're toggling. This way you will always ensure the toggle sync sends and receives data properly.
it comes from a bit of an object-oriented point of view, all my shop items have common traits like like purchase status and item name. Then they all have their own individual functionality, and I dont want to bloat one single script with all the items' individual functionalities.
the common script keep track of the purchase status variable (i.e. purchased or not) using the VRC Enable Persistence component. In ClientSim this variable would save properly - if I bought the item, then quit and reloaded client sim, it'd remain purchased. However in the actual VRChat client if I bought the item and then rejoined the world, the item would be unbought again
I feel like you'd need to narrow it down more. Like change and save variables on both scripts. Then rejoin to check if both of them load the data properly or only one.
Or just split up the scripts into different game objects. That's what I usually do.
yea, currently they're just on different gameobjects
didn't do any testing, but as I implemented something else I realized there is a non-zero chance my common script was on manual sync mode and I never called RequestSerialization
building onto this. I also forgot that if you're doing manual sync, I believe the object needs to be active for the sync to actually go through
Just posting my observations regarding an item I want to allow my players to purchase and persist across instances:
- If the player purchases the item, and the item is set to be inactive initially, the player's purchase does not save if they never toggle the item on (since I used an UdonSynced "purchased" boolean that is meant to synchronize on purchase). Therefore, the player will have wasted their currency if they leave without ever toggling it on.
- The item's active state persists; that is, if the player has the item active before they leave, it will activate itself when they (re)join an instance. If the item is inactive before they leave, it's supposed to be inactive when they (re)join. However, disabling the item prevents it from saving its inactive state via RequestSerialization, so the item will enable itself on join even if it was disabled on leave.
I think this is ultimately what caused my issue above (regarding using multiple behaviours on one object). I'd have to create a multi-behaviour object to test and see for sure though
If you're doing any sync the object needs to be enabled.
Your scripts that use udon sync or persistence should always be on GameObjects that are enabled at all times.
If you need to disable parts of it such as the visuals, then you should only disable the visuals.
ooo yeah, good point
I use the Jump Counter from the persistence preview packages but instead of jump I count the variable "coin" with it. It's so often weirdly close to each other with numbers and I was already way higher, so something is resetting it and I wonder why?
if you run into coins, you get one...
enemies and falling out of the level costing some...
But there is nothing that could cause numbers to align...cuz then all makes no sense ^^"
on PlayerObjects it's the same as any usual synced variable
for PlayerData, the only available array is a byte array
oof
I did figure out a dumb/lazy way of having an array in PlayerData.
Just looped through the array, and saved every element as an individual variable, and named it as Item[array Index]
then when you load PlayerData, just loop through again and assign all of the values back to the array.
arguably less work than figure out splitting up your data into bytes and putting that into a proper array
is ur world public? I wana try it :3
yes it is but I am afraid that the coin thing is not working. I just used the prefab of the jump counter in persistence examples and used another variable, grabbing not jumps, but the coings I was counting in that logic. But I am not resetting it in code or saying all players should get the one of the player currently getting coins. I need to fix that, cuz it otherwise makes collecting coins absurd xD
https://vrchat.com/home/world/wrld_9cda943f-92d2-4fe8-a2a1-0c046e0c2768/info
I see. Ill check it out soon
A friend sayed he is already over 600 coincs and no problems, but I check with some different friends and we're often close to each other in points...makes no sense >.>
on your coin enter trigger, you're getting any player from the enter event
that means if Player B sees Player A walk into a coin, both players will attempt to set themselves as the owner. Player B will also get Player A's current score and increment it, then assign it to their own score
it looks like they're overwriting their own score with another user's score
I use the player entering the triggerzone and branch it with "get is local" before doing the thing...I wonder why it still uses the wrong player then, cuz usually there should nothing happen if the player having the coin is not the local player x.x
there's no branch in the first graph screenshot
Oh damn...I see that the enemy that takes points is asking that...the coin is not x_x
so that lil thing might rescue the coin thing xD
I always wondered why Interact and stuff is local, but Trigger starts global if you're not doing the thing with branch + get is local
how does this work, exactly? i'm struggling to figure this out with no access to TryCreateAllowlistedVRCUrl. i'd love for users of my world to be able to have a high level of interactivity based on their own playerdata but i cant seem to reason how to do this without user input each time
(also if anyone else knows please ping me 🙏)
intuitively it seems like you'd have to prepopulate an inputfield and then store the result of that in vrc's persistent layer? is that the way until the trycreate method becomes available to udon?
No rush for a responce, but if anyone could tell me how to make this toggle first default as on as it always defaults as off no matter what ive done to the scene
you call the event to toggle objects in OnPlayerRestored, which pretty much happens as soon as a player joins.
If the player doesn't have the key, the default value will be false, which is what is causing everything to be default off
I guess you could just UnaryNegation it there
I should have probably mentioned that i have a very barebones understanding of all this. had help in this channel before practically step by step to even make this toggle to begin with. Where exactly in there would I connect a UnaryNegation to without breaking everything?
try it there. flip this bool before putting it into SetActive
I've put like 10% thought into this
this should at least default on when the key comes back false
but is kind of a messy way to do it.
It worked! Thank you very much!
anyone know if ProTV has a volume persistence plugin yet?
would be super cool if it was just a little prefab that did it, and you could change it's key name (to not conflict with any other keys you may already have)
I actually implemented that in the world I'm working on, then realized that's a bad idea when I left it set to the max and got blasted with way-too-loud music upon joining
yeeeeeeeeah need a default volume option too hahahahaha
My world's default volume is 0.1
and I still sometimes get people saying that's too loud xD
Like wtf, are their computer volume and headphone volume at 100 or something? hahaha
yeah my default was like .25 but I went in game and played something quiet and cranked it up, then the next day went back and the default thing playing was way too loud.
lmfaoo
so I removed it from persistence 🙂
maybe I shouldn't have that persistent then
im trying to add persistence to my world, to save a day night toggle, but i keep getting this error "CS0103: The name 'PlayerData' does not exist in the current context"
im using the latest sdk
make sure your script has using VRC.SDK3.Persistence; at the top
now im getting another error
"CS7036: There is no argument given that corresponds to the required formal parameter 'value' of 'PlayerData.TryGetBool(VRCPlayerApi, string, out bool)'"
sounds like you aren't providing a variable for the out bool
not sure what could be wrong, here is where im getting the error (37,23)
public override void OnPlayerRestored(VRCPlayerApi player)
{
if (!player.isLocal) return;
bool savedNight;
if (PlayerData.TryGetBool(player, playerDataKey, out savedNight))
{
isNight = savedNight;
}
ApplyLightingImmediately();
}
just wanna add that i have no idea how to code in c# so im using chatgpt to do it, i can send the whole script if that helps
ah.
is this a C# script (says MonoBehaviour near the top somewhere) or an U# script? (says UdonSharpBehaviour near the top somewhere)
want me to send everything?
no I don't need it
you only put out savedNight, you need to specify the type. eg. out bool savedNight
if it's used as an argument and the variable had already been declared, it should be fine
nope, it usually makes me declare the type too
i would expect it to not need that, but it does
huh I've done that without a type declaration, fascinating
really.... hm, it's let me compile and run with it
suppose I should look at that
this code works for me just fine
idk if i did something wrong but i changed from out savedNight to out bool savedNight and im still getting the same error
maybe it's an IDE thing?
ill attach the whole thing here one sec
visual studo just tells me to do that but it isn't required, i don't know
doing out bool savedNight just gives an error with multiple declarations
not sure, but i know i've declared the type before and it works fine, and i thought if i didn't it gave me an error
yeap my function is identical to yours and it runs
definitely feels like GPT code
yeah i said im using chatgpt
i'll have to check when i get home
try removing the line for bool savedNight, and edit the function to have out bool savedNight
i thought they did
still the same thing
which line is line 37 for you?
probably not this, but what if you try Networking.LocalPlayer in place of "player"?
then that will always evaluate as true
I just managed to get the same error hold on
oh sorry, i meant within the TryGetBool line. not the If statement
nothing changed, same error
huh.... I changed it to out bool savedNight, got the same error, switched it back to out savedNight, no error, switched it back again to out bool savedNight, no error.
oh.. okay
huh? lol
me when the compiler isnt consistent
neither works for me
what if you try a bit of a workaround?
bool success = PlayerData.TryGetBool(Networking.LocalPlayer, playerDataKey, out savedNight);
if (success) isNight = savedNight;
is your scripts actually compiling? I'd double check and force it to compile
find any of these green files and click on it
then look in your Inspector and click "Compile All UdonSharp Programs"
it does nothing
you should at least see it load, and a message appear in the console when it's done
oh sorry, on the console it says
[UdonSharp] All Unity C# compiler errors must be resolved before running an UdonSharp compile.
UnityEngine.Debug:LogError (object)
this also works without the error. replace your TryGetBool part with this
the same error we're seeing is probably further up
you're definitely saving the file after editing?
vscode....
whats wrong with it
VSCode doesn't do any syntax or error checking for you, so mistakes in the code can easily be missed that would otherwise be caught
it's easier to install and work with though
there's also the VRChat SDK > Udon > Reload All UdonSharp Assets menu button, but i don't think that would work because there needs to be no errors to recompile.
if it's still broken, you can try Edit > Preferences > External Tools > Regenerate Project Files
did that, nothing changed
if you click on the .cs file in Unity, what does it look like in the Inspector? does it match what is in VSCode?
it does
do you have any other additonal errors above the one about the C# compiler errors?
here?
no, in your console
you still need to declare the variable for savedNight by adding bool savedNight; above it
that third error may also be a problem
ChatGPT moment
oh yeah, it did use three even though it shouldnt
all of the Set functions only take 2 arguments, but it's decided to shove in the out bool for no reason. lol
lol
check near line 75 for that SetBool function, and remove the player from it
isNight is actually supposed to be there
i guess it thought it needs the player there because TryGetBool needs a player
should be just PlayerData.SetBool(playerDataKey, isNight);
don't really blame it for that mistake. a toddler could easily make the same assumption
it kept giving you the same error because all errors need to be fixed before it tries again
lol, okay i did what you said, but im still getting the error on line 37
and on the console it says
SkyboxController.cs(77,28): error CS1503: Argument 1: cannot convert from 'VRC.SDKBase.VRCPlayerApi' to 'string'
you sure you change SetBool to how I specified?
because that sounds like you kept the player, when that's specifically what you need to remove
its this right?
oh, the other player
my bad
the error changed
"SkyboxController.cs(79,8): UdonSharp does not currently support node type 'ConditionalAccessExpression'"
can you post that line
its the last one here
ah that yeah
oh, just remove the ?
yeah why is that there...
lol, i imagined that could be the issue, but didnt wanna break it any more than it was
it's basically a short way of writing if toggleSound is not null, run Play()
but it's unnecessary most of the time, especially if you're not making a script which you'll be distributing
okay, not more errors i think
if it's for your own project, you don't usually need null checks for variables which you know you'll assign in the inspector
thank you everyone
yeah that makes sense
chatGPT just really likes making sure any nullable type is not null before doing anything with it
even if it doesn't make sense, like where it assigns localPlayer to Networking.LocalPlayer and instantly checks if it's null, even though it should use Utilities.IsValid rather than a null check for a VRCPlayerApi
which is why @unique sandal that I highly, highly recommend that you do not use GenAI to write code for you, especially in the context of VRChat Udon
it just doesn't know a lot of the nuances of it, you don't actually learn anything, and in the end when it inevitably doesn't work, it can often be difficult and confusing to bugfix.
yeah, im aware of that, but it know very little about coding in general and i needed a very specific thing and couldnt find it online
I've been reading AI generated slop at work lately... this has been a huge waste of time and resources.
if it was something more complex i wouldnt use AI
you should at least try and learn the graph if not code
i thought it wouldnt work as good as code
there shouldn't be much difference for basic things
ill try it next time
PikaPetey has an insane NPC system with tons of features made entirely within the graph, so you can make anything you want in the graph
it's a little slower and doesn't have quite as many features as U#, but it's not bad
Ye, functionally they're both fine. Main struggles I noticed when I tried graphs:
- Math equations that are one line in code are a BUNCH of nodes in graph. Can be unwieldy.
- Escape sequences in text don't seem to work in some nodes? I had trouble with string and date formatting because of this.
- Huge large graphs can lag or crash the editor.
256 nodes is a main limiter id say, you hit it way too fast (and it works slow as hell in editor when reachin the limit). so graph is just a u# tutorial, perfectly usable tho until some point
but life is way easier with u# (once figured out syntax and boundaries), especially now with llm stuff that can generate you some code (not the whole script in case of u# i guess, but tedious parts of it), while getting graphs from em is not a thing
Why are you using the Local Player as the key? How is that even supposed to work?
Ah dang it, my discord didn't fully scroll down lol
idk lol, i know very little about coding, that was all gpt code
My world too as a ton of scripting to it thats like 80% graph
I practice a good amount of defensive programming so my code is full of null checks and the like. But that is mostly for runtime stuff, less for code that runs at start.
i do too, i just don't bother sometimes
Is OnPlayerRestored suppose to be able to be called twice on the same object by the same player?
For context this only happens on player #2's side, not player #1
it's called per player in the instance and new joiners, unless you mean it's calling multiple times while passing the master as the player on your 2nd client (which is a bug that can occur in build & test)
ahh that would be it
yep, i knew it. i can't use TryGetBool without declaring the type
(and no, i don't have any other bools in this script named "savedState")
right, because you didn't declare the variable yet
if you do bool savedState; in the line right before it, it'll work
ah okay, i thought you were saying it wouldn't require me to use that at all
but it's more compact to just do it there, especially if the variable only needs to exist within that scope
Is there currently a way to have PlayerData (or another method of saving player data) accessible from multiple worlds?
I can theoretically go the GET request route, but since it's going to be player's coins, I don't want people being able to easily modify them.
not in an easy or in a supported way, no; persistent data is purely per-world
the manual route would be having the world output a save data string that the player can then input in a different world
Can anyone help me with what i'm doing wrong here? I'm trying to have LTCGI start disabled when the user doesn't have a PlayerData for it but it's always starting out Enabled and it's really getting on my nerve. Keep in mind, I have limited code knowledge and basically everything here, I had lots of help with (They're asleep rn but I wanted to get this working tonight)
I have no idea how else to tell LTCGI to stay off other that literally LTCGI_Controller._SetGlobalState(false); lol
huh I'm glad you mentioned this - apparently I started implementing a toggle for that but never hooked it up
but yes, that looks like the right way, according to the LTCGI docs
So, idk how to explain this other than, I moved my project to my m.2 nvme (I was tired of it taking 10 years to open), opened it, cleared my local playerdata, hit play and.... the ltcgi is now properly off lmao
Nevermind, I lied. The toggle is frickin backwards. I don't understand this. Why is it now, when I enable and disable this, it's now backwards? I enable it, LightVolume enables and ltcgi disables. I disable it, LTCGI enables and lightvolume disables. 
I’m sorry if I’m asking this in the wrong channel, but I was wondering if anyone knows how to do this. I want the character that I have in the world to look at the players and look at them as they’re moving kind of like the clip I attached. I did watch a tutorial, but I’m a little bit lost on the script part and how to attach it to my model. Here’s the link for the video: ( https://youtu.be/aAauz34t1gU?si=iNy6ycmsarkChQPJ ). I’m stuck on 3:08 of the video because I don’t know what script they used or how I’m supposed to write it like am I supposed to write something before it or am I supposed to write it in like a certain place? If anyone knows what I’m supposed to do, it would be really appreciated < 3
I have not seen alot of tutorials explaining this concept, so I have decided to share my "how tos" with you guys :)
Feel free to ask in the comments, if something is not clear or could be done a better way
the game from this video:
https://store.steampowered.com/app/3124820/Tartaros/
In this tutorial they are likely creating a C# monobehaviour, which is the standard unity scripting class which can be attached to gameobjects as components. However, they also don't have much in the way of security so in VRChat you can't use those.
However, you instead have UdonBehaviours. Udonbehaviours are a lot more locked down and don't have access to all the same capabilities, but they are nonetheless a general scripting interface that can absolutely achieve the same effect.
Udonbehaviours come in multiple different flavors, either udon graph or udonsharp. Udonsharp is the one that is most closely similar to regular unity monobehaviours, so for this case I would recommend using that, as you'll be able to use mostly the exact same code as in this example.
To start with making an UdonSharp script, you would find a place in your unity editor project file browser and right click > create > U# script.
Once you have that script created, you'll need to edit it. You can get away with notepad, but it's not going to have any syntax highlighting or autocomplete which is IMO the best part of writing C#. To do that, you can connect visual studio or jetbrains rider to unity. Let me know if you have further questions on how to do that.
Once you're actually set up to write the script, it will be mostly identical to the script in that screenshot. The one difference is that you won't have access to the player transform - instead, in VRChat you have player APIs. In this case you would replace player_trasf.position with Networking.LocalPlayer.GetTrackingData(VRCPlayerApi.TrackingDataType.Head).position and it should work exactly the same.
Once you have the code set up, you would simply put it on a gameobject as a component. Scripts don't do anything unless they exist in the world. You can put it anywhere but for organizational sake it would make the most sense to put it on the same gameobject as the animator that it's plugging into. Then you would need to drag the animator into the public reference of the script so it knows where to send the data, and you're done.
While doing this I bumped into an error. Can you tell me if I wrote any of this wrong? :>
on line 25, you are missing a semicolon at the end
attributes not valid: You put all of this inside void Start() when it should be directly inside the class instead.
facingpoint: the example from the video forgot to include the definition of facingpoint. Looks like that should be another public transform which is then pointed at the head of the animator.
Sorry it took me a while to see this but thank you for the help I was able to get it to work!!!! You're really amazing and thank you for being patient with me!! I appreciate a lot!!! 🫶
Just double checking with persistent real quick
If I want to store any (supported) data on a player, permanently, all i need to do is create an object with:
VRC Player Object- UdonBehaivor that has a synced variable
VRC Enable Persistence
and on RequestSerialization(), the values of said variables will be saved, even when the player leaves and rejoins, and I'll know when said values are ready to be used using public override void OnPlayerRestored(VRCPlayerApi player)? not sure if that last method is an actual method or if its just part of the demo scene
That's correct.
OnPlayerRestored is fired after all persistent data across all your objects has finished being decoded.
Additionally, OnDeserialization will fire on the object individually before that, when the data specific to that object has been loaded.
oh, that's neat: so receiving persisted data is exactly the same as receiving serialized data from someone who ran a manual sync?
yep!
nice (:
A word of warning, there are circumstances where PlayerObject persistence data can be lost that, unfortunately, the documentation makes zero mention of.
For example if the network id assigned to it in editor changes, all its associated data can be lost. Also I have no idea how it will handle the PlayerObject's synced variables changing. So I'd test that before trying to change them if you have a live version of the PlayerObject and don't want to risk wiping everyone's data.
Just a quick question about what you guys think would be the best way to handle this situation:
My game has a bunch of roles, and each time you dont get a role, your bias/weight for that role goes up (its a persistent value). What would be best/most efficient way to store these values on my PlayerPersistentData object?
I was thinking a byte array, but, i wasnt sure what I would do if I wanted to add a new role
My second thought was just a bunch of bytes for each role, eg [UdonSynced] public byte RoleNameBias;, but was wondering this would be slow/inefficient to save
and, i guess, also a bonus question, how do I set default values for players who have never joined the world before? can I just do something like [UdonSynced] public int testVariable = 5; and it works?
you might consider using playerdata to link keys with values more explicitly, you can access whether it's been set before with TryGet
https://creators.vrchat.com/worlds/udon/persistence/player-data/#accessors
playerdata has some caveats that make it undesirable if you have a large amount of persisted data, so if you were to use player objects with persisted variables, setting some default value should inform you that they've never written a value before when you access it in the player restore event
as far as actually saving it, i'd personally use bytes as a data structure of some kind
I could do this- but there was one thing originally that stopped me from doing so
- They dont seem much different than player objects to me?
How do I define a value? Do I really just set the the value, and if it doesnt exist, it creates one? what if I change the name later?
I mean- i guess changing the name doesnt work with player objects either, bad example
What If I input the wrong name somewhere and create a new key by accident? Can I, as the developer, delete that value on all players?
the interface is more like a dictionary, which is easier if you're doing more explicit linking of like a specific key to a value as opposed to writing bytes to a variable in player objects or creating x amount of variables for the amount of persisted vars you need. if you're using bytes it doesn't really matter i guess. it's probably more simple to start with keys ("someRole") and assign a value to them if you don't anticipate needing a lot
you can't delete a key afaik, i'd normally make a const var in a script defining the key and use that to avoid mistakes
I like the idea of a using a const to not make a naming mistake
oh but uhh
there might be a problem?
I dont know if I did something wrong- but I dont know if I can test player data in the editor on linux
When I went to the example scene and pressed the "Set Player Data" button, nothing happened
i forget exactly where it is, but in the top vrchat dropdown there's a way to browse your clientsim playerdata, which would probably indicate whether it worked
re-opening example scene to test
oh
maybe something just borked last time
because it worked with no issues this time
Okay, so I guess, to confirm:
- Create a bunch of
public const variableKeyName = "KeyName" - On
OnPlayerRestored(VRCPlayerApi player), check if the keys dont exist, if they dont exist, set default value?
The biggest strength and issue with PlayerData is that is it basically an untouchable PlayerObject. Plus side you won't have to worry about accidentally wiping all the data. On the down side, you can't intentionally wipe all the data either.
so like, should i use them or not 😭
i just did
so much scripting
and i feel like a player object could have worked
you can use both; they have seperate data limits
This is what I did to make sure that, when a player joins the world, if they are missing keys, set keys to their default values
there is currently an error in it that im fixing by just having a hardcoded value in the RoleHandler (the first for loop)
Also- will probably swap Awake to Start
oh wait
this isnt the dev server
LOL
lets go post that update in the dev server, not here (said i published a new branch)
Maz
Looking at some of those names of variables looks like whatever game you’re working on is gonna be exciting
<3
Keep at it man! And good luck
What’s some stuff that will break persistent variables? From my understanding they’re similar to synced variables, but I’ve heard that there was ways your project could get screwed up and then mess the variables up as well.
Dont reset your network ids
Is there any problems that would force me to reset me ids to fix?
technically no; resetting IDs is just the quickest and easiest way to fix conflict issues with network IDs. But it is also like taking a sledgehammer to a wall in an attempt to fix it.
Any network ID conflicts can be fixed manually, if needed, by using the network ID utility; it will tell you which objects have the conflicts, and you can go in and manually fix the IDs rather than nuke all IDs when regenerating
so if you have a world that doesn't have persistent data (or at least data you don't care about), you can save yourself the effort of manually fixing it and just reset IDs
Ok nice
I noticed that it's possible for people to be in an instance while loading different versions of the same world
But I guess the networking IDs still match up!
So If I have an existing world but want to add new persistence data types on top of whats already existing, will everyones persistence data be safe?
Will it work or get corrupted?
adding on top is fine, if it's new keys and/or new variables
Worried if this goes wrong and peoples saves get deleted my world wont be so popular anymore :L
Its gunna be a tense moment when I have to update
Oh also while Im here, Ive been having reports of persistence save data getting lost, I feel kinda powerless whenever people report that
Was hoping there would be like a backup system or something
You can make a backup system with how people used to do world saves. Using text inputs that people can save. Though that would require the users to take that step, which you can't really rely on
I wouldnt even know how to begin with that
You'd just have a program that would gather all saved info and convert it all to a simple string of characters. Then loading it would do the reverse. Could be as simple as that or you could take it just a little bit further and introduce some level of obfuscation of the string to keep people from just manually editing their values to keep people from tampering
That would be alot of parsing hmmm
Yea, honestly might not even be worth the effort to some extent. Could spend that time trying to make your current system more robust
Imean its uncommon, but seems to randomly happen, people come to me "My data gone nuuuu" and I cant figure out a cause cause its rare 🙁
Just a simple idea would be to make a check method that wouldn't allow the data to save if it looks like it's going to be a problem. Like when a player joins the world and data loads, save some sort of overall idea of what that data looks like and then compare when you go to save it again. If the new data is basically empty or just not what you should expect, don't save at all. The player will loose some progress but not everything at least
Are you waiting until the player receives their data loaded event before you attempt to write any data?
It loads data at the start and thats it
That doesn't answer the question
I dont know what you mean, when they load in it recovers their persistence data just fine, they play for a bit in which theres a combination of automatic and manual saving and then they leave.
Only to find the next time they play their data gets wiped...
Is it possible that you are writing a blank auto save over their existing data by saving before the old data actually loads?
No, it only auto saves after having completed a match, by then youve long loaded your save.
what happens if a player joins just as a match is ending?
If you aren't explicitly checking that your data was loaded correctly before trying to save, you're going to lose data.
You cannot make any assumptions here.
I mean... I dunno, Last I check it automatically runs the on player restored node, upon doing that it sets all the save variables up as intended, all within like a second, theres no way anyone could save in that amount of time
Well... is there a way to manually call upon a restore player?
I'm not seeing the problem, as soon as the world loads in, the players data gets loaded and then applies to the variables
It only saves when they complete a game or they want to manually save and thats long after they have loaded...
@quick imp #world-persistence message
(this solution is not practical unless you have lots of free time) isnt it also possible to make a custom server tht listens for GET requests and pass in some encoded string in the get request, your server picks it up and understands its not actually meant to be a get and stores their string, then can return it when the game calls a get request formatted in a different way so your server knows the difference
If your going that route, you could also have multiple saves/backups. But yea, that's possible. Though I've never done anything with servers and so couldn't tell you the specifics.
Me either but i had the thought:p
Just a couple quick questions for people more experienced than me. How expensive is it usually to use the find function to find a game object.
Also, what’s a good way to accomplish windows that work for quest as well
It's going to depend on how many game objects it might need to iterate through before finding the object
It iterates through all of the top objects in the hierarchy, correct? (Also my bad I meant to ask this whole thing in world optimization)
If it's only searching for a name then in worst case it will need to integrate through every single GO in the scene; if you're using a file path format then in worse case it will search through every possible GO that could form that path
I think. I don't know the specifics of how it works, but that seems like a reasonable educated guess.
All right, thanks, I remember reading something about find only looking through top level game objects and not iterating through all their children so hopefully I should be good. Also, it’s probably gonna be max of 5 to 7 players. So that should help cut performance for other things.
Interesting thank you for the references
How many bytes is one bool
a bool is 1 byte in the context of networking
I think in a script it should only take up 1 bit
but if you network it, it's 1 byte
Memory access is by the whole byte, though. I think realistically, even if it only uses one bit, it still stores the value in 8 bits I would think. The alternative is when you use an integer to pack bits, which you can toggle with bitwise logic. Or when bools are packed into a C struct. And the reason is that a byte is the smallest amount of data you can store or retrieve in an operation. Secondly, if the bool was alone, it would force other data to be misaligned to the nearest byte, making memory access slower, hence, at a minimum, everything is byte-aligned.
My explanation touches on how data is stored in computer memory. As to why it makes sense based on data byte alignment and storage and retrieval size.
that makes sense. sizeof returns 1 byte for a bool
Now if you have a ton of bools to store in state, you can learn to pack and unpack them from an integer.
I think that is how avatar parameter values are transmitted over VRC too. You get 256 bits. Floats and ints take 8 bits, bools take 1. They're all packed into a data structure up to 256 bits long. On the contrary, you'll have 32 bits in one integer to mess with packing and unpacking booleans in your code.
so im trying to save a float array, bool array, and texture array i can see anything for this in the documentation so im guessing its not a thing? also if i do the vrcobject does it just keep a clone of a game object and its componets and settings? and if so how would i update this player object or does it update per frame
it depends on if you're using PlayerData or PlayerObjects
the only available array type in PlayerData is a byte array. So you'd possibly need to break up your data into bytes, store that in an array, then save that to persistence
Another option would be to just save a separate variable for every element in the array
PlayerObjects however can persistently save any data that can be networked, so arrays of syncable types work perfectly fine with those
to reference a PlayerObject through a script, you'd need to obtain a reference to it by using GetPlayerObjects
When you enable persistence on a PlayerObject, all it does is persistently save the last network state. So how often it saves purely depends on how often you are doing a network sync on the PlayerObject
can i do a player object that also has a udon script on it that has float arrays for example and i can just pull those arrays from that objects udon script right?
ive never really used them is there a way to do a player object that doesnt duplicate?
or just duplicate for the local player and nothing more
that would just be.... a normal object, no?
yea but im doing a lot of floats and what not so i feel like getting a bunch of floats separately from perstiance is gonna lag tf outta people
is there a way to check if its someones first time joining the world?
with playerdata you could simply check if a key retrieval fails, probably via HasKey; if it does fail, you'd do your first time setup and give them that key, which would return true on subsequent joins
https://creators.vrchat.com/worlds/udon/persistence/player-data/#queries
perfect thanks!
can i reset all players presistance in my world?
not directly, you could include a version number on players when you save. if you need to reset, you could increment that number in editor and update the world. if it's lower than expected when you read from the player, you can reset their keys (this is assuming playerdata)
you would have to define how to reset their keys though, which might be a pain if you use keys throughout your scene
Question what is the limit of world persistence
My only knowledge of something similar is of rec room stuff which technically you can use 16.384 gigabytes of storage for one player
-# yeah I know it's extremely high but there's literally no need for that amount
-# I'll also explain it, basically you can save 20k characters of utf-16 in one string, you can save 4,096 of those strings, and you can save a hundred of those 4,096 which one converted that's about 16 GB
You can save 100kb of PlayerData data and 100kb of PlayerObject data, both compressed (so on average you can store a lot more than that)
This limit is per world, per player
Cool, I barely used more than like 30 bytes on rec room so I should be good
Opps and this will remove their player data as well?
Not just reset a float to 0 or something
that isn't the case no, you can't forcibly "remove" a player's data unless you reupload the world afaik
you could instead use player objects for the persistence, that allows more control over the data, but it's not as simple to work with if you're already using playerdata
if you actually want to enforce like a full clear, you could disable a lot of the systems in your world and inform the user to reset their data manually if they're seen with an out of date version on their persistence
(some people don't know how to access the data reset in the options for the world, so it may be useful to provide images that show how)
Ooof vrchat should add
Anyone have a simple graph script that just persists a single bool locally
Or does persistence do some black magic?
persistence is literally just
- have a value
- change the value
- save that change
- apply the change to the scene
- load that value when a player joins
there's not much magic or complexity to it
this is a very basic example
is there a reason you have a separate bool you interact with? or is it just because it's annoying in graph to route the player and key around multiple times
it definitely improves the readability of the graph
yeah it might get a bit cluttered with the key/local player nodes in more places, idk
oh I was referring to how zSkull did it as more readable. I hate having to chase lines all across a graph to see what the inputs are
anything relevant to the node should be near it, even if you're reusing it somewhere else
it's how i tend to do it in my scripts too, it just makes sense to me
because i usually use persistence just for UI toggles, so having that separate bool means i'm not loading the persistent value every single time i make a change, i'm only saving it's last state
i only load it when they initially join and actually need it loaded
it makes a lot more sense (to cache the state) if you're only saving the playerdata in larger intervals instead of setting it each time yeah
for stuff like a leaderboard i made a while back, that i just had to save and load values every time, no intermediate variable. cause in that case, i was actually needing to access the saved value consistently
like accessing it across players?
well, it had an option to change from a regular scoreboard to a highscore board, and when it swapped, i just loaded the saved persistent highscores, i didn't need to have an intermediate synced variable
it was saved on a playerobject with enable persistence anyway, so the highscores were already synced
E
I have a problem related to synced variables in PlayerObject.
I have a script which is attached on the same game object as VRCPlayerObject and VRCEnablePersistence.
What I am doing in the script is initialize all of these synced variables on OnPlayerRestored, and call RequestSerialization to sync those initialized variables to other players.
The trouble is, even if SerializationResult says it was successful, none of those variables are synced.
And what I found was, decreasing the number of synced variables makes syncing successful.
I thought at first like "Is it a rate limitation thing?" but SerializationResult.byteCount is only around 150.
Does someone notice anything wrong with my script?
Is there any special limitation for synced variables specifically on PlayerObject?
Early joiner's log
Late joiner's log
When I comment out some variables like this, it somehow works fine.
That might be the exact same case as mine…
Thank you…!
I am relieved to know that I didn’t code wrong but it’s sad to know as well that there’s a very inconvenient bug…
Incredible
I wonder if this issue applies to arrays
i'm needing help with making my code save and load persistence data, the interaction works and adds value but i want to save values when people leave and rejoin
don't load persistent data in Start, it's likely not been sent to the player yet. You should be loading in the data in OnPlayerRestored instead
so public void OnPlayerRestored
I think it needs override but yeah
whatever the autocomplete suggests
yeah i removed the override and it working thank you
The correct signature should be public override void OnPlayerRestored(VRCPlayeApi player)
I think I am going to need some assistance with creating a string based save system for my world, Persistence is proving... rather volitile as reported by players 🙁
How so? Should be as robust as anything else.
People randomly lose data for an unknown issue, very variedscenarios every time they report 🙁
Are you using PlayerData or UdonSynced for persistence? Apparently something something player object network id differs, UdonSynced persistence resets all your data, ive never had issues with PlayerData though + it works independent from network ids
Using player data
Yeah, if you have really important playerobject data that you need to be rock solid in a production environment, I'd recommend writing down their network IDs somewhere special so that if something breaks or you need to migrate something, you can always manually set it back to what it's supposed to be.
Ive changed and cut some features of the game to reduce the save files size for new players, as it was locally saying that the player data was in excess of 151kb which exceeds the 100kb limit (Im using quite a few arrays) Im guessing thats likely the cause, Kitkat told me that this data gets compressed in the actual game though though Im kinda annoyed i wasnt warned about my save data size in the build window, It likes to be pedantic when it comes to android world build sizes but itl let an oversized save file slide no problem ):(
I am hoping this was the issue, otherwise I have no idea
I honestly don't know what it might be, because if your save format was too big it should fail to save at all.
Getting a rather frustrating error:
2025.10.22 18:19:37 Error - [Behaviour] Value cannot be null.
Parameter name: Received a null value when encoding a Byte
System.ArgumentNullException: Value cannot be null.
Parameter name: Received a null value when encoding a Byte```
It doesn't tell me what Byte is null, just that the entire PlayerObject is failing to serialize
I do know it's only for players with data before this started occurring, it started occurring when I added a few additional values to the object, and they are all initialized to a non-null value
if you've added any new fields, this might help (basically check them for null on restore), not sure if this is like still an issue or not
I think that's exactly what I needed, thank you!
network id as persistence identifier is bad mmmkay

so i've been messing with persistence and during testing in unity and vrchat when another player joins this happens to my code is there a way to fix tbhis
this only happenes with these added once removed it looks normal
If you would like someone to review your code, you'll have to post your code
well the code works the player object is my issue
Putting a playerobject on something means that it gets duplicated, once for every player in the instance. If you're expecting this UI to only exist once then dont put a playerobject on it
Also, if you just want persistence you don't have to / probably shouldn't use player objects. For just storing e.g. what a player has unlocked or how many credits thex have player data is usually the better way.
hey . are u done with ur world ?
yeah world has been worked and finished thank you its ECHO REEF
can you dm me the word name please
is it your world or its a commission base work ?
thats my world
so nice
any way i can adopt this graph for a float value?
literally just replace the bool for a float
use a float variable instead of a bool, and use SetFloat and TryGetFooat
ive done most of it, the unary negation part is the bit im stuck on if that helps
just don't use unary negation. all it does is flip the value of the boolean, so it does not apply to a float
do i just skip the unary negation and just connect that straight to savedfloat?
the only reason it's there was as an example for changing and setting the value of the bool to be something different
in the top of the graph where i have the Set (Variable) node (the red one), you set it to be whatever value you want
okay, think ive got it, one more try before i go off, 5:30am rn
thank @light laurel
So is this change to lock out players out of older instances just for player objects? Because I don't see how player data would've been affected by layout changes.
And I guess what I'm wondering is if network layout changes in general will affect the lockout? Or only changes to persistence enabled player objects?
"All objects that exist in both versions (selected by Network ID) must have a matching variable and component layout. Udon changes that don't update variable types, order or count will not break compatibility. Updates to non-networked parts of a world are also fine, e.g. simply moving an object in the scene."
Seems to include every networked object, not just persistence ones
I wonder if there is a reason for that, because other objects usually don't affect persistence. From what I understand at least.
i'd guess that because of how tied persistence is to udon sync (especially inside player objects) it's probably easier to check networked objects for changes and not try to infer which of those are persisted
It is clearly marked though. But I guess that's in the scene and the server side check might not have access or something?
But yeah, I hope it's not too limiting. Testing to figure out the nuances also seems like a pain.
Maybe it's just also just my case being a bit unique. I update a lot and have a decent amount of publics I don't want to ruin for people.
maybe there is concern that the bugs resulting from incompatible networked objects could have a knock on effect of corrupting persistence data
Or just a general desire for overall stability. Even outside of persistence, I can see there being benefits to ensuring people in the same instance are running compatible networked code. A lot less for world creators to have to account for when pushing an update (or at least, a more predictable thing to account for).
Yeah the "lost data" thing happens in more worlds...Recentely in the umamusume themed game world people's saved loadouts started to get erased at random.
Sometimes it's just that (and not the current build equipped) sometimes it's both.
Worst part is that "Kyoto Racecourse" have not been updated since september, and everything was fine. This save lost thing just started recentely.
:)
is persistence saved on cloud or machine
Cloud
How would one go about doing an "or" logic with graph?
Id love to do if month 10 OR 11
Is this basically it?
just do
Logical Or (Boolean)
Get Month > Equals (10) > ┛ |
Get Month > Equals (11) > - ┛
what you have may work, but i'm not sure how exactly that Logical Or for the Int works, so i wouldn't know
there's no way to persist data on a per-world basis right now, is that correct? curiously thinking about cross-instance leaderboards. my understanding is it can only really be instance-based at the moment 🤔
well, persist *cross-instance data in a tos-friendly way of course
its persistent per-player per-world. for leaderboards only untrusted urls with voluntary submissions
In theory a peer to peer approach where each client tracks the highest n scores they've seen and syncs their lists together with others could work in a limited fashion. In practice udon isn't secure and people would/could hack the scores and "infect" others with the manipulated data.
this is the approach im taking! 😊
it is a bit unfortunate there is no world global data store yet; even if the size were extremely limited like <5kb, it'd be nice to be able to pack some stuff in there
There's not really a good way to pull off world storage like that though anyways.
VRChat's network model for instances is basically brokered p2p. The code you write is only ever run on client's machines, one is just designated by the photon layer (which handles the brokering) as the master of the instance and is considered authoritative to the other clients for some things.
This means:
-
- there's no reliable way to trust any client is going to run the code you want and only the code you want.
-
- race conditions could occur if multiple instances are trying to run the same code to update the same stored world space at the same time.
There's really just no feasible way to implement useful global world storage in a way that can be updated by arbitrary players via udon unfortunately.
these don't seem insurmountable 
just gotta make concessions that feel ok 🙏
It kinda is insurmountable though in brokered p2p 😅 Without an authoritative server, you can't run trustable code. At the end of the day there's not getting around that you're having to trust arbitrary client inputs to your global storage. Leaderboard hacking is already a problem on standalone games as is, but it would be many times worse here.
There are ways to get around the race condition issue, but we'd have to leave behind the generic storage and instead have a specific implementation of a storage space that can intelligently handle simultaneous inputs and know how to resolve them. Ie, a specific leaderboard API as opposed to a generic world storage one, which takes submissions and just inserts them in order of value.
In essence, people would ask for this feature, would get it, then complain even more about all the hackers making their leaderboards entirely useless, and no one would use the feature because it's unreliable.
It would be cool if they could spin up like a fake client where we could run world code on or something. Sounds like it could be possible theoretically, but would probably take a ton of work.
with regards to leaderboard hacking, are you saying vrc allows clients to run completely arbitrary code? i dont agree that race conditions are a problem here if you're willing to accept the consequences of latency; i'd imagine the API would be similar to PlayerData, but with different limitations like lack of realtime guarantees (unless you stuffed the backend for this on the BEAM 🥰 )
idt it would be reasonable for players to have to build out a distributed model themselves in udon; that'd all be on vrc's end, at least in my mind. unless you meant something else?
Photon has a concept of a Photon Server which can run its own code and behave in ways more akin to a traditional client-server model. But to my current understanding that would be a fundamental overhaul of VRChat as it is, and cost them significantly more in money and resources to run for every instance. It would be nice for a lot of other reasons if it were viable, but yeah, not realistic to happen.
Proof by counter-example: are you saying that EAC has been effective at stopping all hacking on VRC? 😉
Yes, people still use hacked clients and can execute arbitrary code. EAC and signed code are barriers of entry, but it's a cat and mouse game that even billion dollar competitive multiplayer game companies fight constantly and still can't defeat.
With a server, you can at least do the logic there to say "user did <thing> that was doable for them, add # points", then pass the final score to the database in the end and the client can't touch any of that directly.
With brokered p2p the way things are now, the only world data getting saved to VRChat is the user data to your own account. No one else in an instance can set that except your own client. Worlds can make your client run code based on inputs from other clients, but in the end each client can only touch its own data directly. With a global world persistence however, everyone would be allowed to modify that shared data freely. At that point it's just an API endpoint and an assumption that we can trust what the client says is true for everyone (which we can't).
I can give a direct example with VRCanvas. Technically that world does have global persistence: things users do in any instance affects all other instances, since the canvas itself is global state. However:
- People can and have called those endpoints directly, and not just from within VRC. You can do that with photon too, VRChat just asks you nicely not to and it's just a banable offense if you get caught, but not everyone gets caught.
- The canvas is a specific type of global persistence with custom server code, not a general data store. And I have to pay to host it.
- The canvas is not a leaderboard. I'm not trusting people to say what their score was and ranking them, or letting them tell me the state of the entire canvas, I'm just getting requests for individual pixels on the whole.
- It's an obscure system for a small world, meaning it's got a relatively small threat surface compared to a theoretical global system used by all worlds.
i think you can generally trust clients and not worry too much about cheating... i also think this hypothetical system would be a no-brainer even with just a single lambda as a relay (assuming payloads <5kb); very cheap, too. this is a solved distributed computing problem i feel
I tihnk you can generally trust me to hold onto your wallet and important items
I think we can trust most clients in general. But leaderboards aren't about the average, they're about the extremes. 10 hackers in a million players take up the top 10 spots on a leaderboard. Global persistence requires the assumption that everyone is playing fair, because it only takes 1 person to ruin it.
To be clear I'm not talking about hacking in terms of the gameplay, I'm talking about people having access to the values sent to the API directly. Again, assuming this is a general data store, then 1 person could simply replace it with an empty string and delete everything for everyone too. Narrow it to a specific implementation like a leaderboard, then they can just send bogus data to put them on top. That's the issue of trust.
i feel a data store for things like for-fun leaderboards doesnt need the same security guarantees as financial data
while that's true, Prismic has made like a dozen good points why it's not as easy as it sounds
people already get upset because 1 person poisons their highscore system with a cheat score
all of them are kinda easily accounted for imo
Easy makes it seem like it's a problem you can just throw more time and resources at though. This is a network design/architecture problem, not a resources one.
it's a solved problem... idk 😅
gives examples of why it isn't a solved problem
"it's a solved problem"
We'll agree to disagree lol. I'm not going to pretend I'm a total expert on these things, but my career background is client/server software. If it is a solved problem, I'm unaware of such a solution within the constraints of VRC.
But if one could be found that'd be cool. Would love what you described if such were feasible.
didnt really anticipate the back and forth but i see it as solved; there are a lot of already battle-tested solutions for this. multiple clients submitting updates to a single server is very classic
For race conditions yes that's solved: you just have to constrain things from a general store to a specific one with insertion/update logic.
But again, we'd be straying away from general world storage at that point and into more of a leaderboard API. Which would solve that problem, but the bigger one of trust remains.
idt it's necessary to dump a full spec for this but i do think vrc's existing relationship with AWS would lend well to literally just a lambda. validating inputs is another topic but not terribly difficult depending on the constraints, if you're ok to guarantee MOST (not all) inputs are valid
no it's the same issue. distributed global storage is so solved that it's something cloud vendors sell for cheap
I think the lessons learned from recent events is that VRChat wants to have less stuff relying on AWS lol
i agree, but lambdas are cheap. you could setup a single server in your* bedroom to handle all updates for US east for global data storage with some concessions
it's not too big a deal
Wasn't talking about the storage infrastructure itself, was talking about the structure of the data. For a general storage, suppose you have an object that says the number of total fish caught in the world is 1000, and one person says it should be updated to 1003, and another says it should be updated to 1001. The correct answer we'd want is 1004, but in order to determine that we'd need logic that is aware of the structure of the data being saved and the updates being received to reconcile them.
that isnt a schema issue; it's the same thing from earlier
uhhh but, but but. i didn't really anticipate a back an forth on this, and it maaay be going in circles... i'll say VRC has an open sr swe position open that yearns for this kind of expertise. if you feel up for it, you ought to apply! 🙌 😊
Yeah, apologies for the circles too haha. I enjoy the discussion, and like I said I certainly don't know everything, so if there's stuff I'm not aware of for me to learn that's always a boon. I'm just presenting what I know. I'm also content with my current swe position hehe 😊
feel that
Yeah :(
I really wish we could have roblox' persistence api, but I'm afraid that's not happening with Photon.
Yup, they gain the performance and trust benefits of a client-server architecture, but along with the costs.
I'm still keeping an eye on S&box, supposing they keep VR support as a priority.
s&box seems really cool on paper. i think they're going for something like roblox as a platform, but it's on source 2, the editor is similar to unity, and it uses c#
if you wanted to do leaderboards in a vrc world, probably the only realistic approach is the one speed runners use: runs are recorded and submitted for humans to validate and add to the globally viewed leaderboard
the propagated leaderboard mentioned above is actually viable, but the data becomes very sticky. it'd require maintaining it if people did spoof entries
i mean first you make it notable, then you spoof-prove it.😉 vrc got anticheat in 6 years or smth
It's funny how everyone seems focused on leaderboards when it comes to persistance.
Meanwhile I just want a same PlayerData key marked as "shared" to be useable in multiple worlds (of same author, important distinction) locally so a set of worlds can act as areas of a big adventure...
Y'know, transferring your stats/inventory over, RPG stuff. Or farming in a map and using the ingredients to cook in another, stuff like that. 
Yeah - that's the thing I want too, I don't care about leaderboards 🙂
Nah I'm on board for that too. Would love to be able to carry settings and data between worlds.
Yeahhh, would be nice if they cared about that
This is heartbreaking that you cant even come up with your own solution because the data is cached per window and you dont get actual live data when calling get methods. This breaks so many possibilities and games... They're probably trying to save on bandwith but this is too far
Yeah that post exists because of active cheaters in existing worlds, really sucks we can’t do anything about it
multiple instances....Oh, that's what they do? Playing in one till inventory is empty or they die then just do an action in the other instance where they had all full to refill the inventory?
considering nobody can interfer with a private one where they're alone...sounds nasty
I am hoping to get it to where my and my friends can decorate our homeworld within vrchat (using gravity-less items and such), how would I enable persistence so those object stay in place for everyone?
are these decorative objects also PlayerObjects?
Not as of now? Right now, I just have a button that each time its push it spawns said decoration that both plays can see/manipulate
to sync those you'd have to save both the position and rotation, and then load it when the player joins
but if they were PlayerObjects, and are also pickups with VRCObjectSync, all you have to do is at the VRC Enable Persistence component and then its position and rotation will automatically be saved
both approaches have their pros and cons
"to sync those you'd have to save both the position and rotation, and then load it when the player joins"
How does one do this?
using PlayerData
https://creators.vrchat.com/worlds/udon/persistence/player-data
PlayerData is a key-value database for storing persistent data about players, such as their score in a game or their preferences in a world.
position is a Vector3, rotation is a Vector4, so each object would need to save it
you might want to have a button that the player would press to do the saving, or you'd setup an autosaving interval
i.e. every few seconds, it saves the values
loading the data would be done in the OnPlayerRestored event; you'd then retrieve these saved values, and set the object to the position and rotation that was stored
on the other hand, if they were PlayerObjects instead, all you'd have to do is add the VRCEnablePersistence component. This will cause it to persistently save the values of the VRCObjectSync component
Whats the downside to the playobjects cause that feels easier?
a PlayerObject creates a duplicate object for every player that joins; if that's exactly what you want anyway, then there isn't really a downside, but if this isn't desirable, then it could be considered a downside
Another notable downside is that Ownership of a PlayerObject is strictly enforced, the network owner can only be the player that owns the PlayerObject. This means that, for pickups, only the assigned player can successfully pick up and move the object
Oh ya no, I dont want duplicates for everyone involved, guess the other method it is
Something I've been wanting to test and intend to is compressing data as a single string and writing a small string parser to save and load larger databases in one small area.
Has anyone messed with this before?
Think of it similarly to a password system, but the passwords setting the data directly through string parsing.
Yeah I just did it again last night, helps a lot by optimizing syncing data for multiple variables into one
Sweet! So its not just me. Glad it works okay.
You can also do this with byte arrays, that'll get you into the deep end of serialization real quick and give you a little more control with less allocation
And if you still want it as a string to copy/paste you can convert byte array to/from a string using base64
Ooo thats dope! Gonna play around with that.
FFS...more "fun" persistance news...
So, the Umamusume-themed running world will have a tournament soon-ish. For this players fine-tune their builds (stats and skills) and train a lot.
Guess what happened now that it's almost tournament time?
Some pseudo-crasher is going around deleting the data whish should be persistant...Man I swear cheaters are such a plague
I have no idea if it's just that the save/clear buttons can be accessed easely to modded clients but that sucks...
So, any tips about making persistant systems stronger against this type of attacks? I'll try passing the advices around devs I know. Cause really that kind of sabotage isn't fair, there should be ways to defend against it
(I mean general tips, I didn't personally see the dev's code)
a function that clears the player's data should never be callable over the network in the first place; they should start the name of the function with a _
yeah that's the first thing I need to ask the dev about, also the clear shouldn't save anyway
you can also further safeguard network functions by checking for NetworkCalling.CallingPlayer, and checking if the player that called the function is also the player that is meant to have the data deleted (so if you're deleting your own data, the CallingPlayer should be the local player)
but my guess is, their deleting function both doesn't start with an underscore, and is also public; so even if their own code doesn't call it over the network, malicious clients can
what if the "crasher" somehow make the target player's client call functions? wouldn't localplayer still match?
somehow, sure. But for that to happen, they would have to modify other player's clients, the creator's code somehow has a network function that lets them call a local function, or VRChat has a HUGE security hole in their networking
so, very very unlikely
Also I agree the saving itself should be all local to begin with.
I wonder if there's an exploit since the stats need to be read at race start...The most worrying part is that it isn't just the equipped/current build but also the loadouts that get reset...So not even a case of "let make this people stats X and when they save that save this instead of their choice" it's without players interacting with the objects. Just instant wipe
that all sounds to me that they haven't properly protected some functions to prevent them being called over the network
but again we'd need to talk with the dev directly, here we can just shoot in the dark....
yeah, the underscore thing, then. are there other advices?
(also for personal notes...is there even an equivalent to this protection for the graphs...?)
Graph follows the same rule, you name the event starting with an underscore
the only other thing I could think of is checking in OnPlayerDataUpdated for any unexpected, drastic changes in saved data
Should I worry about any overhead if I were to add this check to a looping (self-calling) function?
I would assume not? Just one more extern on the pile
Best way to prevent scripters from messing with stuff in my experience has been to never use public networked events
All events should start with that _ like Legos was saying
That underscore prevents those events from being called over the network and locks them down to local only
When possible, use data instead of events for networking things (like turn a bool on or off or send out a specific int to cause an event, then turn it back off)
It takes a bit more planning and troubleshooting but it's made the world a better experience in the end
I’ve heard multiple times lately that client abusers can still take control and misuse any udonsynced variables as well
If that’s the case then it’s an unsolvable problem, no?
the ultimate best solution is: never assume your code can't be broken or exploited, people will always find a way
So true
100%, code defensively
the games I make literally don't give a shit if you cheat. big woop, you skip progress. what do I care
Just make it so when it is broken in a way only a script could cause, something really funny happens
If you try to spoof my username in my world the whole place turns upside down lmao
haha that's great
Yeah I use frenetic’s anticheat to solve all the problems I can, but I don’t code expecting to be able to block client hackers like that
He did attempt to add a solution for client abuse in that too but in the end couldn’t do anything about it
I've been thinking about making some watchdogs too, that'll check the status of various security things Incase something happens
Yeah that's the main issue with client abuse, an anti client thing can't do much if they just turn it off
(which is something they very much can do)
Yeeeep
I have very limited energy, and you might be the same, and it's up to you to ask, what's more worth the effort: developing my game/world/etc, or trying to develop anticheat?
Exactly why I use the prefab for it 🙏
I just like watching my anticheat things turn on when someone tries to be a goober it's so funny
I’ve made a similar (but way worse) anticheat before and using someone else’s is way better than trying to solve that problem myself lol
That's true
Sometimes it's just not worth the effort
But imo it's worth it to preemptively code in a way that makes it difficult to break stuff
Never impossible, but make it not worth the effort
But there’s no way of knowing if I’m actually doing something that has any affect on clients without having one to tell me
It would be a waste of time to try adding that and then find out it does nothing, hence why I don’t try at all lol
I've had the same logic, like what do I even do if I'm not aware what clients are fully capable of
Valid, I was lucky enough to have 1 or 2 people who liked helping with the security stuff.
Basically just avoid events that are called over the network at all costs
pretty challenging for a multiplayer game, no doubt
And make it so stuff goes based off of names or roles or data rather than whether or not something is turned on
Yeah this is what I used to do until I heard anything synced is susceptible, now I’ve started using some networked methods* here and there since idk what else I’d do 🥴
Networking is okay
It's just the events that aren't
Ah
I'll say this, data is pretty safe
And add redundant checks occasionally
Like most of my "important" security stuff has 3 or 4 checks at bare minimum
I assume it's because they can just force ownership? PlayerObjects are pretty great to prevent that.
It'll check like if you are the one actually sending stuff, who's the master, if you are close to the item being toggled or not, stuff like that
Oh yeah, forbidden pickups are fun
Pickups that do silly stuff if touched
Put em far enough away no one would find em without a client
Good point, didn’t consider this, but I’ll still probably have an issue on one of my normal scripts eventually 😂
Oooo icic
Put a forbidden fruit somewhere far away and inaccessible and completely obliterate anyone who picks it, I’ll try to memorize that lmao
So like when someone runs that thing to pick up all pickups, they get yoinked
An apple would be biblical levels of peak
Exactlyyy
ooh I didn't know there was such a thing, I'm totally going to have fun with that
Just make sure whatever happens it still abides by tos ofc
Like my go to is flipping the world upside down
Yea I knew folks could claim ownership over anything they want but all my pickups in my current world are only grabbed with playerobjects 😅
send 'em off to the floating point precision madness zone
Or destroying the objects that make up the world