#udon-networking
1 messages · Page 22 of 1
VRCPickup.Drop is network callable. There is a parameter to pass a VRCPlayerApi as the instigator and I believe this is for ownership transfer requests if you have custom logic. Though even in my public project, I just have a method anyone can call over the network to tell the owner to drop it themselves
well more specifically I'm talking about like OnPickup()
I want to know if OnPickup() can be maliciously called over the network
Ah. I don't believe so since that's an VRC internal override method
probably why OnPickup is only run for the local player that picked it up and to get that pickup networked, you'd need to send a custom network event to all
If you wanted to be extra careful, you can do an ownership check against the local player
While clients have been able to call methods, they haven't been able to modify the body of the methods afaik
what sort of networking does it do anyways? I'd like to use it as a non-networked object where only one exists in the scene and every player has a local use for it
In my experience OnPickup and OnDrop do not network
I’ve always had to manually add a synced bool for others to receive a pickup event
Unless that’s not what you meant!
Ahh, I have a good way of expressing it: would SendCustomNetworkEvent(NetworkEventTarget.All,"OnPickup") work?
But from what you're saying, it does not
I don't think so. You'd have to in the body of OnPickup send a custom network event to another public network callable method
pickups will automatically try to owner transfer when picked up, that's like the main issue i can think of in a networking context; you'd just use synced mode none if you wanted local pickups (or they could be manual/continuous if you didn't have object sync or something relying on ownership on the object, that'd effectively be local too)
At least not as much as before since we got signed Udon
I'm having a problem with a world I have where I need a synced animation that plays once every time a button is pressed. I was trying to toggle the animation parameter on and off but something's not working and there's no way to indicate what it is
Maybe this is better for the general channel but just tag me and I will respond
your "SendCustomEventDelayedSeconds" node is missing the event that it was calling previously
Weird that was put in there just earlier
I'll try it again I'm pretty sure it's still not going to do much
Nevermind actually, I think it works. How do I make it sync for everyone?
instead of SendCustomEventDelayedSeconds, you can use SendCustomNetworkEvent instead
Oh yeah that might make sense
I was doing that because I was trying to delay the time between the boolean switching but that might be better
is this meant to play the animation and then reverse the state? like i'm not sure i understand the delayed event unsetting the bool again
unless you're using the true bool as the condition for the animator between two states
If I only set it once, it will not change back when the button is pressed again
I think you can just play the state directly rather than flip a bool to make that work a bit easier, not sure
Yeah I tried doing that but it wasn't synced. So sending the network event and playing the state off it might work?
you should try to structure it with exact states like this if you can, using true each time isn't as safe
oh right, it won't be synced because you're only setting the bool true for the person that pressed it, and not everyone else
so sender sets it true -> false, but everyone else just sets it false
and if the intent is to get an animator that stays in some position and syncs to everyone you should prefer using synced variables over networked events, mostly for the sake of late joiners
Let me try triggering the animation with the network event and see if it does anything first
It works, but should this sync for everyone?
this is probably the clean/vrc style way to do it, assuming you have two distinct states and can go between them with true and false in the animator
the sync mode should be set to manual if this style is used
Ok I'll try that if this method ends up not functioning
that is good way
but never merge flows like this! make copies of downstream
because compiler cant compile correctly and can give unpredictable results
just dont merge flows
its just one of those udon quirks
not sure what you mean
Hello, who knows how to change the color of the fog through the UI? That you can switch from the standard to a different color and then everything is the other way around.
@twin portal knows more about the details
Looks like this time it's fine, I think the issue happens when you split the flow, and then combine the flow back together
So it's generally recommended never to combine flows to prevent the issue in the first place
@fallow mountainIt seems to be alot better now but I was having issues where the monsters worked fine on the hosts side, but on a joined users screen the attack animations and even walking animations wernt being networked, its still a bit janky now, made a little preview of how well it plays hosts side vs a joined players side
FPS for the host goes down from 110 to about 70 in the lobby when a player joins and goes down to about 60 in game
are the attacks and walking done with send networked custom event?
going from 110 to 70 when someone joins is likely related to playerobjects
Yeah theyre triggered by network events, before they wernt working and I couldnt see the cause, but now it magically works again yippie!
The player hitbox was a big hitter, the graph changes you suggested improved the situation a bit though
u might want to check the network throughput and usage etc... in debug menu 4 ingame
maybe clogged
Well before it wasnt working AT ALL! But in this it was working perfectly
I guess the only thing I can note is the positioning of the Giant hand boss being a bit delayed from the fast movement special attack
It was one extreme or the other
Oh and this only worked in continuous sync mode, manual it didnt do anything either
I dont really know the difference other than manual needs joiners to serialize?
I still need to understand the differences in these scenarios
you might want to make it manual, and only send a starting pos and ending pos... (optionally starting server time) so everything can be caluclated remotely...... and minimize network usage
both continuous and manual will work for late joiners, the difference is continunous is interpolated (i.e. not precise)
Starting position and ending position of the nav mesh agent? I wouldnt even know how to do that
I did have the foresight of designing the health system of the enemies to not care about where their physical body is in the game or if they are synced, so it should still provide a fun experience for all players
The attack triggers are locally triggered to trigger a networked event so even if an enemy is delayed but hit someone on their local client, should still attack
dev menu 7 should tell you what is using the most network
and maybe it is related to your fps drop
Il give that a try next time I get on
What were the controls for that, shift plus F...... something
Been a whilse since I last used debug
Got it
Continuous: Any synced variables that you have will be "continuously" synced, meaning whatever value the Owner has, the value will be synced with all players without you needing to do anything else. It'll share the value every... second or so? But you can set this value to automatically interpolate, so if a moving object is being synced, you'll see it move smoothly. mostly.
Manual: Any synced variables will only be synced if the Owner calls RequestSerialization. You'd then use OnDeserialization on the receiving clients to "set" whatever those synced variables are for.
Makes sense that the script stops working if you switch it to Manual, as it requires a bit of setup to make it work
but weirdly, in my latest experiments, Manual can actually sync a bit faster compared to Continuous
The other day I tested manual sync, looks to be capped around 8.5 kB/s (docs says 11 kB/s so not too far off). (measured by observing the new network stats Stats.BytesOutAverage and looking at dev menu 6)
- If you manual sync less than 8.5 kB/s, you can sync (and be received) multiple times a second in real time without issue, but still it jitters quite a bit (+- > 50 ms with a 10/9 hz frequency) (in dev menu, the delay seems to be always 0.05 seconds).
- If you manual sync close to 8.5 kB/s or just above, your network will start showing
sufferingslowly building up to 100, but still able to serialize multiple times a second in real time (measured with OnPostSerialization), but on the receiving end it will deserialize in one-second bursts, exactly every second, so it no longer is "real time". (maybe this is "hitching" in action buthitchesPerNetworkTickreads zero... idk) - If you manual sync something much larger than 9 kB/s, it is throttled and it can take many many seconds or tens of seconds to arrive/deserialize.
- Curiously the new network stats "throughput" shows that it throttles to around "0.5", but not sure if this means 0.5% or 50%.
- Screenshot:
I have not tested continuous, so idk if it is receiving in one second bursts or otherwise, but the "network tick" does seem to be exactly one second
I can share the testing script if you want
but you need the beta SDK
for network stats
looks like internally to VRChat the maximum frequency is 20 hz? to be confirmed
it does say your max out seems to be 16 kb ? so that might be why. considering the average is 8.5 kb/s
and also looking at the Bytes in on the Receiving side. it says 17325 bytes aka 17.3 kb/s which is why the suffering is as high as it is. ideally you dont want that to be so high
which indicates you are outputting Way more then the 8.5 kb/s
I guess the 16k was probably before the network started queueing up so the first burst was 16 kB,
which seems about right because I am sending two dummy string variables, each 400 characters, each char is 2 byte
at delay of 0.1 seconds ( which is less than 10 hz because delayed events are delayed more than the waiting time because of Update timing)
2 x 400 x 2 x 10 = about 16000 = ~16k
But in dev menu 6, the out is 8.5 kB/s
Curiously, if I use dummy size 300, which should be ~12k, my out is actually at ~6.8 kB/s
Also, the max seems to be MUCH smaller (almost half??)
(screenshot of sender)
wait are you using the CustomSendNetwork event? or the Deserialize method?
manual sync, RequestSerialization
in a custom event loop 0.1 seconds
hmm.
@fallow mountain you are right. at 8.5 kb/s out it suffers. that is not a good thing.
just tried my self and it indeed suffers at 8.5
this is even lower than the doc's "11 kB/s"
question beta or release ? @fallow mountain
beta sdk
seems to be that with the beta we indeed are getting a lower amount of data .
was testing these
https://vrc-beta-docs.netlify.app/worlds/udon/networking/network-stats
cause in release its 11 kb/s
interesting
i tested it my self with a uint[] and yea. it diff did struggle.
with a 1 request per second
now the problem is that people with face tracking and full body will struggle even further....
Curiously, the 8.5 kB/s corresponds to Stats.ThroughputPercentage showing about (or just below) 0.5
I don't know if 0.5 means 50%, or 0.5%
And in Udon Stats.ThroughputPercentage seems to be the only thing we can reliably track the throughput, in Udon (without opening dev menu 6)
The bytes out average is running average (not real time) and I guess the max is also just historical max (not real time)
So maybe if we stop sending stuff when Stats.ThroughputPercentage is above say 0.4 or something, we can ensure network don't suffer (at the cost of dropping packets)
beta docs say 8-10KB is to be expected
Total outgoing data is hard-capped at approximately 18 KB/s. This includes all network overhead however, so in practice it is unlikely you will see more than 8-10 KB/s.
that prob means below 0.5% or close to
its diff is 8.5 kb/s which is going to break quite alot of worlds ngl. since alot of them uses just close to the edge of 11 kb/s
where does it say 18 kB? I cant find the page
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.
0.5% seems very low (what is the other 99.5% for?)
well im guessing it means it has 0.5% left to 100% thats what i am thinking.
but considering the Suffering can reach over 100 it could be something different.
At lower usage, the Stats.ThroughputPercentage actually drops to 0.4x or 0.3x close to 0.4 (at about 6.8 kB/s) (forgot exact number)
So it should be a "used percentage"
8.5 x 2 = 17... close to the "hard cap of 18 KB/s"... could it be the other 50% is reserved for networked events with parameters?
if 0.5 actually means 50%
maybe i should ask in beta discussions
hmm.
this isn't new to the beta, the doc has said 11kb/s, in practice that's been more like 8 - 9kb/s
you should probably measure the incoming rate in addition to outgoing if you want an accurate representation of hz for something you're doing
you might want to look into a decrement style send timer if you want to achieve a specific rate, not sendcustomeventdelayed that effectively zeroes out the remainder
oh yeah i measured the incoming rate, it seems even if the sender is sending at the correct hz, if sender is suffering, the receiver will get the packets in one second bursts (no longer real time)
and yes, i should adjust the timing for accuracy
if sender is not suffering, the receiving will receive ok at the real time Hz
Do variables on a continuous sync Udon have to have the "Synced" tick enabled?
or will they sync regardless of setting
yes, they need to be set synced
Ok thats good
So the only things that should sync is the output of network custom events
I want all the AI logic to be calculated by the host
if it's just networked events you can just use manual instead
I tried manual but then it stopped sending the networked events
as I was saying earlier, it takes a bit more setup to make manual work
if your script is setup to work for continous, it's not going to work at all if all you do is flip it to manual
if it's on an object that has multiple synced scripts, they can't be different types, so i'd assume you're using vrc object sync on the object as well or something, which uses continuous
At first the AI enemies were all one object under a continuous, that worked perfectly but the host was getting hammered in framerate so i tried a manual sync, no good, because the objects need object sync, so I make them all a child of a parent which handles the nav mesh agent which is object sync and then the child under manual would handle everything, again worked, but only for the host, every joined player saw the enemies in idle animation while moving around, so now Ive switched them to continuous under parented object and they seem to work for now
The framerate is acceptable, at least under 1 joined player
I hope the load doesnt scale with more players :3
are you checking for nearby players every frame or something heavy on the host? it being continuous or manual isn't going to change performance that much unless you do a bunch of logic on receives or try to send large arrays you create every frame or something
The enemies have a sight system which every 0.2 seconds increases the volume of a search sphere trigger collider, once it touches a player it thrinks back down to 0 then begins to reexpand again, the location of the found player is sent as a vector 3 to the AI's nav mesh agent
I do this to provide a (hopefully) non taxing way to find the closest gameobject with name
I know theres more accurate ways using the nav mesh agent to do crazy stuff and I found tutorials but that was a bit much for me, I also cant imagine it being ran well if theres like 30 monsters in the arena at once
Ive also had bad luck using rays so I didnt even bother trying lol
But anyways instead of per frame a monster just expands its search sphere outwards till it finds someone then proceeds to advance on that position
The only downside of this system is that my arenas would have to be relatively horizontally designed
do you continue to send if someone remains in the 0.2?
oh sorry that's the interval you increase it, not the radius, but i think the question is still relevant
Once the sight system collides with a player it immediately shrinks back down to a size of 0 then begins to expand outwards again, every 0.2 seconds it increases the sphere size by like... 20 i think, cant remember but it does the sizing in chunks instead of linearly expanding in size
If a player is really close to the monster, it will update the players position every 0.2 seconds, if its far away, it will take a few seconds for the monster to get a vector 3 on the player
You could use that profiler thing to possibly see what scripts are taking up the most resources to run
having my internet ko'ed by lighting, it's occurred to me there is a really great way to test how your world performs under unideal networking conditions: do your world testing while connected to a phone hotspot
Oh yeah - there are some tools to simulate slow networks I've used for professional work stuff, absolutely a good idea
gonna dust off my dial-up modem
huh I wonder if I still have a modem
@silver hill the LoS checker that I'm using that seems pretty performant
Takes the range to check, FoV to check, transform of the monster's eye, and a list of players current alive in the round
looping through the list of players it first checks if the distance between the eye and the player are within the range; then it checks if it can linecast to the player; then it checks if the player is within the FoV; if all true it adds that player to a list of "seen" players
then that list gets sent to the monster
limit the amount of colliders/triggers you're using; they can be expensive
and I suspect resizing colliders is expensive as it likely involves making a brand new collider
Interesting...
I dont really wanna make this a stealth game using Line of sight though
I recorded this, Im not too sure what to make of this information, can anyone provide some insight?
Me and a friend just watched the monsters in the test arena then killed all the monsters and just waited around to see how the game performed
Lag slowly got worse and worse even after all the monsters were killed and returned to their object pools
very simply, you are sending more than ~8.5 KB/s, so the queue builds up continuously until it gets worse and worse
queue?
yea like unsent data
So theres like a hard limit per second that VRChat can handle?
Righto, so Im guessing the list states the objects that are the biggest culprits?
i guess
So all these objects sending 12b... some of them are off, why would they be sending data? :L
they are not currently sending, it may be historic data
I see, that makes sense
that "Size" is how much that script would send if and when it is serialized
I guess maybe scripts that dont need consonst serializing could be turned to manual
But then im not sure how the manual mode will affect network custom events...
is this debug menu 6 or 7?
they show the same info, but 7 sorts it so the objects with the highest impact are at the top
that's odd. should be able to use both
(7 didnt work for me either)
Maybe VRChat updated so that 6 is just the default and only option?
would be odd to remove such a thing bc that sounds useful to have
the network clogging and lagging should kinda be two different issues though
networking alone shouldn't be causing performance drops
Yeah, the drops only seem to come when someone joins
It doesnt seem to stack with more people joining, at least from what I can see
I had 2 joiners wait around while I did some testing in a game
ive run into a very strange issue and just want to see if anyone notices somthing silly I may have missed.
I have a old function PlayBtnClick() there is nothing wrong with this function and works exactly as needed.
I have a new function I am migrating too called playBtn()
in this new function it is casuing an out of bounds issue with an array on the object owners side, only if a non owner runs the function. testing as revealed that both functions provide identical arguments to the server. so there must be some other reason for the crash not directly related to the code itself.
attached images are of the code of both functions and the crash location the location of the out of bounds crash is line 1105 in the 3rd image
i think at some point you might need to restructure the whole project, if it is too big and too chaotic that it cannot be debugged
that list includes everything with a UdonBehavior set to any networking mode besides "none" even if they are not actively sending data; you want to look at the Bps column to see what is most consuming your networking
what is the function in picture 3? and what does the error say
index was outside the bounds of the array
both functions end up running through that code and both provide the same arguments so im trying to figure out why one is causing the array size to end up differant in some way
what does it say, which array
also for debug menu 4, the "suffering" there seems to represent the amount of network syncs that haven't gotten sent out and are stuck in the queue; you ideally want this at a solid zero; if it's hovering around like ~100 or below, it's unideal; if it's growing then there are likely some network syncs that will never ever get out
one sec going to double check
appears to be h.cards
I see, I mean I did see the enemy AI test working through the eyes of the host and a joined player they seem to keep up just fine
the enemies perform there networked attacks on time
Can you define the "lag" you are encountering? Is it network or fps lag?
you're probably running host-only logic that is tanking the host's fps
that is completly seperate from network performance
Ye
network performance should have zero impact on player fps in VRChat
I made a gameobject destroyer script that lets me single out stuff thats lagging
the player hitbox was a big hitter but cant seem to find anything wrong with the logic
are you manually giving players a hitbox that you move onto their location every frame?
I was but now its every 0.1 seconds
try using this instead: https://creators.vrchat.com/worlds/udon/players/player-collisions/
Udon has three ways to detect when a Player and an Object Collide - Triggers, Physics, and Particles.
I also iumplemented changes suggested by Tekton, that helped a bit
The hitbox also acts as a visual representation for the host to target their AI enemies with
it does a few things
revive mechanics
Incase you wanna have a look
I take it that's a VRC Player Object?
Yep
moving it onto the owning player's current position should be relatively cheap and should cost equally as much for everyone in the instance
hmm check if it is big enough etc, i dont have the full code so idk
if you have gpt maybe you can upload and tell it to check
my main advise from the little I've seen is to try making the monster chase based off a distance check to living players in round instead of a collider you're changing the size of
added some logs but they raise more questions then asnwers
looks like its a perfectly valid array index so wounder why it thinks its out of bounds
I think maybe the function might be getting double called somehow
yah, what is at that line?
which i?
h.cards[i]
the i in that line is on col 62
after checking closer it does apear to be double calling
this is normally true, but if you encounter suffering that doesn't resolve (you request serialization more often than it can send) which grows a list or queue, left unchecked it will eventually drop fps
it played card 46, sucsessfuly prosssesed it and imeditaly tried to play card 46 again onto the same spot it allready exists
is it caused by too much memory being consumed by the growing queue?
So now the question is why does the new function end up double firing?
or is some of their code looping over the queue?
if it's a list it could be pretty expensive to pull things from since it'd shift everything i think? if it's a queue i don't know what they're doing
i don't think it's memory either way
at the very least it doesn't seem like a single queue as some things can get stuck forever while other behaviors are allowed to go straight through (but that could be explained by each individual behavior or group of behaviors getting its own queue)
so I have this object pool with manually synced scripts on the objects but when I call RequestSerialization it doesn't serialize nor does it sync for late joiners unless the objects have been turned on and off at least once
Are manually synced behaviours synced to late joiners, or will that first happen on the next serialization?
they are synced
ah, that's nice, was kinda expecting them not to be
When someone joins your world, the OnDeserialization event will fire for every Networked Object in the world with the latest data, and they'll run whatever logic you have in place to update things based on that data.
yup, that was what i was looking for
are the objects always enabled?
what do you mean
of course they are not always enabled otherwise I wouldnt have an object pool
I enable them before calling RequestSerialization if that's what you're asking
You're using the built in VRCObjectPool component?
yes
coolio, can you send the contents of the script that is refusing to sync?
or is it too big
namespace Diplomacy
{
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class PlayerNameDisplay : UdonSharpBehaviour
{
[UdonSynced] private int playerID = -1;
public TextMeshProUGUI DisplayText;
public override void OnDeserialization()
{
Debug.Log("Deserialized ID : " + playerID);
UpdateDisplay();
}
private void UpdateDisplay()
{
if (DisplayText == null)
{
Debug.LogError("PlayerNameDisplay not linked to a DisplayText!");
return;
}
if (playerID == -1)
{
DisplayText.text = "Loading...";
return;
}
VRCPlayerApi[] players = new VRCPlayerApi[VRCPlayerApi.GetPlayerCount()];
DisplayText.text = VRCPlayerApi.GetPlayerById(playerID).displayName + " " + playerID;
}
public void SetPlayerID(int id)
{
Networking.SetOwner(Networking.LocalPlayer, gameObject);
playerID = id;
UpdateDisplay();
Debug.Log("Serializing " + name);
RequestSerialization();
}
}
}
OnDeserialization never gets called unless it's been returned once
they are enabled by default in unity as well
Did you build and test with more than one client?
those are the first players logs
do you have any other script components on the same gameobject?
no only the canvas renderer and the textmeshpro
This is.. interesting
disabled gameobjects or scripts don't receive deserialize right?
I wouldnt expect them to the problem is that they behave differently whether they had been activated before
anyways this was supposed to be a quicker way to do it than doing it myself but at this point I'll just do it myself
yeah it works great now I should have just done it to begin with
but now it leaves a gap..
since the text isnt getting disabled
I can just make it ignore the layout when it's blank
i tested it just now and disabled scripts do receive deserialize, but not if they start disabled (they need to have fired start)
vrc's pool disables the objects initially, prior to start presumably, so that might explain what you were seeing
just so you know that will not work. you are setting the Owner to the Local player always. for everyone. since OnDeserialize will always run for all people including those that just join. so its going to override everytime its being called
I don't set the owner in OnDeserialize
oh nvm it was hard to read it since the formatting in discord. where are u calling SetPlayerID ?
in an event
thats runs when?
and what are you trying to do.
as I said i've already gotten it working, it's just a list of players that have clicked a button
why exactly are you setting the owner and having a UdonSynced on a private? you can do it without needing networking.
how do you suggest I do it without networking
since you are using Vrc Player Object. you can manage it all with a Manager. that only exist once and is always owned by the master / instance owner etc.
I am not using player objects no
because it would make no sense?
there's a pre defined finite number of players that can join the list
using player objects would also mean I'd have unecessary objects for everyone who didnt click the button
how many people are you thinking of? in terms of how many can be in the world versus joining?
80 vs 7
and what are those 7 gonna be doing?
playing a game
using player objects would also make it needlessly complicated to have multiple instances of it running
only those who have joined would cost anything.
the other 73 wouldn't require extra and their objects would be disabled. but then again with 80 people its likely not gonna be synced considering the Data limit of 8-11 kb/s 8.5 kb/s with the upcoming update we have in beta atm.
unless those other people cant see the game? but i presume they can?
they can
also what kind of game? a card game?
I just want to say I hate that game but also it's awesome and I think it's great that you're making a VR version 🙂
ahh that type of game.
do you know how many objects each player will own @prisma lance ?
that needs to be synced.
every piece is properly synced already
So I changed around the enemy Ai Udon behaviours to manual sync again, I did a playtest using both my accounts in a game so i could see their perspective and it was working just fine, a little bit of delay but that will be fine for all I need, but then after that game I opened up this test arena to watch to see if the enemies were animating correctly, for the joined player they were in idle animation for most of the recording then at the end I saw a few of them begin to do animations, very strange.
Im guessing in manual sync networked events just dont work?
I had the thought maybe if I send out a networked event to tell everyone to request a serialization that could update their animation but how can i do that if networked events dont send under a manual sync
they should work in either, i've only used them under manual. i'm not really sure what you mean by telling everyone to request a serialization, only the owner can serialize synced variables and everyone will receive the latest udonsynced variables when it's distributed
network events send under manual.... but only the owner can RequestSerialization
network events work with manual sync but there are a bunch of footguns with combining them together because they take separate paths and work best with vastly different strategies
if you're having trouble with synced data not getting applied correctly on the receiver, you should NOT solve it by asking the owner to send it again. There's two possibilities:
- The owner failed serialization and it never got sent at al
- The late joiner has the data but applied it in the wrong order
The former should be obvious and doesn't sound like what you're getting since it is generally working, just one small bit isn't. That points to the latter. And in that case, you just need to review your codepath coming from OnDeserialization and ensure that it's applying things correctly in the right order
Im currently not using a deserialization node
are you using onvariablechanged?
nope
then how are you applying synced data you receive from manual sync?
Just using networked custom events to tell everyone "This animation is now playing, play it"
it works perfectly under continuous sync but the performance drops for the host
yeah don't do that, it's redundant and may arrive out of order
the reason it works under continuous sync is because it blindly sends over and over again which technically makes it work but is wasteful
I had the idea of after every behaviour change it would send a network event to start a request serialization
But now Im not even sure of the correct way to use serialization lol
Synced variables are one thing, network events are another. If you receive an event saying "an animation is playing!" and then you go to check the data and it's empty, you won't be able to do anything. Then half a second later you receive the data but you never apply it
That's what OnDeserialization is for, it fires when the data arrives. You don't need to send a networked event to tell people they have received data, because by definition the network event has no idea whether or not they have actually received that data yet
So sending a custom networked event telling everyone to change variables and play animations is wrong? :L
requestserialization is something you want the owner to do when they set data. It kicks off the process to send data. It's not something that receiver clients need to concern themselves with
So... request serialization could be sort of considered a networked event in itself? and then on serialization is the output once received by joined players?
Re the video: Your sender is suffering, meaning the send queue is building up. In my own tests if sender is suffering, I can observe on the receiver side the deserializations will no longer happen in real time (or real frequency) but will be in bursts at variable intervals, I guess probably because the queue is sending big packet followed by some smaller packets. And since your queue is building up, it means you simply are sending faster than your bandwidth allowed, at least for a period of time, and everything afterwards are delayed or queued up. Which explains why your late joiners are not receiving the latest information in time.
Not technically, but in essence yeah requestserialization sends things over the network
There are a few reasons why requestserialization isn't exactly a network event, like how requestserialization is a 'request' which just flags to the internal system that stuff needs to be sent. It doesn't directly send a network event which means the number of ondeserializations that get received are not guaranteed to be the same as the number of requestserializations. If you fire it rapidly, it will throw away most of them because all it cares about is the most recent state, not every intermediate state
But that's what you want because most intermediate information is unnecessary for the purpose of syncing the world. In the rare case where you do need to explicitly define intermediate events to show how you got from point A to point B (such as firing a gun), that is what network events are for
im just strugling to figure out why like 8 enemies walking around can cause a network snag, how optimised do I gotta be damnit! lol
if you're suffering with 8 enemies you've probably got something wasteful hogging bandwidth when it doesn't need to
how often are you doing requestserialization in the most extreme case?
Almost never
ive strictly been using custom networked events to trigger the animations/attacks
how many network events do you send per second?
2 per enemy at most
that should be fine
In this video it shows GameStarter is 54 BPS, do you have any other objects which show a non-zero number?
the Ai goes through a host computized cycle every 0.4 to 0.6 seconds, everytime it goes through a cycle it increases an integar by 1 and goes up to like 40, once it gets to 40 it resets to 0, as it increases if it goes to a certain number count it will trigger an attack
The gamestarter shouldnt be doing anything during a game?
it appears to be sending once per second
Just doing a fixed update for debug display data
once per second 54 bps isn't a problem though
I can only think the enemies are going through their ai cycles at a non uniform time so maybe the constant mini updates are doing something?
- Request serialization is for syncing (you can call it sending) variables, as in stored data like int or float or bool. As in sending a parcel. When the receiver / late joiner gets the parcel, their Udon will automatically fire OnDeserialization for you, so you can so stuff if you want. These data are available for late joiners.
- On the other hand "sending" a custom network event, is not sending anything, there is no parcel, it is just telling the other guy to do x (i.e. Event X is triggered). Late joiners won't receive these if they are not there when Event X triggered.
- The two are distinctly different ways of "sending stuff", they are for different things. Just wanted to repeat this just to say, you have OnDeserialization to let know you have received data. You don't need another Event to tell them.
I dont know the capabilities of computers really so Im not sure
you already have this debug menu pulled up, that's a good place to look for excessive network usage. Look through it to see if there's any concerningly large numbers
I dunno man, pretty much every object is just 12b I assume thats small
and thats just the ones that are active
start with deleting all the enemies, put only one and see if you have problems
you delete half of the world to find what is hogging, and then repeat basically
yeah, the enemies sound pretty light weight
Il start it up and just lounge in the lobby and see what happens
can you send all your scripts
Just recorded a benchmark
The giant hand in fast motion sounds like a machine gun that also splatters spiders 😛
Looks like it is the enemies causing the issues
I hope these network issues dont stack the more players there are? :L
I like to think the host sends out the data in parralel rather than one at a time?
- how many enemy types are there? just two?
- no they dont stack with more players per se
- parallel or one at a time: depends on your script...
I dont know the terminology I meant I assume the host sends out the updated data to everyone at once rather than one at a time
Each enemy type is different and i plan on having many different enemies
As you can see by the debug spawner Ive already implemented 8 enemies
They all use a different script but they all work very similarly
why is this networked?
So the hand has 2 momvement animations, normal walk and charge, that networks (hopefully) the 2
can this be triggered many times in a single frame?
should only go off on trigger enter for the attack trigger
and even if it did i made an isattacking bool to gate more than 1 attack at once
hmm i dont see anything obviously network hogging in the scripts
ok
i think it has more to do with your player joining, it jumped from 0.5 KB/s to 1.5+ KB/s
which means every player takes up 1/8 of your bandwidth, that seems a lot
i see...
the out: 0.5 KB/s jumped to 1.6 KB/s
well the hitboxes will be running but those too arnt doing much
but apperantly they do
oh? i think i uploaded the hitbox script earlier if youre interested
or why is the host sending so much data to a new player
are there other scripts running? for networking
hmmm
player hitbox and player persistent object are all i can think of...
perhaps all the debugging is uneccesarily using data?
but thats all local i would have thought...
btw in your spider, none of the variables are marked as synced, so nothing is sent when you request serialization
its intended only for the host to use, thats why
then you wont need request serialization
ye thats what i thought, i probably addedit trying to fix the issues
ok on your P HITBOX 2...
every 5 frame... you are sending a bunch of network events, that is a lot
hmmmm, but theyre gated arnt they?
ok at least one, in lobby
i gated all behind an isowner bool, so i thought everything would be ignored for everyone except the owner
idk, maybe there are other scripts
if you have a script sending enough to cause clogging, and you gate it so only one person does that... then you're still going to clog that one person
the game is fine locally, just dialing in the network issues really
very functional game in singleplayer
why would the host suddenly generate an extra 1 KB/s when someone joined... u need to look at all the scripts i think
vrchat reduces some outbound networking when you're alone so that could account for a small amount. But if it continues to add 1KB/s again for every new player, that would be a problem with your stuff
damn... gunna have to parsec another account CAUSE I HAVE NO FRIENDS! :`(
would that be accurate since its essentially an internal LAN?
local test still goes to a server and back, it's accurate
it goes through actual server i think
^
And btw, using frame count as tick rate can be inaccurate because one person can have 40 fps while another have 200
for the P HITBOX 2 in particular
I see the hand and spider scripts, where's P HITBOX?
i just did it to reduce local load, didnt need to be accurate
@frozen igloo it was from a bit before #udon-networking message
@silver hill Well you are still sending network events based on it, and judging by dev menu 6, the max rate vrchat can network seems to be 20 Hz at most, so if anyone is running at >100 Hz it will exceed the limit quick, and send events faster than it can handle
That is assuming the packets are small enough, and under ideal conditions
i see
20hz is the base network update rate, but it can bundle multiple messages per update. That's not where the limit is coming from. That said, yeah it still seems like an excessive number of network events
seems like you're using network events for things that really shouldn't be
lots of wasted bandwidth doing it that way
anything where you are repeatedly sending the same network event in order to ensure that everybody has heard the message is a huge red flag to me. Those should be variables
similarly, anything where you receive a networked event and use it to immediately set a variable and do nothing else... should just be a synced variable
This is a pretty small, reasonable amount of variables. But by sending them through network events you are making your own architecture less reliable AND more expensive
interesting, ok
can i suggest a duct tape method:
check if clogged or throughput bigger than half before doing stuff
and only do stuff if it is not
(the graph is wrong but you get the idea)
ooof, will need to learn what those nodes are
please do not repeatedly requestserialization at a fixed rate 
that defeats the whole purpose of manual
just... requestserialization when a variable changes
ok
- my understanding is, isClogged means the queue is forming and you are no longer sending stuff as fast as possible
- throughoutPercentage is a new node only in beta SDK, 0.5 means it is maxed and being throttled (and queueing, you dont want to start queueing at all)
- there is also a suffering stat node, which seems to go up to 100 and related to queue size
- what it means is, stop doing things when you are over a self imposing limit, you will never suffer
ooooooooh! saucy! so i could make it so worst case some attacks wont trigger
yeah something like that, make the mobs slower or something
along with proper optimisation of course heh...
yes this is really a duct tape method, it dont fix your problems, it just hides it
is there a way to just yeet the queue alltogether?
no way, you gotta wait it out
boooooo... i guess inter game time could do that
got it, il spend tomorrow implenting changes based on your feedback, thanks for your time so far, @frozen igloo @fallow mountain @twin portal
I just updated my SDK and noticed this, I assume a value of 0 means unlimited? or should I set it a finite value for it to work at all?
it's 5 by default if it's related to network events (i'd assume 0 means 5 in graph)
no, 0 means it is a "legacy event", and the custom event will operate in the same way that the "old" custom events did
Definitely improved the situation, I can have probably 4 times the enemies in the arena now and it can keep up
Just the slight issue of the suffering increasing even when I kill all of the enemies... wonder whats causing that to happen
It only seems to start going down when the joined player leaves...
with the new network event rate limits it will display suffering if they're trying to send more than they're rate limited to; your main networking isn't actually clogged there, it's some specific event or events
you're sending some events too often
unless you actually need them sent that quickly, but i'm not sure what you'd need in the lobby
yeah networked events
With no enemies walking around I dunno what else can be causing it to grow
I adjusted the hitbox to have a slower update rate
since it doesnt need to go so fast, i also gated more fixed update events thought that should only really improve local FPS performance...
the beta also lets you view how many events are in the queue, so you could use that and post it somewhere and see if the queue is continuously growing
Oh? how do I do that, that sounds hella handy
Does it also say what objects are sending them?
kind of
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.
you can use those two functions to either view the entire queue, or just the queue for a specific event
also can you sorta describe what/how you're gating logic? fixed update can fire multiple times a frame, it's not really ideal to do logic there relative to update unless you need to do the logic in fixed update for a specific reason
Just a simple "Do you own this object" check
if you dont, you dont have to do all the other stuff
why would a hitbox impact the networking?
Cause it does alot more than just act as a hitbox
The game will have revive mechanics so it also needs to tell other players that the owner is dead and needs reviving
It also externally tracks the players status
What you're describing sounds like a player manager
Sorta
I might change how it works, its an early implemented feature that Im not sure even works anymore
the hitbox itself shouldn't have any networking tied to it; for creating a revive trigger you can use a combination of player position and a "isDead" and/or "canRevive" bool to know when and where to trigger it
Yeah will likely change it
hey i'm new to this style of networking, i've watched a few tutorials and saw some code but essentially whenever there's a state change for a variable in U# is the pattern:
Networking.SetOwner(Networking.LocalPlayer, gameObject);
// Critical Section Changing Variables Here
RequestSerialization();
So any time a different player wants to change something, it needs to request ownership because only the owner can modify variables? How does this work if both do SetOwner at the same time, would it just not update properly? Should there be a mutex on the set owner? or does it wait?
the setowner will assume it succeeded if you just call setowner
race conditions can happen, but generally one player will "win" and the other will roll back their synced variable changes that weren't valid i believe
there's a way to reject ownership transfer requests detailed here, it's a graphic a little bit lower
(it's important to note that i believe this still assumes the ownership succeeds, but fires back OnOwnershipTransferred if someone rejects it)
https://creators.vrchat.com/worlds/udon/networking/#requesting-ownership-advanced
if you have some important state you want to modify and can wait for the owner to update it, it's better in some cases to pass the intended change to the owner via network events with parameters (which allows you to send data up to the owner)
you could probably also explicitly ask them to transfer ownership to the new intended owner for a bit more control over it yourself
this is on the beta currently though
https://vrc-beta-docs.netlify.app/worlds/udon/networking/events#sending-events-with-parameters
so for things without a lot of data if i want to ensure both messages get through (right now) i could do something like
while(Networking.LocalPlayer != Networking.GetOwner(gameObject)) { // should only be a frame or two
Networking.SetOwner(Networking.LocalPlayer, gameObject);
}
// critical section
RequestSerialization();
i'm not sure if that'd just hang your game or not, but it should succeed regardless of you winning the race condition or not. i personally don't like the idea of putting that on a while-loop. i believe people would still receive the variables you sync out after an ownership change regardless of who wins, but synced variables are a singular state
so if you had something like a score counter and incremented it on two players fighting for ownership it'd fail to account for their scores sequentially and just apply once if that makes sense
i.e.
player A increments from 0 to 1
player B increments from 0 to 1
the resulting score is 1, not 2
if you do need something like that (a delta instead of an absolute change) it'd probably be better to send that delta to the original owner
could always do just a for loop with like 3-5 retries
i'd want that to be 2 though :/
yeah server client could be a better approach
the [[UdonSync]] variables don't seem to be atomic
they are atomic if you read them in OnDeserialization, not if you read them via the variable changed callback
thanks for the help
Also if you want multiple players to be able to affect a variable without throwing leadership around you can try using the new events with parameters that's in beta; although as race conditions will 100% occur it is best to use it in cases where that will not be an issue (for example a counter that everyone can add or subtract from and the order of these subtracts/adds aren't super important)
UdonSynced variables are only ever guaranteed to be eventually consistent. Intermediate states can be dropped, because the spec only guarantes that everyone eventually has the same state.
when accessing a vrc object pool's object through it's index am I guaranteed that it's the same object for everyone?
I just recently needed to pull and return objects from a pool, and indeed I had to change ownership around like crazy. How do I use this beta features? I'm quite noob yet, tried Google but couldn't find it 😅
You using the VCC?
I tried sending the var to the owner but the pool HAD to be owned by the returner that was calling the function. Yes I'm using vcc
are you using the beta version of the sdk?
Oh you change it there? Is the event thing in both graph and U#?
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.
Beta docs, awesome 👌🏻 thanks
Uuhh will this be good for changing a var or calling a return to a pool for multiple players?
I think the variable was working but not the returning to pool, I will for sure test it but just wondering
you can send an event that tells the networked pool owner to spawn objects from the pool or return them
returning to pool, you can send an object id with the event
ReturnToPool(int id)
Oohh I can send the id. Ok I will try it. I did try that to tell the owner to return it but for some reason didn't work maybe is that! But this seems exactly what I was doing, talk about timing right? lol for once the code gods smile at me 😂
Thanks for the help 🙏🏻 I'm on the right direction now for sure. I'm doing a FPS style system with health and all. And actually is working now but this will make it more polish for sure
I didn't like the ownership change one bit, 3-4 frames of pure caos and bugs
I had to place like 3 blocks of code to avoid and sync stuff after every return
Just note that atm VRChat is limited for pvp where the player's position matters; the latency on player position is pretty massive and the only way I know to mitigate it is by making players move really slow and probably also limiting network load as much as possible
Oh thanks. Forgot to say is pve so far looks good but the disclaimer is there bc I saw quite a bit un desync between players, but is not that serious is more of a casual shooting system so all good 😁
Still i picked up VRChat dev like week and a half ago and have been really impressed with the modding capabilities feels like a sandbox I love it
it's basically unity with some features disabled and some added
And I've been making a horde survival game, myself
Yeah and the enviroment setup was a breeze I love the VCC so friendly, not gonna lie a tear rolled down my cheek bc of how easy it was
Mmm maybe I should keep you on my speed dial 😂
Do you found a way to not have to pool all enemies before hand? Or that's impossible? I really don't know many network enemy pooling patterns or tricks
Everything that you attach a networked behavior to has to be present in the scene at build time so you can't instantiate anything like that unless the networking is handled by another object already in the scene.
Also you're going to need to sync its active state anyways so I doubt you'd be reducing your code complexity that much
Mmm true, yeah I was all happy using instanciation at the beginning, big surprise 😅. Also my AI is tracking the players via collision, I liked the way you see the sensor zones, so I'm adding a player tracker to each player spawning(a collision box), is there like a default collision on each player I could use instead?
Last question I promise 😂
Udon has three ways to detect when a Player and an Object Collide - Triggers, Physics, and Particles.
Also I use distance to player for detecting players, myself; that might be faster and you can create a sphere wireframe or add a sphere mesh with the distance radius if you want to a debug visual
the advantage of a distance check is that you can choose how frequently the check happens
Genius I will do that, how do you track all player distances?
Ohh
Do you do iterations on all players for all AIs?
I have a DataList that contains a reference to all players that are currently alive in the round and I loop through that
Figured ok awesome, is always so nice knowing you are doing things like other people so I don't feel like there is a wonderful solution I'm not using 😅
Was reading through and noticed this and felt like adding that this is largly only true for people not on the PC platform. There are outliers where PC players are extremely laggy, but generally, you can get away with more tight networking sensitive gimmicks if the lobby is PC only.
This has been tested by me quite extensively where I have made two versions of a world called Murder Classic wherein I have a version that is PC only and another where android users can play. On the PC only version, the death logic is calculated by players themselves seeing if they are within kill boxes and on the mobile compatible version, the logic is if your assailant sees you within a killbox like their knife or gun, they tell you to die… Eh… Forcibly respawn? Yeah forcibly respawn might be a better way to put it.
I wouldn't exactly recommend knife fights, but the latency actually is not bad and feels a lot more fair than say running away from a quest user in murder 4 when they're the murderer, having some distance away from them and still dying somehow (assuming they don't throw the knife)
Even quest users being on the PC platform through like virtual desktop or hard wired via quest link is so much better for latency
How many people were the the instance at the time?
Iirc more than 5
There have been cases where the soft cap of 12 was hit in both cases
I played my worlds a lot when they were new
It might work fine at lower player counts (like sub 16-20) and worlds that aren't too network heavy and where all the players live reasonably close to the relay server and on the players are on the slower side and not moving too erratically
Thats a tall order
The thing was this was consistent. If you don't believe me, I'd recommend doing an experiment
Would personally like to see more data on this like if worlds should have platform aware combat logic
there's not really a perfect tradeoff, if you're basing it on the receiver's perspective a knife might not hit at all when an attacker grazes them because of latency and interpolation; the attacker also has to wait a lot more time for the receiver to understand that they've been hit and send the respawn/death over network if you can't reliably inform the attacker that they succeeded, maybe that helps pacing in some cases; as opposed to firing some sound/particles on the attacker and sending a kill event
for melee i think either can work, for shooting i don't think you can really get away with receiver logic as easily
i don't know that there's a specific distinction between quest and pc when it comes to their send rate, but it could be the case; though it could also be that the average quest user's latency is higher being on wifi? you could verify it with the simulation time of a player when they're on pc vs quest i guess
also note that people you aren't actively looking at will have significantly higher latency; so when entering vision it could have a couple or so second delay
I know Terrors of Nowhere's pvp gamemode is an extreme version of VRC's player position synching not playing well with pvp
High player speeds in fairly open maps allowing for erratic movement combined with receiver-side detection and a network heavy world. Saw someone with bad fps/internet be unkillable.
I thought this was only their IK update rate not overall network quality
I'm pretty sure the way it works, VRC will prioritize what is within your vision for faster network updating (on the receiving side).
they definitely use distance (you can observe this pretty easily by having someone far away teleport near you); i haven't personally noticed directional
I could have sworn I read the other day in the docs that it was based on vision
But the docs are so scattered that it's going to be hell to find it again
good thing I took a screenshot; found it:
maybe this? i don't know that they'd do that specific thing for players
definitely possible
testing it using build & test, my attempts to create latency spikes were so inconsistent that it's impossible to tell
maybe better off measuring the sim time or finaldelay of the player itself
https://vrc-beta-docs.netlify.app/worlds/udon/networking/network-stats#per-game-object-and-per-player-statistics
assuming you can pass a player to that like you can with sim time
The information we are looking for is likely in debug menu 4, but half the things in that menu don't match up to the docs
What are all these "Q" values, for example
basing it off the "final D" (final delay) stat in debug menu 4, it seems to use both distance and LoS
it's still super inconsistent, though
you're right
from what i'm seeing here they start scaling from > ~0.3 dot to the player, i think it modifies where they start their distance scaling
I can believe that
I imagine they want to prioritize good networking for the social aspect of VRC where people near you > people not close who you are looking at > people not close who you aren't looking at
Was just testing the new network events with parameters, I am a bit surprised by its performance
I could send an event carrying a few parameters (floats and doubles) at at least 60 Hz (limited by my own framerate)
The sending and receiving characteristics are very similar and good delta consistency from one event to the next
This is much more responsive and with predictable timing than serializations (much lower Hz and much more variation in deserialization delta times)
It could improve the game a lot for a lot of things
Is there any way to prevent VRCObjectSync from overwriting the position set by the object owner? This is something VRCObjectSync is pretty aggressive about doing on newly activated objects and it's super annoying to deal with.
I see there is a TeleportTo method but it takes a transform, which I don't have for what I am trying to do; just a Vector3 and Quaternion
like you're trying to activate an object and you position it at that time?
you can try FlagDiscontinuity()
I have tried that
the position gets overwritten even if I delay it a frame
I suspect when an VRCObjectSync gets activated it waits until it gets the last know position sync, updates the transform to that position, and THEN starts respecting ownership
Which means I have to wait an arbitrary amount of time, then set the new position, and I guess pray that an edge network spike doesn't screw me over
you could write it yourself in manual sync if you actually want that control, but it might be a bit annoying if you're using vrc's object pool in tandem
I've built my own object pool
I think I could be having a similar problem lol. Trying to spawn items from a vending machine and it's not syncing well at all for me
from what I can tell, the Object Pool needs to work in a kind-of specific way, and tends to break if you push it too far
like there's an assumed way it's meant to be used and starts to break down if you go outside of that
Hey is it me or they just released today what we were talking about yesterday?
yes, the new network events are now on the live SDK
Just as im using them, god timing xD
So I'm trying to determine if an object's ownership transfer was due to the previous owner leaving. Now I had assumed that checking previousOwner.IsValid() in OnOwnershipTransferred would work, but turns out the leaving player is still considered valid at this point.
Would I be able to put into a singleton script a list of players in the world populated/depopulated with OnPlayerJoin/OnPlayerLeave? Or in other words, does OnPlayerLeave always trigger on all scripts before OnOwnershipTransferred can trigger?
i think if they fire in a specific order you might not be able to get around that very easily, i don't know that there's a good solution for that specifically without some sad arbitrary waiting around or checking after the fact. maybe you can work out some way to defer the leave event to the object and check against it (leave fires, you push an event to all relevant objects and check at that point if their cached owner happened to be that leaver)
maybe it works in some way that can consistently check for that, so i don't think you should give up if that's more applicable
not sure what you're doing exactly, but i think you can try to design around like some positive flag so you can infer that transfer if needed
like designing around ownership as the sole factor isn't very clear compared to designing around something like "well they're holding the pickup and they're also the owner"; considering a state more like that on something you're implementing might help
OnOwnershipTransferred(VRCPlayerApi player)
{
isOwner = player.isLocal;
if (!isHeld && isOwner)
{ // Player became owner without a positive event/trigger }
}
OnPickup() { isHeld = true; }
OnDrop() { isHeld = false; }
Update()
{
if (isHeld && isOwner)
{ Some sync logic or something }
}
i guess i didn't really answer the question and i don't know the answer as to the order, but i'd assume it's consistent (one firing before the other and gets sent to all objects subscribed to that event i'd think); so maybe in your case it's more clean to just check in the leaver event if the leaver == cachedOwner and decide what to do at that point
OnPlayerLeft(VRCPlayerApi player)
{
if (player == cachedOwner && Networking.IsMaster)
{
// Some logic for a leaver's object, master should become the new owner if master check works as intended here
}
}
OnOwnershipTransferred(VRCPlayerApi player)
{
cachedOwner = player;
}
what is working so far is this bit of code withing the OnOwnershipTransferred:
if (!_gameManager.IsPlayerInWorld(_lastOwner))
{
//Stuff
}
else
{
//Other stuff
}
meanwhile in the GameManager singleton:
public bool IsPlayerInWorld(VRCPlayerApi player)
{
return Utilities.IsValid(player) && _playersInWorld.Contains(new DataToken(player));
}
where _playersInWorld is a DataList the populates/depopulates with OnPlayerJoin/Leave
i think personally i'd check the contains against their player id rather than creating a reference to their player, are you saying that ownership transfer works properly then?
Also my OnPlayerJoin is a bit funky to support late joining and edge cases; haven't properly tested it yet:
public override void OnPlayerJoined(VRCPlayerApi player)
{
if (player == Networking.LocalPlayer)
{
_playersInWorld = new DataList();
VRCPlayerApi[] playersArray = new VRCPlayerApi[VRCPlayerApi.GetPlayerCount()];
VRCPlayerApi.GetPlayers(playersArray);
foreach (VRCPlayerApi p in playersArray)
{
if (Utilities.IsValid(p))
{
_playersInWorld.Add(new DataToken(p));
}
}
}
else if (_playersInWorld.Contains(new DataToken(player)))
{
LogWarning($"Player {player.displayName} already in world");
}
else
{
_playersInWorld.Add(new DataToken(player));
}
}
It would make no difference, right? We're still checking the same thing.
yeah it makes no difference really, it just looks nicer than doing an inline new datatoken i feel
I've long, long ago given up on trying to make DataToken stuff look pretty, lol
does your bool function work as you'd expect? like player leave fires before the ownership transfer?
That's how I got the idea. When I had the test player leave, the OnOwnershipTransferred errantly tried to access information that had already been removed by my OnPlayerLeave
I'm going to try a new networking design principle: all objects with networking behaviors on them should be active on scene load and never be disabled. If that causes issues, find a way to work around it.
that's how i tend to handle things that can become "inactive" either through setactive or a combination of components
i usually cache the transform i treat as the main transform and it becomes pretty much the same as operating on the transform if you remember to replace transform
[NetworkCallable]
public void ReceiveSync(Vector3 pos, Quaternion rot, Vector3 vel, int state)
{
Debug.Log("[AI] ReceiveSync triggered on player ID: " + Networking.LocalPlayer.playerId + "Position " + pos);
syncPosition = pos;
syncRotation = rot;
syncState = state;
velocity = vel;
}```
So im trying the new events and im trying to send this 4 variables over the event, on my client they are changing(im loggin the receivesync on all players) the other clients are logging the recivesync event that is called on the lateupdate excluisively. but the values on the params im sending remaing the same does anyone know why? im doing something wrong?
transform.position is the position of the game object the script is attached to
not the player
YEah this goes into my AI object, so thats good, im using that position to move itself the one calculating the "destination" is the master and is passing teh resulting movement here
So I only calculate the navmesh on the master, and this are synced over the new events... but Im even jsut debugging like pos alone and see if it changes but only on the local/master player sadly
Oh quick tip, if you're using VRCObjectSync, have it as the only networking script on the parent of your object group and put all other network scripts on children objects
That was a doubt, I really dont have a objectsync bc I was "manually" updating its position, but Im not even able to send the position properly, like it prints no changes on the clients
VRCObjectSync has some capability to turn off networking for its object and having other networking scripts on it can mess up its ability to self-prioritize, severely reducing networking performance
Do I need to make my AI like a child of an empty with an sync to work?
yeah actually I get a warning if I use it. So I wasn't
The actual function is being called I can see the log activating it from the master but no params are sent
im only calling it from behind this if (Networking.LocalPlayer != null && Networking.LocalPlayer.isMaster) so i know is being called properly
also did you check if there is any suffering happening on your network when values don't appear to be changing?
on debug menu 4 this should be zero or hitting zero occasionally
because you're trying to send out networking data at a rate faster than VRC allows
so unsent networking updates pile up more and more
mm only on the master tho still not normal?
I assume only the master can make it go up since is the one sending it
if this number is never hitting zero, you can have network updates that will NEVER get sent out
Oh god it nevers goes 0 is going up almost as fast as my bills
you want to have this at a solid zero during periods when up-to-date networking is desired
Each person has their own independent rate limit applied to outbound traffic. Suffering is an indicator of how much is piling up behind that rate limit.
Maybe is bc im calling the event every frame?
yes that would definitely do it
is sending just like a vector 3 tho
lol xD i read it could send 4 bites easily and ssumed wrong 😛
it can, just not every frame
I've been experimenting with getting around the limit by distributing my ai among the players in the round
I'd recommend aiming for 10hz max, at least to start. And if you need more than that, pack multiple sets of data into one event at a time
mmm so what is a good practice when calling this event? like mine is just sending this SendCustomNetworkEvent(NetworkEventTarget.All, nameof(ReceiveSync), transform.position, transform.rotation, velocity, (int)currentState);
you're hard limited to sending out about 100 events per second; so if you have 20 AI guys sending out events 10 times per second, you'll be trying to send out 200 events per second and hit the limit by double
mmm I see rn my clients were on suffering 0, but I only had 1 AI, before asking more I will set this to send 1 event every 60 frames and see how it goes
when it comes to positional data, my own experience leads me to believe that events are the worst way to sync that data
network events are designed for events, synced variables are designed for state. That has a variety of implications in how they are implemented and what they are best at being used for
probably just want that position to be continuous synced and set it to lerp
I'd personally recommend starting out with VRCObjectSync and maybe going with a manual sync solution if you have a specific need
how would you do it? im currently sending like 2 positions and lerping so sending 1 position every second prob will still look good
I shall once again link the sacred texts
Events vs State: https://www.youtube.com/watch?v=Na95i4ZD68I
Precise timing: https://www.youtube.com/watch?v=h47zZrqjgLc
Presented by Wayne Coles at SDC 2023 as part of the Code stream
Most games have some form of network functionality, and most game-engines come with a framework for serialising across the network.
Network teams will generally focus on friends, matchmaking and other online services.
This talk dives into network practices for games written with ...
In this 2011 GDC session, Bungie's David Aldridge discusses the programming that drove Halo: Reach's online networking.
Register for GDC: http://ubm.io/2gk5KTU
Join the GDC mailing list: http://www.gdconf.com/subscribe
Follow GDC on Twitter: https://twitter.com/Official_GDC
GDC talks cover a range of developmental topics including game des...
what I'm doing in my own project is having a VRCObjectSync and NavMeshAgent in the parent of my AI object, and then putting the AI script into a child
The most important thing is to not consider network events as raw packets. They're not, they have a bunch of infrastructure on top to ensure that they are reliable. That makes them awesome when you need a singular, reliable event that you can guarantee the other side receives - but they're waaaaay overkill if you just want to sling some position data over
Would they also be good for regular small changes to a large array so you don't need to sync the entire array every time? I'm trying out an event driven object pooler for a pool that has 300 entities in it
That's not a bad use, yeah. As long as you occasionally sync the whole thing and ensure that the events applied to it are deterministic
Is gonna send more in the future for sure, rn im just trying to make it work, but I getting the suffering to rise even when is off also I got like 0 scripts anywhere else that send stuff over the net
So idk what is making it go up mmm I guess I need to look somewhere else
if you just want something to work, then the first thing is to not send network events every single frame
lock it down to a specified frequency and then play with that frequency to see how much you can get away with
or build safeguards that listen to Networking.IsClogged to stop or slow down what they're doing
Yeah this was the simplest I could think of but still is not that event, just set it to run 1 per second is still staggering, I will do more test and maybe can be back with what was happening, at least you guys point me in the right direction thanks ( I have been doing this for like a 4 days tops)
try looking in debug menu 6 with RSHIFT + ` + 6, it'll tell you networking info on all networked objects in the world, should be able to see what scripts are still sending data, and how often
If the data you're sending out is over a certain size, it will turn from one event into multiple behind the scenes
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.
YEah I read that but is like 4 vectors 3 and an int, and for this purposes it was just a vector so I doubt it but yeah also I will keep an eye on that
this should count as 3 events behind the scenes if I understand correctly; and trying to send a position + rotation in an event will always use at least 2 events
wait no, I'm wrong, lol; ignore what I said
so this is my net use on the staggering local player no info is being sent if im nto mistaken?
or im reading it wrong?
I think that readout only shows synced variables
it's primarily documenting "serializations" which is where it packs up sets of synced data and sends them off. Network events are a little bit outside that system so this readout doesn't cover them
Oh no events got it, btw @frozen igloo I didnt noticed we are on the chat with haven... im working on their thing
yeah you found the right place after all 
I didnt want to bother you and here im lol
well better to ask in here because if I'm not around other people can chime in
I actully got a functioning one with udonsync stuff work okish, but I want to make it more robust so here im xD
But is like 8 scripts lots of crap I put it up as fast as I could bc I didnt want to let them hanging but now that there is a working prototype I want to take my time with this one
Like you look at it for too long might crash xD
yeah, that's the perfect place to be for watching those two videos ^^
4 vector3s plus an int is 52 bytes. if "every frame" means 60hz, then that's 3 kilobytes per second for the data raw. The overhead on top of that of serialization, headers, string for the event name, it adds up quick so it's probably a lot more than just 3KBps, probably more like 5KBps. The global limit is flexible and hard to put a single pin on it as it too varies depending on the structure of the data you're sending over, but a general guideline is that over 10KBps is way too much, and avatars already take up 3-5KBps too
Mm yeah I jsut implemented a 1 per second interval lets see how it goes
Also it seems pretty deterministic without the need for any extra synching
The owner on the object pooler is still the only one allowed to pull stuff out of it to prevent race conditions. When an object is requested from the pool, the owner selects an index and then puts that index into a networked event. The network event is sent to everyone, including to the owner, and it is that network event that activates said object by the index and sets the correct value for the parallel bool array.
oh nonono, it is because he didn't do [NetworkCallable(maxEventsPerSecond: X)]
it defaulted to 5
so the queue just builds up without actually using the bandwidth
you could probably use a bitmask (byte array, 1 bit per state), it'd only be like 38 bytes for 300 objects
if synced variables get clogged, they start throwing away some packets (though they do ensure that the latest state is synced, they throw away old packets that are no longer relevant). If you use network events for position data it's saying that you care so much about this position data that you NEED everybody to know not only the latest state, but also it's entire history, even if there was a brief hiccup in yours or their internet, you need them to have EVERYTHING.
If this is a card game, yeah, absolutely! Individual events can be super important. But a large amount of data for games is not. Even in some situations where you think it's important enough for that, I'd encourage you to still attempt to bake your high-bandwidth items down to be state based, because that indicates to the systems getting your data from point A to point B that a lost message here and there is fine, as long as the current state is correctly received
i could send at 60 Hz with a few parameters easily (with the limit set to 100), so the bandwidth should not be a problem, my frame rate was actually the bottleneck
I'm going to assume that a int array acting as a byte mask and an equivalent bool array are going to use up similar networking bandwidth behind the scenes
I could be wrong but if you run into that maxEventsPerSecond it should hit an internal limiter on the event, not global suffering
it does actually increase suffering if you push more events to something than you're limited to
theirs being 5 that's why it said suffering
I think in my testing it still calls the event and queues it, but the events only leave the queue at the MaxEventsPerSecond rate
oh yikes, well don't do that lol
it doesn't globally suffer
it just displays suffering, other networking isn't limited
from what i observed
Yah, from what I've seen it appears that suffering more-or-less just represents the number networking events that failed to get sent out on the last network upload
I guess events behind that limit are counted in the thing that calculates suffering, which is not something I would have expected to be done but it's probably not a bad idea
Ok pls dont hit me guys xD.
It turned out when I limit it to 1 per second it was solved, I was just launching "test my last build" LOL but stuff is going through, no suffering is happening and all is good thanks guys. Now my AI is not actually moving but that I can solve
overall philosophy on network events vs state still stands, but yeah it did seem a bit overly tight to what you were trying
you're using 2 arrays? sorry i'm not sure i understand
Can events arrive out of order? Or can they be reasonably be expected to arrive in the order that they were sent out?
From what I understand they're expected to arrive in order, but I don't know how strict of a guarantee that is
Is all fixed and I love it 😄 thanks again guys, I was just sending data like a maniac that was all (im kinda new to networking and I abuse the web like is my local machine xD)
I have an array of pooled objects and a parallel bool array that represents the "active/enabled" state of those pooled objects
the order is guaranteed as long as you aren't hitting the rate limit
the thing that gets "synched" in this situation is the bool array; the object array is a constant
it kinda notes the case where events arrive out of order here (the text at the lowest part of this section)
https://creators.vrchat.com/worlds/udon/networking/events/#example
thanks for the info
vincil it's like 38 bytes vs 300 bytes if you use raw bools for the array compared to packing the bits
the system method isn't exposed from what i remember, they have a way to pack a bool array to byte[] or int[] i think, i ended up implementing it myself in my kinda helper static stuff
- Custom events arrive in order, docs says its guaranteed
- It seems there is no real way to reliably measure bandwidth in udon. With just serialization, it throttles at throughput 50% but if I send custom events on top, it goes above 50%. So I can actually set it to send lots of events and make throughput go up to 70+% and still serialize without throttling. So throughput is no good.
- Suffering only slowly builds up to 100 for serializations, but even at 1 suffering, it is already too late, serializations are being throttled.
- IsClogged is basically useless because it only returns true when suffering reaches 100.
- it seems there is no way for serialization to limit reliably at this moment because for some reason custom events are taking up throughput while not contributing to the throttle-triggering bandwidth...
I think VRC has multiple different bottlenecks you can hit in the background and you aren't told which one you are hitting
there was a whole chunk of networking debug information exposed recently that I haven't gotten around to playing with yet, but I'm certain that a significant amount of deeper understanding of rate limits and throughput could be gleaned from them
I think what they exposed is the same info that we've already been able to see in the debug menu 4, right? What is the doc page for that again? It's so hard to find this stuff sometimes.
A number of networking stats are available to Udon via the VRC.SDK3.Network.Stats static class.
that is actually how i find out how it is throttling, using the new network stats
that was my test results
Some of it is, yes. But there's some new stuff, and also now that it's exposed to Udon, we can take it and build display interfaces for it to visualize at a deeper level
like graphing over time and comparing directly with what was sent, for one example
also, btw, curiously, it is not just the sender who generates traffic, the receiver will also output (send out) about 25% of the sender's out (not sure whats going on)
that's probably the traffic to ensure it was received
two generals' problem moment
There is new per-object stuff, but every global stat listed is also present in the debug menu 4, right?
BytesIn/OutMax, ThroughputPercentage, and RoundTripVarience, HitchesPerNetworkTick aren't on any debug panel is it?
looks like they are
But actually maybe the debug menu 4 also got updated
On the doc page for it (listed lasted updated in August of last year), you can see those values present; the only addition seems to be time in room
the bytes in/out... do they correlate to the dev menu 6 numbers? (in/out) because i remember seeing them very different
I'm pretty sure it represents the entirety of stuff sent in/out, including VRC-sided stuff unrelated to your own scripts
you mean the one on 6?
debug menu 6 I think only lists your own networked objects
as in the ones unique to the world
I'm pretty sure the stuff that is synching your position in the world won't appear in debug menu 6 but will impact the outbound data you see in debug menu 4
Actually I change my mind; I don't believe debug menu 4 shows you much if any of the networking being sent out by VRC's internal systems
That would explain why you get throttled way before hitting 100% throughput
- Anyhow, we know the serialization starts throttling at about (just below) 9 KB/s which is half of the 18 KB/s hand cap. (Confirmed by dev previously.) We can use
Stats.ThroughputPercentageto see how much we are using (it throttles at0.5), but once you start sending custom events, it will increase this number without actually using the 9 KB/s allowance for serialization...- Considering in practice you don't want to start throttling because it will no longer serialize in real time i.e. lag and visibly desync...
- Therefore the only way to really control it is by manually calculate and predict and hard wiring your scripts to not send out more than x KB/s.
- For both serializations and custom events, it seems receiver will output say ~25% of the sender's output. (Maybe someone can test independently to confirm) Which means, if two people are sending stuff together, they will each use up
x * (1 + 0.25 * n)of bandwidth, where n is the number of people, assuming this scales up linearly.- If there is 40 people in the instance trying to serialize (and send events) (say in a massive battle game), on average, each of them will have have a hard limit of
x * (1 + 0.25 * 40), orx * 11 = 18 KB/s, meaning(18/11) KB/s=1.6 KB/shard cap, of which 50% = 0.8 KB/s is the throttle limit of serializations, and the other half is for custom events and voice and other stuff.
- If there is 40 people in the instance trying to serialize (and send events) (say in a massive battle game), on average, each of them will have have a hard limit of
- Conclusion: 1. Calculate->test->hard code your output KB/s and don't rely on stats to throttle yourself, 2. be very conservative if you don't want throttling/desync in your game, especially with more people.
- And taking network lag and jitter into account and gameplay variations, you don't want to cap at the theoretical max, maybe aim to use about 50% of the bandwidth on average will give you enough head room for deviations. Therefore you really only have a "time averaged" 0.4 KB/s for each player in a 40 people instance.
I have noticed no difference in networking performance between 2 players in my world, each being given at 55-75% network load, and 8 players in a world also each with the same 60-80% network load
2 players, each with 35 AIs with VRCObjectSync:
meanwhile 8 players with 271 of those AIs spawned in the world
Ok I just did a little test, I have a player object so every player is sending out a large serialization payload (2 dummy strings at 300 characters each) at 10 Hz, and custom event with a few parameters at 10 Hz, so all the players are sending the same amount of data at the same frequency. Some observations:
- Player count 8: Interval Q is red, Hitch Q is red and yellow, the Delays are very red and uneven and many seconds
- KB in about 55, KB out about 9.5 (bottlenecked and/or throttled?)
- in Menu 6, the Hz are very uneven and clearly not 10 Hz, showing 3..4..6..7 Hz
- Player count 6: Interval Q still red, Hitch Q is yellow and generally better, Delays are still red but more even and 1 or 2 seconds
- KB in about 55, KB out 10.5 ish (bottlenecked and/or throttled?)
- Player count 4: Interval Q yellow at 0.7 to 0.8, Hitch Q is green near 1.0, Delays are green and white abd about 0.35+ seconds
- KB in about 55, KB out 12 (seems much healthier and data is actually being sent out)
- Player count 2: Interval Q yellow at 0.85, Hitch Q is green as before, Delays are ~0.32 seconds
- KB in about 20, KB out 10.8 (scaled down nicely)
- Not sure how much this is caused by my own CPU bottlenecking (probably only for 6-8 players) but these numbers do seem to scale down as player count goes down (these also appear in your test)
- In both our test, the KB out seems to be capped at 12 KB.
- In my test, exceeding this cap probably contributed in network being throttled and making the stats red, the more people, the worse. This can be explained by added people also increased the output for everyone.
- Between 2 and 4 player count, the "KB in" was more than doubled (as expected), but the "KB out" also increase by about 1.2 KB... so the increased output per player count isn't as big as expected, but definitely non-zero and is significant enough to take up usable bandwidth... 1.2 KB is 10% of the 12 KB observed cap. This is just 2 people.
- From 2 to 4 people, the "KB in" increased by say 25-30 (which is more than the extra "KB out" from the two people so maybe a fluke), but KB out is also increase by the 1.2 KB. The ratio is say nominally 5%.
- The theory usable bandwidth (without player overheads) may be...
x * (1 + n * 0.05) = 12 KB- Applying to our cases as test (assuming my actual usage is about 10.5 KB/s) which might explain the observations.
- 2 players:
x * (1 + 2 * 0.05) = 12 KBx = 10.9 KB/s <-- I am sending less than the limit, network ok - 4 players:
x * (1 + 4 * 0.05) = 12 KBx = 10 KB/s <-- I am sending at the limit within margin of error, network ok - 6 players:
x * (1 + 6 * 0.05) = 12 KBx = 9.23 KB/s <-- I am sending above the limit, network bad - 8 players:
x * (1 + 8 * 0.05) = 12 KBx = 8.57 KB/s <-- I am sending way above the limit, network very bad
- 2 players:
- Applying to our cases as test (assuming my actual usage is about 10.5 KB/s) which might explain the observations.
- In any case even at small player count, because of how VRChat syncs, even at low "KB out" the game will introduce delay of at least 0.3 seconds on other players (and can go up to infinity) in order to smooth out the intervals to make things appear smooth or at a realistic frequency. Therefore VRChat is really not suitable for fast pace shooter PVP gameplay (where 0.3+ seconds lag is literally unplayable).
- Screenshots are representative:
can tell you some numbers later tonight. since im running a fairly big instance of 40+ people potentially with the new sdk in and so it hits roughly 9-10 kb/s out with 60+ players normally.
Just discovered there is an undocumented limit... a hidden hard cap put on Network Events on PlayerObjects, firing faster than 10 Hz will be queued up and suffer, regardless of player count. Even 0.099 second intervals will queue up, but 0.1 will not.
- Non-PlayerObjects do not have this limit and is tested to fire at 60 Hz at least without problem.
- But there is a workaround... make a copy of this Player Object (2 objects) and they will (together) fire twice as fast without queuing.
- Make a pool of them for even higher frequencies...
- Curiously the deserialization rate limit is tied to the Hz of the object in Menu 6 and can be like 12 Hz i.e. more frequent than Network Events on one PlayerObject.
TLDR how to prevent Delay increase and make your networking smooth:
- Step 1: Calculate your "non-delaying bandwidth
x" usingx = 12 KB / (1 + n * 0.05)wherenis the expected max player count in your instance.- E.g. Your game is 8 players, therefore it has
12 / 1 + 8 * 0.05=8.571 KB/snon-delaying bandwidth.
- E.g. Your game is 8 players, therefore it has
- Step 2: Test below using 2 clients, so Network Events will use actual bandwidth. Both clients should be sending and receiving at the same time, to simulate a live game.
- Step 3: Don't fire Events, only firing serializations, tune it to be below
x * 0.5by adding up the per object Bps in Menu 6. Less is better.- Note: The "Bps" in Menu 6 is only for serializations and do not include Network Events.
- Step 4: On top of the serializations (keep it on), start firing Network Events at maximum frequency your game allows, to see if the total
Out: KB/s(at the top of Menu 6, orKBytes Outin Menu 4) is still belowx.- Now if you are still below
x, your game should have minimal Delays up to the player count you used in your calculation. - This test simulates the moments of activity spike where the output will cause VRChat to add Delay to remote players, causing lag and desync. You are basically testing for the worst case situation so it won't happen in a live instance.
(I have tested this method to be pretty spot on for a 4 player instance, I could toy with the point of Delay to make it Delay/Suffer or not)
- Now if you are still below
"Non-PlayerObjects do not have this limit and is tested to fire at 60 Hz at least without problem."
I don't believe it is possible to send anything over the network at that event. If you are referring to manual sync, you can call "RequestSerialization()" all you want, you're only requesting it to happen, not making it happen. It appears that manual sync works on a tick rate outside your control where every tick it asks "has serialization been requested between now and the last tick?" and if so, it sends out the data.
"Each manually-synced object is rate limited as a factor of the data size. The more it sends, the more its send rate is limited. Scripts can call RequestSerialization as often as they want, but Udon will wait until enough time has passed before calling OnPreSerialization, sending the data, and calling OnPostSerialization with the result." - https://creators.vrchat.com/worlds/udon/networking/network-details/#manual-synchronization
Networking in Udon can be challenging! Try to keep things simple until you're more experienced.
they're referring to events i think and you can actually achieve very high send rates with events
I'm just confused as they are talking about a "more players adds extra networking consumption" that I have not observed in my own network saturating world
manual and event networking are reliable, due to acks presumably it does actually raise another player's outbound when someone is sending more data, i can record this happening if you'd like a view of it
I can only assume is uniquely a manual sync thing as my world's network capacity is being used up via VRCObjectSync and events
continuous might be unreliable as it's expected to emit continuously (as the name suggests) but i don't know for sure and rarely use continuous myself
Do we have the tools to change the update rate on continuous and/or make it sleep yet?
Actually it might already automatically change its own update rate; but iirc we can't make them sleep
i think it does actually sleep with just vrcobjectsync when the rigidbody sleeps? but i could be wrong about that
vrcobjectsync will sleep itself after a period of no movement
but iirc last time I checked, custom continuous Udon scripts will not have the same behavior
yeah i'd expect them to just constantly blast variables, i think moving away from continuous is ideal in most cases
if what is said about manual is true, doing what I am doing with my world would be impossible with manual sync
which part?
Having several networked, nav mesh agents distributed across the players (and by several I mean it can reach 280 networked nav mesh agents at 8 players)
maybe continuous works out better there. i tend to sync destinations instead, i think zombie survival does that as well to support a large number of agents
I thought about that but the system is way too chaotic and the nav mesh tricks I'm employing are too dependent on the accurate position of other nav mesh agents that I need to sync current position instead of destination
have you tested the upper limit you can support? like i'd assume continuous adjusts their interval across all scripts to try to support smooth playback, but i haven't used it enough to know
with a nav mesh agent speed of 3, I can have 35 agents per players with zero suffering under normal networking conditions
around 40 is when suffering starts fluctuating
is that at ~4hz? the interval should display in shift ` 6
i've tried something realtime with a more dynamic send rate, but never really completed it as i kinda stopped when i felt i'd explored it enough
https://vrchat.com/home/world/wrld_73048d56-2197-4ff5-bede-ddcba2206bfe
yah, ~4hz while moving
Also found an annoying networking caveat with SendCustomNetworkEvent targeting the owner of an object.
If you assign ownership of an object to another player and on the same frame target the new owner with a SendCustomNetworkEvent using NetworkEventTarget.Owner, it will work correctly under most circumstances.
However if you are doing a lot of these ownership reassignments at this time, it is very consistent that while your script will still correctly recognize the remote player as the correct owner, your SendCustomNetworkEvents will start coming out before all ownerships have been correctly assigned, causing some of them to go to the wrong person.
i don't think i'd rely on that, sounds like unfun race condition territory, i think i'd probably prefer to send an event to all players with the intended target as an int and have them take ownership themselves when they receive it
the warning message here is triggered by a custom event being send to the master instead of the newly assigned owner
It's actually VERY consistent; it's always 7 custom events that end up going to the wrong player when it's 32 x 2 ownerships + 32 events being sent out to a remote player
13 ownerships transfers happen too late, which means 51 made it in time
Yes the 60+ Hz I was referring to network events with parameters (on a non-PlayerObject)
Manual sync in practice I saw actual performance ~17 Hz max (when trying to do 20 Hz), I guess it depends on data size to adjusts downwards from the 20 Hz ceiling
(But Menu 6 will report 11 to 12 Hz... so I guess practically you don't want to go over 10 Hz)
oh thinking about it, while you can generate events at up to 100hz, the rate they're actually sent out over the network is probably still limited by a network tick rate
or rather, they won't be sent individually instantly, but rather as a batch
in my case it was limited by Update() frequency i.e. framerate, and it did go out "instantly" as in the receiver can receive it at about 60 Hz intervals as expected (but with some delay)
so there is no observable "tick rate" that i can see at least
However for serializations if you serialize at say 10 Hz but more than say 8.5 KB/s (throughput 0.5), the receiver will receive in 1 second bursts, that could be the "tick rate"
But it didnt apply for events
does debug menu 6 hz stat get effected by events?
And for the receiver, update rate will be HEAVILY impacted by what group that networked object belongs to in that moment
what you are seeing in debug menu 4 is the delay for their avatar sync I'm pretty sure
i think no, it is just for serialization data, except the Out KB/s at the top does include events
(maybe the Delay also affects events, but other stats are all serializations only)
yes
(but probably also their events and serializations, i suspect)
you can see how much delay is impacted by having your two players close look at eachother and then far away looking away; while you are close and looking at the other player:
and then when looking away at a distance:
and the more networking activity is going on, I suspect the smaller the steps between the fastest updating groups and the more delayed groups is going to be
Also note that this behavior can be pretty inconsistent
interesting, then Menu 4 could be just avatar... does it affect objects they own? wondering if objects are grouped similarly (distant objects can't serialize as frequently? or send events frequently?) (what about distant player owned object that is in front of you?)
They seem to be grouped by a combination of distance and if their mesh is being rendered by your headset and/or camera
Networking in Udon can be challenging! Try to keep things simple until you're more experienced.
"Udon's networking prioritizes synchronized game objects that are currently visible to the local user.
Udon periodically checks the visibility of all mesh renderer children of synchronized objects. This is used in the quality of service behaviour of Udon's network load balancing."
and:
"The current group that the player is in. Grouping in this context is an internal networking system used to combine multiple objects together by distance so that their data can be sent together." - https://vrc-beta-docs.netlify.app/worlds/udon/world-debug-views/#debug-menu-4
These are the tools you can use to debug your worlds in-game.
Actually thinking about it, will a player camera trigger a mesh as being visible? It seems like it should.
so it is periodically checked for distance and visibility...
I guess vrchat would use Renderer.isVisible for visibility checks because that is the most straightforward method, if that is the case, Unity docs would suggest yes, any camera should work.
Which also explains why visibility check is not done on player avatars because they are never culled, so they are always "rendered" I suspect
how bad is it if I exceed the synch limit? like using 400 bytes instead of 200?
Or should I utilize manual synch at 10Hz instead?
the manual synch is from serialization?
400 bytes instead of 200 bytes? Neither should exceed the limit... how frequent are you syncing? What matters mostly is the Bps of your objects (for serializations)
How often you want to sync depends on what you use it for, if it doesn't need to be synced often (like game mode selection) then you only sync it once every few minutes
(Or as frequent as it needs to be, maybe it only needs to be synced once)
And which limit you are talking about? The frequency limit (~10 Hz)? The self imposed Bps limit before raising Delay? The hardcoded Bps limit before throttling?
the hardcoded limit, I am wanting to synchronize float arrays.
Ima just say that there will be three arrays of 17 floats, and an additional array of possibly 10 floats...
I plan on synchronizing this to have essentially a dmx show run off of an in world controller and be synchronized to everyone in the instance, so it would need to be synched fairly often, but it isnt the raw outputs, it will be processed a bit. this is just the part of the system that has the least amounts of variables
so.. perhaps at 20Hz?
not sure how much I can do before it would start throttling in an instance of 80 players
continuous is limited to 200 bytes, i'm pretty sure you can't sync arrays over continuous anyway though; you'd want manual
https://creators.vrchat.com/worlds/udon/networking/network-details
what are those floats for? positions? if it is something transient like positions, yes, use continuous, so vrchat can manage it internally
even if the fixtures have their own smoothing applied sometime after the networking? i.e. the network variables goes through some stuff, gets applied to a texture via shader, which is then read by VRSL, which moves the fixtures with its own smoothing?
if you sync with continuous mode, you can even select if it is interpolated like
[UdonSynced]
public bool synchronizedBoolean;
[UdonSynced(UdonSyncMode.Linear)]
// This float will be linearly interpolated
public float synchronizedFloat;
https://udonsharp.docs.vrchat.com/udonsharp/
which can be None Linear Smooth
in your case it will be None because the numbers (representing textures) cannot be interpolated
but why you syncing at 20 Hz? you want each image to play an animation?
i see....
well.. yea an animation, the texture it will apply to is essentially an artnet gridnode
I don't know what that is, but it does sound transient, so continuous is the way to go
I just dont know if you want to interpolate between your floats or keep them distinct numbers
Because I dont know what they are fo
for*
ok, so...
the variables are set up like this in each of the arrays.
RGB hue
RGB distance (the circular type)
dimmer (intensity of the light)
strobe
pan
tilt (pan/tilt is the movement angles)
Cone angle (width of the light beams)
smoothing (for VRSL's internal smoothing)
GOBO speed (rotation of the light pattern)
GOBO select (rotation pattern selection)
RGBAS delay
RGBAS delay power
RGBAS delay offset
pan/tilt delay
pan/tilt delay power
pan/tilt delay offset
Probably... too many variables. these are all floats
One of these could control 16 13CH fixtures, and the delay will affect how they respond.
VRSL will handle the smoothing, so interpolation is not really needed
these are the output values from a controller signal generator in world.. though I feel like I should synchronize the controller inputs at a much lower frequency, though it would likely be more variables
And regarding player count, even if there are 80 players, if it is just one DJ (?) sending out the data, you can treat as if there is only one BUT, the question is, what sort of data output do you expect to see from other people in a typical situation, and what other the DJ maybe sending, because that also affects how much bandwidth you want to allocate globally, and between scripts
How... dynamic or responsive do you need them to be? Can they just be updated every half a second? and let your smoothing do the magic?
they need to be responsive... though I feel that some parts, especially the delay, doesnt need to be updated as much, like twice a second...
Should I split this array up into smaller arrays by how much they need to be updated?
also, is the 11kb/s limit globally for the entire instance?
are you trying to recreate VRSL?
effectivley, I am trying to recreate a VRSL gridnode video input, where this system could replace the video stream input with a in-world generated gridnode texture input
ah, neat
is the 11kb/s udon networking limit per client or per instance?
not sure why you are asking me?
hm.. not sure, was wondering if you knew, as the vrchat udon networking page doesnt really specify
there's a lot it doesn't specify
11 KB/s is per client outputing serializations
but really it is more like 8.5 ... 9 KB/s
outputting? its for putting stuff into the networked?
So the values I have in my case would be outputted from one client at maybe 7 kB/s, then the other clients get the values on deserialization?
So if I were to output pan/tilt at 10Hz, this is for serializing at 10Hz, not deserialization?
so therefore, since the input of the controller has several values that change only occasionally, I can serialize on value changed? unless im thinking of it wrong?
You can think of serialization as sending, and deserialization is receiving
If you serialize (the sender), you don't deserialize, only other people (who are receivers) deserialize
If your values only change occasionally, you can just send (serialize) when they are changed, yes
But for any object, if you serialize, you send all of the synced variables on that object at the same time, regardless of whether any or all or only some of them are new values
successful test of 280 independent, synced AI nav mesh agents across 8 players:
continuous sync?
you can expect a max output before it starts to struggle around 8.5 kb/s out bound data. aka how much data each person sends out. this use to be 11 kb/s roughly but since the update its closer to 8.5
With my script, it would be one person sending the maximum, or it being spread over a few people if multiple were on the controller, though I decided to go with networking the controller input variables on value changed as those would need to be networked anyways for the sliders and toggles
one object can only be synced by the owner (one player)... so it depends what do you mean by multiple people on it
you mean at the same time, or rotating out after a while?
one by one?
VRCObjectSync is handling the positional data, yah
ngl VRCObjectSync might not even be necessory. you could prob do it manually and get away with less data use
yeah if the ai paths are predictable, you really just need to send like the starting point and target point, and let remote clients figure out the entire movement sequence
the ai paths are not predictable
if the ais are predictable, you dont need to sync anything other than the conditions the ai is based on really
oh ok
btw how does it look like on Menu 6? are your NPCs in groups with different delay times?
I'm going to assume so
and does continuous sync generate traffic if the npc is not moving?
iirc VRCObjectSync update rate is partly impacted by how fast the object is moving; and VRCObjectSync will sleep itself after a period of inactivity
i mean you can do predict alot. and do alot of calculations based of different values. cause otherwise the price in data is gonna be roughly 48-60 bytes per second per AI which is Alot lol
what is considered one object in this case?
A whole ui panel?
one object with udon script(s) on it. if scripts are on different objects, theyd have different network ids and can have different owners.
ah, yea there will be multiple objects then...
is it simpler to make networked buttons/sliders in udon graph or U#?
I did try to make a networked button/slider in a previous world, but it just.. didnt work
personal reference really
i learnt graph first because i cant code
which later gave me the needed concepts to learn coding in u#... but really personal preference
graph is "easier to read" for non coders i guess
Silly question, if you have VRCObjectSync on an object, can you have a second script set to manual sync, or would it want to be continuous?
hm.. I plan on coding it in U#
I have networked buttons and sliders in U# atm but I can’t compare it to graph for ya @-@
I even have an object that updates its position to the slider handle pos when the value is updated so it looks like you’re grabbing a physical slider instead of the flat UI one lol
that is interesting, though im fine with flat ui, as the controller will be like a touchscreen
the logic will be the same
i see, though.. to call networked variables in my U# scripts to use them, how should I do that? cant seem to figure that out
you mean, the networked events with parameters?
there's 2 ways to do it in U#, this section details an example (have to switch the tab to the "UdonSharp" example)
https://creators.vrchat.com/worlds/udon/networking/events/#sending-events-with-parameters
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.
your function needs to have parameters, and then you need to give it the [NetworkCallable] attribute
I see....
looking at it, I must not have more than 8 callable parameters per event? so if I have an array of 11 floats, I should split it into two so it can be sent in two events?
Although, looking at it, it would be unlikely to have more than 4 parameters per event if I set it up right...
ah..
Just watched those videos last night and the "I Shot You First" video is really good. Having time to think about it and my own project, I could fully switch over to this kind of architecture in my own game but there is one VRC limitation that currently makes it impractical and another more open ended question that gives me pause:
- There is no ability to tell VRC that you want a remote player to have the fastest possible position update rate regardless of visibility and distance. (At least as far as I know. If there is a way please tell me!) My project has monsters that can spot and then chase and attack a player. Because a remote player's position could be delayed by over a second, keeping ownership the same could result in the monster damaging the remote player while on the remote player's end they are clearly out of range of the monster. Instead the current solution is to reassign ownership of the monster to the player being chased, with all the complexities and pitfalls of that approach.
The ability to get short delay information on that player's location would hopefully make ownership transfer unnecessary and would probably be the #1 biggest improvement to my game's networking.
- The chart towards the end of the video is a little confusing but it appears that they were able to accomplish their architecture with only a 31.25KB/s upload on the host side. 31.25KB/s is greater than 18KB/s, but not by that much. I can already imagine all the host-sided optimizations of what data gets sent out, but even then it is a struggle to imagine accomplishing anything remotely similar even if we had an equivalent upload limit. Maybe the way VRC handles networking is too general purpose and inefficient compared to the specialized approach in Reach? Or maybe I could accomplish way more than I realize.
I'd love to give it a try, but again the big limitation mentioned above makes that a moot point. A distributed approach is a necessity.
with sync component - all forced to continuous, with manually added scripts - forced to slowest one (ie all manual, if at least one of them is manual)
@tulip sphinx Is it the same deal with stations? I noticed that the example chair has the interact script set to continuous
@warped latch uh, does it even has any synced vars? im not sure but Continuous is just a default state when you create new behaviour
Ah, cool (I have like 600 stations, I really don't need the network load)
just to add some context, reach's multiplayer he discusses involves mostly discrete states. their own game does not use this same network model for the pve mode with ai involved. a lot of his discussion is around tooling and how they handle state which isn't applicable in vrchat (specifically semi-reliability of data, while this is kinda similar to eventual consistency, this lower level of control isn't available in vrc)
the most applicable portion is his mentions of the client netcode, but it's not a focus of the talk (showcasing how they originally approached grenades and what they ended up doing to minimize perceived latency)
that isn't especially applicable in vrc as it'd be odd to run that through the master (their concern is more on cheating, which they mostly prevent by validating things through the host instead of allowing clients to push their own changes)
in vrc it's difficult to make meaningful prioritization in the same way, data must be serialized if you anticipate the host dropping or ownership changing hands; this differs from reach where the host is managing the server end effectively (deciding what data is relevant to who and how important it is)
the comparison of data isn't 1 to 1 because in vrc you're only sending the data out one way, you're not handling it to all clients; the amount of upload just isn't directly equivalent
If at all possible you do not want to combine VRCObjectSync with any other networked scripts on the same object. It results in a lot of undesired behavior. Put your other networked scripts into child objects.
I've tried it before. It was surprise, semi-invisible issue after surprise, semi-invisible issue.
- could be achieved by placing the player in a station that is set to mobile on their end but immobile for other players, then having them serialize their pos/rot over the network and lerping the station toward those synced values for remote players
I’ve done all this except the smoothing which I plan to do soon
Players update the pos/rot synced values much faster than their actual movement
But you would get slightly desynced animations since that would still come from the normal vrc response
You may be able to solve the delayed animation problem by just rebuilding the walk animation on the controller on the station but that is probably too complex for me 😅
This is giving me ideas which is horrible for my sleep schedule
You could accomplish the same with an invisible object that transforms onto the player on update, right?
I still am worried the background auto prioritization would result in potentially long update times. Though with the per-object FinalDelay stat they added, this can be tested now.
I love reading every idea that can be described as "VRC doesn't support this/do it well, let me use VRC to simulate the right behaviour"
For this host-sided logic, I'm more worried about the host having accurate hard data about the player's current position than it being visually up-to-date
Well then yea why not just serialize pos/rot for each player?
Having a monster swing at thin air and hitting some other person clearly out of range is way less of an issue than a monster in the distance swinging and you being the one getting hit
i think in vrc it's most fair to run that hit detection on the receiver unless you have a small amount where you can keep the picture relatively in sync (with smaller intervals between updates)
ownership also can't change hands on playerobjects so dynamically trying to hand off the units isn't really a thing you can do
receiver-sided hit detection has situations where the receiver will never get hit because the targeting position will always be incorrect
i think that's more fair to the player than running it on the host at a low update rate unfortunately, i don't think either way is great
that's why my current method involves a distributed approach
this is also how I can get 280 networked agents in a single world at 8 players
(although the need to pass around ownership in real time lowers the practical number to something closer to 200)
An idea kinda popped into my mind wherein the master can determine the rough location of an Ai and occasionally sync transform and possibly goals of the Ai and then once within a specific range, the syncing can "detach" for a set period not necessarily transfer ownership though this would require your own interpolation code unless you have control over the goals and can mask the delay between sync steps with local code
i think that's valid, but i would say imagine that a player running an ai is lagging and stops seeing position updates from others; they could have an ai send multiple hits to a player during that time
alternatively, imagine the targeted player is lagging, they've now lagged and during that time were hit multiple times despite being nowhere near units on their end
i think the latter is acceptable, but you have multiple players you're relying on in a distributed approach, with a single host you only have one; though i'm not against a distributed approach and i think it's probably the most valid way to run them in realtime
well to clarify, my approach is using receiver-sided hit detection with one important caveat: if a monster is chasing someone, the chased player needs to be the owner of the monster
transferring ownership to the targeted player introduces complexity and at worst one player is running every unit, but if you can make that work it's probably more fair
yah, and why I'd like more accurate player positions so remote-sided hit detection can actually feel good. Sure lag spikes can cause issue, but if a player is having a good time and then a lag spike kills them, they'll likely be more angry at their internet than the game.
The thing is, vrchat uses a delay to act as a smoothing period for remote player movements, which is at least ~350 ms or more and can vary unpredictably (the Final D on Menu 4), so you are always viewing the player with a very uncontrollable latency... so ideally ALL hit detection (or any quick paced gameplay really) should be local to the player
So yea setting the chased player as owner seems the best strategy, no worries of latency and position accuracy
In PvE game it is a bit easier to run things locally, but in PvP games you gotta design away the problem, make it so latency matters less
So from the testing I've just done making the player sync their current position and then a debug capsule on remote clients instantly transforming onto that synched position, I'm pretty sure the lag is doubled again on the receiving side to allow for smooth playback
So I think the visual (and presumably remote player's GetPosition) lag is double the network lag
What you're looking at is simulationTime. It is a timer which runs slightly behind to allow synced data to have time to arrive. It automatically adjusts up and down depending on network conditions and other factors, and is exposed as a getter for both players and objects
Actually I'm curious if FinalDelay actually represents the time between when the sender sent the data and the receiver received it; the description is kind of vague.
I can think of a way to test this.
is it what the "delay" represents in Menu 4 and 6?
(what are interval delay, group delay, final delay?)
btw what is difference of "Linear" and "Smooth" interpolation in UdonSyncMode?
and how are they applied exactly? smooth between missing information? smooth towards new information?
(is there a world that demos this perhaps?)
Linear just goes to the value directly in a straight line, smooth takes time to build momentum towards the new value then slows down as it reaches it
this world has an example (have to bring a friend to see it work though)
https://vrchat.com/home/world/wrld_0e5da807-4ced-4017-a25c-1b2f39ecd097/info
is it smoothstep or smoothdamp? if you know
so looks like FinalDelay does not at all represent how long it took for the data to get to you:
finaldelay is likely the sim time offset, sim time requires you to do some math operation to get the resulting delay, finaldelay doesn't; if you want to align a lerp with ik you could use finaldelay directly as the interval from what i saw
On the plus side, it looks like you can actually get some reasonably fast updates on a remote player's true position
And from what @strange token said, you have some ability to override a remote player's visual representation. That means you could design a visual representation of players in your world that works better for pvp than VRC out of the box
if you use stations and advertise their position at some faster rate you could, but it's not great on bandwidth
that rate is already 10hz for a vr player that's near you i'm pretty sure
for desktop players it's more like 4hz
<70ms seems workable for a pvp game though, does it not?
70 ms? sync a player object as target?
I'm doing a more proper test right now
i wonder if using network events with parameters to sync will bypass all the background delays
Unfortunately using manual sync, the delay balloons at higher player counts; like 200-350ms
i wonder if it is feasible to build our own networking engine with network events with parameters and something similar to https://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking
in udon
using events does seem to keep the delay low:
you'll send to add extra code to make sure you don't oversend events during periods of network instability, however
How is your timeToArrive calculated? using server time difference?
yep
[NetworkCallable]
public void GetPositionSync(Vector3 position, double SendTime)
{
syncedPosition = position;
timeToArrive = (float)(Networking.CalculateServerDeltaTime(Networking.GetServerTimeInSeconds(), SendTime));
}
so 6x ms difference... seems to be slightly more than your previous screenshot?
was your FinalDelay just SimulationTime?
maybe; didn't look into it that deeply; was more interested in seeing actual latency
and how is your ping? just wondering if the 6x ms is approximately the (RTT of the sender + receiver) / 2
~42
also note that events will occasionally come in late under heavier network load
Maybe you can try to check if Suffering is 0 before sending the event, to make sure it don't add to queue (by dropping the event)
Or NetworkCalling.GetAllQueuedEvents is 0
On a side note, would it be possible for VRC to give us the ability to mark our behaviors with different sync priorities?
not that i am aware
Because manual sync is structurally a way better way to handle this position sync, but the lack of priority control has me wanting to use events instead to force high priority
Got a couple of canny posts that may interest any other security minded folks, working within the limits of what VRChat provides:
https://feedback.vrchat.com/feature-requests/p/modifiable-queue-for-defaulting-network-ownership-when-players-leave-or-add-a-sy
Got another alternative since I brought up making a Well Sync System:
https://feedback.vrchat.com/feature-requests/p/allow-disabling-of-the-initial-sync-for-joining-players-with-manual-sync-scripts
Does anyone know how to get the value of a tag set on a player
you get a tag with GetPlayerTag
Yes but how do I check the tag value
with.... GetPlayerTag, it returns a string for the value
oh wait they don't really have a value
they either exist or they don't
or am I misremembering... have to look at it
you still don't have GetPlayerTag yet
a set source? what do you mean by that?
As in when the local player has this tag do thing
You can use Networking.LocalPlayer as a ref to the local player
Though you should also cache the value of LocalPlayer as it's an extern
give me a sec I can figure out what you're trying to do
I believe the output string will be null if they don't have the tag, so you can check for that
It’s effectively an admin only button using tags
alright so if a tag isn't set, GetPlayerTag will return null or the empty string
ok and if it is set
if you use the node String > IsNullOrEmpty, it'll be true or false depending on if the tag has a value or not
if it is set, string will be the value of the tag that you had set previously
do note that these tags are not synced, they're local-only
and are generally considered kind of outdated
but sometimes have interesting uses
I thought they just updated them?
no, you have to network the tag-setting to everyone manually
which is probably a lot easier to do now thanks to the network events with parameters
Oh while I'm thinking of it, "NetworkCalling.CallingPlayer" can be used as an extra security feature for your open, network-callable events. You can use it to check if the network event call is coming from who it should be.
smart, didn't think of using it in that way
I've transitioned my world to a system with no "live" ownership transfers and the lag of a remote owned monster hitting you doesn't seem at all bad
\0/
Thanks for the suggestion @strange token
Glad I could help! 🫡
does GetNetworkDateTime() return the actual date and time (which timezone?) or is it just an elapsed timer converted from GetServerTimeInSeconds()?
I'm having a lot of trouble trying to make this little flashlight gimmick.
Trying to make it so desktop players have a flashlight that is just attached to their heads and VR players have a flashlight that uses VRCPickup (this part works fine)
Trying to get it to sync properly is my biggest issue atm because of the way I want the players to unlock the flashlight (I think)
I'm trying to make it look like you unlock the flashlight from an interactable "flashlight" object you find on the ground. Nothing is syncing, and I've broken it so now only the first person to click it gets anything then nobody else can interact with it
I can show everything if someone is willing to help, just don't wanna clutter more than I am
is the picking up exclusive to one player, as in one person picks up, another person can't?
or is it a "give me a flashlight" button, and it gives a flashlight to anyone who pressed it
so you are giving multiple flashlights to multiple people?
Anyone that presses it, the flashlights have the player object script on them
So they're supposed to get their specific flashlight when they press it
ok PlayerObject yes
VR players, you want it to be Object Sync, but nonVR players, you disable Object Sync and VRCPickup and just attach to the players head (no need networking)
The "unlock" interactable should just be responsible for enabling the PlayerObject owned by that player
Yeah, the issue is mostly just networking the light so other players can see it, and then networking the actual flashlight model with just VRCPickup/ObjectSync so that everyone can interact and see that for VR users.
I think just typing it out kinda helped? I have the flashlight game object disabled and then am enabling it and moving it into position when the player clicks the unlock, but the enable isn't networked. So that could be it?
Does VRCPlayerObject or ObjectSync track enabled/disabled states?
It doesn't, so I have to network the enabling/disabling of the game object... smh me
I think you've got the concept of what you need to do down honestly
Just needs ironing out the kinks
You could set up synced bools for if the flashlight is unlocked, what mode the player is, switch things around based on that
With PlayerObjects makes things easier, as the Owner is always the player the flashlight belongs to
on the pickups, you might need to disable pickup for non-owners
(not sure if it is automatic)
Is SendCustomNetworkEvent viable for enabling it or is it better to just use a synced bool?
it almost is, but without a synced bool, it becomes much more difficult to sync it for late joiners
currently on interact with the spawner if you're on desktop, I'm disabling the meshrenderer, box collider, and the pickup script. So need to create synced bools for each of these?
and the flashlight itself
You can use event, and let the PlayerObject themselves handle turning on (and sync a bool e.g. IsUnlocked), maybe they are all enabled (gameobject) and only the components are turned off in the beginning
This way you dont need to make an array to remember the bools and sync it seperately
think of the synced bool more as a "state"; you don't have to have separate ones for each component if you're always toggling them on and off at the same time
true just one named something like "flashlightDesktop" or something that does all 3 lol