#world-persistence
1 messages ยท Page 4 of 1
almost I think you want OnPostSerialization to be OnDeserialization
this will currently just run twice for the sender
kinda hard to tell without the larger context, but even then I don't think the OnPostSerialization is utilized correctly.
I'd recommend using the method described in both of these videos above, where ApplySerialization or HandleSerialization is it's own separate custom event that you make, and is called both in ondeserialization and also immediately after you run requestserialization
I wonder if someone's made a chart for the network functions and who runs them and their order.... maybe I should make one
yea this is how I do it and it's extremely reliable
that is what the video above shows 
well yeah but I'm personally just not a video learner. I like hundreds of pages of manuals and charts and stuff
I get impatient
Ahhhhh lets see...
I assume serialization is the packing of data to go glocal and deserialisation is once its been decompressed and applied?
yes
Alright so im guessing the weapon change will be visually delayed for everyone else but besides that we guuchi?
only problem then is that udon graph has a shitty bug which makes these kinds of structures not super reliable. Which is why I'd recommend applyserialization being a separate thing which both of these sources send a custom event to run
By not being reliable what would that entail?
the compiler might not include the variable functions on the second one
so it would just Gameobject.Setactive null, and crash
Oh i see, that would be pretty crap
Ahh so Im guessing Id put deserialisation further back in the graph?
Or forward
left
whatever location works to make the structure look like this
I see :L
I see what you mean so I made the external udon change the internal itemnumber int before carrying out serialisation, or at least thats what I hope you mean lol
ops forgot to plug in the set itemnumber on the custom event but yeye
that's still using this structure though
Format your code to look like this. Separate the part that handles data from the part that applies the data to the world. And stop plugging multiple flows into a single input
Alright so it applies the Itemnumber and all the weapon customization variables, then sends a local custom event to apply and se tthe gun active
(The other end of the chain of customization appliers)
yeah that's it, that's the general structure that makes networking easy
what, like in order to run multiple clients? VRChat has tools to do that built in, you can launch multiple local test clients at once and it goes through the relay servers so it's fairly representative of how networking is gonna work in the real world
Oh? last I checked it threw an error when I try offline testing or something
So yeah I just used multiple accounts to test stuff
I kinda figured it was a legacy feature or something
Also this, this is new to me
well it's what it says on the tin
you have VRCObjectSync on an object that has another UdonBehavior, and that other behavior is using manual sync
ObjectSync needs continous and you cant have mixed sync types on the same object
Well damn, how am I gunna make a gun move with a networked player if I cant sync it?
put the manual sync udonbehaviour on a separate child object of the pickup
Oh... I see, that might need some work
I just remembered yeah, It stopped letting me use more than 1 client for testing
I think it was post the anti cheat update
oh that's why
you are targeting the wrong launcher
in the SDK, in the Settings tab, check what path is here
I think it does the EAC problem if you're targeting "launch.exe" instead of "VRChat.exe"
Oh I see, I assume that disallows any activity outside of your test world, so you cant play the normal game after testing
it's EAC that blocks it. You can't have more than one game running EAC at the same time (for example you can't launch Elden Ring and VRChat at the same time)
but local testing ignores EAC
Oh I didnt know that seperate games under it were affected
But like you wouldnt be able to then join on friends after you're done testing right?
Figured, that would have been a pretty hilarious oversight if so lol
though I've been having a kind of related issue recently. If I run 1 or 2 local clients it's fine, but for my world I need up to 6 clients, and usually about half of the clients get a popup saying "You're in a local test and cannot join public worlds" yadda yadda, even though I never attempt to leave the world, it's just a few seconds after joining the local instance. Then a bit later those clients get another popup saying "Unusual client behavior", then it boots those clients to the error world
it's not anticheat doing it
or else I wouldn't be able to launch 6 clients in the first place
VRChat is confused about somethin and I haven't investigated what
I use the VRC Quick Launcher in the "Tools" tab of the VCC btw. It's awesome, automatically opens whatever howmany clients you want when you build, then automatically moves all the windows where you can see all of them at once
Oh i see
โ๏นโ
How you controling 6 players with only 4 limbs
I mean on a good day I might be able to use my tongue to control the 5th but 6! you madman
Remembers episode 1 of no game no life anime
I thought it was possibly my code doing it but this is a practically empty testing world, not my main thing, and the same thing happens
Can anyone tell me what I'm doing wrong here?
I'm trying to make a object activate when I interact with a cube. In offline test everything works fine but when I upload to my world the cube I need to interact don't show up and I don't know why. Can anyone help pls?
the ACTION section looks a bit odd....
Interact starts that block. easy enough.
Then I'm going to assume "LocalPlayerHasBeenRestored" is true and it will continue to the next block.
In that block, you check if the object "botao" is active, and if it is, continue.
This means that if this object is disabled, the Branch will never continue and activate the object
Which seems a little odd. Why check if something is active, and only activate it if it's already active?
did you mean to do some UnaryNegation and toggle the object on and off?
Is OnPlayerRestored called when that object / owner's data specifically is restored or should you be checking against the player who's data is being restored && the current playerobject's owner?
yes it's for every specific player, OnPlayerRestored provides you the player whose data is being restored
That was other test I've tried to check if the problem was there. Actually it disables the object if its true
But for some reason in offline the button to interact shows up and when I upload to my world it doesn't
Yeah but only when its uploaded
This is the rest of the code
idk if this will help
is it because of the data you are loading?
I can't read graphs ๐ตโ๐ซ
But it seems like when your data is loaded, you set the active of object?
Is the object the button?
is "botao" the button?
No, its a gameobject the button must activate
if the button itself it separate and not referenced in the script then it won't be this script that's causing it to disappear
is this a PlayerObject?
The button is also a gameobject and I added the udon behaviour on it
and you assigned "botao" to a GameObject in the Inspector? I believe it will be "Self" if nothing is entered
I mean the action is to disable the "botao"
Yeah
huh.... something else has to be disabling it
what if you put the script on a second GameObject
I was making the button the botao
So I tried to add another object to be the botao
In offline it looks like this
When I upload it look like this
The left object is the button
Yeah
And maybe its something with the persistence
Yes...
that's what I was going to suggest
ClientSim + Local Testing + Live can all have different persistent data
if I'm reading right, you load the key JumpDataKey, which loads to false by default if it doesn't exist
so when a player joins for the first time without any prior data, it will immediately disable the object once they load in
and I don't currently see a way where the bool will be set to true
but your past code may have set it true, so persistently it would sometimes load in
I will try creating a new data key because this one is already in use in another script that uses Int
welp that would do it lol
if you try to load a key with the wrong type, it will also give it the default value (false for bools)
Like what would be the right type for bools?
what do you mean what would be the right type for bools?
Bools are just a type of a data
0 or 1
bools is a type
true or false
I would slow down a bit on whatever your doing and figure some of the basics out or your gunna be banging your head against a wall
https://creators.vrchat.com/worlds/udon/persistence/player-data/
this shows the types of data we can store
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.
I was talking about the string data key
well that can be whatever you want
think about datakeys like a variable
that you can access and modify
the string if just how you refer to it
I think found the problem and it was with what you said before
The update part set the gameobject off and I forced to make it set activate and now he shows the botao
Probably not a persistance specific question but it started happening when I implemented PlayerObjects:
What usually causes players to be sent to the void as soon as they join a world?
It keep happening when there's 3 players or more in a world I'm working on, but I have nothing (that I remember of) checking for specific number of players. (and even so it should only be the related graph that crashes, right? this problem probably is something more worrying to fix...)
Also just to confirm, when a PlayerObject gets "turned off" in the hierarchy after a test build that means there's a problem in it, right?
Sounds like a spawn point problem and not related to playerobject
PlayerObject tuned off is normal, the original is a template that gets copied, but the template itself is always off
a spawn point problem?
player spawn correctly alone or with a second player, not falling throught the ground or anything
I have a checkpoint system, but it should only TP after "OnPlayerRestored" tho...
OnPlayerRestored is called for every user whenever someone joins
So I imagine it could be related to that
I guess since it's a world focused on persistant experiments that's...a lot of objects relying on that ye...
especially since I got lazy with the minimap system, just putting a "if player enter the trigger, turn a bool true and the object off. onplayerrestored if bool is true automate same action" on the tile (not a playerobject) instead of having a manager array. That's probably A LOT of new objects checking who have unlocked which part of the map.
Oh well, time to make a manager to alleviate a bit of that
It can get squirrelly real quick
Ok yeah, even that small test section was already 75 tiles. That adds up super fast
Now it's just one manager running a "For"
Ok I am confused as heck now...
Finally was able to test out the manager (something at home caused some AFK hence the delay) and now only the 1st player isn't stuck in a loop...
still weird that it works with 1 or 2 players but not starting 3. I don't even know what to ask if I go to the networking channel
You probably gotta drop some code
if I even knew which graph to look at that is
hopefully you can tell where in your own code networking stuff is being done
if it works for 1 person, but starts failing when more are being added, then the networking is probably getting goofed somewhere
it works for one
and works for 2
starting 3 it act weird
Also I'd really need to learn when it's safe for UdonBehaviors to be set as "none" someday...
because the console don't tell me anything...I corrected all it asked to each time I work on something
which means you have a logic error
but feels like that's...a lot of IDs
actually yeah that is a lot of IDs....
do you have a lot of objects with Udon scripts on it or lots of pickups?
a good chunk of them probably are from the pixelcanvas... ^^"
a ton of Udon ye
not that many pickups
but most of them are generic event senders. "if interact, send "event name" to "target udonbehavior"
it's mostly the target (managers) that do the heavy lifting and loop a lot
but the 3rd player thing only happened recentely and I corrected the game mechanic that had lot of UdonBehaviors for it (the persistant fog of war on the minimap)
Now a logic error wouldn't surprise me, i barely knows what I'm doing with these graphs
How do people usually rule out these kind of things?
a looooootta debugging
yeah no that much is obvious xD
was more asking about what method to follow to even find what would cause it
I don't know where to start
can't think of anything aside of "remove stuff from the scene till it stop breaking
that is sort of a method
but with many interconnecting scripts it might just not work at all doing that strat
ye, plus if I mess up it's destructive
you just have to systematically narrow down where the issue could be
I would currently be thinking, it has to be something network related.... so I'd go through and reread, go through the execution in my head and figure out where anything could possibly go wrong
and when it comes to networking I usually just enable printing out the OnPostSerialization result stuff and just make sure it's actually successful \
considering there's an idle game with auto-click feature AND the playertracker for the minimap...that's the two things that would fire the most time
then next I usually check "am I using/updating variables at the right time" which usually involves me printing out the variables on Update to a TextMeshPro, so that I always see the current value, and see if anything obvious is happening wrong with that
but idle existed since the very start of the map so it worked fine
oh yeah and with so many things going on, maybe something is hitting limits
yeah I mean...I don't even know how the world even work at all
could be 1 person it's fine, 2 people doesn't reach it, but with 3 limits start getting reached
there's the idle game, the respawning nodes for making the house, the minimap, puzzles...the pixelcanvas that itself is already 32x32 resolution
that's...a lot
to be fair the scope of the map was "work on it till you find a limit" but it's sad if I reach it at the minimap... xD
"we can finally make exploring interesting, also we can't add anything more"
a minimap may not need any networking
nah it's just the "fog of war" that's persistant, but everything is run locally
but that adds a lot of individual bools to check which tiles you unlocked (only happen once, on PlayerRestored, but like you said earlier that fire for every players each time, with how many things are persistant in the world that's...a lot)
75 for the small test area
Like, can be completed in a minute" small...
(in a normal world I'd probably make the tiles really big, here was just to feel like you unlock as you walk)
hm but wait what part was failing? Loading this "fog of war" persistently, or things syncing between players?
It doesn't fail. It's PlayerData and check LocalPlayer's keys (each player unlock the map on their own. 1 tile = 1 bool)
I just said it still relies on OnPlayerRestored for loading the states and that's a lot of keys being called
Since there's all the other persistant systems too.
that's why I'm so confused about the potential logic error, because everything works when alone or a 2nd player (like you said I'm probably hitting limits of something)
The easiest way to understand how much is going on would be to visit the world, honestly ^^"
yeaaah...
I updated the description so people know there's a problem.
Also you don't see much at the start, there's a whole house to build and the underground labyrinth is way bigger than the new one made for the map system...but you need something from the house to unlock the entrance
well I've rejoined the world and it seems some fields have default values rather than being loaded
there's another person in the instance with me
interesting, this one didn't used to break
it's not the maze, it's the whole world
when there's 3+ some players get stuck in a loop of rejoining
or sent back to their home world
ah ok
as said before 1 or 2 players seems to work fine
but 3+ is a hot mess
The score tracker (not really a leaderboard, order is by who joins first) I know it breaks when people leave and someone else rejoins
so at least this one can be explained
local test 2 cliens. Works at first, freeze for the player when the other rejoins (break completely on the rejoining player's side)
just me not handeling players IDs correctly I guess
but I'm still new to networking
The leaderboard prefab has the ordering setup. IDK if you have taken a peak at that
nope, whole world was just me taking shots in the dark (especially the very early stuff like this)
I'll go look at it
๐ซก If that is the case, I would revisit all your code. Especially as you improve
I'm sure a lot of things breaking are because of bad PlayerID
like...100% sure at this point xD
it's hard to single out because this world has so much in it
I get a couple of scripts crashing when the third player joins
I would go clean slate and make sure your systems are working 1 by 1
that's what I fear... ^^"
there's also older systems (like the house and nodes) that are just stuff made compatible with persistance
but that were from when I was even worse at UdonGraph than now
the world is a hot mess xD
I can relate w/ that one. My older features are very obvious that I had just learned how to do them ๐
and it's nothing compared to how much I crammed in the on-persistant test world I had before...
the most I can parse is there's a handful of things trying to reference nulls..... and errors of the persistence keys being invalid
(a portal to it is actually the reward for the idle game, since it's still private, but I could guide you)
for the keys I think I know
since I changed the map from stand alone tiles to a manager
but that should only be if you already had a save...unless I finally hit the limit
I know it's probably that because it's a new error from when I updated it
now the nulls I don't know (aside of invalid PlayerID for stuff like the score board)
I'm trying to parse the raw hex dump of the errors...... but my guess (emphasis guess) is that when you're loading persistent data, the VRCPlayerApi is invalid, causing a cascade of values to be null references; I see the null failures on getting the player's display name (string), three bools, and an int or two
But yeah blank slate and making everything their own cleaned up prefabs seems like a good thing to do
why this happens with 3 people, no idea
yeah if this is a practice "scratch" world, it's an option to go "welp I learned a lot from this but it's too broken beyond repair" and start anew
yeah
the systems you actually want to use, port to a world by itself and start debugging then
or to apply the relevent stuff to worlds that are actually reasonable in scope
ye
this world is basically brainstorming visualised
I was gonna say earlier that I feel like I'm just walking around inside a mad scientist's notebook in this world
that's pretty much spot on with the notes written on the walls... xD
if you want to have some fun looking at the other world I could give you a hint to skipping the grind
that's the least i can do to thank you for trying to help
||It's actually hinted at in the wall where I say hello to the player, but there is something related to holding a mirror. One is unlockable with the idle game, but your player mirror works too. There are things written on another wall only mirror can reflect, the next step is beyond said wall||
I would join to show but I feel like that'd be 3 players in world right now... xD
these doohickeys?
||ye, just try to reach what you see, they have collision||
||the text itself is just references to memes from friends||
also wait...the wall don't show ||on the mirro||r now...?
uh...why did I changed it again, there should be a reason...
oh right, because the underground labyrinth's points at this from under as a hint if you missed it
also thanks again for this screenshot, that gave me an idea
(ok I found the "invalid keys" it's because the non saved tiles were still in the array...silly me)
Oh...
Yeah no that's a loooooooooooooooooooooooooooot of calls for PixelCanvas to initialize...
(and worse than what the image suggest when you realise how much it has scrolled down)
Ok I must rethink the logic but again...that means this many objects to manually link in the array if I make a manager
Working in graphs is such a pain for stuff like that...
(unless there's a way to mass dump references from hierarchy to an array but I never found it...)
@visual cargo I guess this would be my best bet if I had to only nuke one asset to test if things go back to normal.
Or just removing the alt resolutions and see if the world works for 4-6 people then break again (aka: limit is pushed back but not solved).
ok, instantly 3 players works again, it was the PlayerObject
4 still breaks but removing only x8 and x16 isn't much (from 2.3k to 1.9k) so it's expected
x16 alone is almost under 900, so theorically I can double the number of players...?
ok let's NOT local test 8 players again...the clients went "unusual behavior" error... xD
guess I'll have to stress-test live
after 8+ hours redesigning the thing...I'm back to square one
I really need to introduce a custom delay, even without using OnPlayerRestored it keeps breaking the world at 3 players
without save it runs fine at 3+ even tho all the tiles still initialize to make the canvas white tho...
it's really just...something caused by persistance in the PlayerObject
It's so weird because the "update material" thing runs a networked(all) custom event so it should be as heavy but no, it's only when I introduce loading persisted data into it that the world breaks...
guess I'll really have to bit the bullet and asign all the tiles one by one in the inspector to run an array with the custom delay...so much pain for such a gimmick asset.
fyi you can multiselect GameObjects and drag them into an array all at once, instead of dragging in each and every one individually
or you can do a weird thing I did before; have the tiles add themselves to the array, by calling a function that automatically expands the array and puts itself in
how..? when I have stuff mass selected, if I click on the object that have the behavior it just deselect everything...
do I need two hierachy open or something..?
you have the object with the behavior selected first, then click the lock icon on the top right to keep it open
there's a...
ever notice this doohickey?
very tiny and very easy to miss
I didn't know about it until a friend told me
oh ffs it could have saved me months at this point of always going 1 by 1 on every thing I made over the past 4 years...
I had the exact same reaction
especially the book system and whatnot
Well if it really works I should be done in uh...just the time to relink together the custom delay from before and dragging the thing I guess
how do..uh...
I'm confused...
the interface is different
also just tried it only put the 1st one in the 1st cell...
what about the interface is different? (I moved the hierachy right next to the Inspector so I could take a video of it easier)
did you see where I dragged it in
I should maybe have mentionned I'm a graph user...
oh it would be a real shame if this didn't work for graph....
yes, but in the number here just give me a block sign, won't allow me to drop at size
only in the cells

uh oh!
the cursor just becomes this
unless it's in a cell
yeah...guess it's U# exclusive...
yes, I did, it gives me
everywhere I hover, except for the cells
especially for a 32x32 canvas
could make a dummy U# script purely to hold the array lol
I...guess
how do you access U# scripts from a graph again?
you can't just drag the Ubehavior like for a graph to another
you make a public UdonBehavior variable, put the U# script on a GameObject, then drag the GameObject into the variable
oh the game object directly, gotcha
might actually be a viable option for the plan B I described
program variables can get arrays right?
if it's the same as in graphs ye, int and int[] both exists for example
actually It's probably better if I do two arrays (one listing the tiles and one for the color assigned) so it only need to run one object checking which tile was interacted with instead of all tiles checking if the pen touched them
ok so ideally you make a U# script that lierally all it does is public GameObject[] myObjects;, then it'll appear in the inspector
Then in your graph, at Start assign your array to the other array
at start? Oh right...I totally forgot you need a dummy/math array to modify one on another behavior...
well I just need to match the length anyway
the rest I already did in the past
yeah it'll probably be easier to just copy the dummy, rather than change your entire graph to keep referencing the other array
yeah usually I go
A[] = B []
Make changes to specific indexed of A[]
B[] = A[]
because sending change on only specific indexes act super weird but full array A = array B seems to work consistantly.
ah like networking changes to arrays?
between graphs ye
like here let say the pen touch "tile 404", only the color value of index 404 on the reference/dummy should change
but it just act weird when it communicate between graphs trying to do that
sending the whole array works fine
so I just do the calculations internally with a math dummy
it probably didn't work because OnVariableChanged doesn't fire when you change array contents, only when you change the entire array
ye
because, technically, the array never changed
it's not a hard workaround so it's fine
but thanks for the reminder
uh...I guess I should had asked how to create a script first...
rut roh
I swear the pain of working on this is reaching cartoon levels xD
at this point I'm better off comissioning someone who knows what they're doing xD
guess I should had specified more often I'm not a programmer ๐ธ
ok let me rephrase it as coder then
took me 3 attempts to make a script, now saying it's invalid (but at least Unity stopped crashing
you went to Create > U# Script to make it right?
that's really funny
and then create when it says it misses one
should make two files that look something like this
what does the console say
source is null
it bugged when u made it or something
tell that to Unity that made them I didn't rename them xD
I swear this engine is just messing with me at this point xD
sometimes it's out to get you
have you recently said aloud that a certain game was bad, and that game was made in Unity?
Unity tends to hold grudges
I don't even have time to play games
spent the last 8h trying to debug this thing
and that's jsut today
that canvas alone is ruining the world I'm this close to just delete it and pretend it never existed xD
I understand why Prismic answered they have no intention to work on a similar thing when I asked if it's ok for me to try making one (asked since it's similar to their VRcanvas world)
...Yeah no, don't touch this can of wasps even with a long stick xD
oh
it's create -> U# script
not the VRchat dropdown...
no wonder it didn't worked!
sorry I missread cause I'm tired and assumed the VRchat thing was...in the VRchat dropdown
silly me
a VRchat thing in the VRchat category what was I thinking xD
well at least it's becoming silly enough to give me a good (nervous) laugh
console
already did
and deleted the old two files?
yes
it's not happy with NEW.cs
which was created following the instructions ._.
at this point it's maybe healthier that I take a break
I mean the only break I took was to make food
Take a break. ๐ซก
also I know it's just skill issue on my part
only time I ever used U# was to mess with an existing script by shifting a value
I had a similar experience when I started U# vs Graph
I kinda wish the QoL was just the same for the inspector...Like this whole shenanigan started because you can mass-drop in one and not the other...
we can only hope Soba has it
and the problem before it because persistance in PlayerObject seems to behave like OnPlayerRestored even if you don't use it so the network still get saturated...
even with me trying to introduce a delay to load tiles one by one
How big is the data you are saving and loading?
like I don't care if my prefab takes 10sec to load as long as nobody freaking crashes...
a whole pixelcanvas...
32x32 tiles and 24 possible colors
So, 32x32 (1024) length array of int 0-23 basially?
I mean righ now it's just stand alone tiles having one variable stored inside
but that means a shitton of calls on loading the persistant data
and every user is loading everyone elses data?
hence me trying to use a manager and array instead
so at least it only load one object per player
but 1k+ objects to drag manually, it's killing me
yet I lost a full day
at this point it would had been faster to just do it
but I don't even know if it would do anything because it's my first PlayerObject
it's been a nightmare to work with compared to PlayerData
I've made a fog of war that unlocks as you walk in the tiles in like...An hour or two with PlayerData
and it's practically the same except instead of a bool it's an int since there's more possibility with colors
and you collide a pen not the player
I don't want to make the canvas a PlayerData thing because it makes sense to instantiate them
and I want to use some of the PlayerObject data to not burn all the PlayerData one...
if it was the only asset in the world honestly I'd make a PlayerData version
but the whole point is to learn both sides of persistance
I was gunna say, I think this would be cheeze w/ playerdata
yeah no with Pdata? piece of cake
So you want a PObject w/ their persisted data basically
even the fact you don't have to reference other objects to retrieve the data it's SO GOOD!
like, Pdata is really good, I had a blast working on it since the public beta dropped
Pobject on the other hand had been a nightmare non-stop to work with
This could be the case of trying to use one thing when the other version is far easier
like ur overengineering it
well eah but the whole world uses Pdata so I fear of running out of data if I make a wholeass canvas...
the point of using both sides is also to allow the prefab to exist in an environment where the other side's data mays already be taken
the thing about PlayerObjects is, they're practically the same as the networking that already existed
and you don't really need ot reference other objects to get PlayerObjects, they're linked to the player, you can get it from them
it's a bit on pupose that I use all the PlayerObject data on one thing (max resolution before it beaks without kicking a single player is 58x58 so I still scale down safely)
yeah but the problem is still the same. when loading the data there's so many tiles it just kicks players because networking is insanely saturated
hence me deciding to redesign it using two arrays on one object
but graph is a pain having to drag manually
which seems odd..... it should only be loading about 4096 bytes per player? usually even smaller due to being compressed
my guess is it's less the ammount of data
like said max for 1 player is 58x58 resolution
not kicking them, just reaching data limit
and I scaled down a lot
so it's more the number of calls on same frame
loading the one variable on all the tiles
did you factor in the number of colors? they're not bools
yes they're ints
ye
but yeah i'm not nearly close enough to the data limit in term of size
probably the calls that flag as spam
because if I let it run enough I get "failed to authenticate" and can't even connect on normal VRchat for like an hour xD
so with your max resolution, each player is storing 13,456 bytes raw
in my testing players could store up to 300,000 bytes with no issues
when I close the localtesting clients and it reaches the number of players where it is known to not crash tho, the one in a loop loads correctly
I'm gonna modify my PlayerObject tests to mimic your amount of data
I mean I could send you the prefab xD
your graphs scare me
as they should xD
I don't count how many times I had to separate some because i hit the limit of nodes...
Gotta learn U# brutha
like the "persistant skill tree" it have one manager then one graph per thing you upgrade
yeah at this point even Unity itself is scared enough of my graphs to crash at the thought of me creating a script, it's getting real bad <w<
Nah graphs just get outta hand w/ anything complex
I think when it comes to processing lots of data in even semi-complex ways, graphs just explode
I cannot fathom trying to do what ur doin in graph
xD
Like in U#, sounds easy enough
the scale is big but the pixelcanvas itself is simple compared to everything else in the world tho...
literally just triggers that change one variable when the pen touch (copying the pen's current color) and assign a sharedmaterial off the reference array
and the pen changing its own when colliding with the palette's objects
like...that's it
then just save the tile's int value, run the "update material" on load
and yeah that's literally it
Yeah this doesn't sound too bad in U#
I'm sure someone in U# could remake it in an hour
the objects themselves are just cubes
or quads
I wouldn't have done colors tho without just getting a basic bool for the tiles that are changed
quads seems more appropriate here
why would they need a bool?
Like if I were you
I would have made sure I can load 32x32 worth of bools for each player
the pen have the array of colors for its own collision, the tiles check the pen's array so the indexes for a wanted result always matches
well like said before I started with 1 player (since I was unaware of this problem and was trying to make the thing work at all) and reached 58x58 before it broke (no kicking, just the graphs not responding)
so in this context it was "pretty safe" to assume that scaling down to 32x32 would be fine
since 58x58 was fine
(not bools, literally same canvas with stand alone tiles)
Nah what I mean is like being able to load the data w/out a hitch.
Cause something you got going on isn't working right.
it worked fine with 1 player...you know how many people I get to answer my invites even when there's 60+ firends online? Zero ๐คฃ
so stress-testing was uh...I tried
I AFK'd in the world all the time
Make an alt and use the quick launcher
and always test w/ the quick launcher with multipel clients
launching 6 clients rn to test
since the problem start at 3 players I wouldn't had seen it even with one alt
maybe for the 58x58 yeah 2 would had triggered
for context on how long I AFK'd trying to test stuff live in that world (since the last time i had to wipe my data)
(synergy hits once every 10 seconds)
jokes aside yeah I get so few responses it's probably better if I start making alts
naur just use the client launching tool lol
even tho I still have issues with it....
so I successfully, persistently load an int array of size 3364, totaling 13520 bytes when serialized
this gets compressed down to 6878 bytes
takes about 1 second to sync
I'm aifraid to send you the package...
hit me
knowing how my graphs works on luck and hopes we can take bets on something looping or whatnot... xD
rather than real problems
ok I'll send the V2 in DMs, since it was the last stable version of the prefab before today's rework
oh ffs I try to put it in the scene to test if nothing broke and Unity refuses to build now
ok what graph that V3 also uses conflict now...
It's just UdonSharp that failed to compile
and you've only got the one U# script no?
OH
right
I created that thing earlier...
forgot it existed...
ok it builds now...thanks for the reminder again
guess I'm really earning this "mad scientist" nickname you gave me when you 1st visited the world...
To be clear I didn't say I had no intention to lol, just that I had no plans for it currently (which is still the case). The concept has been in my notepad of potential world ideas, I just don't have any present drive to make it, and when you expressed interest in your own version of it, all the less reason to want to spoil that ๐ In fairness to you as well, doing it in graph does sound like a pain ๐
yeah I worded it badly, it's true that the focus was "at the moment"
but man this is giving me such a bad time at this point it's healthier to wait for whenever you'd make it the correct way xD
also the thing totally works and saves, the prefab itself was finished like, weeks ago
it's just this networking issue that's literally world breaking
I dunno about a "correct" way haha. My approach to syncing the freedraw canvas was thousands of defined functions that people could call on the network, instead of a more sane approach involving proper networking.
sending players in a loop of rejoining, back home or worst case scenario "fail to authenticate" timeout
Nice! You do sound pretty close then. Maybe take a break to work on something else involving networking but simpler, then try coming back to this one later from another angle?
learning how networking works instead of taking shots in the dark may help ye... xD
I don't want to make it local/PlayerData tho, the world already uses a lot of persisted objects
the whole point was that PlayerObject is instentiated so players can see everyone working on their canvases
(they spawn with an offset using the player's ID to not overlap)
and a dummy "player have left" canvas replace the PlayerObject when someone leaves
(unfortunately just a dummy, never found how to duplicate the canvas content on player leaving)
but yeah solo you can already try it in the world
I'd suggest taking a look at the leaderboard prefab in the example central
right, I need to fix mine as well
It's in U# too ๐
oh boi...
I mean reading a script isn't too much of a problem
my brain just freeze when I have to write in a "list" format like coding
I think you'll manage ๐ซก
Feel free to ask for help, I am more familiar with U# over graph
Even when I was designing for ProjectAincrad that always was a problem that I had to use Canvases in Obsidian instead of writing notes...it's beyond just learning to code
Graph makes me wanna gouge my eyes out
I think something like this would need a proper execution outline, a plan, rather than creating it flying by the seat of your pants
a design of a data structure optimized to work for a painting canvas would be ideal
yeah I mean, the PixelCanvas bug was unexpected, the thing itself works "fine enough"
doesn't really help that graph doesn't give as useful of errors
but yeah these tiles can't be stand-alone that's for sure
U# will tell you the exact line and column an error occurs
are there many other errors in it aside of players vanishing to the shadow realm? (since you're looking at the package I sent)
just one, a bool being angry
a bool...
I don't use many in this one, check the resolution buttons
for the generic object switcher
OH, or it's the canvas reset button, right I never finished that one on the old version
ah I found it
since it needed the bigass array xD
Target_Switch array in the ObjectSwitcher_V3 script on Resolution8x8, the array has no GameObject in it
script crashes attempting to access it
yup, you didn't put the dummy in it like I said in DMs xD
I always have a dummy empty for stuff like that in my projects (probably a bad habit)
they crash all the time when there's an unused part...
probably better to check if it's null first and then handle it lol
I usually check for it, then output to the console telling myself I forgot to assign something
believe me in graph? always put a dummy, you never know when something crashes just because why not
always fill all the fields
same thing will technically happen in U#
you can check if an object is null and ignore it or whatever, instead of letting it crash
that sounds useful. dunno if there' a proper way to do the same in graph
I just put flex tape where things break and hope for the best
dunno how the co-op puzzles in that world don't break when I'm so bad with networking xD
interesting.... canvases handle ClientSim remote players just fine, can get over 3
ah right, I usually just build&test ._.
I mean, the limit may be higher on your end since you don't have the rest of the world running
remember that I have the idle game and whatnot (even tho they're local, the score is just fetched once a second for the leaderboard)
I managed to get to 4 players
does it still break at 5+?
as the fourth player joins, they get stuck in a loop
now have what looks like more meaningful logs
ok being stuck in a loop of rejoining is what happened ye
soooo this looks like where Network.IsNetworkSettled becomes useful
"you're doing it too quickly" ye we already figured that was the problem... xD
but yeah no wonder it just timeout the player after that rapid fire of warnings
when a player joins, they load the data for all PlayerObjects in the instance; it seems when it gets to around 3-5 or more, it becomes too many requests too quickly, but we never stop to wait, it just keeps requesting over and over until VRChat just thinks there's a problem and restarts
and now we have this lovely infinitely-growing wall of dead canvases
yeah
well at least the problem is successfully replicated
I would want to know how to slow down, that's what I tired to do the whole day today
I got rid of OnPlayerRestored to introduce a SendCustomEvent(delayed) with a random range
because I don't care the other the tiles appears, just that they don't fire at the same time
but even calling this one would mean they all call it at some point
so, it's the snake bitting it tail
I think you'll still want OnPlayerRestored, but also use IsNetworkSettled to further determine if the data is ready to use
never heard of that one ._.
something gets caught in the loop trying to use data when it isn't ready yet
This doc covers Networking Components, Properties and Events you can use in your Udon Programs.
IsClogged Returns true if there is too much data trying to get out. You can use this to wait until the network is unclogged or to adjust your logic.
wouldn't that one also be useful?
that's outbound data; our issue is inbound data, when a player is joining and attempting to download the data
ok
still might be good to add. It's another thing to wait for to really make sure everything is ready to use
So OnPlayerRestored -> Is NetworkSettled? -> yes? then IsClogged? > yes? now start using data
if either is no, wait like 2 seconds and check again
uh shouldn't clogged be no? xD
er yeah lol
curse bools
I always try to make bools where true is the "continue" condition, but IsClogged is flipped
should be IsNotClogged imo
like that?
I keep the range so they don't just all fire as soon as things are ok or it will instantly clog again
That works, except I'd just do OnPlayerRestored > call Check network Ok, then put Check_NetoworkOK where OnPlayerRestored currently is
Merging noodles can cause the compiler to freak out sometimes
for comparison that was what I made earlier today... xD
since I assumed OnPlayerRestored is when it's safe to load data, as it's how it was made look like when I was learning
but yeah extra checks don't hurt
Problem was that Start and OnPlayerRestored would still fire at the same moment for everyone, rendering the delays that comes after useless (hence my back to square 1 when I started to write here)
I should probably add a "is initialized" bool as killswitch for this loop...just as a failsafe right?
...
as expected from OnPlayerRestored fireing at the same moment before the other checks occurs, player 3 still bugs
which make sense since everyone is trying to run OnPlayerRestored BEFORE asking the other questions
and will try to run it regardless if it is used anyway...since it comes before the checks
I guess same would apply even to Start...
I really see no solution aside of using the "array in a manager" method instead of stand alone at this point
even if there was an equivalent of NetworkSettled" as "OnSomething" starting even they'd still fire at the moment it's fine...yeah no there's no other solution as graphs goes. I must just do it even if it's tedious
At least from a single object starting point I can make a slower pseudo "for" like here to make sure not too many things runs at the same time
Actually, regardless of PixelCanvas I should add the NetworkSettled to the idle game now that I know it's a thing...Just to make it a bit healther at higher speeds
finally, I think it worked...
well the rest is too much for my lack of sleep, I'll continue tomorrow
Thanks again for the help (like, really. being able to bulk drag&drop will be life changing in terms of saving time xD)
Oh, I get what you ment by that now!
Once I copy the reference starting state, I don't need it or even to communicate with it since I already have valid arrays inside the manager Graph. That also means way less networking between different scripts. Took me long enough but I think I get it now.
Only issue I see is to define when it needs to be set and when it's already valid. (to not reset everytime the player rejoins)
Since a default empty array is of length 1 I guess I could check that before sending the initialize event (or just save a "Is_ArrayReady" bool)
hey guys, could i get help with understanding how the rate limiting works?
I've been getting reports from my players that they are losing their data outright or not saving the most final player data. I am guessing it is due to the player data being limited by the amounts of updates its sending but I am quite unclear when it comes to networking, the screenshot is a snippet from the debugger.
Is my assumption right or could there be something else at play?
If you read the Networking.IsClogged value exposed to udon, that indicates you have data queued to send. Leaving the instance while you are clogged is a surefire way to lose that pending data, so one way to help with this would be to show a warning message to the user while they're clogged, telling them not to leave yet
Got it, ill have a look into that, thank you for the help
is it possible to suppress this message?
it comes up when you use FindComponentInPlayerObjects and I'll have like 40+ of them later on which will be very funnyyyy. If its editor only I don't mind
isn't that the whole point to tell you it found something when you call FindComponentInPlayerObjects tho? xD
it's suppose to return that component, but it spams my console on success xd
https://creators.vrchat.com/worlds/udon/persistence/player-object#methods
PlayerObjects allow you to automatically give each player who joins your world a copy of a GameObject, such as a flashlight, a health bar, or a sword.
Waait can you send a network rpc like this, when you're not the owner of this player object? the rpc gets blocked for some reason, although I'm trying to tell the owner to handle incoming requests
Upon requesting, the master of a system will send a response back to the one that requested- similar to this:
otherPlayersPlayerObject.SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.Owner, "RegisterSuccess");
Like yeah, I know that I can't take ownership of it, but why is the rpc blocked as well? We can hide functions with the _ from network calls, so I thought that should be possible
(maybe my code is cooked one sec)
Update: Console says it's blocked but the rpc call is getting processed heckkkkk
you could (and should) cache those results inside your script
but I need to sync it via dictionaries and listssss
as of right now, I'm syncing playerIDs so I can access them via a list
I could still create a seperate list where I save the cached results but that's work xd
it's an object reference, what do you mean you need to sync it?
yes, have a separate list or dictionary where the components are cached locally
okay fairrrr I'll just use a dictionary where I can use the playerIDs as key for the dictionary. Is it expensive to call FindComponentInPlayerObjects? Atleast to a certain degree, where caching it would make more sense
moderately, yeah. It's definitely doing some work
the exact cost doesn't matter, what matters is that caching it inside your script is a free performance boost
alriight thank youu ๐ will do that then
Is there a way to directly refer to a persistence object when calling custom events for them? so far I only know of calling upon them as gameobjects so am currently using a "middleman" script to send data to them
Or rather can you have a gameobject save its parameters without making it into a playerobject?
Sounds like you want to use PlayerData? Those keys can be accessed from anywhere and have a check for local player
hmm, lets see
So Im using an object to keep track of all the stats the player has
But it needs to be referenced for logic to activate things in the game
How do I ue this playerdata?
dios mio
well the big difference in PlayerData is that it doesn't have to exist on a GameObject or one specific script; it's data that's part of the player
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.
Oh ya? can I make veriables on a playerdata?
pretty much. but you probably only want to use PlayerData to save and load the data, but when you actually want to work it, you put it in a variable
you could adapt your current middleman to just save and load the data to PlayerData instead of relying on PlayerObjects saving it
That sounds good, how can I set up a playerdata?
the link I posted is the doc page for it
Oh yes
you essentially use the OnPlayerRestored event, which fires once all persistent data is loaded and ready, and use it to either store the data to variables to work later, or (if you detect the data is not there yet), create default values
then you use the functions for the various types, like SetInt, SetFloat, GetString, TryGetString, to save (set) and load (get) the data
Oh I remember now I was told about this, Playerdata creates itself right, you dont manually set it up I was like "Saywhahhhhhhh"
well. yeah kinda I guess. you have to tell it to save and load the values and stuff
Lets see... I dont think Ive tried it before
Well at least I can stop using the "persistence save" as a persistence object so i can target it with event custom without having to middleman wireless transfer that shuzz lol
PlayerData can be called by local or normal networked objects, they don't need to be PlayerObject.
For PlayerData I just make a dummy UdonBehavior to note down the key names.
All categories of the dummy are strings, the names reffer to which key types the names reffer to.
(So the "Key_Bool" are names that point to Get/Set Bools nodes specifically, etc)
But there's obviously better ways to keep track of that... ๐คฃ
You can either use the keys directly, or like suggested use variables like you'd normally do and just have:
For Loading
"OnPlayerRestored -> Set <variable> value to <key> value"
(initialize variable to match the key on rejoining the world)
For saving
"Custom event -> Set <key> value to <variable> value -> request serialisation"
(update the key to the variable's value, request to sync the data with server)
Im reading up on the article, I have a pretty good idea on how to implement it
PlayerData is pretty easy to use, and not having to constantly reference objects cause you can call the key from anywhere is suuuuuuuch good QoL it's fun to use
In hindsight having my savedata not set up correctly has actually made it easier to quickly implement and debug a feature lol
neat
Please save your scene in a different folder than the VRCDefaultWorldScene.
Afaik the VCC still deletes and re-adds that every single time the sdk is updated.
If you're not using some sort of version control software, this can easily lead to lost work.
Same for any UdonBehavior and whatever else people customize.
I don't count how many time I had to remake my object switcher and search which references were broken in the scene... ๐คฃ
It's so cursed.
It is...Especially for the most generic ones xD
I tend to backup every time I do any sort of upgrade, so what happens? the scene just deletes randomly?
I just upgraded the SDK and it did not overwrite VRCDefaultWorldScene ๐
It's taken ages of writing and rewriting code to figure it out but I've finally integrated saving with my procedural map generation system :D
Cursed question:
Let say I have a "username check", then after it the object only interact with the user of corresponding name.
Would a single key work without conflict (since in theory only the whitelisted user can interact with their assigned object in the world) or do I need a custom key name per object?
If so, do all these keys count toward the total of data saved for everyone, or only the one the player is assigned a non null value? Since the object check for username before doing the persistant part, the key shouldn't ever be called on anyone but the intended player.
I'm a bit confused, since I guess they still all exist on world side, but wouldn't be used by everyone (for other checks I set a normal sync variable) I'm asking that because I was about to only use one key name (the object would only check its owner's key) and was aifraid it may conflict..
Why is persistence needed for this?
Is this about some kind of item that the owner/local player is granting or otherwise sharing access with one specific other player for?
The username check don't rely on persistence yeah, but the string that is saved (portal ID) is put on the player's door and they can edit it anytime. So that needs to be saved.
I was just worried to just having a generic "YourPortal" key may overwrite with the local value instead of the specific player's.
It wouldn't need any save if it just redirected to "home" (or if the player tell me their target world beforehand, but it would be a pain for maintenance if I had to edit and reupload every time, so I just let players edit it themselves)
Basically:
- Player get assigned an appartment they choose in the world (static value no saving needed)
- Player can set a destination to leave when entering their fake appartment (edit at will, need saving the string)
- Because it's assigned by username, only that one player can see their own door's menu (hide on start if check fail, no saving needed)
- Player rejoins in front of their door (static value no saving needed)
Probably just a case of me overthinking, in practice everyone have their own value for "YourPortal", there's no reason for it to bleed on other doors unless I mess up OnPlayerRestored
I hope it is fine to post here, I migrate the unity version it was 2022.3.6 and I just update to the newest version 2022.3.22 and is taking an eternity this loading window...even I closed the project and open but still show this...any way to make this window finish please?
Hello everyone, I have a question about saving a string with persistence. I tried saving a string of ~600 characters, it loads fine on PC, but not on Android. Are there any extra limitations on Android? (feel free to ping me to answer)
that's...A LOT of characters...
Not sure about limitations, but you could try cutting it into smaller pieces and calling it throught:
Addition( string , string )
or
Concat( string , string , string , string )
depending on if you can make parts smaller if they're about different game mechanics in the world
If I'm not wrong, 600 characters is around 1KB ? But Iโve solved the issue, I was using the OnPlayerRestored event and I was calling a function to process my data immediately. I added delay of a few seconds and now the data loads correctly on Android
I'm using a weak android phone to test and maybe because it's slow, data from persistence take more time to load
oh, maybe the network was just clogged then, yeah
If that event is called the data should be there, if it's not it's a bug
Unless they forget to check which player was restored..
Is there a way to make it so that if someone activates a trigger in one world, then something will change for them in a different world the next time they visit it, using persistence?
in a different instance yes, but not an entirely separate world
persistent data is only per-world
Thanks for the reply, that's a pity.
I found another thing to fix in my "player registered to a fake appartment's door" system. Related to the question I had last time about "should I make a generic key (and the world will hopefully call the object's owner data) or unique keys per player".
For I reason I don't understand, the system DO recognize the players (as in their menu is accessible on their own door, the menu of other players is despawn for them. And same for each registered player, so far so good)
BUT as calling the saved data (shared generic key name) goes...It update all portals to be the same as the late joining player's locally (or straight up break sometimes) instead of checking each menu's owner for processing the data of the owner.
I didn't really get an answer last time so I'll try to confirm again:
If I make a composite key string (generic key name + username) it would prbably get rid of the overlap, well as long as the player don't change their name...But then what?
- Would that mean all these keys are on everyone's data or only the composite key on the related player's data?
- It would need a failsafe to not crash when the player is absent, wouldn't it?
- I'd need to make arbitrary callls instead of relying on "OnPlayerRestored"...I guess?
- Else I guess I could move to PlayerObject, and find a way to teleport them where the specific door would be located...Maybe that makes more sense for that one?
When someone loads on via OnPlayerRestored
Are you checking if the player is local or not?
New players could be overwriting crap
also I wouldn't use their name as a key. What if they change it?
You can use Guid.NewGuid(); for your keys instead
I check the owner of a reference object, since it's assigned at start (before PlayerRestored have a chance to fire) and make the menus disappear correctly. For each portal it should call the key with the owner of the related reference object assigned to it, not the local player.
OnPlayerRestored is here to make sure the check on everyone's keys isn't done before it's ready
Yeah that's why it was supposed to be a single generic key (since you can't own multople doors anyway) then go per player...
hm odd. It seems RequestSerialization() doesn't work on a PlayerObject(), but only in ClientSim.
Behaviour is Manual, only 1 client so surely they're the owner, but it simply does nothing when called, like when a player isn't the owner. But I do an ownership check right before I call it so the client would never attempt requesting unless they were owner! lol
and it works perfectly as expected in-game so I'm not too worried abt it tho
just added #if UNITY_EDITOR to emulate as if it worked so I can still test in-editor, but if anyone is interested you can try replicating the issue to see if it's a just-me thing or something
Are you making Player A load Player B's persistence data? Pretty sure that is not allowed. When Player B joins you gotta at least get Player B to request some serialization so their data is sent to everyone. It makes no sense for Player A to remember Player B's portal in Player A's persistence data because you will have to remember all 13 billion different people's portals.
Player A is telling player B's portal to call Player B's data
or at least should
guess it would make more sense to just have a generic refresh then...
Actually yeah, no real need to do it via data, just refresh the portal that's already updated for everyone else, just like the unsaved one is when another player joins. Guess I just overengeenered that part
Ok I have a weird persistence bug.
I have a script on a PlayerObject with a ton of code, but the relevant code is simply this part:
[UdonSynced] public int AchievementCount = 0;
// this method is only called if PlayerDataReady event already fired...
public void _UnlockAchievement()
{
// some other code related to unlocking an achievement
AchievementCount++;
RequestSerialization();
}
There is another script that simple reads from this value and displays it on a UI, but otherwise does nothing to the value.
The bug:
Sometimes when joining the world, AchievementCount will be equal to a very specific value: 1065353216 even though there is only a couple achievements unlocked (out of a max 300).
I noticed that this value 1065353216 just so happens to be the integer bitwise representation of the float value 1.0f. So I don't know what that's about, but the code is way too simple for this to be something wrong with our code. Has anyone ever encountered this or maybe have some input?
Are you sure the issue is not in the display code?
I'll double check that, I was just thinking about that.
Yeah I'd be curious how you are getting the value and inputting into the text field(?)
Otherwise the only idea I have is to try this instead:
AchievementCount += 1;
But it's most likely on the receiving side if I had to guess.
(The other option is something weird on their servers going on)
Hmm, what about trying to rename the variable? What if you had it as float before or something and their server still has it saved as such. Just wildly giving out ideas though lol
The variable was always an int. Changing to += wouldn't hurt to try, but would indicate bigger problems with UdonSharp/Persitence in general if that fixed it. I don't have access to the UI code at the moment, waiting for someone else on the team to get back to me.
I've ran into similar stuff once or twice in the past month, which is why I'm suggesting trying that.
You ran into a problem using ++ that switching to += fixed?
Not specifically that. That was just an idea thrown out. Renaming the variable or looking at the receiving end where you display is probably more likely to help it.
I've seen this before on regular non-persisted synced values and saw the same kind of thing where it looked like a float was being reinterpreted as an int. It was actually the float value for a neighboring field iirc. I'm not entirely sure what's wrong but I have a feeling there's some bug with the serialization or how it keeps fields lined up between versions of a world
Oof... Sounds like more reason to just ignore storing everything individually, and once again just save all your persisted variables in a single json string.
... perhaps even a versioned string...
I've been using a single string of json that's been serialized from a dictionary and then saved with PlayerData. It's been going okay, certainly no issues like the above at least.
Idk, but I felt like it made more sense to structure persistence like that to me. PlayerObjects for me are more for in world sharing of data as opposed to saving it imo.
i agree. playerobjects donโt seem like the kind of thing for saving data, playerdata does
although, for something like persistent currency per-player, playerobjects seem easier for that kind of thing
Yeah, I'm worried that I'm missing out. I made the system the way it is right now and had no reason to explore alternatives. Even the coin example, it's easier to just add a key and a few methods to get persistence running, so I tend to just do that too.
isn't it less steps to implement persistence on a PlayerObject? All it does is save the last networked state; so if you already have it working with UdonSynced variables, all it takes making it persistent is one component
As I said, it's faster for me with my current system. PlayerObjects require a lot more boiler plate compared to just a reference to my save manager and then two methods for saving and loading.
I have yet to use player data because everything Iโm saving works fine in player objects. Wonder when Iโll run into a situation where swapping is necessary ๐ค
I would even hedge to bet that you'd never need to switch, if PlayerObjects solve your implementation well enough
I've got objects that:
- I want everyone to have a copy of
- have changes on these objects synced for all players
- have this data save persistently
PlayerObjects solves these objectives so easily that it would just make it harder if, for some reason, I would need to convert the saving to PlayerData.
A save manager system makes just as much sense, if your objective is to have all of the data all in one place though. There's no one solution to everything
I've got some other numbers that I deliberately don't want to sync, so PlayerData will probably be easier to manage it with
Seems like you have the opposite problem to me then. Is the grass greener or is not necessary at all? Who knows.
After all, PlayerData is basically just a prefab with a nice static interface to... A dictionary on a player object (well, a fairly complex byte serialized dictionary that supports a variety of types, but... A dictionary)
Playerdata is an extra convenience layer on top of playerobject, so you have the option to use whichever is more comfortable to you
Honestly just supporting instantiated synced objects is game changing so far ๐
Saving on top of that is just icing on the cake
Between those two separate features, there was an initial assumption that they would exist independently, but a change happened when there was a realization that the two of those features actually go well together to solve a singular problem which is spread across a wider area
That's the brief, abridged history of persistence at VRC
You technically can using another trick as long as they visited both in the same session
I think i missed something everything was pretty much fine now i have 128 errors something about unity 5 not supporting rigid bodies etc anyone know a quick fix for this . Please help ๐๐ผ
Is it something to do with non-convex mesh colliders?
Yes
On a side note @wary summit you've helped me so much that you're on the permanent guest list for any event that I throw irl or vr much appreciated
you can't put non-convex mesh colliders on rigidbodies. Convex is a simpler version of the collider, it's a toggle on the mesh collider which wraps it so that it doesn't have holes.
If you don't need the extra precision of non-convex then just set them all to convex
If you do need the extra precision, then the best option is to recreate the shape out of primitive colliders like box, sphere, capsule

i couldn't find this in the doc, and im new to too syncing things in general, what setup do you need to sync sliders and toggles ? is there a source that explains it or a tutorial, i just wana have UI sliders Synced for player so they dont have to constantly change them when they join the world
im using Cyantriggers currently for the setup just as a headsup
https://creators.vrchat.com/worlds/udon/networking/
Synced Slider example.
I believe it's in Example Central aswell
Multiplayer experiences are the heart of VRChat, so creating a world that reacts to players and synchronizes the data between them is key.
or do u mean saving?
Just realized this is in #world-persistence
I'm not personally too experienced with cyantriggers, but they should have access to PlayerData. That should be the easiest way to get started with simple persistent preferences. If this is supposed to be a local preference that other people don't need to see, then it's not too complex, would look something like this:
- make an udon script that has a custom event
- Make the UI element send that event to the script
- Have that custom event read the value from the UI element
- Plug that value into a PlayerData key
- Add OnPlayerDataUpdated
- set it so that if the player provided is not local, don't do anything
- Inside of OnPlayerDataUpdated, get the value from PlayerData
- Apply that value to the world or preference however you want it to behave
- Also apply that value to the slider or toggle with SetValueWithoutNotify in order to show the accurate state when it loads from persistence and prevent infinite loops
quick question regarding persistent variables on playerobjects:
if a playerobject with persisted data was removed during an update to the world, will the data for that removed playerobject be cleared at some point, or will it take up space until the player chooses to clear their data for that world?
if in a later update a new playerobject with persistent variables is added, can it potentially pick up parts of the persisted data of the removed playerobject? (even if that requires the new playerobject to be very similar to the removed playerobject)
if it's a PlayerObject's saved data, I think not? It would need to be able to recognize it as the exact same object, networking wise
maybe depending on how VRChat does that, exactly, maybe it could reload it
- if you previously had data saved on a player object, it is associated with the network ID of that player object
- if you remove that object and publish a new version, it doesn't immediately delete all player's data of that network ID, but the next time someone enters the world and saves data, it will overwrite the old version with a new version, and that new version will not include the data from that object.
- If you add a new object and you want it to be independent, then you don't need to do anything special because new objects will never be assigned an old network ID unless you clear network IDs. They always take a new, unused one.
- if you did want to reconnect a new object with the old data, then you would need to manually edit the network IDs to assign the new object to the old number (if you know what that number was)
Hi, I need your help with the TLP UdonVoiceUtils setup. Could you please respond to this post?
Please dm me
I cannot, unless you changed it to where I can, Hold on, Let me try.
just changed it
[02:47:39.672][ClientSimPlayer] Failed to locate player persistence view ID for PlayerButton/SpinCounts
Has anyone encountered this error before? Just added a very simple persistence player object and it is throwing this error
Fixed it, for anyone who finds this with the same error: Had to fix some network ID conflicts in the Network ID Utility.
So, i'm trying to set a volume slider and audiosource volume to 0.5, and enable a bool called shuffle by default. both variables are persistent, as i save the data after changing it, and load the data in OnPlayerRestored. but, even though i set shuffle to true, and the slider's value to 0.5 on start, when i test in clientsim with no playerdata, the volume is zero and shuffle is false. this makes me think it's loading the empty data in OnPlaterRestored. why would it load the playerdata is there is none? is there a way to prevent this?
void Start() {
shuffle = true;
volumeSlider.value = volumeSlider.maxValue / 2;
UpdateShuffleIcon();
UpdateIndex();
InitalizeSong();
}
public override void OnPlayerRestored(VRCPlayerApi player)
{
if (!player.isLocal) return;
shuffle = PlayerData.GetBool(Networking.LocalPlayer, ShuffleKey);
UpdateShuffleIcon();
paused = PlayerData.GetBool(Networking.LocalPlayer, PausedKey);
if (paused) Pause();
var savedVolume = PlayerData.GetFloat(Networking.LocalPlayer, VolumeKey);
volumeSlider.SetValueWithoutNotify(savedVolume);
source.volume = savedVolume;
if (logs) Logger.Log(name, $"Saved bool \"shuffle\" = {shuffle} <b>|</b> Saved float \"musicVolume\" = {savedVolume}", LogColor.Aqua, true);
}
This is the volume slider.
public void UpdateVolume() {
source.volume = volumeSlider.value / volumeSlider.maxValue;
PlayerData.SetFloat(VolumeKey, volumeSlider.value);
}
may be slient sim, may be actual behaviour...
just store some extra bool gotSettings or smth and if false, do default values.
so if player never touched them bool is false
my logs actually confirm that it loads the persistence data even though there is none, because entering clientsim with no playerdata still gives me the log about what the saved values were
makes sense, i'll try in VRC too first
yep, it's the actual behaviour
it kinda seems like a bug that it runs OnPlayerRestored even if they have no playerdata in that world
OnPlayerRestored runs regardless if they have existing data or not
yeah, i feel like it wither shouldn't, or there should be a way to check if they have data or not
could i check if the keys have a value yet or not?
precisely, that's why it runs regardless
if it didn't run, it would be difficult to initialize data for a first-joiner
maybe TryGetBool or TryGetFloat is what i want
yep the success bool will tell you if it exists or not
if you don't use TryGet, and a key doesn't exist, then the function returns the default value for that type
okay, so this seems to be what i'm looking for then
// This
shuffle = PlayerData.GetBool(Networking.LocalPlayer, ShuffleKey);
UpdateShuffleIcon();
// Into this
if (PlayerData.TryGetBool(Networking.LocalPlayer, ShuffleKey, out bool savedValue)) {
shuffle = savedValue;
}
else shuffle = true;
UpdateShuffleIcon();
that makes it a lot easier to set default values
TryGet returns the value of that key, not if it was successful or not
the out bool is if it was successful or not
oh, that's kinda weird
it is, a bit backwards from how some other functions do it
yeah, exactly
wow that's.... just why.
yeah that's kinda annoying, makes it a little harder to use
I was just looking at my own code, seems I did it with a HasKey() check then a GetBool().
yeah as it is, I don't even know how I'd use TryGet, it makes no sense for flow control at all
yeah, if TryGet wasn't backwards then i would use that
yeah
so this, is what i want
if (PlayerData.HasKey(Networking.LocalPlayer, ShuffleKey)) {
shuffle = PlayerData.GetBool(Networking.LocalPlayer, ShuffleKey);
}
else shuffle = true;
UpdateShuffleIcon();
I actually have this, not sure if I actually need to check the data type, but I was being defensive:
myLocalFunc(PlayerData.GetBool(player, KEY));
}```
i don't think checking the type is necessary, for a music player anyway
which is what i'm making
sure, you may not need it, and I probably don't actually here either.
maybe I should just write a not-stupid wrapper.
huh. the documentation on what the Try* functions do is really ambiguous.
the docs say it outputs "string value, bool success" but not where those are. The intellisense tooltip suggests the out variable is indeed the value, not success
yeah that's waht i was thinking
yeah and TryGetString() returns a bool but the out parameter is a string
this is why clear and concise documentation is SO important
doesn't seem so
I do remember reading this page and going "wtf" and just ignoring it for the moment
so it's the documentation that's backwards. even better
I don't think it's backwards, I think it's totally unclear, it doesn't actually describe the method signature at all.
which IMO is a bit bizarre for API documentation
it says the output is "bool success", which led me to believe thats the value of if it was successful or not
but its not success, it's the value
no no, that's success
what it doesn't say is how it outputs this
it doesn't say which value is returned and which is an out var
which again, is bizarre
the Get* methods do return
i feel like someone significant should know about this
but the TryGet* methods return a bool for succes and out the actual value
I've stopped ranting about bad docs, nobody wants to hear it.
so it can be fixed
if you want to put up a canny I'll gladly +1 it though
i haven't done that before but i think i have an account on there
it can just use your vrchat account
yeah that's probably what i did
excellent
Got an issue of Persistence data starting to get lost between instances, is there some sort of loss of data that may occur with too much persistence data for a world as additional values are added onto it, or something that may cause it to get lost between updates? Values have not been renamed at all.
Is there also some way to determine the overall persistence data payload size?
the size will show up in the logs when its being loaded, and persistence can fail to save if its too large
I don't remember if it appears when saving data as well
Ahh, cheers. I'll dig in and look. It's not entirely failing, but I've noted some missing variables so I was wondering if it could be cutting off part of the payload or something.
Just to confirm this is what should be the payload size, correct;
[Behaviour] Sending 176 bytes of instance metadata over 1 bunches.
I'm not seeing any errors, which is extra frustrating. It's just decided to eat a few variables. Like the personal credit balance for my partner's save.
Is there no way to dump the whole persistence payload out to debug?
Thankfully, I have found and have fixed the bug causing a loss of player credits
What was the bug?
So the Terrariums are meant to add a passive income tick. For some reason I cannot discern, for anyone not the owner of the game's master controller script would see it pull 0 and write 0 to your Personal Credit balance on the first tick. So I've stopped that tick for now til I can rewrite it.
What's especially odd is the PC balance is entirely local, and it happens while staring at the loaded PC balance so it wasn't like it was getting ahead of Player Restored completing. I even tried putting in a check for that.
Zero-grove is it?
Not sure if your bug is completely related to persistence, I noticied that sometimes when buying an upgrade, and someone joins before you save, it reverts it to locked BUT still ate the money you paid (lost so much credits having to rebuy terrariums...)
Maybe there's other problems like that resulting in "variables being eaten" because it happens even within an instence
Yes!
That's a... new one. I will look into that, thank you.
Just so I have the process straight; A purchase is made, but someone joining after that but before you can save reverts it?
That's probably technically a persistence bug, in that it sounds like the new player joining somehow forces a reload of progress on their end, but as they're not the instance owner it's supposed to only load their personal credit balance.
Ah-HA caught and fixed, thank you @dense owl. Turns out I didn't have an explicit check for local player on player restored in the upgrade controller, so it'd flag the incoming player locally for yourself and fire loadgame off.
Awsome! Glad that I acidentally helped resolving an unrelated bug. xD
But yeah, that's why I was thinking yours was the same bug, or at least the same kind of flaw
.
"pull 0 and write 0 to your Personal Credit balance on the first tick"
...Kinda sounds like it try to do "Credits current + Passive income" before PlayerRestored, resulting in "0 + Passive" at this point in time.
Maybe add a small delay after OnPlayerResored (or a flag that gets turned on) to make sure it never tick before or on same frame (VRc is weird when it comes to operations order, it may run just before it should sometimes, so...1 frame delay is always safer)
That or a check only being true for master, resulting in the "false" branch being called even if the upgrade is active?
That's what I thought too, but it seemed to fail consistently. I rebuilt the passive tick from scratch within the main game controller, which optimized things a bit anyway, so now it's only ticking for the master and just tells all players to update their own credit balance after it's done the station account. It fires a little slower now (once every five minutes), but I upped the credit amount per-tick to balance it.
Needless to say, if anyone runs into any more bugs in The Zero-Grove, do please DM me so I can fix them!
hiii- does anyone know how to add persistence to a toggle?
i added it to a cube that is a toggle and this happens
that component is only for PlayerObjects
you'll need to save something like that to PlayerData instead
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.
no idea of what you're typing there, how can i add it to something like a cube that was made into a toggle?
literally a cube
there are two kinds of ways you can save persistent data; PlayerObjects, and PlayerData
What is Persistence?
they both work a little differently
The component you're trying to use, VRC Enable Persistence, is for PlayerObjects, it won't do anything unless the object has the PlayerObject component on it as well
based on what you're doing, you probably don't want a PlayerObject anyway. You want to "save" the player's selection to PlayerData instead
so adding this it should be fine, right?
no..... that's the exact thing I'm explaining you probably don't want to do
a PlayerObject will create a copy of that object for every player that joins the instance. If this is a toggle button for a setting, every player doesn't need their own copy, do they?
oh i read now-
let me explain better, this is basically a grabbable menu, so yes people need to have it in local if that is what you mean, but i wanted to make so when they switch the settings it all remains as they left
right, so if it's meant to be local, it should be saved to PlayerData instead
PlayerData is sort of just like variables, they're just a name and a value (referred to as a key-value database)
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.
to save a value, you use one of the "Set" functions listed on this page. For example, you can use something like SetBool to save a bool that's true if it's daytime, and false for night
you probably already have an internal bool that tracks the object's toggle state
i don't program in java tho- i just use the pre made toggle script found in udon sharp
Java....?
huh?
this is Udon and U#/C#
oooh you didn't write any of your scripts.
unfortunately you need to write some Udon in order to have persistence in the way you want it to
this example project that is part of Example Central sort of does the same of what you're trying to do
https://creators.vrchat.com/worlds/examples/persistence/post-processing-settings
Save and load bloom settings with PlayerData.
no- i have some sort of PTSD on that, plus all the tutorials i ever tried always make me write code for hours and then it always turns out to be wrong, so i kept avoiding programming
you could code in the Udon Graph instead, if the written stuff confuses you
it confuses me a lot- btw i'm trying to find how to open the udon graphs, i did use that a few times
you can open the tab by going to VRChat SDK > Udon Graph
I would pin this tab somewhere
then you can edit any graph script by selecting it and clicking "Open Udon Graph"
Wherein I put my script dealing with PlayerData on a VRCPlayerObject...
Are we not able to save playerdata in the form of a list []?
Might have to use a FOR to get it done if not :L
Such a pain ๐ฆ
I assume a setup like this will be adequate?
both playerdata and playerobjects support arrays
Oh, Looking through the playerdata section of the Udon it didnt have any nodes for arrays
Having a real issue here, these send custom network events are refusing to send to the event custom, Ive debugged using audio ques to the point where I know for sure its these nodes that arnt working, I can even directly bridge the gap and then the logic works but I need it networked...
can u post the whole thing
Its quite expansive, lets see...
So fixed update, it passes through some checks before finally getting to the network event, Ive used sound ques to ensure that every step of the process works correctly which it does, when I switch out the send custom network event with just bridging it works, the only thing that isnt working is the network event
network clogged?
Hmm, well it doesnt even trigger once
Tried deleting the nodes, re adding them, restarting unity
I definitely know the nodes are to blame, Ive debugged it to pinpoint these as the root cause
u sure? try debug message, and check in game?
put a debug after the send custom events
Ive never used debug nodes, Ive always just had it play a test sound whenever it passes through, and thats working just fine on test
use debug, and see how many times it runs
Oh yeah I know it fires once per frame (Im gunna put in a gate to stop it doing that once I get it working) Whats important is that I know the prerequisite checks are correctly working for it to then go to the networked custom event
So I know its working as designed
I know its not optimal to fire off a networked event once a frame I will stop it doing that but Im just trying to get it to fire once
Its going against everything I learned doing this...
Ghost in the machine lol
that is the problem, it can't send every frame, that is way too often
i bet the network is clogged
this is kind of terrifying.
But it must work the first time its called right? at the very least...
Is it? it lets me debug even in game where you cant use the console ๐
you.... can use the console in game
and sound is an interesting choice, given that there are cases where they will not fire
I still like using sound ques to check stuff is working
debug info that I need to see, I usually slap into the TextMeshPro on an Update loop so I can always see it
Using sounds does bug out if its called once every fixed update but so long as you ear it for the first half second you know its working
Oh yes text mesh pros I use for debugging too
Ok so got back to trying to make it work, used custom events instead of networked custom events, worked.... Perhaps Udon devs just made it so that network custom events wont work under a fixed update node?
If thats the case a simple message telling you this cant be done would have been handy :L
It was because the syncronization was set to Continuous, I set it to none in hopes that the persistent object would stop lagging the hosts game whenever another player joined.
I'm trying to cut down on the amount of updates I'm generating while tracking player location and saving the data. There are other systems that are watching and using the persistence info, and having them check their portion of the data and do a string comparison to see if it's changed, doesn't seem that efficient. Is there a good way of checking "has this chunk changed since it was last loaded"?
Playerobjects receive ondeserialization when they change
Playerdata has the event onplayerdataupdated. Inside of that, there is an info array which tells you what happened to each key on that update, including unchanged, changed, or restored,
If I tried to save data that matched the data already in the playerData, would the info reflect that it did change, or did not
I was told in the past that VRCplayerdata can have arrays, but I cant seem to find any such node, can ayone advise on this?
Above is how I originally planned on saving loads of variables in an array :L
That's odd, can you show what happens when you open the node search and go into the playerdata section?
PlayerData can only do byte arrays right
No playerdata setBool[]?
I mean I was gunna do it this way but if people say theres a playerdata array then Il do that instead
it's SetBytes there, that's the only type that can be an array
although I did do it in a really jank way with ints.
Ooooh? Bytes... thats new to me
huh I feel like I'm having a mandela effect moment, that's wild that it doesn't have other arrays
you might be right
Ooof, alright
if you got a bool array you might be able to get away with casting them to byte?
Well in this case then moving forward with this logic, it threw an error "Index was outside the bounds of the array" I assume I have to addition+1 for it to work
yeah, I'd pack 8 bools per byte, but you'd have to do some bit manipulation which is clunky in graph
Im new to bytes, so would have no idea