#udon-networking
1 messages · Page 25 of 1
- Even if you sync the transform of your elevator manual sync at 10 Hz, it shouldnt take up too much bandwidth... just check the Bps of the elevator in game
- Or just use VRC Object Sync...
adding rate limiting yourself is a good idea
the elevator itself isn’t synced, but the pos/rot of players is because I need to update the station they’re in 😛
I’ll add rate limiting to my notes 🙏
Might be a good break to take between the complex tasks I’ve left myself to struggle with 😵💫
i seem to remember you were using stations on an elevator that lowers? you can get away with a pretty low update rate if you're parenting the station to the elevator, but idk how your setup looks
is the stations just so they dont clip into the floor?
if you tie the Y of the station to the elevator you will achieve that
but you won't be able to jump while riding the elevator
an alternate solution would be move the entire world, rather than moving the players
Hm I have a moving object with a sphere collider that should hit a trigger collider setup with a script like that, but it does not react if the sphere bumps into the script, what should fire true if the name equals what I search for. I hate code. Why is it not working? :0
OnTriggerEnter is a different event
I bite whoever invented Udon x33
wait until you find out the collider shapes of local and remote players
I bite all
its truly boggles minds
ok still not working when use OnTriggerEnter. I will just animate the whole crap now instead of making it modularly and reusable. I want to finish at some point and not find out one lil thing a whole day~ so doin all by hand now instead of automatized
make sure your moving object has a rigidbody
thankies, that was the solution it seems...then...I can fidget everything back and indeed do it that way. I need holidays x_x It's not called OnTriggerWithARigidBodyColliderEnter! xD
haha, one of the things you just gotta know. Many like that in unity, but it's a real fun time once you have a good collection of those fun facts under your belt ^^
Yeah and until then I whine myself into a puddle of despair ^^"
oh and while you're here, you might want to know another bit of trivia - rigidbodies and colliders behave best when you move them during FixedUpdate. It gives them time to run through their physics all the way before rendering. You can move rigidbodies on update, but that's outside the physics loop so it can result in things phasing through other things or not calculating collisions correctly
In scripts, this is "private void FixedUpdate()" and in animators, this is "Simulate physics" - both achieve the same thing of moving stuff during the physics loop, which makes physics objects they are moving behave much better
arigatou sensei UxU
isn't it just a capsule?
For the local player it is
Remote players have a sphere collider at their "root" (between the feet right under the hips bone in the default pose)
They also have a capsule collider, but thats only for raycasting for selecting the player
can i be loading 2 urls at once if i waited through 5 second delay
2 urls with 5 seconds between them yes
For the RequestSerialization command, does it sync up variables that are not tagged as UdonSynced or is it specifically for variables with that tag?
specifically UdonSynced variables
Thanks! That helps a lot figuring some of the examples out 😅
i have a question:
i do have a gameobject that is animated (Transform) Position and Rotation.
how can i make it sync to all players? the object it on different positions for all players somehow even when using the Object Sync Script from udon.
Would be cool if someone can ping me with a solution/tips or documenation ive read the VRC Documentation already.
@sterile goblet if it has vrc sync component and noone can control its state then it should be plenty
it has VRC Object Sync
and its everything but not synced
I have an Flying object that moves it Position and everytime someone is joining the Object is on its start position on their screen.
The state cant be controlled.
It is just an Flying Object
This is literally everything
wait, its moving how
Animator
well ye, then everyone plays their own animator
How should i animated/move it then?
In Normal Unity Games i work on i can make a normal sync script for animated Objects but udonSharp is so hardly limited
ive used this
if you dont mind it be just a random point of animation but being looped for everyone then this
its desync so heavily
ye we have tried this Component already but the Desync is too big
It is kinda sad that we cant sync animations in 2025... you can do this since ages in Unity by default
It would help so much if VRC had an actual Github to contribute and open pull request
in the JoinZone example, what does [SerializeField] protected UdonBehaviour[] targets; do? I know SerializeField makes it available in the Inspector, but I assume the rest just grabs all the players in the instance?
utc isn't going to align well enough across different devices for that, if you want something accurate you can send the progress of the animation periodically and work out the time since the packet was sent
in your case if you want something simple using object sync, disable the animator component for remote players, enable it if they become the owner at some point
void Start()
{
animator.enabled = Networking.IsOwner(gameObject);
}
public override OnOwnershipTransferred()
{
animator.enabled = Networking.IsOwner(gameObject);
}
I did exactly this
The delay is still to high
you worked out the animation time based on the packet's age?
or do you mean object sync
i did disabled the animator for others and only the Owner had it.
and transfered it to a different person once the previous owner leaves the instance
theres a delay of around 3-5 seconds
Also when you open the Ingame (Big) Menu you stop syncing with the movable object.
in one of my own worlds i do something like this, but adjust the speed multiplier of the animation instead so it doesn't result in any snapping if there's discrepancy beyond the initial packet
That will most likely make the object sync well with other players but if you have an platform on that moveable object it will start to desync you
and i sadly found out this is a limitation of UdonSharp and nothing u can fix at your end
i'm not sure what you mean by platform, like you're using something to keep the player on a moving platform?
Yep
the best way to approach that is with stations if you want to see players in the correct location relative to the platform, but using stations has trade-offs and you'll have to handle the relative sync yourself
Thats what we want to avoid
im aware of it
but we want to fully avoid stations
Ive checked UdonSharp now and it is not possible to make iot perfectly sync without some adjustment on the core
have you considered moving the world instead of the platform? might not be applicable to your case though
i can only open a feature request and hope they will look into my example C# Code
Yes but this is also not a good idea for what we do
We do a Meme world with many places to visit its not a good idea to move the world :D
Ive thought about all the ideas you provide to me (i appreciate ur effort) i made sure to think of every single possible way before i did even ask here
Im very good in C# and ive checked UdonSharp it is too limited to make it possible to sync Players on movable platforms
the reason for this is because Positions of players are local and not synced. Theres no sync for Collisions, Physics, Platforms in VRC
In short: A Player on a Movable Platform saves the position Local and not Global for other players. Once you leave the platform you are synced again. the only way to be in "sync" (fake sync) with other players is you have to be on the same position of the platform at the same exact time
this is almost impossible to do in a efficient way
i will just contribute to VRC and open up a feature request with an example C# Code and hope they will implement it
Does anyone know why UdonBehaviour[] targets is used (in the JoinZone example) to send custom events instead of using the networked event?
Or is this largely because of DataList not being able to be sent through the network event?
(Also a bit confused with how this is working when it comes to getting all players in the instance)
I'll paste a snippet of the code if it helps:
public class PlayerJoinZone : UdonSharpBehaviour
{
[SerializeField]
protected UdonBehaviour[] targets;
protected void SendEventToAllTargets(string eventName)
{
foreach (var target in targets)
{
target.SendCustomEvent(eventName);
}
}
public virtual void OnPlayersChanged()
{
// Only the owner should run this logic
if (!Networking.IsOwner(gameObject)) return;
// Propagate Player changes to everyone
foreach (var target in targets)
{
target.SetProgramVariable(nameof(Players), Players);
}
// Players is a Datalist, which does not trigger OnVariableChanged events, so we propagate its event manually
SendEventToAllTargets(nameof(OnPlayersChanged));
}
it looks like the intent might be to allow creators to add their own graph programs to the targets array, which if they had a corresponding datalist Players and event OnPlayersChanged, the program could listen for changes to the player zone and react
Oh shoot, you're right x_x. I guess it doesn't really get all players but tries to get Udon graphs as you mentioned
Thank you so much ❤️
The canonical way to sync stuff is just using GetServerTimeInSeconds() (or GetNetworkDateTime())
Same logic as video players syncing their playback times
Or more precisely GetServerTimeInSeconds() + Stats.RoundTripTime() * 0.5
If you use local device UTC to sync it is going to vary unpredictably because someone's device clock can be off by any amount
not sure why you're referring to using the rtt in that way, the server isn't passing you any relevant info that you'd be intaking/using relative to a timestamp in that way. if it's from another user it's delayed by their one way trip time plus the server trip time to you; that's not rtt/2
You can locally override the position of remote players though?
You can not Sync or override the position of a Player while it is on a Platform.
Yes you can :)
do it and proof me wrong 👍 never seen it in vrchat
Good show me :p
I want a non-desync demo im ready to get educated
Let
Me see :)
Ok what now?
Where is the platform
Want me to give you a little tour?
I just want to know where the platform is that moves by an animator where two players can stand on without a desync
thats a world that spins around the object
let me join and I'll show you
But not a platform
lol
i want to see an Unity example where you clearly change the transform rotation and position by using an animator controller
players can be outside and fly space ships, and if you look at the spawn area from the outside you see the players in the correct position in the spawn (on the ring)
Because they are on stations.
I think you're misunderstanding
I think you are misunderstanding me.
Kitkat,
Show me a Plane that moves Up and Down, Left and Right and It Rotates by using an Animation Controller. Let 4 People Join that Test World with that Test Plane and let them Jump on the plane and walk on it.
UdonSharp is limited here and it will Desync between all Players this is a common and known limitation of VRChat.
Then you are a super hero and the only person world wide with a solution.
Sure, if you think so
But Far Citizen literally has this implemented.
I use a modified version of my floating origin package https://github.com/KitKat4191/JetSim-FloatingOrigin
I think you missunderstand about what i talk
The only way a floating origin would be possible in VRChat is if you could locally override the position of remote players.
If you want to rotate the local player then that's a different question entirely.
If you only want to stick to a moving platform that translates and doesn't rotate on any other axis except the y axis then that's possible.
I talk about platforms that change YXZ Axis and Position.
And about players that jump on that platform to different times.
Do you want to rotate the local player, yes or no
Of corse.
Why do you want to rotate the local player
Because it is a mechanic i want to add?
Can you be any more specific?
Do i need to be specific? If the platform rotates the player rotates with it.
You can rotate remote players upside down, but not the local player.
What i want to do is simply not possible with Udon. i dont even need to discuss this
Have you considered that you might not know everything in the entire world?
Ive read the UdonSharp Documentation i am not an idiot.
There is a lot of undocumented behavior in VRChat...
You don't need to be an idiot to not know the answer to everything.
It's okay to ask!
Thanks, ive read enough to know that it does not work i dont need any further help i appreciate ur try to help.
- Players walking inside the moving platform
- Players seen on the platform from outside
The stutter is simply from the rotation source of the ring being a bit wonky.
You don't have to be sitting or stuck to "be" in a station.
You can use station mobility Mobile to still use the default VRChat player controller and locomotion while still technically being "seated"
This allows you to reposition remote players so they're in the correct position relative to the platform they are on.
Circumventing the "desync" you would normally observe from players being synchronized in world-space
Is it possible to randomly assign roles to a list of players through a DataList that has player tokens or is there a different way to do this?
Currently I also have player objects assigned so im not sure if there's also a workaround through that but im not sure x_x (also didn't want to interrupt the other convo, my bad)
Basically trying to achieve something similar to that of prison escape, murder, among us, etc. With the random role assignment
Is it possible
Yes
Are you asking how it would be best to synchronize the "roles"?
I guess, though initially I was trying to find a way to communicate to each player's player object to send info about their role but I feel like that might be a stretch.
Though I haven't figured a way to randomise or shuffle the DataList either 😦
You can do pretty much anything you want in Udon, it's Turing complete.
Well im using udon sharp, yeah
you wouldn't really want to use so much data on syncing an array or trying to sync a list. its better to find out how many roles you have. then create an Enum for it and make that Enum of type byte. that allows you have roles and such at minimum cost.
The ServerTimeInSeconds you have is what the server have when it sends the number, therefore when you receive it, it would have been behind by your RTT / 2, so to infer the "real time server time", you add RTT / 2 to get it
But you don't have to, ServerTimeInSeconds has been good for video players and people arent complaining
It is not peer-to-peer, the RTT is between you and the server
Other people get their ServerTimeInSeconds individually from the server
has anyone tested how many continous sync variables we can have per person before it starts suffering?
Well there's mainly 4 roles but they're distributed across participants (e.g. 4 scientists, 5 guards, 6 bystanders, etc). The enum method sounds like a good approach though, but figuring out how the player is assigned that role has been the tough part for me
Unless I can add the names of the players as a ref for the enum which the sync would be manual to avoid any high amounts of data too
Are you really claiming that VRC is making a webrequest every single time you call Networking.GetServerTimeInSeconds()?
And that it's not corrected for time in flight?
how else would you get the time? it has to request it from the server.
Yes, of course it has to request it once when you join the instance. Not every single time you call it.
well then you wouldn't get accurate data on the server time through. unless it has a internal call that happens every so often to update it.
yes`? and are you lol?
so say you have 16 people in a world. and you want each person to be synced. you would have to have each person have a copy of another persons object with their components etc. and only the owner can reqeuest a serialize.
i don't know where you're getting this from, but it isn't correct
and if you then click a button for instance that says Sync. it would Request serialize for that local person and sync everyone else @fast pulsar and all they would have to do is when this happen is make sure to run whatever you need to run locally. and update your copy of their object with that data.
photon docs describes how it is get
if you know a correct source that says otherwise can you share?
you mean this? "all clients should have the same value"
have you considered the rtt stat you're touting has only been exposed in the last update? that people have used a shared timespace for years?
like it's just misinformation to say it's not being offset up front to tie it to the server's picture
Then you should know that they can be ✨*synchronized✨ with each other!
If you didn't know that then I'm glad I could help!
Yeah. I have a Player Object set up where all players get a copy of a game object that stores all the different roles which are hidden unless they are given the role and are in the game. Making sure the randomisation is done on the Owner side is definitely a must though so that way everything else like teleporting, showing the role n stuff is all done locally. Mainly the randomisation part will be tough unless I can use the enum for that which I could see the enum storing the roles and a randomised order of numbers while respecting the number of players participating. Then I can run the players through that so in a way its randomised without directly trying to shuffle the List itself. The players would take that enum data in once then read off the role they were picked from that
i don't know what you're suggesting as the alternative, if you believe it awaits an async call during your code execution i don't know what to say
well
that is not the call being made by udon
Yes, and Networking.GetServerTimeInSeconds() is not async!
You get the number immediately!
even if you randomize it that shouldn't be an issue. other people dont need to know that info. all they need to know is what role that user got. since the roles are fixed. so for instance say you have Admin, User, Guest. Admin = 0, User = 1, Guest = 2. that means no matter which one a user gets other people would know by whatever number it is.
well udon video players use ServerTimeInSeconds to sync afaik so you can use that
True, I understand that, its just moreso making sure people have different roles in each game
The roles are randomised like prison escape, murder n stuff as well. My bad x_x
yea you can still do that. everytime a game ends the owner shuffles the list. then ensures everyone else gets the update by requesting a serialize.
and the number indicates what seed it used.
you can just have a byte that is synced. that way you have up to 255 possible different seeds.
😮 a seed!!! That actually would work perfectly
just make sure you actually sync the seed. not the list or any of that. also make sure its manual since it doesn't change often.
that way you atleast can make sure its always the same.
and then you could run some verification to ensure its the same for all people
Ye
and use System.Random rather than UnityEngine.Random! for encapsulation
https://booth.dev/blog/2021/02/26/the-problem-with-unityengine-random
Does Random work for U#?
it should, i remember seeing the nodes in udon graph
but as the article suggest you may need to write using Random = System.Random;
Alright 👍
Yes, I'm using it for a few things, exactly how Uzer Tekton said
also I'm making a local RNG variable with new System.Random(); in Start()
wouldn't Networking.GetServerTimeInSeconds() be calculated as server time at world entry + current real time since startup - real time since startup at world entry?
how they actually track the offset is more like photon internal or not something i've seen personally, but i'd expect it to be realtime relative. there's likely an initial passing of 3 or 4 timestamps starting from the client; you can read about NTP if you want to see what they'd likely be doing, but it allows you to acquire an offset to the target clock and the round trip time
Well to clarify, I assume at world join, behind the scenes, with whatever method the desire to use, they link real time since startup to the server time, and GetServerTimeInSeconds() is based off that link using the difference in real time since startup
i don't know that it'd be realtimesincestartup exactly, it's certainly possible, but yeah the polling is likely comparing a cached offset to some time source when you call it
GetServerTimeInSeconds() and GetNetworkDateTime() jumps in whole milliseconds (there is no sub-millisecond resolution) whereas real time since startup gives sub-millisecond numbers, but it does seem to be cached only at instance entry, but prolly not calculated from real time since startup because of the millisecond jumps
I guess VRChat simply reads the PhotonNetwork.Time from their plugin and reports it
But only does FetchServerTimestamp() once at entry
However if you calculate it manually with real time since startup, naturally it should line up
at the lowest level both photon plugin and unity has to use the computer clock to keep track, so it's effectively the same
(although changing the computer clock with the game open won't actually change the numbers, cannot hack time)
Hahaha hackerman!
Idea: you might be able to make something equivalent to VRCObjectSync using a combo of a manual sync object with a continuous sync child
the manual sync contains the a bool saying if the continuous sync child is active or not; if active, enable to continuous sync and use the values it gives you; else disable the continuous sync object, save its last value in the manual sync object, and use that value instead
imo we shouldnt have to manually do all these gymnastics to "fix" vrchat's continuous sync, if it just goes to sleep when not needed...
I suspect VRCObjectSync is itself a manual and continuous behavior stitched. I'm pretty sure a continuous behavior by itself would not be able to support late join sync if not constantly outputting data
kinda - from what I understand, vrcobjectsync is a secret third thing, and that's why it's not as easy as it sounds to just "add a toggle to pause continuous"
At the very least, more networking tools does seem nice if it is at all possible
That's kinda what I've been suspecting because there is no built-in interpolation mode for Quaternion in Continuous sync.
"secret third thing" makes it sounds like they map to these three options in PUN for Object Synchronization:
- Reliable Delta Compressed - manual?
- Unreliable - continuous?
- Unreliable OnChange - vrcobjectsync?
that's pun 2, i don't think it's directly applicable
Of course this is pure guessing but this might give insight into how they do what they claim (or rumored) to do...
- manual sync:
- reliable
- "guaranteed to be received with an internal optimization mechanism which sends null if data doesn't change"
- continuous:
- unreliable
- "received in order but some updates may be lost"
- "this means no delay in cases of loss"
- vrcobjectsync:
- unreliable but sleeps when no updates
- "received in order but some updates may be lost"
- "If updates repeat the last information, the PhotonView will pause sending updates until the next change."
"guaranteed to be received with an internal optimization mechanism which sends null if data doesn't change"
?
i don't totally understand the point of pure speculation, this comes across as some sort of authority/fact sheet to a casual viewer and if you don't actually know, this type of thing can be pretty misleading. i feel the need to correct misinformation that's relevant to what i've worked on and it probably detracts from this channel's purpose or deters people from posting about actual issues
like certainly discussion about networking is good, but i'm not sure i really understand the point of this
it's okay, you don't need to understand, you always accuse something you don't understand as misleading misinformation
this is misleading, it's literally misinformation, i don't know how you can be okay with that
That's quite condescending.
hey i am just trying to talk about how udon networking works, if you know something we don't, feel free to contribute
speculation has value - it can be used as the basis for building a hypothesis and tests needed to validate positive or negative. It does not need to be interpreted as a claim of 100% truth
Wow.
It only has value if the speculation changes based on the observations that have been made. The scientific method and all that.
"guaranteed to be received with an internal optimization mechanism which sends null if data doesn't change"
I'm pretty sure is incorrect but it doesn't mean that it's invalid as speculation
and it was clearly presented as such
i don't think prefacing with "this is just a theory, but i think this is how it works" leading into posts that read like a comprehensive overview; like that doesn't give a blanket pass and this is definitely something casual observers will pick up on and use if they have no concept of it otherwise
nice official confirmations, at least now we know
Theories need to be comprehensive in order for tests to be comprehensive. Being comprehensive doesn't make a theory any more "claiming to be true"
i dont remember where i read about manual has internal optimizations, so i thought that was what it meant
maybe it was just about hitching
that's a perfectly valid stance to take on something that is strictly a question-answer forum, but it becomes overly restrictive in a conversational research context. This channel has ended up being a bit of both and it's important to recognize which is which
"I'm pretty sure is incorrect" is not official confirmation though. Official confirmation would be running tests to verify behavior or looking at source code
allowing one speculation to override another speculation is just arguing without any connection to the truth. Same for standing behind a speculation that has been disproven with test results
yeah ok just tested manual serializing the same data does not really shave off any Bps
so that is that
actually, why not? isn't this opportunity for optimizations?
what you're describing is delta sync, which is not implemented yet. It's on the backlog, will get to at some point if we can find time for
oh great
in the meantime we can actually Udon this
newer versions of PUN have delta sync, but vrchat is based on an older version and has a ton of custom stuff built on top of it
generally, if you assume that photon documentation applies to vrchat you're going to run into a lot of misleading info
doing it with udon naively will break late join sync because the relay server stores the most recent serialization and will repeat that to late joiners when they arrive. If your recent serialization is only a collection of the data that changed, they will not have the full picture
if you do it on separate objects so that one object stores a complete state and then another object stores a smaller set of changes since that complete state, that can work. But you need to be careful to link them together with definitive hashing, not blindly throw data around
ok but what if I do a basic if (newvalue != oldvalue) RequestSerialization(); so it only syncs if there is an actual change in value? i mean not even going into the delta stuff
if my goal is just to decrease throughput
regardless of how you detect it - yeah, doing requestserialization only when your data changes is a good practice and if you are intentionally relying on triggering it even when it hasn't changed, I would certainly question why
good point... because some suggests manual sync at a certain Hz can update more frequently than continuous, so it is like one way people are manual syncing
e.g. manual sync at 10 Hz vs continuous seems to be capped at 4 Hz or so?
and it is more "reliable"
Continuous sync rate depends on if it's on a held pickup or not. For VR players it will be higher than for desktop players.
yeah, continuous sync is a very mild, slow system. It has a lot of compensation to allow for there to be many continuous objects. It's not surprising that manual can go faster, but the important thing to keep in mind is that if you set manual objects to go faster, you won't be able to have as many of them
At the same send count the total bytes out can be lower with Manual than with Continuous. #world-persistence message
Worth a note here for how that works, which might use useful for context. Request specifically means to add to the serialization scheduler (I don't know if there's a specific scheduler here, but can be thought of like this) and the serialization will occur when it comes up. The frequency that an object's schedule will come up is a combo of overall data rate and per-object data rate - the data sent being a result of serialization of all behaviours on a given gameobject combined, as RequestSerialization() operates on a per-gameobject level, not a per-script level (which is why you can't mix manual and continuous sync).
You can go well above 10hz if you are syncing near to no data.
The unknown here that I haven't tested, and thus should be taken with a grain of salt, is exactly how the serialization size of one object affects the rate of others - if it's global, or if individual objects update rates can be slowed down. I do know that objects have an actual serialization rate - this can be seen using the standard VRC debug overlays. Players have em too, to sync IK and expressions.
I mean it is global
but if serializing a lot of data on one slows down only that one object, or all objects - i.e. if scheduling an object knows to call a given object less frequently with respect to other objects vs just slowing it down in general and crossing your fingers.
wow that's not at all what I would expect given that continuous uses 8 bit serialization and manual uses 32 bit serialization. This might depend on the specific variables you're syncing?
This is interesting - wasn't this shown to be different at some point? Centauri wrote a whole thing on it.
or, well... manual still uses 8 bit if it's below a threshold. That could be part of it
I wonder what the deal is here
Centauri's analysis was bit off.
Was there info about this difference posted somewhere? I'd live to take a peek
i expect the overhead is more likely on the net object being continuous, it probably sends some superfluous data that's only used in continuous, the size stat differs from the actual output; it was just an 8 byte variable when i tested that
Happyrobot's serialization size calculator is the closest thing we have at the moment. It's linked in the most recent pinned message.
Happy's still says that less data is sent in continuous, so I'm kinda curious.
it's possible the new network stats are wrong, but they align with the debug menu
The "best case" / "worst case" is due to the variables not being sorted by type, which means that more header has to be wasted when the variables happen to be in an unfortunate order.
I don't know if this is fixed yet, but I sure hope so.
y e a h
I was watching the networking video you posted since am still fairly new to networking 😅 but I assume when you do RequestSerialization, for everyone else that isn't the Owner it automatically fires OnDeserialization?
(Also that video you posted I feel has saved so much headache when I was looking for similar videos on YouTube, so thank you ❤️ )
When you do a RequestSerialization. Everyone fires the onDeserialize. Apart from the owner. The owner gets onPostSerialiazation.
Gotcha. I haven't normally seen OnPostSerialization around in some code but im guessing that counts when code runs after the RequestSerialization is called?
Indeed. Only for the one requesting it
It's used for doing things you want to happen after a serialize
So you run owner and remote in seperate methods
I use post heavily for sync
Wouldn't it technically be higher on manual? Considering the combined cost? Of having it confirm that others have received the data?
I see. That makes a lot of sense then given the nature of it being called Post yeah
is it good practice to have a condition to check for isOwner before doing anything in a block of code that's going to run the request?
Well. For the purpose of debug yes. Since you can use it to check if someone is trying to run it if they shouldn't etc
Yea
I remember back in Arma 3 I've had to use that condition a couple of times just to make sure it's running on the Owner's side but at the same time it probably might be fine if the code is being executed by the owner in the first place
A big limitation with manual is that you're hard limited to about 30-50 scene-wide manual syncs per second; makes it terrible if you want to use it for syncing the position of several erratically moving objects.
Meanwhile continious does not have that limitation. As far as I know you have no limit to the amount of scene-wide continuous syncs you can have per second, the only limitation being the kb/s out limitation.
Absolutely, I completely agree. Continuous can definitely be better in specific situations like with your ai sync.
Well technically it's using whatever the "secret third thing" VRCObjectSync is using, lol.
I do need to test my manual/continuous hybrid idea.
Well I ran into a snag where it doesn't seem to be possible to send a DataList of player data into JSON because of the Reference Data Tokens included in it (mainly to sync it with others). 😦 is there a workaround for this?
nvm I decided to pull the playerId instead as a string then had it convert to an array later on.
yah if you want to sync VRCPlayerAPIs, you need to do it via playerId
I mean, if you're insane you could sync the display name instead
But what if my name is a 5-byte-wide emoji
If I can't fit my family of 3 dads, 6 children and a dog, all of different races and nationalities, as a single character of my name, what's even the point of having a name
My name must look like the back window of my car - with those family stickers missing their heads and a sticker with a really long and overly verbose thing about who I am that somehow involves my husband who is in the army who is the third cousin removed of some other rich guy
I actually tried that first and learnt very quickly not to do that again xD
I wonder though, unrelated, but is it possible to hide a person's avatar from everyone else (with exceptions for some players) and then have a character model in the world be puppeteered by that player and seen by those that can't see the original avatar.
Or is this potentially too much information to send over the network? 😅
Basically one team sees the player as a monster while the monster team can see eachother normally
I think that's doable
If you want to sync display name you need to do it by the player id you get through vrcplayerapi. You can just get each joining player and store them locally for each player. And if you want to later find them all you need to do is get them by Id.
Ye I did it by playerid and it's working now
Yes we can?
I'll have to tinker around with what might be possible then since I really want to try and make it possible to keep things spooky for the other team without breaking character. Even thought about maybe immobilizing it unless there's a different way
Like, why are you even responding with a "we can't really" when you clearly have no idea?
Just don't say anything at that point. Avoid spreading misinformation for one second.
I'm assuming you already know about desynchronized stations?
Negatory but now I'm curious on what it does 👀
ohhhhhhhhhhhhhhhhhhhhhhhhhhhh
I just had a quick glace at the docs and now im excited XD
So, if you ensure that every player gets their own VRCStation, and that they're always "in" that station you can change the Station Mobility locally
You should use Player Objects for this
Right, and I assume the desync station would make it so you can move around outside of the avatar?
set the station mobility to Mobile locally, and then to Immobilize on a remote player's station if you want to desynchronize their position.
Lloool I never spread mis information
You literally just did.
here's an example of desynced stations, this is the same physical space in this example (not teleportation); there was a good post in udon-showoff that has a similar concept
Nope
I assume position as in the avatar while others can still see the player as an invisible entity?
(that would explain how some of the mmd worlds work now that I think about it)
The entire player will be moved
their voice source too
which might be problematic for your use case
you can use a station animator though
Yeah
That's not really hiding then lol. So you are incorrect once again kitkat
Well if it's just putting the avatar somewhere on the map safe that's fine enough in terms of hiding tbh
I basically just want to have the player appear as a monster towards other players pretty much
What you can do is you can put their player far away but still keep their relative direction correct
and locally increase their voice distances
So once again misinformation kit Kat lol
would that make it so the voice is attached to say the puppet character maybe?
yes
here's another example that makes it a bit more apparent, i like this one personally
Now im hyped
thanks @cold laurel ❤️
Once I get it working I'll be sure to post the result into #udon-showoff 😄
Awesome! I can't wait to see it
It's someone moving in a station?
beyond a certain distance the player is effectively culled, from a remote perspective
unrelated to avatar culling though (like the vrc setting)
Well yeah. That's just culling doing it's work
it's not, it's custom culling
Looks like the default one ngl.
you mean the one that turns into a diamond?
Yea
i made that orange thing myself, that's the representation i made
If anything if that happens I could always have the avatar just hover above outside the ceiling so that way the player isn't going to be culled like that 😂
I don't know kit Kat you have a trend to spread incorrect info
i'm literally moving the player behind the remote player from the remote perspective, i implemented it myself, i don't know that telling me it doesn't work that way is relevant
We have access through udon to unity culling? Or is it some sort of custom shader
Yeah I know you are
the conversation they're having is mostly related to desynced stations, Hostile
the examples occala is showing is using those
it's not the VRChat culling settings, they're using desynced stations to mimic it
Oh yeah I was starting to notice the odd station stuff.
I wonder if VRc consider it a unintended bug that allows you to do so
no it's not a bug..... it's literally a function available to stations
though by some contexts I'd consider stations as a whole an unintended bug.
real
Wait really why?
I jest a bit. mostly a stab at stations just being really jank in general
one of these days they'll get a rework, I hope
Oh because of how messy they can be in general?
yeah
working with stations you kinda have to go into it prepared that they have some very odd, illogical behaviours that you just have ta roll with
Ngl never really looked much into stations. The last time I really saw them used was easy back with the horror maps series. Huggies horror series it was called I believe
@twin portal would you per chance know what kind of problems can happen if you disable a object that has a continuous sync on it. And then reenable it say 2 seconds later?
not specifically, when you disable a gameobject with an udon script on it, some things still run, some things don't
If my script involves disabling objects, I usually put the script on a different object than the one that gets disabled in order to avoid any headaches that come with disabling it
I think from my latest projects, you can still set variables and I think call custom events, but Unity events like Start and Update won't run
those were manual sync objects, but I wouldn't be surprised if a disabled continuous object would no longer send updates
no wait, I think someone was troubleshooting some NPC enemies with continuous sync the other day, and they still try and send data when disabled?
Hmm. Is it known how reliable continuous is? Like say with more people it gets worse or something ? For instance if I want to have a controlled say 40 bytes synced. But I do not want the cost of manual and its reliable guarantee. I wonder if it actually is possible to have it just do the continuous sync for say 30 frames or so and then disable it
To have a wierd psudo continuous controlled sync.
that feels like something that probably won't work that way, but is worth testing
Yea. All through that does mean every user need to validate changes and ensure the object gets disabled locally for every other person.
But then again how would they know to activate it again.
Have some sort of a dirty flag system for each person?
that's the thing I was talking about where some things still run, some don't
I'm pretty sure you can call a custom event on a disabled object that enables itself
but as you can probably tell even at this point that it can get troublesome to manage. What works and what doesn't? I dunno; so it's generally not worth pursuing, there's easier ways
Yeeaa
what you'd need to test if the object still sends data even if it's disabled
That is true it's something I never really considered since I was always using manual
but you can't have a script that say, increments a number on Update and set that number to continous sync. Update will stop running when the object is disabled
Yea
It's just that I really want to avoid the cost that manual had for everyone in the world
I would hedge to bet that continuous sync sends acknowledgements as well, so you'll likely still see that bandwidth increase as player number increases
I just need to test it. Put simply.
Gonna get 48 people and get them to do 3 different worlds with no udon. With cont and one with manual just to see the baseline cost for no udon and then for cont and manual
it doesn't from what I've seen
makes VRCObjectSync pretty good for high update rate positional syncing is an environment where networking is a concern
Inside PlayerObjects on Start, is the Networking owner guaranteed to be set on Start?
or what event should I check in when the player the object was instantiated for is the owner?
Oh yes it is. I didn't read the docs :(
Regarding Ownership. Is it always best to change ownership on things like doors to people that interact with them or is it not necessary?
it is better to take ownership because you can immediately change the bool and sync it, instead of sending an event to owner and waiting for the owner to sync a new bool... less perceived lag
unless your door is susceptible to multiple people trying to spam it open and close then maybe you want to make a cooldown timer or just send event to owner to prevent weird desync visuals
Right, yeah
I guess that's good to know to ensure not everything is on the Owner. Thanks!
only the owner can sync the bool state, but you can send an event to tell the owner to change the bool state and sync it, rather than taking ownership
so yeah you dont have to be the owner if you just tell the owner to do the thing
An ideal system involves as little ownership transfers as possible. There are situations where ownership transfer is necessary and there are situations where implementation speed is more important than optimal implementations, but in general you should try to avoid ownership transfers.
I would recommend seeing if you can make a system where when a player opens a door, they open it locally and then report to the door's owner that the door has been opened so said owner can sync the state to everyone else.
Ye. I think i did something similar where I have a proxy script that tells the main code to change ownership before its executed.
So messing around with Events With Parameters, it appears that they're just manual sync in disguise minus the values being saved on the VRC server (and thus not synced with late joiners). They have identical networking performance to manual sync and appear to behave in an eventually reliable way where only the latest event is guaranteed to arrive, no intermediate events. Thus they should be treated as eventually reliable variables rather than just events.
For example if you send an event multiple times with different variables of A, B, C, and D (in that order), if the receiver gets the events in the order of A, B, D, and then C, then C will be dropped and never occur.
This would match the docs saying that events will play in order. If C arrives late then then still having the C event occur would result in the events playing out of order.
To clarify my example involves not changing ownership and avoiding all the race conditions that could cause.
Yep
I'm guessing since the VRC servers don't have to worrying about storing Event data, VRC allows you to use them in a way less restricted than normal manual sync
Actually, I haven't actually tested it. What is the highest hz that events can be sent out? If you send out events at 100hz, do they start to get batch sent out?
i tested at 60 Hz and it appears to be working and that was capped by my frame rate, because i was doing send delayed custom event with 1 frame
so i assume it can go up to 100 Hz
manual sync highest was about 15 Hz in my test, but it wasnt the absolute smallest data
I know that the time it takes an event to go from sender to receiver is notably longer than the ping time, although I suspect a big part of that is events getting artificially delayed on the receiving side to help reduce the occurrences of out-of-order events getting dropped.
Ah, thats fair
Well mine changes the ownership anyway and just tested it with a few friends who live on the opposite side of the world and so far everything works almost instantly for everyone so all is good now. Thanks though ❤️
C would actually be played in this example, D wouldn't play until C had played
How often was that through? I often see manual dip to 5 Hz. With just a 40 byte serialize 2 times a min
it was continuous non stop
if it dips that means it is throttled for some reason, but 2 times a minute is not even any Hz, that is like 0.033 Hz so idk...
I don't know if it also has an impact with the amount of people doing it?
yes, the receiver also generates output traffic, but as long as you (one client) is below your bandwidth limit then it shouldnt throttle, maybe it has to do with grouping and delay
How would it know about the existence of C?
i don't know exactly what photon does, but it'd be typical to include an identifier for the packet, if a client receives things out of sequence they'd be aware of it
this is applicable to manual sync as well
I still kinda wanna try to find out how much data on average each person adds to the output
Well doing some tests using clumsy to simulate awful internet and testing while the world was under heavy network load, I was not able to observe any dropped events.
What I have observed must have been caused by something else in my code (most likely), or is a bug with VRC's networking that only occures under very specific conditions (hopefully not)
I think I found my issue. Turns out you should perform state changes on the owner's side immediately instead of OnPreSerialization. If you wait to do it in OnPreSerialization it can create race conditions that can allow bad code to make illegal state changes that bypass your normal safeguards.
Hey yall, so im having a problem where things work in unity but not in vrc and i think it might be networking stuff.
I have a candle script thats supposed to make it turn on when a collider enters it. however, the thing that holds the collider is instanced per player, i think thats what might be making it mess up in vrc, but im not sure how to go about fixing it.
https://streamable.com/abgrkl
https://streamable.com/b1vuwj
i have the scripts if anyone has time to take a look
How else are you supposed to detect if something successfully sent and reserve state changes that you potentially can't undo?
You mean if I end up losing intermediate states doing this? My system is designed so the receiver of the sync doesn't need those intermediate states, only the latest state
Well im not so worried about intermediary states either, just you saying this makes it sound like what was recommended isn't actually good to do.
Like lets say a user interacts with something and sets a state of something and that state is a bit critical to not have any difference between clients like some animation or something else that would be really hard to undo should VRC actually not send off that data to other clients
to be clear, the owner checks if a state change is valid and only changes/syncs that new state if it is
I mean like here's what I got from you saying that and what I'm trying to get across:
[UdonSynced] private bool State = false;
private bool localState = false;
private override void OnDeserialization()
{
if (State != localState) // protect against initial state syncs
{
localState = State;
_DoFlip(); // do it for clients' end
}
}
private override void OnPostSerialization(SerializationResult result)
{
if (result.success)
{
localState = State;
_DoFlip(); // Would be safe but you're suggesting against?
}
}
private void _DoFlip()
{
// trigger something
}
private void _Flip()
{
State = !State;
RequestSerialization();
_DoFlip(); // is what you're suggesting we should do?
}
Just a crude example
My issue is I have the owner check if an attempted state change is actually legal before performing it; and that fails to correctly check stuff if there is a gap between the state change check and the state actually being applied when there are a couple or more rapid state changes in a row
[UdonSynced] private int interactCounter;
public override Interact()
{
RequestSerialization();
}
private override void OnPreSerialization()
{
interactCounter++;
}
my example is pretty poor and not how you'd want to do this probably, but this would be something where pre or post could fail to capture an increment
post wouldn't be as applicable here anyway, but someone might consider using pre for this
pre is still a good case for something you want as close to the send of the packet as possible, like a shared timestamp; or if you want to have your own packet identifiers per send
What caused issues in my case
private void SetState(MonsterState state)
{
if (!Networking.IsOwner(gameObject))
{
LogWarning("Attempted to set state as non owner!");
return;
}
if (!IsLegalStateChange(State, state))
{
LogWarning("Ignoring invalid state change attempt");
return;
}
_stateInt = (int)state;
RequestSerialization();
}
public override void OnPreSerialization()
{
State = (MonsterState)_stateInt;
}
Note that State, when a value is set to it, it automatically runs through the state machine transition code
Waiting for onpostserialization to apply a variable is reasonable - you're adding unnecessary latency but it's not gonna break anything. And if there's a possibility of it failing to serialize (such as with user input values that aren't always serializable) then you can check for that before applying what could be invalid data
But waiting for onpostserialization to change the data? No, that's backwards.
Waiting for onpreserialization to change data is slightly better and technically valid, but I'd only recommend it for "packing up data before sending" not something like "increment a number in response to user input"
i think for applying data, people probably do it to keep parity between owner and remote logic, like pre or post owner side, deserialize remote side
they do actually behave differently if you override ownership request; the requested serialization will only fire when you're granted ownership over net. it does not fire if you're denied ownership; the two events can be treated as a confirmation of ownership to some extent in that specific case, but it comes with other issues (the request flag isn't cleared if you're denied ownership, if you were to gain ownership through other means, like the original owner leaving, it'd end up firing the serialize at that point)
I noticed that with OnPlayerRestored the reference to the player is not valid when using it in the method, even though on the docs it says it's safe to access the data. 🤔
is this because that the player is still joining or is this some kind of voodoo thing going on x_x
restored fires for every player when you join and for subsequent joiners
if the script is not synced (like a scene script), you'd usually just check if the player being restored is the local player and branch into your logic there
if it's like a player object, you'd additionally check if it's owned by yourself
https://creators.vrchat.com/worlds/udon/graph/event-nodes#onplayerrestored
The crazy thing is that I did do that check but it somehow failed with no reference to the player
Mainly checks like isValid and isOwner
I did do a build and test with 2 clients so idk if I wasn't supposed to run it that way
you probably want utilities.isvalid, not isValid on the player
public override OnPlayerRestored(VRCPlayerApi player)
{
if (!Utilities.IsValid(player) || !player.isLocal) return;
// Do stuff here
}
Ye thats what I did
Does RequestSerialization only work for manually synced stuff?
could be that, but also colliders on pickups are treated differently sometimes when theyre held
its a pain to figure out
You mean the VRCPlayerApi player in OnPlayerRestored(player)? That should be valid. The only situation where I could imagine it not being valid is if the player leaves on the same frame that Restored fires for them, but it's likely that Restored won't even fire in that situation.
Heyyyy my friends how do I sync a material across a network? I want a synced material to be updated
you sync an int number that represents the material
it turns out that syncing like that wasn't my solution unfortunately, however I have now encountered an issue of documented code not existing. Specifically https://creators.vrchat.com/worlds/udon/networking/events#sending-events-with-parameters
Is there a new version im not aware of or is the documentation wrong somehow?
Network events allow simple one-way network communication between your scripts. When a script executes a network event, it executes the event once for the target players currently in the instance.
event with params are relatively new, but i still dont think you can sync materials with them
youll still need to go by an int
Ye I have a system to sync network syncable stuff, I just can't find docs for the params
step zero, get ownership, step one, you set param value on the owner, step two you propagate it via request serialization, step three you apply it locally, nothing changed. send network event, be it with parsms or not is good only for smth that lasts a few seconds top, learn proper manual/serialization based sync
quick question, if you set a player as the owner of object A, will all its children also be owned by that player automatically?
Object A
- Object B
- Object C
- Object D
makes sense
Note that if you ask for the owner of an object which doesn't have networking, it will go up and find the first parent which does have networking. Not because the children have "transferred", but just because of that upward search
Uhhhhh curious, but when it comes to testing persistence data between 2 clients. Is it better to build and publish the world to test this?
Every time I do a build and test it keeps logging me into the same accounts so I just get a bunch of errors in unity that I just don't understand x_x
It may be to do with the fact each time I build and test it loads up 2 identical accounts since there was no error when I loaded 1 client
I haven't extensively tried out persistence data in build & test, but at the very least OnPlayerRestored(player) works correctly in build & test
on one client yeah
and two or more
it doesnt work if you are using identical accounts
well it does work I guess but if you are checking data to make a new player in a list then it would freak out
You mean the way Test & Build works by default? I've never had it fail to work while using multiple identical accounts.
oh by default build and test works perfectly fine. Its just with Persistence data it'll freak out with errors when trying to handle data on identical accounts since you cant really launch 2 separate accounts without building and publishing it
if you want do a build & test using different accounts use the VRC Quick Launcher bundled with the VCC
Every day you learn something new x_x . I had no idea that existed, so I'll give that a try then 👀. Thanks ❤️
Quick Launcher my beloved
👀
I have 2 stations in my world and I want the local player to use the station as normal but go into the other station for remote players. Its a vehicle that is only supposed to exist once but the station is being hogged by other players and not updating remote users. This setup will result in players floating without a model to go with it but it would keep the main object for everyone locally where it should be. Im not sure if this is possible but maybe someone could help?
this is a desync situation, you know, because both players will be local player in their point of view, they will both sit in the station 1.....
wait, why would you want local always at the front?
and do you want just 1 remote player to sit, or any number of remote can sit in and hidden away in the back? what is the intention here?
there should only be 1 vehicle and there are script that reference the 'main' local one. I dont want othr players to mess with the main one as there should only be that one. The extra one is just to sync the other player's position so they dont just stay in one spot
you will need a vrc player object so that a new station is assigned for every player
and have your button/interact to sit your player to the station that they own
and move/not move the station based on if the player is remote/local
so any amount of remote players can enter and stay at the back, each have their own station
and... you dont need any sync station positions, because you specifically dont want it to sync, because everyone will sync they are in front
I want remote players to show where they actually are.
basically
player1 gets in a car and thy drive off, player2's car doesn't move and player1 floats off to player2
so you need multiple cars, one car per player, you can do this with a the car being vrc player object
oki, I attached the player object prefab to the station and it didnt seem to do anything. Pretty much everything in the world has syncing disabled, do I need to enable it for the station, im guessing yes but im not sure
put it on the car, since it is the whole car you want duplicated for each player
the car contains the station so it will be duplicated as well
and you then just sync the car
oki so spawning one with vrc player object on it should give everyone their own when they load a level and spawn one
yes but you dont spawn it manually, it is done automatically
All objects in the world besides the level geometry and level collision are instantiated at runtime, and can be unloaded and loaded at any point and its only set up to spawn one. Its ok if it gives one to someone else but if its relying on being in the scene when building its cooked
you can just disable the object at start or something
It wouldnt be that many objects to deal with but Im maybe thinking of trying to have a station prefab that locks on to the car so only 1 station ends up being required per person and not dealing with several copies of the car with its references and turning off all but the one the current player owns at turning one in use on
not sure how viable a station linking setup would be
ill just have to try it and see how it behaves tho tbh
ill try when I wake up
So for some reason my player doesn't exist for OnPlayerRestored, any idea why this is happening? 🤔
My code for OnPlayerJoin and OnPlayerRestored looks like this
public override void OnPlayerRestored(VRCPlayerApi player)
{
if (!Networking.IsOwner(gameObject)) return;
playerListLocal = playerData.SetupPlayer(player, playerListLocal);
gameManager.SyncData(playerListLocal);
player.Immobilize(false);
OnPlayersChanged();
}
public override void OnPlayerJoined(VRCPlayerApi player)
{
if (!Networking.IsOwner(gameObject)) return;
player.Immobilize(true);
}
you can only immobilize the local player
oh...
So it only works for Networking.LocalPlayer.isLocal?
I had no idea immobilize wasn't able to be executed remotely, dang...
well the only reason im doing it is to immobilize the player until their data is loaded, so idk where to put that code 😦
um but you did check if owner is local, so it should kinda work
is this a player object?
OnPlayerRestored is listening out for synced data from a player object, yeah
wait I see what you mean
🤦♂️
a vrc player objcet?
uhhh I should have worded it a bit better after realising what you meant my bad XD, but the code you're seeing is in an object not owned by the player object, but the code is listening out for a vrc player object yeah.
I might have to wrap a condition to check if the user is local, then run the owner code separately
this was apparently the issue I didn't check for 🤦♂️, looks like almost everything is working now. Thank you ❤️
Udon is not able to instantiate objects and give them network IDs, so if you expect your things to have network IDs then it's not going to work.
VRCPlayerObjects are unique in that their automated setup process does assign network IDs during instantiation.
If you attempt to manually instantiate VRCPlayerObjects, you would be interrupting their process and they would not get network IDs.
However, if you don't need networking then there's not much of a reason to use VRCPlayerObjects for this purpose.
Or, if you do need some object to pass networking through before routing it back out to your instantiated objects, then you can use VRCPlayerObjects for that. But you'd have to be careful to ensure that the VRCPlayerObject is not disabled or instantiated manually, because that will break it.
So uh, how does one determine why network is clogged?
network becomes clogged when suffering reaches >100
suffering reaches >100 when there is too many queued messages
there is too many queued messages when you are sending above around 9 KB/s for your serializations and it throttles and tries to stay below this amount
(suffering starts building up from 0 when serialization throughout goes near or above 0.5)
(which is nominally around 9 KB/s)
Is there some tool or something that would tell me what scripts are responsible for this or what those stats are?
the debug menu 6 in-game shows you which object is sending how many Bps and you can monitor from there
It seems to only be clogged at the very start, but I am also getting debug messages saying OnDrop is delayed so I dunno
being clogged on start isn't usually a problem you need to worry about. Is there a specific bug that you're seeing that makes you think it would be?
if you have a bunch of VRCObjectSync objects in the world, they will all be on at the start and can clog it for a bit before their sleep mode activates
suffering can also be caused by sending too many events or manual syncs at once
Which method would be better to minimize the network stress (assuming their count is fixed, and uses Manual Sync)?
All the Vector3 variables are targeted to be updated very slowly, and the purpose of them being synced is just to at least make late joiner see the variable changes.
- Combine them into Vector3[] respectively in OnPreSerialization and unpack them in OnDeserialization
- Mark every Vector3 variables [UdonSynced] (with respective FieldChangeCallback setup)
you'd probably have to measure the difference between an array and individual vars, my instinct would say that individual vars may be cheaper on bandwidth, but if it's easier to do an array logically i think you should use an array. objects send all of their synced vars when you serialize, regardless of which you've changed, so it's not like making them individual vars will really save you that much unless they do some bundling up to specific fixed sizes on arrays
for your idea on point 2, i think FieldChangeCallback may still fire on any deserialize, regardless of whether it actually changes, but that's something you'd have to test (i don't know if they check for equality, but in terms of deserialization it would likely be assigned again)
using the deserialization event is typically better practice than using the field change for synced vars
My goodness, I thought using Manual sync would only send the changed variables when RequestSerialization is called. Couldn't find mentions on the docs so I pretty much assumed it would, because the network debug (Rshift + tilde + 6) reported lower Bps than the size. That's one knowledge added to my head, thanks a lot. You may have saved one's life and one's project as well. lol
Well, based on your explanation (and also my own measurement of how those Vector3 variables would be in use), I may have to go with PlayerData instead of any of two methods I listed. They will always be in a fixed count, and does not update frequently (happens when player avatar has changed or something like that), I think it fits the criteria.
Bps will only show a somewhat accurate picture of the package size if you were to serialize every second (bytes per second)
Size, or TotalBytes (divided by number of sends) would get you a more accurate picture of the data use
https://creators.vrchat.com/worlds/udon/networking/network-stats
if you're tying some position or offset to a specific player and need it for every player, PlayerObjects would be pretty applicable, PlayerData could probably be used too
Oh that was why the Bps number was always lower than the size, that makes sense now.
And yes, I've already using PlayerObject but only used PlayerData to save the settings data. Now I've digged further and realized that PlayerData is also synced between players via separate Player Data Object [n], so I would use this instead to let my PlayerObject's synced variable size as small as possible and make PlayerData do the job. Thanks again.
When an owner of a manual sync object uses the "RequestSterilization" event, every synced variable on that object, regardless of if it has changed or not, will get synced
Manual sync will not sync outside that, however
Requeststerilization 💀
i have multiple objects each running the same script (toggles for a door) and need to sync a single boolean between all of them, is there a way i could go about doing this for the several distinct objects so that they all are updated with the current state of the boolean when one button is hit?
(i keep having issues with it desyncing but havent been able to figure out exactly why)
heres what i currently have running on each button
Are you saying one boolean decides the state of all doors, so whether they are closed or open? I'd just create something like a manager class where I sync the bool. Then I'd have an array of udon references which I call SendCustomEvent on.
i think in this case, its multiple buttons affecting one door, so multiple buttons each have their own boolean to affect the door
everything works fine when only one toggle exists per door, but for the big gate, i have multiple toggle points
and so occasionally the syncing for new joiners gets messed up
Ahh, then you'd do the inverse. You make one udon to control the interact, but the actual toggle and sync logic is in the manager udon.
i see, thats a good idea, how would i send the info to the manager to open / close the door?
You can call SendCustomEvent on other objects. Simply create a variable of type UdonBehavior
i see, like this?
First udon behaviour: Create a variable of type UdonBehaviour. Then do Interact > SendCustomEvent, but like in your screenshot you put the new variable into the instance socket.
Second udon behaviour: Drag the game object this is on into the first udon behaviour game object (newVariable in your screenshot). Then do the sync code you did initally, but without the interact. Use a custom event instead of interact.
i see! thank you so much for the help
Yeah, I hope that was clear enough.
It's surprisingly difficult to explain everything sufficiently after doing it so many times, it becomes automatic.
no no i think thats a great explanation, i was unaware that was even a possibility
basically making the buttons dumb and just sending the event to a central manager
Yep, the principle of "single source of truth" is really useful in networking to avoid desyncing issues.
yea in your central door script you want to set local as owner before touching and syncing the bool,
and to avoid multiple people spamming different buttons trying to steal ownership you can put in a cooldown to refuse ownership request for like a 1 or 2 seconds or something
it seems to work wonderfully, thanks for the help
This is probably a dumb question but do playerObjects have problems with reparenting? Trying to get it to rigidly stick to another object and its mostly fine until a collision happens and then its off a bit. Attempted to reparent but I dont think it likes this
XD Position is syncing but its deffinitely not moving with it and almost moves as if its scaling the movement
make sure you're parenting to something with a scale of 1 on each axis, i can't really tell otherwise without seeing the code. i don't know of any specific issues with reparenting playerobjects (though it's not something i typically see and am unsure if its networking would be happy with that)
unsure if its networking would be happy with that
I am also trying to parent to a local only object. This is a really niche usecase but this is the most viable setup for my project I can think of
it looks like it's an issue on local (right hand client)? presumably their own player object station? i think it's more likely an issue with how you're parenting or the hierarchy, the networking is kind of beside the point if the local portion isn't working properly
Previous best attempt was moving it in fixed update with the only problem being collision desync for a frame
if you want to keep it stuck on the vehicle like that, do it in a late loop that happens every frame (LateUpdate for example)
i feel like parenting should work in this case too, but i don't know enough about the setup
scene:
StationPlayerObject
Floor
racer
armature (rigidbody component)
renderer and stuff
bone
Seat (Link StationPlayerObject here rigidly)
racer, armature, renderer and bone are all scaled to one?
ah, racer is scaled to 0.5
i don't know if that'd affect it, but mixing scales down a hierarchy can make parenting messy
0.5 on a base object doesn't seem like it'd break the parenting to me, so idk
pretty sure ive already tried the late update setup, with it working perfectly in editor and all over the place in game
ill do it again tho
do you have the relevant portion for where you're parenting and setting the position? or could you describe if it's event-driven or every frame?
heres about what it looks like
script on Seat object:
userStation (StationPlayerObject)
OnStationEntered(VRCPLayerApi player){
if(player.isLocal){
userStation.transform.parent = transform.parent;
userStation.transform.position = transform.position;
userStation.transform.rotation = transform.rotation;
userStation.UseStation(player);
}
}
just a basic reparent and set to the exact same spot
position sync is handled by the objectSync component on the userStation
that's being done because you want a station per player, but one vehicle, right?
If a vehicle has a station on it directly it works locally but if they are a remote player they are frozen in place and hog it from other players even though they aren't supposed to be linked
are you certain they're entering their specific station? i'm also not sure what the intent is with the stations moving behind the vehicle before anyone has entered them, but i'd assume intentional
just me trying out object variable sync, also gives easy confirmation that the station is working for remotes. These will eventually turn their renderer off and stash themselves somewhere waiting for the player to enter a station.
when becoming smaller so I can more easily see the station sphere I can see that the station is linked rigidly now but the station only appears to move me using fixedUpdate
does the station happen to have a rigidbody on it?
the thing you were initially showing with the station lagging behind can actually happen with rigidbodies parented under other rigidbodies if the child has any interp settings enabled
no, I had interpolation on for that last test so il make sure its off but im pretty sure the same thing happens with it off since I only recently enabled it
interpolation off actually make it worse
and it's kinematic?
kinematic on station or vehicle?
vehicle no
the station specifically
no rigidbody at all on the station
just setting on fixedupdate causes it to lag behind a frame, and accounting for that frame to lock it in place ends up desyncing it on colision
you could try setting the execution order later than the vehicle, above the class itself
[DefaultExecutionOrder(100)] // After vehicle
public class StationScript : UdonSharpBehaviour
{
tried 100 and -50, didnt seem to change anything
ima try it on the seat script itself rather than the racer script so its lower than the armature where the rigidbody is
postLateUpdate is locking the station perfectly but now the player is lagging behind XD this shouldnt be this hard
lagging behind locally? 🤔
also is this using the station multiple times? or is that not the station enter override event (like maybe this is the call you make upon interact, but it sorta reads like you're using the station upon entering the station which would be an odd loop)
stations seem to move the player during fixedUpdate, which lags behind since it seems to be before the rigidbody physics calculations, so ig im required to move the station ahead by the current velocity and just deal with the 1 frame desync during large velocity changes
There is a station on the local object that then calles to the userobject station, its basically a swap for the synced one.
how is the car moved? in script in update?
if the station is reparented then you shouldnt have to move it yourself
why your car has bone? does it do animation?
Script does movement calculations in fixedUpdate, see video with me trying to reparent it a few dozen messages above.
Yes it will eventually have animations
so, i'm making an ownership system for my tycoon thing, and networking is hard.
i have an Owner Gate, which will by default have a isTrigger collider, and a preset message. when a player enters the trigger, it will tell my tycoonManager to set the owner as the player that entered (the local player), and update the message to include that user's name. then, it also changes an IF statement to make it so, if the player that enters the trigger isn't the tycoonManager's Owner, isTrigger gets disabed, so that they can't walk through.
after a player walks through the trigger and becomes the owner for the first time, i also set a bool to false, so that any subsequent players will not trigger the code to set themselves as the owner, until that bool is set back to true from the tycoonManager (which will happen when that tycoon's owner leaves, because if the tycoon owner leaves their progress will unload, meaning any new-joiners will then have a tycoon to claim)
then, in my tycoonManager, i have a few things set-up, which i'm just not sure about. first of all, i set the tycoonOwner to be the master by default, so that it isn't null, and i can check in other scripts to see if the player is the master, since it means the tycoon technically won't have an owner.
then, i made a getter/setter for changing the owner, so that instead of running SetOwner from my owner gate, i can have the getter/setter change the tycoonOwner variable and the actualy Networking owner at the same time.
i have a few other things in my tycoon system that i want to make sure the tycoonOwner is the owner of, so i made SetOwnership, and i run that before doing any logic i figured would need an owner for. i originally thought about sending an event to all scripts that need to have an owner set , when the tycoon owner changes, but i don't think i can do that without having the manager have references to all those scripts, which is just not viable.
CheckOwnership is just there for a specific other script, so that it can only run code past an IF statement if the owner of the object is the tycoonOwner.
OnOwnershipTransferred is something i don't really know much about. but what i'm wanting to do with it, is if the ownership changes (like from the master to a player that touched the owner gate), update the tycoonOwner variable, and run UpdateCurrencyOwnership so that the new tycoon owner is also the owner of the object pool for this tycoon (it's not a VRCObjectPool, it's my custom one). and if the tycoonOwner leaves, i added OnPlayerLeft to try and check for that, but i feel like i could get that information from OnOwnershipTransferred too?
also, i don't even know if ownership over my custom object pool is necessary, because none of the objects are actually synced. that's why i made my own, because i didn't need or want the objects synced. but something i noticed, is that when one of those objects enters my Collector's trigger (which runs tycoonManager.Money += (value)) the Money display would not update for both clients i was testing with. so i figured i would need to set the tycoon owner as owner of the currency objects and the collector, since i already have the FieldChangeCallback and UdonSynced on the Money variable
i just realized that canInteract on my OwnerGate should be synced, so i at least know i can fix that
also, if someone reads this and doesn't know what i'm asking for, i just want to have someone know what i'm trying to do right now and make sure i'm not doing anything wrong or strangely. my OnOwnershipTransferred method i don't even know will work, so i'm also asking if that will do what i said i wanted it to do
and, do i need to set oenership over my objectpool objects if they don't have any networking on them
local objects dont need to mess with ownership, ownership is for syncing, no sync, no ownership needed
OnOwnershipTransferred
This event is triggered for everyone in the instance when an objects ownership is changed, and includes the PlayerObject for the new owner.
https://creators.vrchat.com/worlds/udon/networking/network-components/
okay, so i changed OwnerGate to have this:
[UdonSynced] private bool canInteract = true;
private void UpdateOwner() {
Networking.SetOwner(localPlayer, this.gameObject);
tycoonManager.Owner = localPlayer;
canInteract = false;
RequestSerialization();
UpdateDisplay();
}
public void ResetCanInteract() { canInteract = true; RequestSerialization(); }
and i removed the logic for setting ownership of the currencyManagers child objects from TycconManager
i did read the docs on that event, it's just a little confusing for me on how to use it right now
if the ownership of the object is manually set, and then that person it was manually set to leaves the instance, does the ownership transferred return the instance master again?
the docs just says automatically assign new owner, and avoid relying on master
so probably no
yeah.. so then who does it automatically set the owner to if the previously selected owner leaves?
you will have to check for the owner again with GetOwner()
the owner will be Networking.GetOwner(gameObject)
it does automatically transfer if the owner leaves, yes, you can't rely on it alone in the way you're wanting to use it
so maybe i don't need to use OnOwnershipTransferred for this.. cause what i want is that, at first the master owns everything, of course, and tycoonOwner is set to the master. then, when a player steps in the owner gate to claim a tycoon, i want to set that player as the owner of the tycoonManager, and as the tycoonOwner variable. and if that person leaves, i want the tycoonOwner to be master again, and to run some other code when they left
yep, as i was saying just that
but you dont want to rely on master, what if the master is afk or is sleeping, i mean the instance master is not a special player or anything, what if the master is playing the game as another player
if the tycoon owner is the master, it means the tycoon will have nothing in it. the tycoon owner being the master will just be like a default state, where nothing is loaded on that plot and the only thing there is the gate to allow someone to claim it and become the owner again. that's why i want to know when the selected owner leaves, so i can unload the enabled objects and basically reset that plot. the player's progress will be saved in playerdata so they can load it in again next time, so i won'thave to worry about progress being lost
i'm not trying to rely on master for anything, just using it as the default state for tycoonOwner basically
...what if the master wants to play the game
what if the master leaves and some random person became the new master
ah, i see.. but then what do i use as a default state for the tycoonOwner?
since an object can't have no one as an owner
it'd be more typical to sync the owner player id on it (the player who has claimed it would sync their id on it), if someone walks into the trigger they can infer if that player actually exists by using vrcplayerapi.getplayerbyid, if they're not in the session you can safely take it over, you'd usually use -1 to denote no active player despite it having an owner inherently
just keep it as is if you are not syncing anything
i'm syncing objects that get unlocked by unlock buttons, and the Money variable i the tycoonManager
so mainly, those are what need to have an owner
but i also have a couple things that use network events, so i use my SetOwner method to control that
anything i need to use the tycoonOwner for, i have those reference the tycoonManager and check the owner through that, so that i can handle the changing of the owner in the one place
that sounds like it makes sense
network ownership should not be part of your game mechanic, just use playerapi or player[1] or something so it is a certain player that has joined the game
network ownership is just for syncing variable
i can see that now, since i realize master shouldn't be the default state
what i should be doing is what occala said, and storing an ID for who owns the tycoon, and when that changes, set whoever the new owner is as the owner of the things that need syncing
also i just tested in playmode and i'm getting a bunch of random errors that i wasnt getting before, so now i'll have tofix that too.. i didn't even change some of these scripts
Hey folks, so the variable isnt getting synced. Anyone know why?
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
using VRC.Udon.Common.Interfaces;
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class DiscardPile : UdonSharpBehaviour
{
public HarpyUnoGame game;
[UdonSynced] private string _currentCard;
void Start()
{
}
private void OnTriggerStay(Collider other)
{
if (!other.gameObject.name.Contains("Card")) return;
if (other.gameObject.GetComponent<VRC_Pickup>().IsHeld) return;
Networking.SetOwner(Networking.LocalPlayer, gameObject); // Networking.GetOwner(other.gameObject)
_currentCard = other.gameObject.GetComponent<Card>().ToString();
game.AddToDiscard(other.gameObject);
RequestSerialization();
SendCustomNetworkEvent(NetworkEventTarget.All, nameof(UpdateDisplayedCard));
}
public void UpdateDisplayedCard()
{
Debug.Log("Updated card to " + _currentCard);
GetComponent<Renderer>().material = game.GetCard(_currentCard).GetComponent<Renderer>().material;
}
}
Ok so now it's like updating very inconsistently
You might want something more like this? Usually don't need network events if you use synced variables.
private void OnTriggerStay()
{
// ...
Networking.SetOwner(Networking.LocalPlayer, gameObject);
_currentCard = other.gameObject.GetComponent<Card>().ToString();
game.AddToDiscard(other.gameObject);
RequestSerialization();
// Update card for the local player
SendCustomEvent(nameof(UpdateDisplayedCard));
}
private void OnDeserialization()
{
// Update card for remote players
SendCustomEvent(nameof(UpdateDisplayedCard));
}
public void UpdateDisplayedCard()
{
Debug.Log("Updated card to " + _currentCard);
GetComponent<Renderer>().material = game.GetCard(_currentCard).GetComponent<Renderer>().material;
}
Also you may want to move game.AddToDiscard(other.gameObject); to inside UpdateDisplayCard(). But it depends how it works, so I'm not sure
Ooooo good shout mate
this worked for me, thanks so much! If I may ask for future reference, whats the different between SendCustomEvent and SendCustomNetworkEvent? And when should I use either?
SendCustomEvent only sends to the local player. You can run the same bit of code from many other places using SendCustomEvent. So it's mostly for code organizational purposes.
SendCustomNetworkEvent sends to other players, so it's actual networking. However one limitation is that players who join an instance don't receive past network events.
so SCE is saying "Make Everyone run this function" and SCNE is "Run this function on everyone"?
No, SendCustomEvent is "make only me run this function"
so does everyone in that instance get the contents of OnTriggerStay called?
oh, yes OnTriggerStay is run separately for everyone.
It's kind of funky because OnTrigger and OnPlayerTrigger aren't themselves doing any kind of syncing/networking. But because player movements are synced, then each player's game is detecting the trigger being entered/exited separately.
I see, this is very odd to me haha
It's understandable. I actually often forget triggers behave a bit differently.
What us the upper limit of an object pool and how many vrcobject pools can we have?
vrcobjectpool doesn't have an inherent limit either way, but it does use bandwidth and will flow into the standard global bandwidth limitations. Fortunately, it does pack the data pretty tight - afaik, it uses bit packing to put 8 toggles in a single state. So you'd be able to get away with pretty large ones in that regard.
I consider the cost of the objectpool to be more like a small overhead - the thing that really matters is the cost of the objects inside.
Do continous udon scripts and vrcobject sync go into "sleep" mode? So they take themselves out of the overhead?
Yah, the "UpdateDisplayedCard" event is likely to arrive before the manual sync occurres. The value was getting synched, but the display update was happening at the wrong time.
Only VRCObjectSync can sleep. Continuous udon scripts never sleep (and I suspect if they did, they would lack late join support)
Actually I think there is an exception. If you put a VRCObjectSync and a Continuous udon script on the same object, when the VRCObjectSync goes to sleep it seems to also sleep the Continuous udon script.
I would, however, generally disadvise putting a script that uses any networking (including events) on the same object as a VRCObjectSync as it results in undocumented behavior. In my case I found having a script that used networking events on a VRCObjectSync object caused network suffering in situations it otherwise wouldn't if the networking event script was a child instead.
👀 wait... wut? VRCObjectSync, a component that comes with the VRC SDK.... is incompatable with UdonBehaviour Continous Sync.... Another component that comes with VRC SDK?!
not incompatible - there's a specific exception made for objectsync and continuous to work together. That exception does fully work, but it also was made before manual sync.
When manual sync was made, it ended up being just so significantly better that there are very few cases where you'd want to do it the old way.
The problem is that networking is managed by a component, added at runtime, which manages all the networking per gameobject. Manual sync is a completely different mode of operation, and so by necessity the entire gameobject must change. Objectsync simply doesn't have a way to run in manual mode, and even if it did it would need to do so by changing the definition of manual mode.
So no, manual isn't compatible with objectsync, because they're very different things
The only two networking methods I can imagine are designed to work well together are manual sync and events (and maybe also continuous sync and events)
events are independent from the sync type, so they can run on top of anything
Do you believe the increased suffering I saw occurring if I combined events (via a NoVariableSync script) and VRCObjectSync on the same object was caused by something else?
I have no idea how that happened, but I know that network events go through a different pipeline that isn't dependent on the regular sync mode
When I have the time I'll see if I can reproduce it in a clean project
quick question for whenever i work on this again, what type are player IDs?
i assume string?
It's an int. Do people often store integers as a string?
you mean I shouldn't be storing all of my numbers as "one", "two", etc.?
well, IDs sometimes have letters and numbers, depending what it is
i’ve never worked with player IDs before, so i didn’t know what they contained
like Minecraft player UUIDs for example, are a mix of letters and numbers separated by hyphens
naur they're just an int that counts up, and they aren't unique to the player's account, just in that specific instance
first player that joins gets playerid 1, next gets 2, etc.
if playerid 2 leaves, they get a new id when they rejoin the same instance
ah right, i think i’ve heard that before
sounds like it makes a lot more sense to use for a tycoon ownership system…
what does GetPlayerById return if the player doesn’t exist? it just says it will return an id if the player exists, but doesn’t mention if it’s null or something else if they don’t exist
ints aren’t nullable are they? if not i guess it would return -1?
ints aren't nullable, no. Although -1 is the most common stand-in for "null". Like string.IndexOf can return -1, for example
that’s why i was thinking it would return -1 for a non-existent player
GetPlayerById gets you the VRCPlayerApi for the player, which I believe will return null if the player doesn't exist
though I think the script crashes if you try to GetPlayerById with 0
oh, right, i meant GetPlayerId
or wait, no i didn’t, man players and IDs are confusing
you'd probably be interacting with the playerId property if anything, i think you're going for something like this
[UdonSynced] private int activePlayerId = -1;
void EnterPlayZone()
{
VRCPlayerApi activePlayer = VRCPlayerApi.GetPlayerById(activePlayerId);
if (!Utilities.IsValid(activePlayer))
{
// Player not found
activePlayerId = Networking.LocalPlayer.playerId;
Networking.SetOwner(Networking.LocalPlayer, gameObject);
RequestSerialization();
}
else
{
// Player is in the session
}
}
seems about right
for some reason i was thinking i would get an int from GetPlayerById, but that’s what i would be putting into the overload
Finally got it, just gonna leave this here if anyone searches for this.
To rigidly lock a station onto an object use Update(). Not FixedUpdate(), not PostLateUpdate(), and not LateUpdate()
LateUpdate and postLateUpdate looks fine in editor but will be broken in game.
Yah I would assume that VRCPlayerApi.GetPlayerById returns null if the playerId doesn't match any player currently in the instance.
that or it makes the udon program crash
I would hope it'd be better programmed than that. There's no reason why it'd need to throw an exception.
it returns null (correctly) in what i'm seeing, the number on the left is the ID being polled, there's a space between the non-null clients because i tested it after one rejoined (making slot 2 null)
Bitwise stuff is really cool. Have the chance to actually use it in my project to pack my network synced data.
I had roughly 18 UdonSynced variables (16 ints, 1 bytes, 1 bool). The behaviour no longer syncs when I add an additional 16 byte variables. If I remove those, everything works fine. Here are some diagnostics ran by me:
- All variables (including the newly added ones) are initialized with some values (like byte.MaxValue or such)
- All variables are private
- I think it's Irrelevant because all the other behaviours uses private synced variables and they work great
- All variables have FieldChangeCallback configured respectively, with get and set (no private get or set)
- Same 16 variables without UdonSynced works perfectly fine though
- In Network debugging, the delay is noticiablely long (other behaviours stays roughly 0.1s to 0.25s, but this one goes over 0.5s)
What could be the issue? Is 16 synced variables too much? (I mean... the other behaviour's variable size is larger than 34 synced variables...)
Update: temporary bug. I've removed the new 16 byte variable's UdonSynced, add UdonSynced to one variable, Build & Test, and repeated until which variable triggers the issue. I've tested all 16 newly added variables and now it works fine. (?)
that is alot of synced variables. have you checked if its suffering?
the issue could be too much Bps, are you syncing every frame?
Question. Since SendCustomNetworkEvent is rate limited to 5 per second. If say it gets triggered 10 or so times instead, will it still eventually execute the 10 or only 5?
(e.g. player runs through trigger, code modifies list on owners end. Very lightweight)
5 events will trigger, the other 5 will be added to the queue
but you can also increase the limit to up to 100 per second
Oh, that's awesome to hear
. I assume increasing the limit applies to the overall world or the script doing those calls?
Also when they queue up, do they wait for the owner to finish running the method being called by the network or does it interrupt it?
they wait until the next second to continue
kinda both. Each function can have its own limit, but I believe it was mentioned that there is a global 100 events per second limit
I see. That would be very helpful regardless though especially if I need to send more than 5 through the network especially during time critical events. Thank you very much for the info ❤️
Hmmmm. I wonder. When it comes to player distance culling, do triggers still accurately execute when the player enters the trigger or would it be better to have the trigger executed by the one entering it then send the data over via CustomNetworkEvent?
Udon's built in Player Collision events use a collider that we're not allowed to touch or directly interact with as far as I know, so I'm not sure what you mean by "player distance culling" in this context.
Say I have code where i need to assign ownership of about 52 items to a player temporarily, is it worth storing the old owners to then assign them back after the operation completes?
if you really need to return the owners, then yeah that's what you'd have to do
but do you really need to do all of that ownership transfer?
you could just have the target player request the owner to do something, rather than transfer
would that be less intensive than transferring the ownership?
it's not really intensive, it'd be more about avoiding race conditions. to your original question, there's usually not much impetus to transfer ownership back unless you have a specific reason
by player distance culling, I mean when someone gets too far from the player they turn into a triangle/diamond
usually when that happens their position can sometimes jitter
Does it? I would assume that only the renderer is being touched by that process.
Maybe? Idk when I've joined a few people some in the distance can look like they're not moving smoothly but jittering unless its just a connection thing for me x_x
Well VRChat does deprioritize the sync data of players far from you, so that might be part of it
Yeah
Has anyone done a "Settings persistance for dummies" yet? I want to make my home world's settings (Perf Toggles, video players volume, ect) persist
there's the documentation you could look over https://creators.vrchat.com/worlds/udon/persistence/
What is Persistence?
put the script on a PlayerObject and put on a VRCEnablePersistence, it will save any synced variable on it
and wait for OnPlayerRestored to load before applying or syncing any variable for next time you join the instance
you don't want to use PlayerObjects for something like persistent world settings
it's potentially relevant if you expect to have a large amount of data on playerdata, but it could be a bit roundabout for a basic setup
if i have an int on a script that's on a player object, and i want to read from that int to display it on a scoreboard, does the int need to be networked?
i've used playerobjects one time ever, and am working on a project that i need them for
if you want it to be persistent, yes
but playerobjects can also have local non-synced non-persistent variables which can be accessed and passed around like any other
Also to avoid any possible confusion, a networked PlayerObject does not have to be persistent, but a persistent PlayerObject needs to be networked
When it comes to player ownership, does an object's movement (e.g. an object attached to the owner's hand) appear more smoother than those who aren't the owner of the object?
or is it the same?
movement is timing dependent; anything timing dependent is going to be smoother if it generated locally than something that has to compensate for the unpredictable timings that networking introduces.
I see. Thank you
I think persistence is over my head for now
I'll wait for someone to make a tool or something that I can just reference stuff into that I want to be persistent, if that's even a thing that can be done.
Recently released a new tool to assist with Network ID Issues: #udon-showoff message
how can i properly network the scores? i'm not sure how to handle the ownership for something like this, or how to handle syncing the position-in-hierarchy of the leaderboard slots. (note: the test button is just a test. i would cache the playerobject if it wasn't a quick little script for testing the adding and subtracting of scores)
(i see why the score wasn't changing for the localplayer now, i have to call SetScoreText() in the ModifyScore method, so don't worry about that)
polling every entry like that might be something you can avoid. it'd be possible to track them with a local data structure and only push changes to data representing the slot that actually updated. the text/position updates don't have to be done in the same instant if data is updated often. it probably doesn't matter if it's round based, this is more like what i'd consider if it's a constantly incrementing score with multiple players
it wont be updating very often, im helping someone with making disc golf in vrc, so the score will only update when a player successfully throws their frisbee into the goal. the difficult part for me, is figuring out how to have the "score" synced on the leaderboard, but not persistent, and the "highscore" display on the leaderboard if "isHighScores" is true, and be persistent. and i dont know how to network the positions of the players on the leaderboard. it's done by parenting them to a ui object, and having a vertical layout group sort them, meaning its based off their place in the hierarchy
oh huh, disc golf would be great actually
you could look up selection sorting or bubble sorting for swapping their positions. the leaderboard itself doesn't need to be networked unless you need to sync the fact that it's displaying the score or high score
like if you want to save on performance for non-master players you could work out some way to sync them by player ID in an array where indices indicate position, but it's probably better to just infer the positions on each player (where they all do sorting logic), which is kinda what you're doing already i think, aside from sorting
All you have to do is each person has a synced score value. and everytime someone scores it positions them self depended on the current scores of others. It can all be done manually and requires only to be sorted and ran when a score happen. But it does need to run for each person. And depended on the max score someone can get you can use a byte or short. If it's scores like 10.5 etc you have to use a double or float.
Cost wise you are only looking at 12 to 24 bytes I believe per person. That's included overhead
It's pretty straightforward if you use playerdata. All you have to do is set, get, and listen for onplayerdataupdated. Check example central for examples - there's even one for settings
I didn't see an example for settings, just niche stuff like leaderboards, pen marks, ect
settings with playerdata is the most simple application of persistence possible. literally just use a value to control your setting, like a bool or float, after you change that setting, save it with PlayerData.Set(type), and in OnPlayerRestored, load that value if the player has that persistence key and apply the loaded value to whatever the toggle does
public class PersistentToggleExample : UdonSharpBehaviour
{
private const string PersistenceKey = "toggle";
public GameObject exampleObj;
private bool state;
public override void OnPlayerRestored(VRCPlayerApi player) {
if (PlayerData.TryGetBool(player, PersistenceKey, out bool savedValue)) {
state = savedValue;
Apply();
}
}
public void OnClick() {
state = !state;
Apply();
PlayerData.SetBool(PersistenceKey, state);
}
public void Apply() {
exampleObj.SetActive(state);
}
}
it's quite literally just save the value when it's changed, and load it in OnPlayerRestored
alright, got it working much better now, with bubblesort and correctly synced/persistent variables
now i just need to know, what would be a good way of having it refresh for everyone when a score changes? would a network event suffice?
just inferring that a score has changed (via deserialize or serialize) would be enough i think. if you expect people to score rapidly, i like to do something like this to lock out rapid calls
on any score change
leaderboard._CallUpdateLeaderboard();
in leaderboard
private bool updateScoreRunning = false;
public void _CallUpdateLeaderboard()
{
if (updateScoreRunning) return;
updateScoreRunning = true;
SendCustomEventDelayedSeconds(nameof(_UpdateLeaderboard), 0.25f);
}
public void _UpdateLeaderboard()
{
updateScoreRunning = false;
// Order them at this point
}
hopefully people won't be scoring rapidly, otherwise the game's broken or someone's cheating
but thanks, that should be pretty simple since i call SetScoreText(); in OnDeserialization and locally
You could use events to do it. And have one owner handle updating it
How can I instantiate an object for every player? Atm when I do it, only the player that spawns it in can see it, it's invisible to everyone else
You can’t normally network instantiated objects, you’ll have to make it a PlayerObject and let vrchat handle generating the objects
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.
Do I need to do anything different when it comes to instantiating them? Or do I use the normal methods?
a PlayerObject will automatically be instantiated for every player when they join
each player gets their own copy of the object
So if multiple instances of this one object can exist at one time, will they all be synced too?
if you make it synced, yes
Sorry, badly worded question. Say I instantiate an object for 1 player, will that object be created for everyone else too?
👍 thanks
So I tried this with the following code but it only shows for the player that interacts:
public override void Interact()
{
Debug.Log("Spawned Tab");
GameObject tab = Instantiate(Networking.LocalPlayer.GetPlayerObjects()[0], tabContainer.transform);
Vector3 location = Networking.LocalPlayer.GetPosition() + Networking.LocalPlayer.GetRotation() * (Vector3.forward * 1.2f);
location.y = Networking.LocalPlayer.GetTrackingData(VRCPlayerApi.TrackingDataType.Head).position.y / 1.2f;
tab.transform.position = location;
tab.transform.LookAt(Networking.LocalPlayer.GetPosition());
tab.transform.rotation = Quaternion.Euler(tab.transform.rotation.eulerAngles.x + 270f, tab.transform.rotation.eulerAngles.y + 180f, tab.transform.rotation.eulerAngles.z);
}
don't Instantiate it yourself.... the PlayerObject will automatically be instantiated when the player joins
I do need to instantiate the object though as I want there to be multiple versions of this same object. They are tabs for a bar and every patron will have their own tab
the PlayerObject will already create a copy for every player. Why not just use that instead?
Ohhh I see what you mean! Sorry! My brain wasnt on the right frequency
if you want to test PlayerObjects, just make a cube that's a pickup with ObjectSync, then make it a PlayerObject too
then do a test with multiple players and you'll see how they work
no new udon scripts required, you could even chuck EnablePersistence on it for fun
for a UI tab you'd likely need to reposition it once it gets created
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.
When you say UI tab, do you mean a tab on the UI layer? Or a tab that uses a canvas?
no don't put UI on the UI layer
Yeah I have it on default but I wasnt sure which you were referring to
your UI canvas, it's just a GameObject that you can reposition like any other object
when a PlayerObject gets created as a player joins, the copy will be created at the exact place that the original is
you can use the OnPlayerRestored event or Start on it to then move it to where you need it
Though maybe for UI you could just use one of those components that auto-layouts them
I can probably just put them inside of the same thing, so that when it's needed it can just be grabbed
Is there a way I can let everyone pick up a player object?
nope, PlayerObjects are stuck being owned by one player
so other players can't pick up pickups
So player objects is out of the question now
Might try and object pool and see if that will work
yeah as far as I'm aware that's a drawback of PlayerObjects; they're intended to be a copy for every player, but specifically only for that player to use. Imagine a system where you want to give every player a sword to fight with, but that's their sword, nobody elses
I was going to suggest an object pool instead yeah, but could potentially get tricky if you want the objects to populate with player's info
The info on them will be self contained
Is it just me or is OnOwnershipRequest not a thing?
It's in the documentation, but when I try to use it, it doesn't exist.
Oh nevermind, the return type had to be bool instead of void.
why not just have a ton of them disabled, and enabled for each player?
I need it so everyone can pick them up, I switched to an object pool which works like a charm
yep object pool works that way pretty much
I am trying to figure out if it's possible to update a vrchat world over networking rather than updating a world manually
Not fully update a world, but be able to modify certain parts, I want to see if there's a way I can update avatar pedestals in real-time without having to reupload my world, it would save a lot of time since my internet is slow
Is this something that is possible? If it is I want to look into it
so the only way to update a world without a reupload would be any streamed data, like string loading or image loading
you could, theoretically, have a system where you have a text file hosted somewhere, then use string loading to download it once you join the world. This file can have a list of avatar blueprint IDs or something.
Then when the string loads, you can have an Udon script that programmatically changes the blueprint ID that's on the avatar pedestal
@light vine
Rule #10
Can any1 tell me how to compile Udon...or help me with compilation errors?
ask in one place please
Is ther someone that knows VJ/LJ stuff? im currently creating a world and i need to know stuff about their workflow and how they get data in the world
DMX most likely
Is there a good document for array syncing? I'm trying to get long arrays of positions and rotations synced for late joiners, practically a history of object transforms. For normal use, it'd only need to add the added items, but a late joiner should receive the whole array.
So I'm wondering what would be the optimal way to sync them. Would it be to sync some other temporary arrays with just the added items and then initiating a full sync if someone joins? Or would a synced array be able to recognize that something has been added/updated and sync only those items? Or am I thinking in a wrong direction?
it's probably more common to handle it at object level in vrc, but if you want to manage a history in an array you can (it'd be 2 arrays in this case, position and rotation)
if you can guarantee a consistent player hosting whatever you're doing, it'd be relevant to cache the history on them and send it over events to late joins, but it's more complex
otherwise you'll likely want to store the full history in synced arrays, if it grows beyond what you consider reasonable to sync in one step or you're updating it often, you could consider using multiple buffers
about whether arrays only sync changed items, they do not, they send the absolute/full state
you could also manage it in a string, but it'd have some overhead on bandwidth in terms of how much you can store
are you trying to manage instantiated items or what's the use-case?
Spray system, so storing the paths where the spray has been applied. I can render the painted texture analytically so it should be possible to 'replay' the data for late joiners to get the current state without having to start syncing big textures... If I can get the data synced.
Practically each frame that a spray is hitting a surface will create a record of the pos&rot. So... about 2kB/s/spray can 😬 it'll get big quick... maybe I can optimize it somehow later...
Anyways, I'm guessing the best bet would be to send the data over events then? Is the bandwidth limit outgoing or incoming? If it's possible to only receive ~10kbps, I might need to rethink some things... but otoh if every connected user can send data at that rate, it might be possible to send the history from multiple users at once like in p2p networks? 🤔 (if I can guarantee that each of them have the same history data) the rendering would probably become the bottleneck at that point though...
Outbound data limit is around 8.5kb / s but that goes down per user that joins
It's actually around 11KB/s iirc but what is contributing to it is influenced by a lot of things.
Also the only thing that is only limited by the upload speed limit is continuous sync. Manual sync has its secondary, seemingly secret limit influenced by how often you are attempting to perform manual syncs.
I imagine that each player is regularly sending out network data that remote players are forced to send data back to the server about to confirm reception and that will count against your network limit.
Okay once again I'm diving into networking but it's been like 7 months since I last touched any of this.
Are there good modern docs for Udon# networking? The https://udonsharp.docs.vrchat.com/ docs have always been pretty lackluster and don't touch on a lot of frequently-problematic edge cases in ownership conflicts and the like
A compiler for compiling C to Udon assembly
For example, a question I've never really gotten a straight answer on is: what is the default fashion in which two clients rectify both players simultaneously attempting to claim ownership of a behavior? And if that default resolution mechanism is problematic, is there a better way networked world makers manage these conflicts?
Unless it changed again from when we had the parameter update then it starts to struggle at 8.5 kb/s. And it was tested when and before the update hit with a single 8.5kb synced string if I remember correct. That updated once per few seconds
No sadly the docs for udon is still as lackluster as before. All you can do is either brute force try things which I really don't recommend or ask around in here. Your best bet is diff asking people.
I'm going to guess whoever gets their request ownership sent to the server last ends up getting the ownership. You should generally try to avoid ownership race conditions like this. You can do this by only allowing a single player to transfer ownership of an object (the instance master or the current owner of the object) or by avoiding ownership transfer altogether
There's no server involved in ownership management though. Or really at all, except for VRChat authentication
why do you say that?
'cuz as far as I'm aware, it's true? Proton just repeats client messages to other clients that are connected to the same address, but the clients are sort of left to figure out the state of their respective game worlds on their own
It is effectively a peer to peer system, if not literally then in spirit
The instance server handles at least a few things. For example an owner of synced data only needs to get that data to the server and from there the server will handle getting that data to everyone else, including late joiners
Specifically with manual sync data when it comes to late joiners
Mhm, I know. I'm... not precisely sure if that is contrary to my point though
In either case. I'm going to experiment a little with parameterized network events and see if my design can fit into the "never change ownership" approach
but... there's lots of reasons why that would be untennable for a lot of use-cases
I'd really love a deep dive on how to finely manage network ownership in a robust way that's highly edge-case resistant.
Wish I could find that sort of information or documentation online x"D
My world uses ownership transfers, but it's exclusively managed by the instance master
Is that using the Request Ownership Transfer pipeline?
It's using SetOwner
Also... how would that be possible? If the instance master isn't the owner of something, they don't really have the authority to confirm or deny an ownership request. Are you bypassing that entirely and --
ahh
yeah, that was my second guess
Curious.
Also network ownership uses eventually reliability, in that there might be a period where it is incorrect, but it will eventually get to the correct state
A small personal nitpick. "Eventual reliability" isn't a thing that's being "used." It just is. There's no special system working diligently under the hood to assure this eventual reliability, it's just the nature of the system that it's NOT immediately bringing things into alignment. So the "eventuality" of this reliability is... really quite variable, based on your specific implementation.
... Is there some subtle difference in the way synchronized variables propagate for late joiners, when the instance master/behavior owner has changed...?
I'm currently investigating a bug where, if a player joins late and the original instance master (and owner of all synced objects) hasn't changed, they get all the synced variables correctly.
But if the instance master leaves and rejoins - resulting, presumably, in ownership of everything they had being transferred to the other player - they do NOT get all the synced variables.
Even more strangely, they do get a few synced variables, but ONLY the ones that the newly assigned instance master was around to see assigned at some point.
if you request serialization and handle synced variables remotely in the deserialization event, they should work as intended for late joins, regardless of the master changing
though there are some nuances in doing that with objects that start disabled or in vrc's object pool (which also start disabled)
Is there any reliable way for a late joiner to know that they've received the up to date state of synced variables?
It looks to me from testing like Start() can run before synced variables arrive after joining
It also looks to me from testing like IsNetworkSettled can return true before synced variables arrive after joining
Finally, OnDeserialization appears to not always run for objects, on late-join, if the current owner of those objects hasn't seen any of the object's variables change since THEY joined (even if those variables were changed from their default values before they late-joined)
Should be ondeserialization. Synced variable states are cached on the relay server so that late joiners can receive it directly from that as they join. I'm not aware of there being any critical bugs with that process right now. Is everything involved in that enabled by default and never disabled?
Nothing is disabled on load
OnDeserialization won't run on late join for objects whose synced variables haven't changed
But "haven't changed" appears to be fuzzy
It seems like if a player joins a world, gets a late-join update for a variable, but then never sees the variable change again... they can then later become the owner of that variable's object, and not send the previously updated variable to other late-joiners.
resulting in a desync where future late-joiners see the variable as its loaded default value, because the variable's new owner doesn't think the variable has 'ever changed' and thus doesn't replicate the value to new late joiners
This is pretty easily testable. If you output a log in OnDeserialization(), and just put a bunch of those objects in a scene, you'll see that, if you never make any changes to those objects, the log inside OnDeserialization never shows up for new late joiners
Then you can see this 'well it never changed for ME' behavior by having two players shuffle rejoins.
Have 2 players load into a world. Make a change to an object and serialize it. Have the non-owner rejoin; they'll see OnDeserialization for the one changed object run.
If you then don't change that object again, and have the OWNER rejoin, THEY WON'T see OnDeserialization run
At the recommendation of a friend, I tried to change my system to basically entirely use field change callbacks.
The same thing happens.
If I set a variable and rejoin with the non-owner, the non-owner gets field change callbacks and successfully updates the changed variable.
If I then do nothing, and rejoin with the owner, the owner rejoins and gets no field change callbacks and never gets the updated variable, even though the other player (who now owns the object) has a different, changed value for that variable
I'm steadily becoming more and more convinced that this is a network API bug :S I was ready to accept it was an error in my code yesterday, but now I've tried rearchitecting the system 3 different ways and the same behavior keeps happening:
If a late-joiner takes ownership of something, but its variables haven't changed since that late-joiner joined (even if those values were updated AS they joined by OnDeserialization), they will not send out those variables to other late-joiners...
Either that, or it's just an undocumented necessity that a player taking new ownership of something must always request serialization after they do so - even if ownership is THRUST upon them automatically by the original owner leaving - or future players won't get updates on late join
If the variable has never changed and the object has never serialized - yeah, you're not going to get ondeserialization. If that's your problem, it's a quirk in the network API that I don't love but it's technically not a bug. I'd recommend ensuring that the master serializes those objects where this matters when they set up the instance.
If the object has been serialized before but then transferred ownership and the new owner never serialized so late joiners get nothing? That would be cause for concern, strictly a bug
Yeah. If a late joiner gets the state of an object when they join - that state doesn't change again AFTER that - and then they take ownership, it appears that future late-joiners do not get updates about that object.
I guess for now I can try just detecting ownership transfer and then forcing a serialization, see if that makes a difference.
Yeah that's a safe bet
Should be serializing on ownership transfer anyway. What situation are you transferring without serializing? Or is that just due to owner leaving?
Owner leaving.
I'd never do an ownership transfer without serializing otherwise, but frankly I was and am making the assumption that VRC's networking api would handle making objects available to late joiners in the event of an owner disconnect, automatically
Yeah that's odd
Well, good news is that I can at least fix the problem in my world by detecting the ownership transfer, and using that as a trigger to force a new serialization by the new owner...
Verified that works
i'm not seeing this behaviour with 2 clients
the original owner makes a change, the non-owner rejoins, the owner then rejoins, they also see ondeserialization in my case; from the initial change alone
do you happen to be using object sync on the object in addition?
Nope, object sync is not something I'd ever like to touch lol
That's really strange that I'm getting this behavior very repeatably and you're not. The mystery deepens...
You using manual serialization I presume?
yeah, manual sync
Also object sync is amazing for what it is and as far as I'm aware it's behavior can't be replicated by any tools we currently have access to. My current project wouldn't be possible without it.
if you mean the upper limit of manual sync yes, if you mean the fact that it can sleep, you can make that behaviour yourself in manual a number of ways
I mean I don't think it's possible with our current tools to make something as network performant for non-predictable positional sync as object sync
if we did not pay the cost as we do for overhead we would be able to make it ourself pretty easily. all through its still possible to make it yourself with full control. but cost would be 48 bytes versus just 27 bytes which is the cost of 1 vector3, 1 Quaternion, and 2 bools.. making one yourself would allow you to do predictions which essentially could make it less costly far less so even. all through the manual limits is obviously making it very hard lol
I would appreciate any help with this. I have a button that sets a count, and syncs it manually. However, it seems that the sync is one step behind the owner. The weird part is I have another script that syncs the same exact way and that one is up to date. Anyone know why this is? The section that syncs it to all players is run on the owner and is this script.
startlives++;
RequestSerialization();
SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.All, nameof(UpdateLives));```
My only guess is that the serialization isn't completing before the Network event does
also between a same sized object sync and manual sync, the object sync is still going to be more network performant because it doesn't require receipt verification like manual does
But why would that only happen on some scripts and not others? And would adding another custom event with just the send network and running it with Delayed event fix it?
The custom event will almost always arrive before the RequestSerialization does. Call "UpdateLives" in OnDeserialization for the receivers
You're mixing two different networking methods and the timings of those two networking methods are not going to match each other
You know, that's probably why the other script works, as I have an OnDeserialization function for late joiners
So I guess sending that custom network event would be redundant
yah, and in this circumstance not a good idea as you can already tell
OnDeserialization is run on all clients after a requested Serialization is completed no?
everyone except the sender
To put it simply, yes. In a bit more accurate description, OnDeserialization will fire whenever networked synced values are received on the object on a continuous or manual sync. This will occur shortly after an owner of an object sends out those new synced values for anyone currently in the instance and, for anyone late joining, shortly after the join the instance
This might also help you: #world-persistence message
Thank you for the help, As an added bonus, one less network event required
Hey, I need some help with teleporting an item spawned from a VRCObjectPool.
internal void SpawnDrink(Transform spawnTransform)
{
Networking.SetOwner(Networking.LocalPlayer, gameObject);
var drinkObject = drinkPool.TryToSpawn();
if (!drinkObject) return;
Networking.SetOwner(Networking.LocalPlayer, drinkObject);
var objectSync = drinkObject.transform.GetChild(0).GetComponent<VRCObjectSync>();
objectSync.FlagDiscontinuity();
objectSync.TeleportTo(spawnTransform);
}
This code works sometimes, but sometimes the item won't actually be teleported, just enabled from the pool. Does anyone know what could be the issue?
Does TeleportTo() work on non-players? I mean, you could simply just set the position and rotation of the object via its Transform
it should work for any object
Yes, and changing the object's transform actually worked less reliably lol
https://creators.vrchat.com/worlds/components/vrc_objectsync/
The VRC Object Sync component synchronizes the transform of a GameObject with all players in the instance. It synchronizes the object's:
i think you're setting ownership on the drinkObject, not the object with object sync on it (drinkObject.transform.GetChild(0) in this case)
i think there are issues with moving fresh objects from a pool outside of that too, related to how vrc disables them on start
Huh I had no idea, hadn't tried this with pooled obects yet
Setting ownership of a gameobject sets ownership of all children, no?
no, why
Why tf wouldn't it?
they can be independent; if that were the case a pool with the intent you're using it for would fail to function if the pooled objects were children of the pool (if the intent was to allow different drink owners)
cause each script (or rather network ID-ed object) has its own owner, why changing owner on one would change it for some other object, child or not.
It's because you're trying to move an object with objectSync too soon after it gets activated. An Udon networked object has unpredictable behavior while it is disabled and shortly after being enabled. In this case, the object sync is moving to where you are telling it to go, but then it goes back to its previous location on receiving that location as a network sync.
Gotcha. I'll try moving it a frame later, hopefully that'll fix it
A frame later will not work most of the time
It has to happen after the objectsync has fully reenabled its networking and will have predictable behavior again, and I don't think the amount of time that will take is strictly knowable.
You'll either have to wait a second or two and tolerate any potential edge cases where it doesn't work, or use a different object pooler that doesn't disable the objectsync object
I suppose putting your object sync in a parent object by itself while putting the child object in the VRCObjectPool instead (so the parent object sync stays active but the rest of the object does get deactivated) might work
that sounds a bit messy in terms of inferring if a pickup collider should be active; or the pickup pickupable, but i guess if the collider isn't a concern
Also I can tell you from experience, being able to handle ownership on a per object basis instead of having to worry about ownership cascading down to children is way more convenient
My own personal pooler that I use, instead of disabling the object, it disables the renderer and collider.
But that will require coding your own custom pooler. Worth it if you're doing something complex, but if you're just doing something simple the faster, more messy approach might be better.
what does FieldChangeCallback do?
runs code when a field is changed
to supplement what kazin said, its used the same way you'd use OnDeserialization to run logic after variable(s) updated
so basically OnDeserialization but for a specific variable?
All my networking is done with field change callback o/
I'm more of a OnDeserialization user myself as it better captures the idea that it's a unified object getting synced rather than a bunch of individual variables
Field callback also fails if the value changes from the same value. To the same one again.
So while refreshing myself on the creator api, I noticed that there's this very stern warning on the VRCPlayerAPI page that says that code should always account for any player's device becoming suspended at any time, as suspended devices do not run Udon. Also, because suspended devices don't run Udon, IsSuspended will always be false for the local player.
... This seems like a huge design challenge. Is there any good documentation on design patterns that are recommended for this issue? Especially as it regards to networking. Like, it would seem to me like you can't even safely call networked events or ask for ownership - even if you check to make sure the owner you're sending networked messages or ownership requests to, isn't suspended - because that player could BECOME suspended during the half-ping between your clients. And then you'd just... get no response
I haven't had to deal with that myself, but you'd probably want to use OnPlayerSuspendChanged and work that into your networking, if needed
https://creators.vrchat.com/worlds/udon/graph/event-nodes/#onplayersuspendchanged
This is a list of Udon Nodes that are considered "Events".
you could use this to detect if that player is the owner of something important, and transfer it if needed
I think I read somewhere that the Instance Master also gets switched if a player becomes suspended, but I can't find where it said that so don't know if that's actually true
Sure, but there's still a vulnerable window where players could think that the owner of an important object isn't suspended, because they haven't received that event yet...
Additionally, which player would assume responsibility for changing the ownership away from the suspended player? How would you determine that, rather than every client independently trying to do it—
Actually, now that I think about it. When you try to change the ownership of an object, isn't that ownership change event sent to THE OWNER OF THE OBJECT to be carried out by them? How would you change ownership away from a suspended player, if the suspended player cannot respond to or operate on Udon?...
the ownership transfer negotiation is optional
so the suspended player doesn't have to respond, the player requesting ownership immediately can assume that they are the owner
I thought all ownership changes were managed by the original owner, even if RequestOwnershipTransfer isn't used
no they don't have to be
that's how you can set owner, then immediately start using synced variables
I presumed that was a temporary local thing, and that other players don't immediately begin respecting your ownership conquest until the original owner fires off an event to everyone saying 'oh yeah they're the owner after all.'
And I presumed this was necessitated because if two players try to take ownership of something at the same time, there has to be some way for that to shake out. If there isn't any verification - automatic or manual through request ownership transfer - on the original owner, then two players could be led to believe they own the object at the same time, and never get corrected... or both correct each other into both NOT being the owner
I was just talking to someone about this the other day, actually. I was scratching my head over ownership race conditions.
If two players seize ownership of something at the same time, they both momentarily think they're the owner and can begin doing things with synced variables.
... Then what? If they both took ownership at approximately the same time, then they both send out events to other players, including one another, at approximately the same time. When each player receives that follow-up event that says "okay but this OTHER player took ownership," how do they know which one really has it? If there's no tie-breaker somewhere, either players A and B BOTH ignore the message, and now both locally think they have ownership (which clearly is broken), or they both HEED the message and LOSE ownership, potentially resulting in no one having ownership at all (which is also clearly broken)
Someone said this doesn't happen because players A and B both send their ownership transfer event to the original owner. And one of those two players' ownership claims arrives after the other, after the original owner has already given away ownership, meaning that that second player gets a callback, under the hood, from the previous original owner that says "uh no I can't give you that, I don't own that anymore"
Does the suspended state even apply to Questies?
And this would necessitate that this ownership negotiation is occurring automatically even if you're not using the manual ownership transfer request system...
It doesn't apply to PC players and considering the age of that documentation I don't see thet ever changing that.
I'd sure hope not! But
I dunno. Is this a 'feature' that veteran world devs all currently ignore, and just hope they'll never have to fix everything if it becomes necessary later?
At least for my world, and my own personal design philosophy, the amount of design constraints it would impose isn't worth it for a "maybe" and I'd rather design without it in mind and redesign if needed in the future.
Don't want to worry about solving maybe future problems when I still have a bunch of problems today that need solving.
I s'pose
I just hope I don't discover that my game world shatters into a million pieces whenever a quest player joins the game
That's why I'm curious to hear if anyone knows if Questies can get suspended
mobile can, i don't know about quest, but it probably can if they can swap away from the app somehow. accounting for suspended state like the docs suggest is pretty unreasonable imo; it has some implications with ownership/master as the suspended player can be the master
from what i've tested and understand, both ownership requests are actually valid, but the server would process them in order, one effectively wins; the loser will see an update on ownership to the winner at some point. using ownership for critical logic is iffy
Do we know that the server indeed manages ownership like I suspected?
I keep hearing people talk about the 'server' deciding stuff but I've been told numerous times in the past that there is effectively no server for literally anything except players validating their credentials with VRChat, and repeating messages between players; otherwise the entire game is, in spirit, peer to peer
There really should be something in the doc that clearly outlines the role of the proton servers
I'm tired of guessing x'D
if that were the case playerids would be unreliable, photon does handle some aspects
ownership is likely one of those things
The networking has changed significantly from the early days of Udon to my understanding
I mean yeah making sure that there is a database of players that messages should be repeated to, would necessitate that something like a 'playerID' exists on Photon... Proton? Whichever it's called
I know it does SOME things, it's just not clear at all WHICH things
especially when so much of the game does act strictly like it's p2p
network serializations include data on who is owner of what; so the "winning" owner is whatever serialization ends up being sent out last
So whichever packet the server decides to send after, that's the winner
you generally want to design around not having simultaneous ownership requests, really
This doesn't rectify the issue with both players, potentially in a close race, initially getting a response from the server that they're the owner/they're no longer the owner
even if the server sends out timestamped messages
Literally not possible 😦
it's not a response from the server actually, they just believe they have ownership
it's absolutely possible
How do you 'design' around making sure two players don't click a button at around the same time?
For my world, there is a lot of ownership transferring happening but the instance master is the sole authority on it
you could send the request for their button press to a central authority (the master/owner)
Sure, yes, though there are significant challenges with doing that as well. Other edge cases that are just as hard to resolve...
... Regardless, it's not really my point here
we've kinda gone on several tangents ;P
the ownership situation isn't great, you can respond reactively (dropping a pickup if you lose ownership while held is a good example)
Also ownership transfer isn't always necessary
You can have clicking on a button instead send an event requesting the owner of the object to do something
Point is, 1) clarity on how important or not-important "IsSuspended" conscious design is right now, would be nice, and 2) what on earth is Proton responsible and not-responsible for
Aye, it's beside the point for me right now MOSTLY because I am trying to use a network-message centric design right now. BUT I still could run into issues where ownership needs to change for one reason or another
and this suspension issue might be one of them
