#world-persistence
1 messages Β· Page 3 of 1
That's a complicated subject. It's not exactly random, and the exact order in which outbound networking happens while clogged is not something I'm prepared to dig into at this moment. But what I can say is that based on my observations, very small serializations (like a single bool) can get through clogging very easily, and do not sit waiting behind bigger serializations that will take a while
No this was a mechanism I came up with. If I saw the method call in the log near the world loaded stuff I knew the general state of things. More importantly I knew the sync'd data had made it. I only wrote that stuff because players were being handed the same pool item sometimes. It is just a point in the start up process that I know I can hook. Like now I get an in-game logger and assign it to the player. That method hands me the player so it is far, far, (far) handier.
Interesting... small clogs vs big clogs
tell me about it π
Will have to be another time, as I have a flight to catch in an hour ^^
good luck with the flight
ha, would be fun to sit next to you on a plane π
and talk udon stuff for a few hours
it sure would... I'm a theorist if I'm anything
Also I wonder how difficult/easy it would be to make a priority sync system in networking heavy environments...
Something to think about
You might be able to come up with something. I have mentioned my pure sync objects and I did just add a conditional sync variant. It works well when you need to sync a lot of data but may need to set a number of properties. You have local copy of the data until you call SyncIf with with a condition and it pushes it at that time.
It was an odd requirement but I have a sailing ship and it only relies on the transform so VRC takes care of the sync duties. Except if the master leaves at which time whomever becomes the master won't know the current settings.
My current project will involve a lot of AI actors with a physical, moving presence that have an impact on the environment. The decisions these actors make are based partly on player positions and thus are nondeterministic. Therefore my project requires and authority to decide how these actors make choices and syncs these choices with everyone else. Thus networking is required.
Noticed all of the Texas Hold Em poker table prefabs are broken now
what do you mean by broken? mine seems to be working fine.
might depend on the creator. which one?
yup! how many people did you start the game with? with 5 people i could not even deal anyone in. both in mine and the creator's official world
DM or give me a link to your world
dm sent. iβll go in to the public instance
Yours is broken as well. I'm guessing something with object sync is broken.
Anyone know if there is a way to see KB used by a save file? worried about hitting the limit
Please don't tell me that persistence is going to be so buggy as to render it a hindrance.
There is currently no way to get this number.
The closest you could get is probably SerializationResult's byteCount
But you can only access that if you're using a player object
and that would only tell you how much that one player object has used
So you'd need to keep track of all your player objects individuall and add them up
And this isn't really accurate either, because afaik the limit is based on the compressed byte count
I'm sorry if there's a better way and I've just missed it. I do not intend to spread misinformation.
Ah okey, from my end I ended up only using PlayerData. I might just manually add it all up as a rough estimate so players are aware
Make sure to add a big buffer / tolerance to account for the serialization header and extra stuff we can't really know about
Yeah I'm aiming for 40 - 50 kb max atm. Unfortunately the save files in my world are going to constantly grow so i need them to last the next few years lol
Interesting! Are you able to share why?
Same world I was on about the other day with using byte[] for storage (thanks for the advice btw π ). I'm now tracking stats, favs, etc for a list of songs in the database and this list is constantly growing so eventually it's gonna hit a limit
Any way to read up on this? I didnt know there was a limit
What is Persistence?
Oh, guess i missed it in there, tnx!
Ah, I see! I think I would recommend using player objects for this. It's going to cause network issues if you favorite / unfavorite something when the list is big.
Using PlayerObjects would allow you to store segments of the list on each object. And then only syncing that part of the list that the change was in.
Already live with it now, it's at 1600 entries and it's chugging away fine (thank god for Array.Copy() )
π
in a good way or a bad way lol? I know PlayerData syncs everything but I've got a wrapper above it anyway which only checks for changes to save on processing
I'm just concerned for the eventual serialization time of ca. 8 seconds per PlayerData update if you're at 50kB
But it's probably fineβ’οΈ
Yeah can always migrate to PlayerObjects like you say if it becomes an issue π
Good point!
PlayerData.SetBytes() was just too tempting and i didn't have to mess with anything else lol
for ref saves are handled every 60 seconds or after a player sets a score ( every few minutes ). So it's not gonna be constantly syncing
Sets a score.. is this some sort of rhythm game?
Yeee guitar hero port kinda but for drums
Also, I'm assuming the autosave doesn't actually serialize anything if there was no change due to that extra layer you mentioned?
Nice! I think I've seen that actually
Yeah only if something is marked Dirty
Oh nice! I think I've seen some of your stuff too. π Sword Art Online programming?
Yeah! 
Hell yeah, projects getting there πͺ anyway don't wanna derail this channel lol
Quick note because I just encountered a few wrinkles. The one thing is that the PlayerData.Info state does not change on subsequent checks. It make sense but I was checking for Added and Changed and once it changes to Changed it would tend to stay that way. So perhaps something like you've done I've had to implement a "dirty" check.
Happy Thanksgiving to those who celebrate... meanwhile to those who don't or who are waiting for the turkey... I have plenty of questions now that I've toyed with persistence. I'll start with PlayerData which I think others may find confusing as well. Please correct me where I've gone wrong and/or offer reasonable responses to problem cases.
PlayerData every player has such an object (that's good) and per the docs it shouldn't be accessed until after OnPlayerRestored has been called. That is easy to manage with a Boolean indicating that OnPlayerRestored as been called. Keep in mind OnPlayerRestored will be called for "every player" so you generally want to insure that you are the player who has been restored.
OnPlayerRestored gives us a point to obtain the designated PlayerObject. I believe we are supposed to (and I do) assign the local player as the owner. I also get a reference to the Udon class associated with that object as that is really what we deal with.
My use case for PlayerData is an in-game logger. A player can set a value which all current players can see (and display).
So imagine in OnPlayerRestored that I want to log that fact. I call _appPlayer.LogInfo and pass a string to log. Should be no issues as OnPlayerRestored has been called. Keep in mind that this is the first (master) player behavior nobody else has entered yet.
All that LogInfo has to do is to set a PlayerData Info object (a string in this case). so basically the following:
PlayerData.SetString(LogKey, message);
Having set it... "all players" (but only one player is present at the moment) have OnPlayerDataUpdated called.
The method is passed the player (permitting us to determine which player's data it is) and an array of PlayerData.Info objects. This could be empty or obviously contain info objects. There is requirement that all players have the same collection of Info objects but in my example every player will have one with the LogKey key.
Another sidenote... since OnPlayerDataUpdated is a public method on an in-game object we need to be sure that we only act on our instance, right? There is nothing "special" about these classes and general rules apply. This is why (above) I set the owner of the PlayerObject to the local player. I can tell it is my copy and ignore any others.
As a general matter I also check that the player has been restored. It should have been but there is no harm in checking and I'm not 100% certain that VRC couldn't call the method early.
We can walk the collection of "infos" and see the keys, check their State and ultimately get the values. Finding the LogKey I output it to a TMP element and it displays my "restored" message. So far, so good.
The State of that info BTW is "Added". I click a button that reports to the logger by similarly setting the same LogKey. Because the data changed OnPlayerDataUpdated is called again. I look up the value for that key and output it to the log. The State of that info is now "Changed".
Which leads me to questions. There is an "Unchanged" State but what sets this? If I set the info value to "OnPlayerRestored" the first time the State is Added. If I change the value to "Button Clicked" the State obviously becomes Changed. If another button sets the value to "Button Clicked" also will it be detected as a change, will it be considered Unchanged? And unchanged from what? What makes the value unchanged? The last time it was referenced in OnPlayerDataUpdated? Doesn't seem likely but let me add a test.
Ok seems to be that the info will be marked Unchanged if one checks in OnPlayerDataUpdated and the value is identical. Importantly it isn't that "any new value" has been assigned but rather "not the previous value". Not a particularly big distinction but a distinction.
You shouldn't need or be able to change ownership of PlayerData, right? It's a pre setup PlayerObject and synced PlayerObjects cannot have thier ownership changed
So big question. What happens if one changes an info value from within OnPlayerDataUpdated? Is it called again? I assume this could cause an endless loop.
And sounds like it'd loop, yah
PlayerData is unique to a player and not a game object. The reference above was to claim ownership of the PlayerObject which I believe is required or a good practice if not.
As for the onwership of PlayerObjects: https://vrc-beta-docs.netlify.app/worlds/udon/persistence/player-object/#ownership
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.
"When players own a PlayerObject, they also own all GameObjects with synced UdonBehaviours within their PlayerObject. They cannot transfer them to anyone else."
"You can guarantee that they will always own their own PlayerObjects."
Also why do you need persistence for your logger?
I see the paragraph but don't see where the PlayerObject owner is set. I might log it to find out but again there is no harm in setting it and we do that for every other game object.
"In the Start and OnDeserialization events, ownership is guaranteed to be correct. "
last paragraph of https://vrc-beta-docs.netlify.app/worlds/udon/persistence/player-object/#loading-persistent-user-data
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.
Not sure what you mean about persistence for the logger. It should just be a mechanism to share data between players. I'm trying to get rid of all the pool logic currently required.
All sycned data of PlayerData is persisted; that's THE reason you'd be using PlayerData
OnPlayerRestored includes a reference to the player whose data was just restored. You can use this to make the Owner of the GameObject
You can use this?
That last sentence appears to be awkwardly cut off and is likely a mistake. Whoever maintains the docs should probably be pinged about it. Is it Momo?
I think thinking of PlayerData as data the player has is the better definition.
Also using PlayerData also means that when someone joins a new instance they'd bring their persisted logger data from the last instance they were in
In either case there is no harm in setting the owner again. Not seeing it in code might appear to be something one forgot.
Also I imagine them loading into a new instance would trigger OnPlayerDataUpdated, causing them to print out the last instance's log data to the new instance they just joined
There is no persisted logger data per se. It is a message sender containing a single message that others can read.
I don't mind the problems (if minor) if it gets rid of an arbitrary pool of assigned objects.
My hope is that we don't have to imagine too much and simply understand how it works reliably.
Why not instead set up an unpersisted PlayerObject instead? Have it customized to your needs without all of the unwanted stuff that PlayerData forces you to include?
What does that mean?
For a shared log create a PlayerObject that contains a synced string. Then onPost/Deseralized write that string to the log. Then whenever you want to write to the log have the player access their own copy of the PlayerObject and use your preferred method of either property or function to change the synced string value and call RequestSeralization.
I don't believe it is quite that simple. That's the reason we used pooled objects. If a player wants to reference sync'd properties they need a reference to the player object for each player. I have it all working BTW with pool classes. I believe it can be made simpler and without requiring a predefined pool of objects through the simplicity of changing a string value.
That single message string is easily reset to String.Empty as the world loads. It isn't like a lot of stuff is going to be carried forward from a prior instance.
The very first log message is essentially "I'm restored" in any case.
I think what they are saying is that PlayerObject is already a pooling system. They get spawned for each player, so you can access the other's objects to read from and do stuff with.
(No idea if that's what you want/need though)
It may be of interest to note that the InstanceId of VRC Player Objects is a negative number.
Yah, PlayerObjects are the replacement for player pooled objects
Well that all makes sense... let me RequestSerialization on a sync'd property and find out who can see it.
Should be everyone, even non owners of that PlayerObject
Also since you're not using persisted data you shouldn't need to worry about OnPlayerRestored. If trying to access non-persisted data prior to OnPlayerRestored causes issues it is most probably unintended behavior that should be reported to VRC.
Is there a load put on the overall udon networking limit when saving persistent data?
Or is it networked separately from Udon?
Persistent data quite literally begins as synced data, so yes. The only difference is that the server holds onto it for next time
Alrighty then... a couple of false starts due to my overcomplicating things a bit as to ownership of a child object but... it is working now. I should tidy it up just a bit more so it isn't a generic PlayerObject but just one for in-game logging. And as you pointed out I can actually use it from OnPlayerJoined and forward which is handy as that typically is the first event I would want to log. Nice to have the pool objects gone.
Trying to understand playerobjects a bit. When it says a copy is spawned for each player, is that copy synchronized? So if, say I have a PlayerObject with a collider and a variable that is used for some collision mechanic, and I have that playerObject change its position every fixed frame (to follow the player), do I have to do anything special to sync its position and the variable? (For context I am migrating from CyanPlayerObjectPool, so I am thinking in those terms.)
Yes PlayerObjects are instantiated synched objects!
have that playerObject change its position every fixed frame (to follow the player)
Afaik you should do this inLateUpdatesinceGetPositiononly refreshes everyUpdate?
I'm at least sure that doing it inFixedUpdatewould be wrong.
do I have to do anything special to sync its position
I don't think you should sync its position. The best would be to do this locally.
public override void PostLateUpdate()
{
if (!loaded) return;
Vector3 position = targetPlayer.GetPosition();
Quaternion rotation = targetPlayer.GetRotation();
self.transform.position = position;
self.transform.rotation = rotation;
}
I have a little footstep script thingy, this is how I track the position
You assign the targetplayer and self through this
public override void OnPlayerRestored(VRCPlayerApi player)
{
if (!player.isLocal) return;
targetPlayer = player;
self = gameObject;
loaded = true;
}
I am having issues with syncing it for other ppl tho, I just realized.
I would make some changes:
private bool _loaded;
private VRCPlayerApi _targetPlayer;
public override void PostLateUpdate()
{
if (!_loaded) return;
Vector3 position = _targetPlayer.GetPosition();
Quaternion rotation = _targetPlayer.GetRotation();
transform.SetPositionAndRotation(position, rotation);
}
public override void OnPlayerRestored(VRCPlayerApi player)
{
if (!Utilities.IsValid(player)) return;
if (!player.IsOwner(gameObject) return;
_targetPlayer = player;
_loaded = true;
}
This wouldn't really use any bandwidth, and it would align with where the players are on your screen.
My scripts flawed. The position tracking is fine it just doesn't sync well.
I am guessing this behavior does not work in ClientSIm? Because when I start clientsim, the template is still in the heirarchy, and spawning a remote player does not create a new copy of the template for that remote player
THis gives a "no suitable method found to override' error
The template stays in the hierarchy in the VRChat client too, but the spawning of remote players should work!
My scripts fried anyways. The positioning doesn't sync correctly. Late joiners are synced
This script would appear synced
Is your script an UdonSharpBehaviour script?
Yes, it derives from an UdonSharpBehaviour. Other overrides (OnDeserialization, OnPlayerJoined, etc) work fine
Can you show me what SDK version you're on? It should be 3.7.4
ok, let me try restarting Unity.
Positions are synced, ty.
Now I just need to figure out how to play sound without it playing x sounds where x is # of players
ok, restarting Unity fixed it. shakes fist
classic
The behavior for the remote player is not working in client sim :/
That's unfortunate, you should make a post about it on https://vrchat.canny.io/ !
Give feedback to the VRChat team so we can make more informed product decisions. Powered by Canny.
I feel like I'm doing something wrong here:
using UdonSharp;
using UnityEngine;
using UnityEngine.UI;
using VRC.SDK3.Components;
using VRC.SDK3.Data;
using VRC.SDKBase;
using VRC.Udon;
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class VRpgPlayerObject : VRpgComponent
{
public VRCPlayerApi Owner;
private VRCPlayerApi _localPlayer;
private void Start()
{
_localPlayer = Networking.LocalPlayer;
SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.Owner, "AskForUpdate");
}
public override void OnPlayerRestored(VRCPlayerApi player)
{
if (!player.isLocal) return;
Owner = player;
loaded = true;
}
private void FixedUpdate()
{
if (!Utilities.IsValid(Owner)) return;
Vector3 pos = Owner.GetTrackingData(VRCPlayerApi.TrackingDataType.Head).position;
transform.position = pos + Vector3.up * .75f;
Vector3 locPos = _localPlayer.GetTrackingData(VRCPlayerApi.TrackingDataType.Head).position;
transform.GetChild(0).LookAt(locPos);
}
}
THis works fine for the local player, but if I spawn in a remote player, the label (that's what this PlayerObject is attached to) does not track to them.
VRpgComponent derives from UdonBehaviour.
Yeah, because you should do this instead
private bool _loaded;
private VRCPlayerApi _targetPlayer;
public override void PostLateUpdate()
{
if (!_loaded) return;
Vector3 position = _targetPlayer.GetPosition();
Quaternion rotation = _targetPlayer.GetRotation();
transform.SetPositionAndRotation(position, rotation);
}
public override void OnPlayerRestored(VRCPlayerApi player)
{
if (!Utilities.IsValid(player)) return;
if (!player.IsOwner(gameObject) return;
_targetPlayer = player;
_loaded = true;
}
using UdonSharp;
using UnityEngine;
using UnityEngine.UI;
using VRC.SDK3.Components;
using VRC.SDK3.Data;
using VRC.SDKBase;
using VRC.Udon;
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class VRpgPlayerObject : VRpgComponent
{
public VRCPlayerApi Owner;
private VRCPlayerApi _localPlayer;
private void Start()
{
_localPlayer = Networking.LocalPlayer;
SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.Owner, "AskForUpdate");
}
public override void OnPlayerRestored(VRCPlayerApi player)
{
if (!Utilities.IsValid(player)) return;
if (!player.IsOwner(gameObject) return;
Owner = player;
loaded = true;
}
private void Update()
{
if (!loaded) return;
Vector3 pos = Owner.GetTrackingData(VRCPlayerApi.TrackingDataType.Head).position;
transform.position = pos + Vector3.up * .75f;
Vector3 locPos = _localPlayer.GetTrackingData(VRCPlayerApi.TrackingDataType.Head).position;
transform.GetChild(0).LookAt(locPos);
}
}
Ok, I see. that makes more sense.
If I have a bunch of variables that I want to save in playerData (updated infrequently), can/should I use const to define key names?
You certainly can. There's not much to say about the "should" though. I suppose as far as best practices go, I'd probably lean towards wrapping it in a property, that'd be pretty clean. But there's many ways to do it and they're all valid
can you give an example of what that would look like? Maybe I'm misunderstanding the VRCPlayerAPI parameter of PlayerData.TryGet
Every player has their own set of data. So the player argument of that function allows you to specify which player's data you want to fetch. The set functions are only able to ever set data for the local player, which is why there is no argument.
I suppose my example of wrapping with a property only applies if your goal is to only interact with the local player's data. If you have a use case where your script needs to read other player's data, then a property is not sufficient
Yeah, it's more of a situation of knowing some stats for each player in an RPG setting, so I might need to get, say, another player's health levels
so that makes sense
Can we not save Data Lists in this?
If you're talking about Persistance, only syched data can be saved. To sync a DataList: https://creators.vrchat.com/worlds/udon/data-containers/data-lists/#syncing-a-data-list-with-other-players-over-the-network
Data Lists store Data Tokens by index, similarly to C# Lists. Most Data List functions are just wrappers for the underlying C# list, so the C# list documentation also applies if you are looking for more specific details.
Like, if I'm saving a byte array in PlayerData, and I want to compare a single byte to a byte in that array, the process seems convoluted then
Saving it into PlayerData is similar; just use the method detailed to convert it to json and save the resulting string in your PlayerData
So I'm currently doing this... to compare a target byte to a byte[] called myListenChannels...
byte targetSpeakChannel = PlayerData.GetByte(allPlayers[i], "vrpg-speakChannel");
byte[] myListenChannels = PlayerData.GetBytes(Networking.LocalPlayer, "vrpg-listenChannels");
DataToken listenChannelsArray = new DataToken(myListenChannels);
DataList myListenChannelsList = new DataList(listenChannelsArray);
bool channelMatch = myListenChannelsList.Contains(targetSpeakChannel);
Like...will this work? Or is this an awful way of doing this?
it is 100% okay to tell me this is awful
I didn't know you could define a DataList like that. It might work though, you'd have to try it yourself.
I think this might work, though
byte targetSpeakChannel = PlayerData.GetByte(allPlayers[i], "vrpg-speakChannel");
byte[] myListenChannels = PlayerData.GetBytes(Networking.LocalPlayer, "vrpg-listenChannels");
bool channelMatch = (Array.IndexOf(myListenChannels , targetSpeakChannel) != -1);
I didn't know you could define a DataList like that
I've never done it this way, but it's not throwing me any weird errors
Also if performance is a concern, and this indeed works, it might run faster because it's using built in C# instead of Udon to iterate through the array
that's my secret: performance is always a concern π
^ anyone know the answer to these questions?
rather, could someone tell me the answers
Well for that last question I'd advise looking up defensive programming
I don't have the code in front of me but it's probably because updating the slider even if there's no change would cause an infinite loop due to the callback of the slider changing
To avoid loops, I usually just use a normal variable on the action itself
and update PlayerData's corresponding variable to be equal to the normal one at the end of the event chain.
So I'm sure it's not interferring with the action itself, even if it writes it's its own custom event.
( And no writing any PlayerData during OnPlayerRestored or OnPlayerDataChanged as a rule of thumb )
But again I'm a graph user, there's probably better ways to approach it in U#.
That set of rules just looked the safest to me in that aspect.
Oh yah, true. Had a stack overflow causing infinite loop the other day when I had two event calling UI elements that would modify the other when their value changed. In my case I ended up adding a bool that would ignore additional event calls while performing this operation.
There is a SetWithoutNotify function for most ui elements that will update the ui without firing events
if (!player.isLocal) return;
does this mean a player can end up loading data from another player? Is this to prevent bugs or an intentional feature?
As mentioned before, the issue is that the slider is calling the method that updates the data. Instead of setting the value, use the SetValueWithoutNotify option, which will bypass the component's onValueChanged. So for example:
float XKey = PlayerData.GetFloat(player, PosX_Key);
if (!Mathf.Approximately(previousValueX, XKey)) {
previousValueX = XKey;
hudAnimator.SetFloat("posX", XKey);
- sliders[0].value = XKey;
+ sliders[0].SetValueWithoutNotify(XKey);
}
ah okay, i will try that
All players load persisted values from a user when they join. That's why it returns a player variable, so you know who it belongs to. So if you only want persisted values to matter for the local player, you use that line.
there is no way to prevent players loading other players data?
By design they will always receive the data. You as the world creator decide what to do with it in Udon. If you use that line to discard it, for nearly all intents and purposes you can consider it not loaded.
Are your concerns academic, performance, or security?
academic, security. Just exploring how it works and what you can do with it. Thought about giving the player some kind of key
make information intentionally available for other and/or selected other players
So Im at the point where I need to figure out how I actually save player data, I have persistent objects set up but https://creators.vrchat.com/worlds/udon/persistence/player-data/ doesnt really tell you anything about actually setting up the saved data variables, do I create a game object and put a component on it to make player data or?
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.
PlayerData and PlayerObjects are two different ways of storing data
So Ive figured out that player objects are only good for temporary local storage, but I cant find how to access and adjust persistent player data
At first I thought it was just a gameobject that youd put into the scene and put a component on it for player data but I dont really see anything
The documentation isnt that good IMO :L
You can use PlayerObjects for permanent data storage.
PlayerData is a singleton
This means you can access it without a reference to it from any script.
In U# you could for example do PlayerData.SetBytes(); or PlayerData.GetBytes();
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.
Ive seen set and get, but where do I go to declare all of the saved variables that i want for the Playerdata?
When setting or getting data you need to provide a "string key". This is basically just a piece of text that you can use to refer back to the data in the future.
Ah.... yes but is there not a place in which I initially set up the key
There is not.
Oh?
The key is "set up" whenever you first call Set...(); with a new key
Whahhhh! that sounds a bit messy! How would I know if there isnt some rogue keys set up through development that end up being cut in the end :L
You can't really :)
And there's unfortunately currently no way to remove keys after they've been set, so you should carefully consider if the data you're saving is really going to be useful for the lifetime of your world
So the saved player data is essentially created at runtime then...
Pretty much
That's certainly a way to put it ;D
Also also, every time you write to PlayerData, even if you just changed one thing, the whole thing will be synced.
And if you had a lot saved in there, you'll clog the network for a while.
I had the idea of using a "Middle man" udon script anyways
That's a good idea
I mostly recommend using PlayerObjects
You can use https://github.com/LiveDimensions/VRRefAssist to have "fake singletons" with U#
A set of custom attributes to automate time consuming references and repetitive tasks - LiveDimensions/VRRefAssist
Ah... Im just using graphs, I never really got into programming
No worries! That's okay too. It is a shame that graph is missing out on these nice to haves though.
Ive never even heard of singletons sooooo :L
I mean Im making do Ive already made a comprehensive minecraft style inventory system with guns and items
health, death systems and whatnot
That's very impressive! You can certainly do a lot with graph despite its limitations.
Oh right yeah one more thing, how do I initiate a playerdata save? or is it automatically executed every time you get or set a playerdata variable?
Judging by that I assume everytime you "set" its gunna do that
Correct
Thisl get unoptimised fast if people dont do this carefully :3
VRChat is already slogging at 10fps on my I9 as it is :L
You bet :3
Also, you should never write to PlayerData before the local player has been restored. You can know this when the OnPlayerRestored event runs with the VRCPlayerApi that it passes being the local player.
So restoring is the loading of data?
That's when VRChat tells us it's "safe" to begin writing to the save data
because it's done fetching whatever save data you might've had on the server
So Il just make a boolean that enables on restore
so itl fail a branch if it tries to save too early
Pretty much
You need to branch on if the player that was restored .isLocal though
ye
Tried setting up a simple counter, running into exception errors though :L I figured this would work, but doesnt seem to :L
maybe a local player on the end of getint? :L
You need to plug the local player into this slot I think
I did, same result
Also, you have to branch on if the player varaible here was the local player
That's strange
Not sure about the context on which to use it then :L I also tried with networking>getlocalplayer but still nope :3
This will get the count of anyone. No matter if they're not you.
Which I highly doubt is what you want.
Yeah no lol
Ok I got it working I think
Yep, is there a way I can see how much space that took to save?
Ah 165 bytes
A bit more than I thought it would use but whatever
It do be like that
Well thanks for the assistance @fading totem
Np!
i changed my script to this, but still have the issue in this video.
(the slider script is just there for context)
i also have the issue of the Toggled_Key being disabled by default, but i want it to be enabled by default. but i don't know where i would tell it to be true by default instead of false
So I made a "global" leaderboard using persistence.
It seems to work fine at first, however, it soon exposed quiet a few people who were clearly cheating...
I assume they did it through clients and calling methods/changing values? I'm not sure how exactly those tools work but I imagine they have quiet a bit of control?
What I got curious about though: Can they change their persistent data somehow?
i would assume that worlds are supposed to be the only thing that can change playerdata
any other way of changing it besides clearing it is probably either an exploit or client
How did you make it "global"?
I let people infect each other with their top most known scores. It's like a virus, if someone visits a public even once, they will have it and will infect any friend they visit in privates.
(It got kinda buggy though after I tried eliminatin the cheaters. The logic is not as straight forward..)
Ah, and name changes is what I'm trying to cover too right now.
Ok, ruling out HTTP shenanigans, then. My remaining guesses would be either an in-client exploit discovered in the overall logic (like using a flying avatar to avoid obstacles in a race) or they're directly triggering a network-callable Udon method (ie one that is not protected with an underscore in its name like _ProtectedMethod())
I protect everything, so I don't think that's it. I just assume that they found some other methods that would give them the points straight up. I removed some potential methods, but it's hard to know what exactly caused it.
From what I understand, this is a fools errand anyways and I probably shouldn't waste more time on this? lol
Yeah, there's not a lot of tools available for you to figure out what's going on there.
or they are just directly editing memory
simplest way to avoid memory manipulation is don't store the value as is
Yeah... I would love to see what tools they use, but I have not even thought about pursuing them because of potential bans.
π€ What does that mean exactly? I did cover up some variables from public to SerializedFields if that's what you mean.
so say you are storing a score. and my score is 1300, if I want to find the value in memory I just have to look for 1300. if I find multiple of those slots in memory then I just wait till my score increases to 1550 and search again. if a previous memory slot was 1300 and is now 1550 I've found the memory slot and just overwrite that memory with 99999.
if you don't store the score as the value the player sees, like store 130 instead of 1300 as the 0 is not used, it starts to obfuscate to the cheater and they have to search harder. this is like a level 1 obfuscation
a level 2 method would be to xor the score with a static number like 0xABABABAB that would make the score hardly ever resemble it's actual value
a level 3 might be to store a hash of the score every time you update it and every time you need to use the value you check the current value against the hashed value and if they don't match then some manipulation has possibly happened and you can invalidate the data.
I protect several important values in my world with a random value xored with the data's value. but keeping things synced when like a master switch occurs takes some thinks to keep things sane. And nothing is foolproof, this will only keep the script kiddies out and increase the difficulty so it should prevent all but the most determined.
Ahh, that makes sense. That's kinda my problem, I'm not sure what exactly people can do. If they can look up values that easily, then yeah, those are good ideas. A bit overkill for what I want to do though.
it's always a tradeoff on complexifying your code vs hardening it against attacks
I wish people just didn't attack, but I know that's not reasonable zzz
are there any video tutorials on persistence yet? i feel like i really donβt understand the system just by reading about it
No official tutorials yet, but we have built 8 examples with info about how to use them, what do you think about those?
Yah you need to think about the difficulty of hardening vs the actual benefit for players. With a shared global highscore this is going to be hard because even rare cheating is going to be exposed to everyone. But for cheating behavior that will only impact those in the same instance and the impact on other players being low, then you can get away with a lot less hardening. For example, it's pretty easy to cheat in ToN but the adverse effects of a cheater are usually handled with a quick vote kick or group ban.
i looked through a couple, but even seeing the script right in front of me, it doesn't really help me to understand how it all works and why certain things are done the way they are. it could just be me and the way i learn things
Yeah, that's understandable - everyone learns a little differently. If you want to know more about how a particular thing works or why things are done a certain way, you're welcome to ask here! You might find it useful just to load up the examples and press play or build & test them to see how they function.
I'd been toying with that idea, but for the reasons you gave (cheating) as well as concerns of running out of space given enough time (though this could be resolved by culling lower scores), and the networking cost for each sync, I decided against it. Even with some cheat protections in place, I still require video submissions for global high scores. I do still want to explore the idea of decentralizing data and using that as a mechanic incentivizing joining strangers in publics to "sync up", but I'm still workshopping both what it would do as well as good ways to do it well.
i also had some interesting ideas along the lines of letting persistent data spread between users like that, although not just for scores
but yeah, time is limited, and hard to secure it enough to be taken seriously
Yeah, and it's going to be a hacky solution either way. Even stuff like name changes have to be covered because we don't have a consistent identifier lol. The complexity of managing everything is a bit high too, not impossible, but considering people seem to be able to just put in semi-bad names like you see below, it's not worth the time to pursuing covering all the edge cases for me.
Yeah, there is some potential there.
Like a being able to look up every person in a guild when meeting one member from that guild or something.
I did have this idea to make an actual virus. Basically give everyone a unique representation of a virus based on their name. Then the game is to collect as many as possible by meeting as many people as possible.
Would be an interesting social experiment at least.
Meh, if people put bad names honestly, more power to them to make the choice to be easely spotted for vote-to-kick π€£
Thing is, I can't find that name on the website. I assume they can just set a different one locally?
Name spoofing is a thing in some clients yeah, A lot of cheaters used it to access admin panel in Aincrad. ^^"
So, it wouldn't be surprising they also make shenanigans like this in other places
Or maybe you can't find it because it was reported and banned already?
Why does the player-object documentation say the following:
"OnPlayerRestored includes a reference to the player whose data was just restored. You can use this to make the Owner of the GameObject"
Which implies that this event is useful, because you can just write something like:
Networking.SetOwner(player, gameObject);
To make sure that each player is now the owner of their player object, yet... on the very next line of the documentation, it states the following:
"In the Start and OnDeserialization events, ownership is guaranteed to be correct."
If ownership is guaranteed to be correct, then why would it be useful to 'make the Owner of the GameObject" as it is written in the prior statement?
Considering there is no punctuation on the first sentence, I'd assume it was a mistake.
Are we assuming that it isn't required to set the owner prior to use, or are we assuming that ownership isn't guaranteed
I'm pretty sure the ownership is already set. That's the main purpose behind player objects in the first place.
ownership on playerobjects is locked. That sentence in the docs might have been a merge conflict that chopped it in half or something
Thanks for the help Puppet and the clarification Phase!
I've been wanting to complain about that line in the docs for awhile now but didn't know who to complain about it towards
FYI you can press the "Feedback" button to submit a comment, including a screenshot
Thanks for the info!
Also the links in the pinned comments to Persistence docs link to the beta version of the docs, if those are different
Thanks - time for a new post to pin, I think, consolidating the info.
Persistent Pin
Persistence for Player Data is now available in the SDK.
Learn how to use it by reading through our docs, visiting VRChat worlds that use it, and exploring example Unity scenes built with UdonSharp.
Docs
- 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.
- 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.
VRChat Worlds
Visit the Persistent Worlds Showcase thread on our forums to find persistent worlds to visit.
Examples
You can open Example Central from the menu under "VRChat SDK > π Example Central".
Or browse through them first:
how can I see the values that are stored wihle in client sim?
It should appear in your ClientSim Player Data window as a float named
There is no such window
you don't have this?
FYI - the page about the window has instructions on getting to it. Highly recommend reading through it to understand how to use it fully!
ClientSim can simulate the VRChat SDK's "Persistence" feature. The PlayerData editor window allows you to debug persistent data in your world. You can open it by going to "VRChat SDK" > "ClientSim PlayerData".
Thank you,
currently I implement the logic for a persistend settings panel
Does the Persistence works with CyanTrigger?
I wanna make a gameobject toggle but seems to not be working at all
there is no tutorial anywhere
no, i doubt it does, and that it ever will. i believe he stopped support for cyantrigger, so it's not going to be updated anymore
Whaaaat? Thats so sad! All my worlds work with CyanTrigger! D:
i know someone else who does the same. it does suck, but either someone else might pick it up, or everyone will have to learn udon
Welp, Ive tried a simple Udon object toggle and still persistence seems to not working
Still getting this
thatβs just the info message telling you how it works, itβs not something youβre βgetting.β
and, for a persistent toggle, you would use PlayerData. not a PlayerObject
Why not? You could easily have a bool on the player object
This is my Udon Graph
Persistence won't work if you don't call it explicitly. Have you synced variables before?
Nope.
well, i would figure that playerdata is more fit for a basic persistent toggle
Depends on your setup I guess
Seems like it's a bit rough in this case because they are using Cyan Triggers π€
I feel like you could just use PlayerObjects for sure. No idea if the PlayerData API is exposed there though.
So- what I need to do?
You should try look up how to sync variables using CyanTrigger.
Actually, I don't know if you have any callbacks for the PlayerObject either π€
Sorry, I have very little clue about CyanTrigger, it might not work for all I know
Im asking right now with regular Udon
.
Did you check out the documentation? They have some examples there, and also there are some examples in the SDK that can help you
Try typing in PlayerData in the graph. You should find SetBool or something
Those examples arent self-explanatory
nothing tells me what triggers the persistence
Then you can use the OnPlayerRestored event to GetBool
You should check if the player is local first
where
Because it will get called for every single one
the player node thing, you should extend it and find IsLocal
The OnPlayerRestored?
Yes, the player thing at the bottom I forgot the name sorry I don't use graph that much
You should make a branch node too
im still lost
The docs has this example at the bottom:
I think you might need to learn some more fundamentals first. Going further is going to be difficult otherwise.
I just want a simple toggle with persistence π©
Uhh, give me a sec then
Basically, to those who worked with CyanTrigger since its release we have nothing to do but learn everything from 0? That sucks
I wish the devs implement CyanTrigger as an option the same way they added CyanEmu
Okay, I tried my hands on it:
(I haven't touched graph in a while though. Try this out, I hope it works for your usecase)
This will allow a player to toggle something, rejoin and have the same toggle state as when they left.
lemme try
Make sure to use a unique save name though. If you use the same save name on each toggle, then they will overwrite each other.
Whats this node?
A string variable that you can create, just like you did with the game object
And these should turned on?
It's giving me this error
And the toggle is not working
You don't need to sync anything in this case. We are using PlayerData, which is a more simple version that does it for you already.
Did you assign anything to gameobject?
assign what?
"gameObject" that you created is a variable that is empty right now
if you go into your inspector you should see it. Drag and drop the target game object you want to toggle in there
You can also just use this instead of creating the variable though
Oh, its not empty
Also, in Play Mode, the trigger cube turns off itself, however its still visible in the scene
and can interact with it
Are you setting a save name?
You named the variable "Cube1" for some reason, but that's not how variables work.
Make it public too just like the game object and then in the inspector you write the unique name
Looks like a lot of basics are needed here.
If you want some simple examples on PlayerData, there are pictures of a few of my graphs (with comments) in my persistant world (in the world showcase thread). It isn't worth much for experienced users but can help if you learn from observation more than explainations. ^^"
Just wanna point out that NONE of the examples in the new Example Central thing in the SDK actually have graph examples. That, and the docs are literally just docs, and lack any sort of step by step tutorial. The jump counter picture in the doc is about the only thing graph users have to go on. Basically anyone not using U# is on their own, good luck learning persistence
They don't? I just messed with nodes till I got stuff working so I missed that xD
I kinda wish there was a good way to take pictures of the graphs, it's always a bit blurry depending on zoom level
But yeah, my world have graph pics for:
"click to add point"
"automated action on loop"
"give to all"
"give to specific player"
"give to spefific team"
"achievement (multiple levels) depending on score"
"unlock trophy for X point" (shop)
and an example of saved door you have to use a key to unlock
There's lot of other stuff but they're 99% not persistance, only the end "is unlocked?" state is saved so it's the same as a "add point" then OnPlayerRestored check if not equal to zero. This alone is enough to make a lot of basic stuff.
there's a whole maze with puzzles but again the only persistance related part is the "check if solved" and reloading part
Also I'm self-taught so take my graphs with a grain of salt, there's probably better ways to do stuff
.
Man, persistance is suuuuch a blessing.
My internet died for a minute while I was in one of those "mine some ressources" world. Not having to restart farming three tiers of ressources is so good compared to before. Finally, rejoining don't feel like a complete defeat making it not worth resuming playing the world.
Oh, which world is that
"MiningForPickaxe2"
does anyone have an example of a basic persistent toggle? like, for a menu button that toggles a gameobject.
You mean something like this?
for the specific example you ask for, that's just be that added:
I'd probably recommend onplayerdataupdated for that use case instead of onplayerrestored. You can simplify it a lot by not having to make a separate event, as onplayerdataupdated will trigger every time the variable changes, no matter what source
Also, request serialization is unnecessary as playerdata is independent and manages its own serialization
how would i set a default value for a key? like, if i want the toggle to be enabled by default for example
is this correct? also, would it be fine to use this same script on 10 different toggles? would the key names not conflict?
private const string Toggled_Key = "toggled";
bool state = true;
public void OnClick()
{
state = PlayerData.GetBool(Networking.LocalPlayer, Toggled_Key);
PlayerData.SetBool(Toggled_Key, !state);
}
public override void OnPlayerDataUpdated(VRCPlayerApi player, PlayerData.Info[] infos)
{
if (!player.isLocal) return;
_ApplyState();
}
public void _ApplyState()
{
foreach (GameObject target in targets) target.SetActive(state);
if (state) image.color = onColor;
else image.color = offColor;
}
it seemed to work in clientsim, although the key was reversed from what was actually happening, and it took about two or three button clicks to get it to actually sync up with how to toggle should work.
it behaves oddly
Yeah, if you have multiple toggles with the same key, then they will overwrite each other constantly. You need to make the key public or something and then write out a unique name each time. (Could write some editor code to generate it too I guess).
What you are missing there is that you don't apply the "state" properly after loading it in OnClick. So you need to load the state, flip it, and then you save it again.
Right now you are only saving the flipped state, then on DataUpdated you just apply the old state even though the loaded state is flipped.
Sorry, hope that makes sense. For some reason I struggle explaining it. It's rather simple, just a tad bit mind bendy.
oh, no i thnk i understand it now
makes sense why the data would be true but the toggle be false
so i just have to add _ApplyState; right after i do SetBool then?
if iunderstand correctly
No, your culprit here is your "state" variable.
You are not updating it's "flipped" state anywhere. Either do it right after you SetBool, or in OnPlayerDataUpdated. I think both can work.
OH, so like, add state = !state into OnClick
Yes, since if you don't do it then _ApplyState will use the old value, which will appear like nothing has changed
How hard would it be, to change the leaderboard example to show how many times a person has visited the world?
Is it? It never saves correctly unless I add those requests in my graphs...
Any time any PlayerData changes yes. But there's always cases you'd rather have it not be called all the time, for example if people add admin override that doesn't affect the player's saved preference, for whatever reason, or idle games since they're so easy to make them relevent for players to stay (similar to how UdonChip have a passive income gimmick) now...
For a general example I'd rather keep it at "call it only when needed" because you never know what kind of conflicts with calling it at OnPlayerDataUpdated could cause in the long run when not knowing the user's complete design. ^^"
Is it safe to destroy the networked player object for the person that left the world?
It will be destroyed automatically.
so, would i not want OnPlayerRestored and OnPlayerDataUpdated in the same program? if Restored is for when the data is first available, and Updated is for every time it changes, it sounds like i'd want to apply the state for both of those events
this is what i currently have. it works for the most part, the only catch is that; if the playerdata is enabled on start, the first time you click the button it does nothing, but any time after it works as intended. but if it's off on start, it always works as intended
i think i can see why it's doing what it is, but i don't really know how to fix it
you want OnPlayerRestored, it is to load your data when a player rejoin the world next time.
OnPlayerDataUpdated is good, it triggers every time a PlayerData variable is changed
but it triggers when any PlayerData is changed without distinction, so it depends on your world's design
if there's just a setting menu it's not gonna be enough calls to cause issues
but in cases with way more frequent calls, it's a lot that adds up
well, i see it has an Info local variable, so couldn't i check what exactly changed?
I'm not advanced enough to use that part, probably if you know exactly what you're doing with it
but the initial check still occurs on any changed anyway
docs say it contains all the keys that were a part of the update, so i'd assume i can check if the updated keys are the same one as PersistenceKey in my script
My stuff is far from being the best way to approach it, experiment if you think something else is better and compare I guess
I mean you're using U# it's already more advanced than what a Graph user like me can do xD
public override void OnPlayerDataUpdated(VRCPlayerApi player, PlayerData.Info[] infos)
{
if (!player.isLocal) return;
foreach (var info in infos) {
if (info.Key != PersistenceKey) return;
}
_ApplyState();
}
pretty simple
probably works
only one way to find out, iterate until it does what you want ^^
i can also have it log every key that it goes through, so i can see what it sees
thinking through it logically, all i had to to was set state = pState in OnPlayerRestored. now it works
also, yes, it does exactly what i thought it does. so my Foreach loop does work
yes OnPlayerRestored is pretty easy to use, it's almost like OnStart but makes sure your PlayerData is up to date before any call are done
before it, the game only have the default values (which is a problem once your player saved changed values, you don't want defaults xD)
so it's a key piece to loading any persistance data
i'm still not sure how i would set a default value for a persistent value. like, right now all my toggles would be off the first time someone joins, but i want them all to be on by default instead
reverse the wiring?
the player don't have to know that the button that looks like it's on sends a "false" and when it's off a "true" xD
even tho...not recommanded, that type of mental gymnastics can lead to bad habits
yeah that's a bit strange. is there really no other way?
I guess you could set the values (without saving them) OnStart, so a player without saved data would recover it...
since OnPlayerRestored is called only after, it updates anyway
It's true that it's a bit akward how we can't just set the default values like for normal variables...
yeah, it really is
could be a good tool to add, something that tracks the PlayerData + allow to set defaults
similar to the normal variables list on graphs
even using a notepad I lost track of my keys past a certain point
still no idea how close to the limit I am for my world
also, correction, this does not work
somehow, this doesn't either. i really don't know why, but it seems to pass through the If statement regardless of whether or not the keys are actually the same
public override void OnPlayerDataUpdated(VRCPlayerApi player, PlayerData.Info[] infos)
{
if (!player.isLocal) return;
foreach (var item in infos) {
if (item.Key == PersistenceKey)
{
Logger.Log(name, $"Data updated! ({item.Key})", LogColor.Orange, false);
_ApplyState();
}
}
}
not sure you need anything this complicated, really...like PhaseDragon said it's fine to use OnPlayerDataRestored as is, most of the time
I'm just a weirdo that rather use custom events because graphs
I think you should look at persistence and the rest of your code as separate systems. The reason there are no default values for saving is you need to explicitly save something for it to be stored. Therefore, you should set and handle the default value in your own system, and only when the save data is loaded do you change the state.
well, now i just want it working to see if i can do it
and, i'm practically doing the same thing as you with the separate custom event. it just looks different because it's code, but my _ApplyState is basically the same as having a separate event in graph
Yeah that too, I often use normal variables "to do the math" and only set the persisted data to be equal to it at the moment of saving. Like if persistance is an addd-on rather than the main system
Same for saving, do your code first and then save as an afterthought. I personally wouldn't ever use the on update event. But maybe that's just me,
And rely on other features like synced variables.
Honestly, at that point like I said before just iterate till it does what you want. Only you knows the exact specifics of your systems beyond the snippets you can share here
Like, what i sent works fine for bools but if something have more states obviously it would need to be modified anyway
Here is actually a good example of what I'm talking about:
I also have an habit I don't know if good or bad, or using a generic "send custom event (eventname)" as my whole button, and resolve everything in a graph used as state manager, rather than putting actions on the buttons so...yeah xD
that was actually very helpful, because i practically made the graph you sent on my own after you said it like that
oh, using the object's state directly to define the saved value, rather than the value to define the object's state? Yeah that's also a good way to do it
at this point we're just comparing "A+B" to "B+A" here xD
this is what i ended up with, it works perfectly and i basically use persistence saving and loading as a separate system now
i feel like i understand persistence a lot more now after talking today
Yup they're their own thing, treat it as an add-on and it's way easier to visualise
and to integrate to old systems also xD
You can do either, it's just supposed to show how to split persistence and actual logic
when i first saw persistence, it seemed like you had to make your whole systems based around it, but now i can see how it's just like an add-on
honestly easier than networking
I can see where the confusion comes from, it was intimidating at first for me too before I realised "wait, I can just make an "set 'Pdata' to 'Variable's value' at the end" and call it a day xD
everything is easier than networking...
god i hate networking but it makes games so much more fun
I made so many systems that works for single player but breaks as soon as I try to add a multiplayer aspect it's not even worth joking about anymore. Networking is just...painful
Yeah that's exactly how I'd do it!
I love(hate) networking code
I just hate it, no ambiguity needed π€£
but again I'm just a graph user, so it's pretty reasonable at my level
it's a miracle my ressource nodes + inventory + build a house + shops even works as graphs...
funny enough, adding persistance to all of them was pretty simple
for the saving side
for the loading part oh boy...making sure only the steps you brought are played on joining, while not draining the inventory since you already paid of course...
that was quite an adventure to figure that one out
Yeah doing that in graph I'd actually hate
it was not as bad as I pictured it to be
but required a complete change to how i approach my graphs
and also some unholy "string+int = custom event name" for the calls
My opinion, if you can make something complex in graph you can easily transition to code
Case in point
if I ever learned to code maybe, but my brain just...it don't stick when it's just words in a long list..
I know it's a me problem, been messing with all kind of game engines since 2009... xD
Of course it's a bit of effort, but it is just muscle memory. You already know English and most basic programming concepts from graph
Just need to spend some time to connect it all
yeah reading scripts isn't even the problem, it's just when I work in this format my brain just blanks
when it's reading one and messing with it, I've been messing with other people's stuff since my RPGmaker days
Imagine it like trying to ride a bicycle for the first time. You just need to start moving
Well I don't want to mean to be annoying, I just like talking about brain stuff. The decision is ultimately up to you
I know, that's the most frustrating part. The Aincrad devs did try to help me on that front when I was still in their team, poor programmers lost some sanity seeing what i can do in graph but how helpless I am in front of U# xD
I was spitting full working mockups but none of it was usable because graphs
Like the Vket huge booth, I made it completely, sent it, and they had to redo it...
(3D team also lost sanity sseeing I was using sketchup but...that's another story xD)
Yeah, you have to understand they experience the same effect of lacking the muscle memory for graph just like you do for code.
And since code is more powerful, it's a lot harder to justify messing with graph at that point
oh yeah, I understand. the rare times they had to help me with something I wasn't able to do because I didn't knew a node's name it was hell. they hate it with a passion and I can't blame them for it
the funniest thing that happened was when someone jokingly said "just wait till he learn about concats" when seeing my old stacks of string.addition and I was like...wait, THAT THING EXISTS?! π€£
I'm that level of "no programming backgrounds before messing with game engines"
funny enough that's how I learned most of what I know from them, snarky remarks like this, because it's so common sense for programmers that, they just can't imagine someone don't know it
and again I can't blame them for it, I have the same problem with teaching art to newbies because it's been so many years that basics are just "common sense" to me
it's just normal past a certain point
Pretty much, but that example is also more so knowledge versus actually writing code.
(that's also what made me build my persistance example the way I did, trying to not assume the person knows how a thing work)
You can't know what you don't know
yup
that's why nodes works for me
from a starting point I see a whole list of things I can mess with
Nope, that's where I disagree lol
even if I didn't knew any of their names before opening the search
I mean once I know what a thing is yes, it would translate
like the concat example, it would only "be useful to learn" the first time if I found it before being told
after that graphs becomes inferior
Most of the API names are the same as in graph.
also let be honest, things are working in graph so, yeah I'm good in my confort zone... know it's a me problem on that side xD
Even a lot of the basic building blocks like Branch and loops are the same, it's literally just a matter of muscle memory, which has to be attained by slowly walking like your are drunk until you can walk straight again
drunk would be an understatement at this point xD
but yeah I know, that's the worst part
(let free the channel since it have nothing to do with persistance anymore, tho)
Don't forget to share a link (here or in showoff I guess?) once you lab your world, kinda curious to see what you come up with once you finish learning how persistance works
i'm actually just re-making one of my most popular worlds rather than a new one. my skills have improved, and so will the world along with them. i've done this to that world about 3 times now
https://vrchat.com/home/world/wrld_26df2010-226c-4b2f-a8d2-a7020f5f1ea0
in total this is probably about the 6th iteration of that world
i figured with my better general world knowledge and persistence, it was due for an update
i was working on a different game using persistence, but development has been very slow on that one, cause it has a few issues i have no clue how to solve
oh, what sync mode should i use for something that's local,but has persistence?
manual i would assume?
PlayerData doesn't require any specific sync mode. It handles it somewhere else
oh i see
when in doubt, manual is always the safest choice
just test it with none and compare if everything behave normally. if not, manual it is I guess
I lied.
i broke it
π€
Im trying to use the example leader board prefab, but change it to adding 1 every time they visit the world. Instead of every time they jump.
I somewhat got it to work, but it added a point for every person in the instance instead of just themselves.
InputJump only runs when the local player jumps. OnPlayerJoined runs for every player any time a player joins. You'll either want to check that the joining player is the local player, or preferably just run it in OnPlayerRestored (but also check to make sure it's the local player).
I worked out a leaderboard for my world with my tetris-like arcades aswell
https://vrchat.com/home/world/wrld_36ead96f-2451-44b4-91c7-65ae9dbd1ff6/
Not sure if this is the right place to post this, but why do they swap the return values on "GetBool" and "TryGetBool"?
They list things in the same order in the documentation as the normal "GetBool" which would suggest the "success" parameter is the second passed parameter. They don't mention anywhere which value is returned where. It took me ages to figure out why I could not get persistence to work, and the only thing that tipped me off is when I dived into VRchat's API code to see what it was actually doing.
Obviously at this point the persistence library is out, so it is what it is. But it would be nice if the documentation was more clear. I think swapping the values on the "Output" heading to read bool success, string value would help clear things up a bit. Maybe some example code showing the difference between "Get" and "TryGet" would be nice too.
eg:
Correct:
bool doesValueExist = PlayerData.TryGetBool(Networking.LocalPlayer, keyValue, out yourData);```
Incorrect:
```bool yourData= PlayerData.GetBool(Networking.LocalPlayer, keyValue);
bool yourData= PlayerData.TryGetBool(Networking.LocalPlayer, keyValue, out doesValueExist );```
That's a valid critique of the docs, but you should really be using an IDE which shows you the correct ordering. The excellent intellisense and autocomplete is one of the big draws to C# in the first place
VRChat does not have doc pages auto generated by API scanning, so these kinds of issues (let alone the functions not even mentioned in docs) so you're going to have a rough time with things like this if you don't have a proper IDE set up.
π€ Why?
It's said error
I'm using VisualStudio with intellisense, it doesn't generate a syntax error because both values are Boolean
So its a Logic error that is created. Since I am new to using persistence, I had to go through checking all of my code, rereading the docs, checking that I was testing properly, checking that I didn't need to enable it on the dashboard, double checking I had the right WorldsSDK version etc. If I didn't have a background in programming, I can imagine something like this being a roadblock for many people.
The word "value" perhaps should have tipped me off, but its so generic it could mean anything (all booleans are a value), and VisualStudio gives no name to the boolean being returned to compare "value" against.
Try* functions return a success/failure.
The out should tip you off that it is where the data will go.
All try functions are like this even in base c# with for example float.TryParse
Ah, I was unaware of this, thanks for the tip! π
Tbh I've not used the base Try* functions in c# before, so perhaps an oversight on my behalf
Something spooky happened with my player objects...
A value on one of the scripts (set to sync None) magically changed by itself. I know for sure it wasn't any other script or me doing it. The value was set to public, but I did not use it anywhere outside of that single script.
Is there any sort of explanation for this?
where is persistence stored locally?
It isn't.
its not stored locally, unless your in clientsim
Persistent data is stored on VRC's servers.
also local test 
it's in the same folder as your logs
is it possible to use OnPlayerLeft() for updating PlayerData? Or is this too late?
The docs specifically state you cannot save persistent data with OnPlayerLeft.
thank you!
what would be a replacement way to save data when a player leaves, if possible?
for example, if you want to save the location of something, but rather than running a loop that updates every 5s, you save when the player leaves
You can't
wow, that seems like something that should exist
saving on leave sounds.. like a bad idea. Relevant checkpoints only
Just save every second or so
As long as it isn't mission critical, a loop will work
well yeah i know, but i feel like a way of saving before a player leaves would be cleaner and more efficient than making a loop to save that data
I'm no dev but I guess the problem is OnPlayerLeft fires after the player has already completely left the world, not existing anymore, so there's nothing for you to access and say "save this player's data".
Maybe if there was something similar to OnPreSerialization but for leaving, like PreOnPlayerLeft, but thinking about it I think a function like that could possibly be abused
if someone loses their internet connection, and it doesn't come back fast enough, there wouldn't be a way to send data over
ok, this make sense for me. In my case it's not so important if the last state is not saved in this case... but for other cases it's maybe important...
Hmm... but why not VRChat implement booth options - direct persistence data saving if important and saving persistent data on player exit for not so important data (maybe with a new event befor PlayerLeft) to reduce network transmisson?
You could base the method of saving on what you are saving.
Saving the players position? Wait for the player to stop moving and be in the same general spot for a number of seconds and then don't save again until the player leaves that area.
Saving Something that only changes after a game round ends? Then save only after the game round ends.
Contextualize your saving and you can reduce your potential network traffic depending on what accuracy you need.
Will a player notice if they respawn in the world .25 meters away from where they were standing previously? Unless your world has something where standing someplace specific down to the millimeter has purpose, they are unlikely to notice the slight discrepancy from geofencing your save check.
Why? It's far more efficient than having to guess when to save otherwise. There's obviously times when you know when it's a good time to save but otherwise having to save seemingly at random and potentially constantly when not needed isn't ideal.
The way JavaScript handles it is it fires as you leave, before you actually leave.
if you really want your data to "autosave" without having to have a script do it every X seconds, you could just use a PlayerObject with Continous sync to store the data instead. Any synced variables on a PlayerObject can be persistent, and they will always persistently save their last-synced network state.
In some tests I persistently saved some pickups, so with both ObjectSync and VRCEnablePersistence, it will always persistently save its last-synced position.
Having anything with continuous sync is also far from ideal though.
anything? anything at all? there is no situation where continuous sync is ideal?
It's the most expensive form of sync, so no. You use it when you have to, but you'd typically not want to unless you have to.
I may not have been following the thread but... wouldn't one "save" the state when the state changes? There should be few reasons to save when exiting if the value was saved when the user "got the high score" or "set a user option".
that'll be the most common use of persistence and Player Data especially, should be values that are not often changing and should be saved as soon as they change. Perfect for doing manually.
Some have had ideas of things they want to be persistent that are constantly changing, though, for example the player's or a pickup's position. This value could easily be changing every frame, and that's too fast for manual sync to be ideal.
So in a perfect world, it would be nice to save these values not every frame, but rather right when the player leaves. The problem is trying to save persistent data while the player is leaving is not possible.
So you either A.) save this value every few seconds, or B.) Let UdonSynced variables do it for you. Other ways are described above, depending on what you're doing there is probably even more efficient ways to save values.
Continuous sync is explicitly described to be best used for frequently changing variables.
It also isn't any more "expensive" than manual. You just have far less memory to work with. You'd easily clog the network if you tried to manual sync something that was changing every frame.
They both have their use cases. It isn't good advice if you tell everyone (or tell yourself) that you should only be manually syncing everything.
It would be nice to sync videos with everybody in the VR world watching exactly the same frame but it is not realistic. It would be nice if VRC could keep track of where your friends were at any given moment but even that is impractical. It takes time to save state so one balances what info would be lost against the time needed to update the state. If a value changes "on every frame" and such information is important then you are stuck either saving it (note you couldn't have done this before) or developing for another platform where frame-based changes can be persisted.
pretty much yeah
we gotta work with our limitations
Don't let the search for "perfection" hinder your designs.
Players arn't gonna notice if they're half a centimeter behind where they left. If anything they may just think you're rounding the saved values for optimisation.
Same for video players, it is common for chat to have a 3-15sec delay when looking at livestreams, people online are used to having a small delay on such things. If a late-joiner gets updated to only be half a second behind that would already be an improvement compared to that.
Aside of idle games that fire real fast, or for achievements checking time played, it should be fine to just save on value changed.
Now if the check for the action to occurs needs continuous checks...is another story
Did anyone made tutorials or write a guide on good practice and workarounds for common situations people default to continuous checks?
(Guess this is beyond just persistance, but such ressource would be useful to many creators. The more we learn when there's better ways than always checking on "Update" the less we need to abuse continuous checks.)
There can be a "significantly changed" check as well. If a bool toggles from true to false one would generally save it, if an int setting increments/decrements one can do the same, if a float goes from 1.001 to 1.002 there is probably little reason to bother to persist the value and it can wait until some threshold of change is met.
Yeah, and even then a simpler stat affecting calculation can be saved sometimes. Especially for RPG maps, it's surprising how little "base stats" need to be saved and can be used for the calculations, instead of saving every each calculated scores
Abstractions also, players would get used to having a set spawn point in their house (saving the house ID, then set position according to index(houseID) rather than save their precise position) if it stays consistant with how other checkpoints works
Does anyone know how I can add the persistence on this script? This script just activate a gameobject and I want it to be saved with the player
first of all, that get isLocal check doesnβt do anything. Interact is already called by the localplayer by default
second of all, think about persistence as saving your methods as different data types. like, youβre just toggling an object, so you could say that a bool is the data type you can save it as. or if it was a slider, you would use a float as the data type.
then, once you have your data type in mind, think about how you can apply that to whatever youβre doing with your script. in this case, you want to create a bool, on interact set that bool to the value you want to change it to, then apply that bool to your objectβs active state.
and if youβre using a simple data type in your methods like that, then adding persistence becomes trivial. all you have to do to make it persistent is;
- create a βkeyβ variable to store the name of your value. keys are made with a string, and you need a unique name for each one in your world.
- after you apply your bool with the changed state, use PlayerData - SetBool to save the value. all you plug in is your key variable, and the bool variable
- you need to load any saved data the player might have when they join, and as soon as they load in fully. to do this; create the OnPlayerRestored event, and connect an βisLocalβ check to the VRCPlayerApi output on it. then, set your bool value to be equal to the output of a PlayerData - GetBool node
oh, and to elaborate more about the key variable; if you are only going to use this program on one object, you can leave it as private (so, donβt check the βpublicβ box), and give it a default value. the name can be anything, but for your sake, you should name it something related to what the key is storing. if youβre making a settings menu toggle program, so youβll have this same graph on multiple objects, youβll need to make the key variable public so you can give each object a different key name
and the name of the variable itself doesnβt matter, only what the string text is set to
Is it normal to just be flooded with this error
probably not!
I've never seen that before though. It's angry at one specific file, maybe temporarily move it out of that directory
oh looks like it's caused by the file being locked. Do you have it open in another editor?
Oh, silly notepad++
Well that didn't help very much, I've tried deleting it as well so it could re-create the file normally but that also does not work
ClientSim persistence is just cooked bro
it gives the same error even though the file no longer exists?
The file is created on Play but doesn't input anything I believe, it just has {} in it
Okay, how do I delete ClientSim persistence data?
Alright, deleting the entire folder worked
Deleting the individual files did not
enter playmode and click VRCSDK in the top bar of unity, and thereβs the Clientsim Playerdata menu button there. in that menu, thereβs a trash can icon you can click that deletes the playerdata
A question about the OnPlayerRestored() event: is the event always called for every player? Even if a player enters a persistent world with PlayerData for the first time in which it has no stored data yet?
Yes, it is always called
highly doubt you'd be able to read someone else's persistent data directly from the servers, but they can certainly view their own memory and edit it
so what is your question exactly? just if this method would work?
you didn't actually ask one
I think there's technically a possibility. but they'd have to be pretty damn dedicated
if the persistent data isn't hashed, then you'll start using it on OnPlayerRestored; on that frame I assume you'll start hashing it. So theoretically they have 1 frame to read the data?
you can store a wide array of types with persistence, I think it will all come down to if you have enough memory to store it
I'm not sure the docs explicitly say the memory limit... I think it's 1MB? or maybe smaller. it's not very big
storing the hash as a string could get kinda big
I think it'd fit though
a SHA256 hash I think is what Unity uses, it'll be 64 bytes?
per hash yeah
I think there's enough room. Doing a persistent save "has a similar bandwidth cost as one UdonBehaviour with synchronization set to 'Manual.'"
which is 280,496 bytes
no wait bandwidth. 64690 bytes
I've never pushed the limits of it ever so not 100% if I'm reading the terminology right
oh wait it does list the limit, "Each world may store 100 kilobytes(KB) of PlayerData and 100 KB of PlayerObject data per player on VRChat's servers."
it will log an error if you attempt to save more than you're allowed to
so you should see clearly if it's too much
yep "per player" it says
yea plenty of room lol
I think limits are only gonna be reached if you're trying to store large arrays of large data types
ikr it's awesome stuff. I hope more people start using it
persistence limit is ~100KB compressed
Has anyone had issues with player objects with persistence resetting for some people but not others?
I have a very simple instance time leaderboard that ticks up once a min for each person and it works great 90% of the time.
But for some people they load in and it just resets back to 0
Please make sure you're not calling RequestSerialization(); on your player object before OnPlayerRestored() has fired for the local player.
I'm also very interested in having a bit more cheat protection/prevention... Just that they probably can use any public exposed functions or variable at all times. I wouldn't be surprised if they could log that in realtime and just use the previous called functions (that requires these hashes) and start spamming them. We're also planning a huge world and it's soooo hard to come up with ideas to prevent any client users to abuse things like spawning synced item drops... We haven't found a solution yet... and I start to think that theres no good way, if we at all times, trust the user at the end
Also persistance will send all persistance related local player data out, once one of the local player keys has been set. While the 100kb max save limit seems plenty, you still want to keep the payload low with like 2-3kb/s so other objects syncs have some air as well
Woops I learned some things:
- Public functions starting with _ can't be called over the Network
- Not any public variable can be called/modified at any time- only synced ones could be potentially changed by remote. (for some reason I thought public variable = anyone can access and modify it, but that's luckily not the case)
- When setting a variable via public function, use a _, so no one else on the network can call it via RPC to forcefully overwrite it (not even sure if that would be possible in the first place, if the function was never compiled in udon to do so)
You can't trust the users...
That much I can assure you they are nasty when it comes to cheating and spamming whatever they can to other players.
For whatever reason some of them think it's acceptable in VRchat because these are just worlds not "real games"
FYI: People can only modify values on objects they own. And the new player objects can't be owned by others anymore, so those values are relatively safe too
How many total bytes of data can be stored in a single player's save? I'm trying to come up with ways to set up saving for a procedurally generated grid, and I'm wondering if I could save a char array of vast lengths, like up to 2000?
you've got 100KB of PlayerData and 100KB of PlayerObject data, compressed, to work with
2000 chars * 2 bytes per char = 4000 bytes which is 4 KB, right? That's awesome if so :D
I think that math checks out
though you'd have to save an array in a PlayerObject, since PlayerData doesn't take arrays
huh well actually PlayerData does allow byte arrays
I haven't really touched it yet but my current project I intend to include saving and I might have just worked it out in my head π€
hmm for a procedurally generated grid though, wouldn't all you'd need to save is the seed?
I wish, but I want player interactions to cause gradual changes to tiles and eventually convert them into other tiles, which can't really be baked back into the seed, so I have to come up with a system that also saves which tiles have been changed and what they changed into so I can then enforce that in the generation system when the player next loads their save
ooh I see that makes a lot more sense. Seems like the array thing will work in some capacity though, can't wait to see that
I still feel like not enough people have done things with persistence
I have no clue because I am not active enough in the world exploration space, but I'm happy to know this is considered a cool way to use persistence :3
There is a lot to consider with persistent world changes. Consider this scenario.
Player a joins world 1. Player a digs a crater in their instance. They now have a crater in their persistence.
Player b joins world 2 and builds a castle. They now have a castle in their world.
Both players now join world 3, what happens to the world? So you get a crater or a castle? or a castle floating over a crater? Saving world changes gets complicated fast
Ah, I see you have grid questions as well.
I was thinking of making a grid-based system as well for an art world, (closer example I know is Prismic's VRcanvas world, the one where we invaded with a logo back when we were still in Argus) except instead it would be small player saved canvases that gets loaded in the world's gallery when they rejoin.
I was talking about it to Casual and BocuD the other day in DMs, asking how big the canvas could afford to be with similar color palette limitations.
Good to see actual numbers here tho. π
If my compuer didn't die just before christmas I'd be experimenting more with persistance...That thing is finally back from repairs but I'm busy with the backlog of world updates that were due for december. xD
Also not being a programmer but just some graph user means I'm limited in what I can achieve...but ideas are here. I don't want to bloat the computer with too many projects at once tho, better focus on one idea at a time or else it's just gonna be a pile of unfinished prototypes.
The question will be silly but are there any way to search for specificallly persistant worlds? Or just "persistance" tag and let the luck decides if people forgot to add it (or worse, added it to clickbait in non-persistant worlds)
Funny enough someone suggested yesterday for me to make an alternate version of my world that allows you to save canvases using persistence, but I don't have any plans for that currently anyways, just coincidental π
There's 16 colors in the VRCanvas palette which is 4 bits each, so using a bit array and bit packing you could store 2 pixels per byte (instead of 2 bytes per pixel with a string). You could also alternatively extend the palette up to 256 colors per pixel and still use only a byte each (only reason VRCanvas is restricted to 16 is network limitations from remote web requests). With 256 colors and without compression though, a 256x256 pixel canvas would use 65KB worth of data. With 16 colors and bit packing, it would be 32KB per canvas. 2000 bytes would be around 44x44 pixels (62x62 if 16 colors).
thanks for the answer ^^
Actually, a few years back I made this thing in Construct2. For context, my mother is eldery so using normal drawing softwares is too hard for her, so when her eyesight became too bad to make her usual craft I made that simplified grid with palette corresponding to out physical beads (since bead art is more forgiving than embroidery)
So I guess that's what I'd aim to recreate
...wait didn't someone made a bead art prefab last year? ._.
Anyway, can't be original on generic ideas, it's bound to happen... xD
That would be great if you did that someday.
I'm sure people would love to make art like in the non-networked mode of your world to save their canvas.
Then just walk up to an empty frame and "upload it to one of the canvas in the world".
Pretty sure that if the frames are sync to the instance owner (but not saved obviously) you can have a gallery way larger than the number of players and just sync on join so the longer an instance lives the more art there is in it.
.
And yeah the networking limitations are a pain...I made a book system back then and it takes so long to load a single image book I gave up on making a webcomic-cafe world. Even if I get permission to link pictures from the artists people will just open a 2nd book while the 1st one load, then a 3rd one, not realising how much of a mess the queue becomes...
Still using 3.7.2-persistence-beta.1
Does anyone know of a bug, that's caused by trying to retreive the PlayerObject script after it the OnPlayerRestore has been fired (which means it should be ready to use?)
Regenerating the Network ID doesn't fix it- this playerobject has been added in runtime soo... just dunno if that has been fixed by now. Build works but editor doesn't .
All these console messages come from udon:
Found 1 possible matches for VRCPlayerObject in player objects for Kyrowo
Network ID 196 for VRCPlayerObject does not match desired ID 195
Failed to find component UdonBehaviour in player objects for Kyrowo
Edit: Updating to 3.7.5 fixed it.
Is it intentional that if you implement OnOwnershipRequest on that Persistance Player Object, a remote user can still get the ownership of it, even if I override it with return false (false or true doesn't seem to matter
)? It would have some use cases for me haha but would not be cool, if it'll be patched one day.
This also seems to be some sort of fake ownership, like the remote user has locally ownership, but it won't be synced for others.
public override bool OnOwnershipRequest(VRCPlayerApi requester, VRCPlayerApi newOwner)
{
return false;
}
OnPlayerRestored is only called if the gameobject the script is attached to is active at the moment of joining?
and therefore missable, even if the gameobject is turned active later on it will never get OnPlayerRestored called?
Player Objects cannot be transferred, they are instansiated and assigned when a player joins
This is how it works for any event. If a Game object is inactive, no scripts will run on it
I understand it wont work because it's an event. Thanks. On the other note, scripts' methods still work if you call them directly even if its gameobject is inactive. Only the MonoBehaviour update methods (and maybe others that I don't remember) wont run while its gameobject is inactive.
sorry I thought you were asking a question. And I was curious and did some testing, like you said stuff like Update and FixedUpdate only run when the object is active, Start doesn't fire if the object starts inactive, but does fire once for the first time it is active, OnEnable fires every time it's enabled (makes sense), but OnPlayerRestored does not get re-fired like Start does if the object isn't enabled at the time of the event being received. Theoretically, when a new player joins it should fire OnPlayerRestored again, but needs to be enabled in order to receive it
No wonder I've had inconsistent results with inactive objects, I often would have stuff not work at all if the object the script was on wasn't active. I made a habit of just putting the script on a parent empty GameObject, and just make the stuff I wanted to toggle children of that GameObject lol
You can also set variables while it is inactive! This is previously all I thought you could do
Yes, I was asking a question... more of a clarification of how "OnPlayerRestored" gets called since I didn't find a doc/fact that the script's gameObject needs to be active first. Your answer was good enough for me to move on, until the last bit made me question my existence. lmao. I had an existential crisis whether or not this is due to it because it's Udon or something else. So I had to test my scripts with inactive gameObjects, but are still working as normal since I know for a fact that MonoBehavior methods dont work if the gameobject inactive, but user-made methods still works regardless (except for code that rely on changes to variables within MonoBehavior methods which cause unwanted results if the gameObject is inactive).
Oh...That explains a lot of things.
I started to make more use of "managers" too, an empty parent with the recieving parts like you said, few months ago but bad habits tends to persist. ^^"
Somehow I wasn't aware of OnEnable, so it's like OnStart but that fire every time instead of just once?
yea it fires as an object is enabled, so every time it's toggled it'll fire again
That can be really useful, I had lots of problems even with a manager to sync my node system, but that could at least give a temporary fix for the collective object's states.
That thing is the basics to aquire the items saved in persistant inventory so fixing it would really help in the long run. As a bandaid "fix" I made the whole thing local for now but...That kinda ruin the "first come first served" aspect of having collectibles that regrows in a multiplayer map
I guess that for the "disable" event I could fire it throught the "Interact" part, sending a networked event whenever someone claims the collectible
Oh i fixed small bug in demo Leaderboard. There is chance Siblingindex can be too high. Add extra check to prevent crash will help it.
Found out prefabs are not adjusted in scale. Might update it because some players who want try it will see weird look like here. (leader board scale was already scaled to 0.005 but slot prefab is still at 1) - just hint to developers.
And positioning issues too. Ugh, not great prefabs.
As Ximmer mentioned, PlayerObjects are not designed to be transferred. They should be locked to the player that they're assigned to. Because of that, there is no intended reason to have OnOwnershipRequest on them, and using it may cause undefined behavior, like what you're experiencing with the fake ownership. Usually the fake ownership is something done on normal objects in order to make ownership transfers work faster (you assume you have ownership before you get the confirmation) but on PlayerObjects that'll just cause bugs because you're never supposed to transfer them. Based on what you described, it sounds like OnOwnershipTransfer makes it use a code path that gets around that restriction somehow. That is definitely something that might get patched and you should not rely on it, if that's what you're asking.
hey if you know about how this work im trying to learn this can you Message me i have question for you if its okay
You're going to have to be more specific about what you're asking. Also, it's less likely to get answers in DMs. Better to just ask your question here, as anybody who may have the answer can reply
So im trying to learn how to set this up to save the players Data Causing im trying to setup a Leveling/xp system so im trying to learn how to do it
What is Persistence?
i have the Xp and level system made its just the saving the data
Soo when they Rejoin all the Playerstats are saved
You're going to have to choose between PlayerObjects or PlayerData. There are some considerations but ultimately:
PlayerData is a simple, easy to use key-value database, like a dictionary. In the SDK's example central, UnlockItems uses PlayerData.
PlayerObjects are a more complex power-user feature, but they allow you to more directly control how you do networking and also gives you per-player objects, which can be useful for lots of things even outside of networking. In the SDK's example central, RPGExample uses PlayerObjects.
If your data is small (<1 KB) and/or doesn't need to be saved very often (>10 second interval) then PlayerData is the right choice.
If your data is larger and/or needs to be saved often, then PlayerObjects are the right choice. However that's not because PlayerObjects simply have higher limits, rather it's because you can split up your fast data and your large data into separate objects, so that they can each do their own separate job better.
Okay i understand that so im trying to learn the player data so the stats are saved in the database but im having trouble setting it up i dont know what things i need too look up
On the page I linked, there is a subpage for playerdata which shows you the functions and events that you can use to work with playerdata
would it be the Simple RPG page i need to look into
since im doing leveling system?
Eh, if that's valuable to you then sure. But it's not a guide, it's just an example. And given that you're interested in PlayerData, it might not be as useful because it primarily uses PlayerObjects. Though if you ever want to add features to your RPG system where players can see some of the systems on other players like a healthbar, PlayerObjects would be the best way to do that and the RPG example will show you how. But even if you use them for that, it doesn't mean you need to use PlayerObjects for persistence as well. You can still keep using PlayerData for saving your persistent data if that's the right choice for your data
so im going for a Fps kinda thing but its abit confusing to me
yeah, PlayerObjects are a great platform for building hit detection systems, per-player guns, healthbars, and other visual things that you need to be able to see attached to other players, or associated with other players like leaderboard entries
Those kinds of things used to be handled by a player pool prefab, but PlayerObjects are a direct replacement that is better. PlayerObjects also have the ability to handle persistence data, but you don't have to use them for that. PlayerData is easier to use for simple, small persistent data
but ultimately it's gonna be hard for anybody to help you if you're not able to express why you're confused
If my answers are not making sense to you it's because I'm guessing what you're asking
Am I missing something here? This is the second (and non master) client.
Whenever I change / add the key for my any non master clients, it calls OnPlayerDataUpdated twice.
When they join we block everthing until OnPlayerRestored(VRCPlayerApi player) has been called for this player. After that, we use the OnPlayerDataUpdated(VRCPlayerApi player, PlayerData.Info[] infos).
So
OnPlayerRestored -> player is ready -> PlayerData.SetBytes("P_V0.1", new byte[5000]); -> OnPlayerDataUpdated is getting called twice (but only, if for the remote player)
This causes some issues for me because I thought it would only be called once
Maybe it's just me and I goofed up but I'm trying to figure this out for hours now
Using 3.7.5 SDK
I'll try it with a new u# script tomorrow 
Were you testing with 2 clients running? I think all clients in the instance will get OnPlayerDataUpdated called; so that all clients will get the new updated playerData of whoever updated theirs.
So thats why 2 logs poped up from that.
Quick question, can I rely on the fact that in Event OnPlayerRestored() the instance master (player.isMaster) is already set even if a group of players enters a new instance at the same time?
I'd say just run a quick test build and see yourself. It probably is, but dont know for sure.
I made a new script- this script contains nothing else than the basic functions and the OnPlayerDataUpdated always gets called twice for the remote player. It's only setting the key once and seems unecessary. We have the same issue in build 
Doesn't matter how many players you have in your instance. It will always be called twice, even if you have like 4 clients active.
Guess it's a bug 
public bool hasRestored = false;
public override void OnPlayerRestored(VRCPlayerApi player)
{
if(player.IsLocal)
hasRestored = true;
}
public override void OnPlayerDataUpdated(VRCPlayerApi player, PlayerData.Info[] infos)
{
NimbusLogger.Log("OnPlayerDataUpdated for playerID: "+ player.playerId);
if(!hasRestored)
{
NimbusLogger.Log("Not restored yet. Returning");
return;
}
foreach (PlayerData.Info info in infos)
{
switch (info.State)
{
case PlayerData.State.Unchanged:
NimbusLogger.Log("key: " + info.Key + " Unchanged - " + player.playerId);
break;
case PlayerData.State.Added:
NimbusLogger.Log("key: " + info.Key + " Added - SKIP - " + player.playerId);
break;
case PlayerData.State.Removed:
NimbusLogger.Log("key: " + info.Key + " Removed - " + player.playerId);
break;
case PlayerData.State.Changed:
NimbusLogger.Log("key: " + info.Key + " Changed - " + player.playerId);
break;
case PlayerData.State.Restored:
NimbusLogger.Log("key: " + info.Key + " RESTORED - SKIP - " + player.playerId);
break;
}
}
}
private void Update()
{
if(Input.GetKeyDown(KeyCode.L) && Networking.LocalPlayer.isLocal)
{
PlayerData.SetBool("MY_TEST_KEY", false);
}
}
That is strange...
Is it ok to add a...
public override void OnPlayerDataUpdated(VRCPlayerApi player, PlayerData.Info[] infos)
{
if(player != Networking.LocalPlayer())
return;
....
at the beginning so that only one call can go through the rest of the code?
That alone will not do it unfortunately
I probably have to implement a counter for every remote player and do the keys x 2 and ensure that it only triggers every keys x 2 OnPlayerDataUpdated 
I need to know if a remote key has been adjusted and I run really tight logic bound to it :x I'll do a bug report and we'll seeee
I mean, it's not might not stop the 2x triggering issue, but a workaround for it to prevent a 2nd one to go through the rest of the code....
unless, you want only the owner to handle some special code during the trigger, then my suggestion wont be good.
yess the owner has to figure out whos calling stuff and call different functions based on that xp
Sounds like a bug
Is the process of creating/instantiating a player object for a new player heavy for other players if the object is large and complex or consists of many objects, or does VRC have some sort of internal handling to make sure new player objects load in smoothly?
More just a curiosity question but Iβm reading up on them now so I can get started with them soon
So im still new to this im trying to Save the data Buts its Having trouble can i get a Bit of support
im stuck on the part of trying to call Players but its breaking it for some reason
im trying to do the PlayerData to Save players Lvl/Xp but its not finding the player
You can't save it for another player. You can only do PlayerData.Set... and this saves it for the local client
PlayerData.Get...(VRCPlayerApi player, DataType data) should return you the data of that player
Are you Free to let me show you my setup and you revise it me and my buddy cant figure it out
post your code where you're trying to save the data
okay so imma have to start it all over But the point of the code is to save players Level Rank And XP
idk how you came to that conclusion. saving playerdata can only be done by the local player, so you'll have to design around that
as in, if the player was in a new instance of your world? yes
okay i have Code made ill show you
So its not saving atm which im trying to figure out right now
Would i just need to add this
This the Code
PlayerPrefs? Isn't it PlayerData?
looks like PlayerPrefs is some abstracted thing built on top of playerdata? You're gonna have to show us the code for playerprefs as well since that's probably the root issue
I'm not sure what PlayerStatsMain is but it doesn't look like it has the same functions that you're calling on PlayerPrefs so I assume it's something different?
I see a few issues off the bat with PlayerStatsMain but it's hard to give any specific recommendations because I don't know what your intention is with it. In particular if you're putting that on a player object then my advice would be different than if it's standalone. But regardless of which way, the OnPlayerJoin function is definitely a problem
Okay now im at the point where the Persistence object is Disabling its self not allowing me to save data
only thing im trying to figure out is Getting the data to save and load on Player Restored
Saving and loading Is the issue im having
got it working
replying to this because it's the one of the few messages in the history remotely related to info I'm trying to find
what's the guideline for adding udonsynced fields to an existing persistent player object?
one of my player object became read-only after I added a field to it, the data can only be viewed but never updated, unless all persistent data of the entire world is reset through the UI (tested this on two accounts), this happened during normal development
keywords: "Serialization failed", "Parameter name: Received a null value when encoding a Boolean" Google.FlatBuffers8
I understand that if serialization fails then the server never receives it so it won't affect the saved data, but that doesn't help when the currently saved data being restored is itself not serializable somehow
null values can't be saved persistently, that's why it's failing
that bool would need to be at least initialized
PlayerObject is such a pain to manage compared to PlayerData..
where exactly "enable persistance" have to be put?
It says "this object and its children" but don't seem to work on the root/parent object...
put it in the same place you added VRCPlayerObject
that's what I did yes
hence my confusion
I placed both in the root object of the template
and then your scripts have UdonSynced variables on them?
have I to place more in every children with a different UdonBehavior?
My graphs do have Sync variables and i request serialisation when i modify them
or is "UdonSynced" something else specific?
for sure I'm missing something here
synced is right, UdonSynced is just how it's done in U#
you could open the ClientSim PlayerObject debugger and see if values are being written
also do the root object have to have an UdonBehavior? or having some only on the chils makes it skip them?
doesn't have to
no unless you expect another script to edit those variables
nah, the "buttons" only send a custom event to tell it to act, but all changes are made internally to the object
found the problem
requestserialisation was just at the wrong spot (which worked for PlayerData but not PlayerObject somehow)
but eh that works now xD
that would do it
I usually add debug logs every time I'm requesting serialization and doing deserialization, at least until I know it's firing properly
I barely understand networking at all, this world is the first one where I even bother with multiplayer... ^^"
but I start to notice patterns in why my stuff didn't work in the past
it takes a bit of experience for the networking stuff to "click"
I made two or three test projects before I fully understood how it worked
also like discussed last time "OnEnable" instead of "OnStart" would help a lot... xD
so many things broke because, well of course it only triggers the first time
the hardest part sometimes is not knowing a node does exist, unless I find it by chance
or a friend with more experience mention it by chance "why you're not using X" moment
it all comes down to amount of experience lol
yup xD
the VRC and Unity docs are a godsend to know what you can and can't do
Unity docs are written with C# in mind but the nodes are essentially the same
that's really helpful when not redirecting to a missing page indeed
joke aside, yeah when you know what area to search at and go link to link it really helps understand the details
I usually just google search "[unity thing] unity docs" or "[VRChat thing] vrchat docs" and pretty much find what I'm looking for
thanks, this looks like that's what's happening, the [UdonSynced] public bool field is initialized to false, but it seems like whatever VRChat does right before OnPlayerRestored is invoked sets the program variable for that player object bool field to null, so it would fail to serialize it on further attempts
this is really confusing
Are you editing that variable on the original? Or the copies?
I don't think PlayerObjects will be safe to access until OnPlayerRestored fires
it's documented that you shouldn't trust any of the persistence until after (or within) OnPlayerRestored
nah I don't edit the variable on the template, only the copies, during or after OnPlayerRestored; I know it is set to false by default because false is the value that correctly syncs the first time a user joins the world if they had no prior data/if they reset their persistence data in the UI and rejoin the world, and it remains false when that new player joins the world again
this is the workaround/fix I chose to apply, it works now:
// Persisted
[UdonSynced] public short lastInitializedVersion;
[UdonSynced] public long firstTimeJoined;
[UdonSynced] public int countJoined;
[UdonSynced] public bool everInstanceOwner;
// ... etc.
public override void OnPlayerRestored(VRCPlayerApi player)
{
if (player != Networking.GetOwner(gameObject)) return;
// HACK: VRChat might restore the persisted program variables of U# primitives to null (currently unknown why,
// but introducing new fields is likely to make this happen), even if the value contained within the program variables
// of those U# primitives had previously been confirmed to be non-null.
// This will cause a "Serialization failed" to occur. VRChat servers will never receive this data, causing the persistent
// data to become read-only.
// For this reason, re-initialize those fields to default values if the program variables backing those fields became null.
if (GetProgramVariable(nameof(lastInitializedVersion)) == null) lastInitializedVersion = 0;
if (GetProgramVariable(nameof(firstTimeJoined)) == null) firstTimeJoined = 0;
if (GetProgramVariable(nameof(countJoined)) == null) countJoined = 0;
if (GetProgramVariable(nameof(everInstanceOwner)) == null) everInstanceOwner = false;
// ... etc.
// ...
}
still unclear whether somehow getting null values restored into newly introduced synced fields is expected behaviour/what's the guideline for introducing new synced fields to persisted player objects
Finally works and changes are shown for both players in real time. Thanks for earlier L.E.G.O.S. 
At the top, is that how you've declared those variables in your real script? Those variables are uninitialized and will evaluate to null
But the bool is false when they join for the first time....in your check for their persistent data, are you setting the bool to false if they have none?
You've got something working but I'm just curious, I occasionally have just the strangest nulls that seem to come out of nowhere
oh and you also don't need to GetProgramVariable in U#, you can just use the variable
Yeah in theory it should be just adding udonsynced fields and it's done. There's even metadata that is used to match up an old serialization to a new script to ensure compatibility (and this code has been in use for over a year when two people in the same instance are on different versions of the world). There seems to be some bugs with that system occasionally and it's definitely got room for improvement. Providing a default value rather than straight null would definitely be a good start, and I assume that's 90% of the issues, but it's hard to quantify so I'm not sure exactly what other issues might exist
finally done for today, pixel-canvas works and can be saved/loaded
I just need to investigate "width*PlayerID" offset because PlayerID always increment even if a player keeps rejoining so...the PlayerObject can end up out of bounds π€£
Also PlayerObjects despawn on player leaving (expected behavior) so I could use it and PlayerID behaviors to my advantage for spawning dummies with the last state where the player's canvas was...Maybe? To display work of everyone who joined the instance, not just active players. (that also means lots of dupes if a player spam-rejoin but meh..)
that's worries for another day, already 1am right now
good day/night everyone
Dummy frame when player leaves, done. (at least the parent object for visual cue of where the canvas will be)
But PlayerObject is confusing to work with compared to PlayerData to retrieve the PlayerObject tile's states to transfer to the dummy's tiles...
With PlayerData I could just say "get this key from player X" and it's done.
Now getting the PlayerObject that's a manager so its variable is the array of Tile objects,
Which then run a FOR to get their "state_current" variable to update the dummy,
BUT on the correct player, not the template...
It's such a pain in the butt in graph, I'm probably missing something again but I don't seem to get it to work because I'm pretty sure it's currently trying to find the template instead of pointing to the correct player...or something stupid like that.
I'll be restarting the dummy updater from scratch, so no graph to show for now, but it's like working with a completely different set of tools even tho it's still persistance.
I don't really understand why they don't just follow the same standard, just one being "PlayerData" set of keys and the other "PlayerObjectData" set of keys. That would be much simpler to call them the same way, PlayerData was a huge welcome QoL on that regard.
sorry for the rant, I just have a hard time understanding this one xD
you can use findcomponentinplayerobject to translate a reference to templates into a reference to specific player's object
if you want key-value data, then just use playerdata? Playerobjects are a different feature, of course it doesn't have the same interface
I'm already using PlayerData for other things and was running out of it, hence the switch. Else I'd be only using PlayerData yeah. ^^"
if that's the case then you can implement your own synced dictionary on a playerobject and then the interface is the same
So i have a question so im running into an issue with my Persistence Not letting my Xp And Level Save When i Take off the Player Object and Enable Persistence The System works but when its on the player object its not working
Can you show what kind of code you're attempting to use?
i can Provide clips of whats its suppose to do and what its doing with Persisence On? if that helps
I'm not certain exactly but here are a few things I notice:
- Keep in mind that everyone in the instance will load and run code on everyone else's playerobjects, so if you have things you want to do only to your own playerobject you need to do
if (Networking.IsOwner(gameObject))in order to filter out so only the owner of it will run that code. - SetOwner does not work on PlayerObjects, their ownership is locked
- Your LoadPlayerData function is just resetting back to defaults.. but only if all the values are already default? But even then, it shouldn't be necessary
okay thank you
im going too make those changes ill let you know if i have anymore issues
If you create a world with 2 persisted variables on a playerobject, and someone plays the world thereby creating the saved data for those 2 variables... and then you update the playerobject so that it has 2 new additional variables that didn't exist before.. how do you handle a player joining a world who already has the old data where there was 2 variables, and now suddenly the playerobject has 4?
I ask because I have a Text UI in the world that displays all the persisted data for a players playerobject.
The old version of the world had 2 booleans that were persisted. So they see 'true, true' in the UI.
But when I added 2 additional bools to be persisted, when players join the new upload, they only see 'true, true' instead of 'true, true, true, true'.
However, if they delete all their data, and rejoin the world, only THEN does the display show 'true, true, true, true'.
I'd expect that for the new variables, if they didn't have it before in their save, the Text UI would at least display 'true, true, false, false', a default value for the new variables. But instead it's acting like the new variables do not even exist.
Is there some kind of check and initilization we are supposed to do in OnPlayerDataRestored() for new variables added, to make sure they are ok for players with old data that didn't have the new variables?
you're probably running into https://vrchat.canny.io/persistence/p/adding-new-udonsynced-fields-to-a-player-object-appears-to-make-saved-data-read
Steps to reproduce (TLDR: bold ): Create a world that has this UdonBehaviour(OurTestObject.cs): https://gist.github.
udon will write null, even for value type variables which is very wrong
π€¦
What in the world, let me read thru the canny and see if it's whats happening for me...
Ok yeah, that seems like it might be the issue
Seems like the best option is to never ever add new keys/variables, and just go with 1 single json string that is persisted... then whenever you need new variables, you just add that to the json.
So basically, anyone who ever uses persistence, should only ever have 1 json string for playerdata persisted, and 1 json string for playerobject persisted... and never add anything else, except to the json string data itself
helps for player data too because it lets you actually remove variables
I've done this before for my playerdata at the advice of bobystar, but now I see it is nessesary for playerobject's too. That's a shame... a LOT more boilerplate code that is more involved than it should be, especially for those who are newer to programming.. and also a hidden gotcha that isn't documented
I mean at least it seems like a bug, dunno when it will be looked at though
Valve Time tm
The issue is that this is a world with creator economy, and the bug breaks the world, so how can we just have people wait around for a bug to maybe, perhaps, might get looked into someday
yeahh
there's also the issue with network IDs being super fragile which can cause you to inadvertently nuke all of the persistent data in the world if you're not careful
Yeah that's why I never add extra undon behaviours to an object that already has one or change the hierarchy between PC/Quest builds
I think you can work around it technically, hai showed an example when they posted about the issue initially. You can do GetProgramVariable on the variable and check if it's null, then reset it if it is
still annoying that it's an issue with how long persistence was worked on
Oh, does that also make it that you can save new data to the persisted variable again?
I think so
it's probably failing to serialize when it saves because it's trying to save null into value type synced variables
Ok, so then I take it I can just do that in OnPlayerDataRestored(), since I already make sure I don't touch the variables until that has run
yeah
π
Someone should write a "good practices" guide at this point if there's not one yet... ^^"
Is this the proper way to make persistent mirror or any type of gameobject toggle? I am asking because I have one issue with it which is that sometimes I have to click it twice to activate one of the states after loading to the world.
Update your isMirrorActive variable when OnPlayerRestored is called
You need to check if the player is local too in restore iirc
Could probably simplify it with try get bool too
The networking documentation claims the max data allowed per serialization is 64,690 bytes when performing manual sync. But the persistence documentation claims the max data that can be stored is 100,000 bytes. How can persistence load anything above the 64k limit of manual sync serialization if you have more than that saved since you are allowed 100k saved data?
Is that data just going to be clipped, will persistence fail, or does persistence just use more than one serialization to cover the whole size but therefor take much longer? Likewise, if it just takes longer because it uses more than one serialization, is it possible that you might read from saved data in a frame where only half the data was retrieved through serialization?
sounds like something fun to test
for your second question, this shouldn't be a possibility because you're meant to use OnPlayerRestored to wait until the data is ready to use right? So no matter how much it is, you're waiting for it to all be ready anyway
OnPlayerRestored is only useful for when the player first joins though, correct? It isn't called every single time there is new data available as far as I am aware.
yeah that's for the start, then OnPlayerDataUpdated would fire while data was changed while they were in the world
I assume this has the same guarantee of OnPlayerRestored that, this event won't fire until all of the data is ready? I mean, it can't provide the Playerdata.Info array without everything being ready, no?
Well OnPlayerDataUpdated makes sense for the last question when working with 100kb of playerdata, but for playerobjects that event doesn't exist, so the question still remains at least for playerobjects. But yeah, I guess it would need to be tested.
while the player is in the world, they aren't going to be loading persistently anymore, they're just using normal networking for PlayerObjects, so maybe OnDeserialization would be the equivalent
Yes, it seems to be the equivelent, which means PlayerObjects are likely limited to the same of manual serialization. Which is why 100kb limit for PlayerObjects doesn't make sense, seems like the limit should match and be lowered to 64kb.
I'm gonna try to test it
you can too lol, no guarantee I'll get meaningful results
I will once I get to that point, at the moment my code is all PlayerData. I'm about to do PlayerObjects, which is why I am asking, so I can get my code right. Once I get further along in PlayerObjects I'll have a setup where I can test, but you may be quicker to get an idea
hm so far interesting find.
people have always asked what the limit for manual syncing is, since the documentation has 2 different values listed, 64690 bytes and 280,496 bytes
so the latter must be correct
afaik the 100k limit is for all saved data while the 64k limit is for a single serialization of 1 behaviour or maybe all behaviours on a gameobject. You would be able to hit the persistence limit by just having several behaviours that serialize close to the 64k limit which add up to >100k total stored.
I'm starting to believe no one knows the limit
~280496 is too high, serialization fails. But I can send it when it is over 64,690
and can confirm this 100,064 does restore persistently successfully for a PlayerObject
oh I forgot persistent data is compressed though so it can probably go a lot higher
so I think it all depends on what you're saving persistently. I just have an array of longs; successfully sends 200,064 bytes in one serialization, on one behavior. This data compresses down to 42,456 bytes saved persistently
numbers are gonna crunch easily
so it seems the 280,496 limit is technically correct. sort of. I guess there's a handful of other data sent that isn't tied to your behavior; I sent exactly 280,480 bytes and it failed, but 280,064 succeeds
but this takes a whopping 35 seconds to arrive
which saves persistently successfully, compressing to 54,726 bytes
so to conclude and answer your original questions, the limit for manual syncing is ~280,000 bytes, not 64kb. This already exceeds the persistence limit of 100k, but persistent data is compressed, so in the majority of cases you can save over 100kb and it will succeeded.
This data was an array of 35,000 longs so..... unless you're planning to save even MORE data than that, I don't think anyone should really be worried about hitting storage limits
Wow nicely done @visual cargo , that's pretty conclusive I'd say. Alright, so that info combined with what @quaint night pointed out, pretty much answers my questions. Thank you so much for the effort
https://github.com/vrchat-community/creator-docs/pull/217/commits/523ec1b0ca8868f183ac33be8fab1a2cb7aaf889
https://github.com/vrchat-community/creator-docs/pull/218/commits/bf973cf7738395987162e078e2fa71619ed4f4a9
https://github.com/vrchat-community/creator-docs/pull/219/commits/4c526fb109dce43a1070d57d214d8e3d1c24c6e6
https://github.com/vrchat-community/creator-docs/pull/220/commits/6c940b0269362b4c520b67e82c1d427d876977f7
Demonstrates data types supported by the PlayerData interface.
Where VRCUrl 
not yet...
So Im having a bit of an issue here, Im working with numerous persistent objects in my game world, the object that triggers is different in editor than the object in Game mode so if you look at the video it explains in greater depth, TLDR I have to incorrectly set the value to X to make it work for testing in the Client simulator and then set it to Y to make it work in game! Anyone got any ideas whats going on?
Im led to belive this is a bug or something :L
momo seems super active tonight.
You can probably use FindComponentInPlayerObjects instead to reference the actual player object you want to get, just give it your objects Transform component
Had a look around but couldn't find such a node
it's in the networking namespace
Oh I see, not sure how I would implement this into the graph though :L
recommend a public transform variable to point to the referenceComponent
Put your playerobject into the transform you made
I assume like this?
Hmm no that made an udon exception
Maybe target needs an input, though ive never used target as of now
you need to get the player obj first so you can put it in the target
how do you get the owner of a player object?
I tried Gameobject, that doesnt seem to work
Use GetPlayerObjects
https://creators.vrchat.com/worlds/udon/persistence/player-object/
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.
This Gameobject[] just seems to be looping back around to the original thing that was causing issues, itl ask for an object ID right? well its not consistent between editor and in game
GameObject[] gameObject = Networking.GetPlayerObjects(Networking.LocalPlayer);
foreach (GameObject go in gameObject)
{
if (go.name.Contains("FootStep"))
{
footStepsMaster = go.GetComponent<FootStepsMaster>();
loaded = true;
}
}
IDK graph but this is an example of how you do it in U#
Hmmm, will have to look into that, having another issue which I kinda thought wouldnt be a problem considering the objects are player persistent objects but basically I fire my own gun and it also makes other peoples's guns start shooting, the gun they use is also synced with the gun im using etc, like the variables are being shared and saved across one another, I thought the idea of persistent objects was supposed to do the opposite :L
Im using networked events in an attempt to make it so that when someone pulls the trigger on their gun bullets come out which works but it makes the bullets come out my gun too lol
You need to check at the start of the events if the player is local
I thought of that but then that wouldnt really fix the issues of the values being changed for like weapon customisation etc
Hmm
I just hate graph. I can't read this.
I would imagine the instance of the sendcustomnetwork event would need to be itself
Actually idk.
Oooof
This is the pattern I normally do w/ local and global stuff
Im the opposite, never could get literate with code, graphs on the other hand :L
There is always time to learn π«‘
Gettin too old now :L Im trying to do all this stuff as a sort of bucket list really :L
One day, I'll actually list my key names instead of going "ConfusingKeyName_WhateverUnusedNumber" then duplicating the base object god knows how many times...
Such a pain to track down now that I want to make an achievement tracker... π€£
Im just in a bit of a dilema here, so my persistent objects in my world while able to sync their position and whatnot dont sync any variables stored inside them, or at least they dont appear to, even basic debug data thats shown via a Text mesh pro such as its position and rotation data doesnt update when I see someones object being used and moved around, can persistent objects even have their values synced at all? It seems very limiting if you cant sync Udon data to other players if not!
playerobject? variables ticked sync? udon behavior set to manual sync? requested serialization?
Dont know what any of that is, I just see synced on variables in the udon graph but it doesnt seem to do anything noticeable
can you show graph plus script in the inspector
The graph is a bit massive to show on one screenshot but I can paste the assembly
1 moment
The example for persistence shows you how to save the 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.
I dont need to know that
So this object is for a player held item that changes into the different items you use in the game using weapon/item ID's
you see it in local view but others dont see what weapon you're using
the assembly is useless sorry to say
Its quite extensive lol, is there something you need to see in particular?
This is the main one, it dictates what the item the player is using, I figured "Synced" had something to do with....... syncing but Im still unaware of what its supposed to do lol
simply marking a variable as Synced does it automatically do all the networking for you. you have to tell it whoβs setting the variable (the owner of the object/script), tell it when to update the variable for all players, and tell it what to do for those other players when the variable is updated. in other words, set someone as the owner of the object, set your variable, request serialization, do what you want with the new value of that variable, in the OnDeserialization event, do the same thing you did with the new value of your variable.
and check in the Inspector where you've put this script, is it set to Continous or Manual sync?
Its continuous
could you just like post the graph file itself
if you use Request Serialization and OnDeserialization in your udon program, you should be using Manual Sync
Ive never used serialisation or deserialisation yet
well, once you do.
hmmm these variables are probably better suited to be manually synced
you're currently updating, I count 6 or more? synced variables every frame
Im trying to understand this in laymens terms, is serialisation the act of syncing variables locally to be transmitted globally?
Whenever you call PlayData sets, it sends ALL the data you have to everyone in the lobby. Atleast I think that's how it works
nvm this is totally diff then what I thought
Im thinking, whenever a player changes their gun it could call a serialisation maybe to then send the data of the Item id, all the customisation option ID's etc
RequestSerialization means "I am the owner and I am ready to send these variables off" and then OnDeserialization means "I have received the packet of data and synced variables are now the latest state".
It's important to understand that the code is running independently on different computers, and the networking is the only thing that communicates between the different clients. All of the code you write in udon is just the instructions for how their client is going to handle inputs and outputs. Networking is the glue that allows different clients to communicate in order for them all to be painting the same picture.
There's a lot of ways to do networking, but the tools provided in VRChat's SDK for networking are best suited to making state-based networking. It's easy to fall into some other pattern that technically works but is sub-optimal because the underlying networking is not built to serve that paradigm well. Certain optimizations on the server side like caching for late joiners only really works if you're writing a state-based system, and those savings can have a big impact.
As a result, when people (like me) say that state-based networking is the "best" thing to do, that's not because other strategies don't exist or are "wrong", it's just because if you follow state-based networking concepts, it will be the path of least resistance as you develop stuff.
So with all that said, here's the basic concept of state-based networking that I would recommend you follow:
- Before you even write code, think about how to express the feature as state or degrees of freedom.
- For example you're making a door. A door is either open or closed, and that is a boolean. Or, if the feature calls for it, the door might be represented as a float so it can rest in any position.
- Or you need everyone to know who is signed up for the next round, so if a single person is controlling that list then it's an int array. If everybody needs to be able to write their own independently, they each get their own playerobject or playerdata entry with a synced bool.
- When you do write the code, separate it into two parts:
- The part of code that works with the data and operates the state. (take ownership, set variables, requestserialization)
- The part of code that applies the state to the world (ondeserialization, onvariablechanged, onplayerdataupdated, set gameobject active based on synced bool, set color based on float, create strings that display text in the world)
I think I sorta understand, Just need to learn how to correctly use request and on serialisation nodes now
I have an idea on how to implement it but we shall see if it works :3
Here's a video with some specific U# examples and more theory about why to do state-based networking https://youtu.be/mNYt11OPXhI?si=A3vkstFNrk95rd2U
In this TLX Spring 2021 session, FairlySadPanda explains how to use the UNU update to make networked games and content that doesn't suck or break.
Check out other talks from the TLX Spring 2021 event at https://www.youtube.com/playlist?list=PLTgqlzYxsEMxiUvVaqBcD5OL9MpdmkiSH
Learn more about Prefabs TLX at https://tlx.dev
Follow TLX on Twitte...
Nice, thanks!
So I set this up, whenever the local player changes their weapon it requests a serialisation, then proceeds to further tell everyone that a new weapon is being displayed, would this be a correct way to do this?
dont SendCustomNetworkEvent the RequestSerialization, you only want the Owner to run that (they're the only one that can)
Oh i see ok
My response to that is this video
and do whatever needs to be done with the variables on OnDeserialization; and I think you want to call that second event, the item one, OnPostSerialization if it and only if serilization was successful? I'm still a bit sloppy with network stuff
remember that the sender doesn't run OnDeserialization. so any setting you do with the variable still needs to be done, but also have things getting done on OnDesrialization (so it runs for everyone)
oh and it doesn't need to be a CustomNetworkEvent, just a regular CustomEvent
the event itself may arrive before the variables (the serialization) does, so things won't get set right
