#udon-networking
1 messages · Page 28 of 1
seize the means of synchronization
One more question though Legos. How many calls of request serialization is too many.
like, in a single frame? or within a timeframe?
Yeah, within like a second or so
You would normally want to stay under 5 per second.
But it really depends©®™
Serialization happens per GameObject btw.
This is why you can't mix sync modes between scripts attached to the same object.
Ok that shouldn’t be a problem
I did not know the other stuff though so that’s very nice
Will save me many future problems
Also, you should use Manual sync for 99.97% of things.
When you say that someone is "on quest" do you mean them being on quest standalone? Or using a quest tethered to a PC?
Because a quest tethered to a PC is the exact same as an index tethered to a PC.
Borrowing from how Bungie detailed their network design in their GDC talk on Halo: Reach's netcode, manual sync should be used from synching the state of things (if something is destroyed; hp values; etc) while networked events are good for synching the reason for state changes (for example a network event that plays an attack's unique sfx and vfx)
Yes, network events are well suited to synchronizing short lasting effects and sounds.
I've also used events to request the owner of a manually synched object to change the values on that object
I find that to be one of the more sane approaches.
For example if I want to send a damage event to someone else's object.
I guess what I'm wondering is, are you asking if anyone has tested the fact that standalone Quest users get suspended when they take off their headset?
Yes, the standalone Quest. I already know that, as far as I know, Udon running on PC is headset agnostic
Yes, I can confirm that standalone Quest users get suspended when they take off their headset. And afaik the timeout before they get kicked from the instance is currently one minute if they're the instance master, and three minutes otherwise.
apparently it's somehow possible that one player can see the other in an instance while others can't, not sure if the player who can see has to be the master or not but that's a thing
yesterday when testing my world that happened, didn't know vrc networking can get that bad
my friends thought I'm going crazy lol but I recorded it all on video
Ah yes, gaslighting has returned
Is this separate from the bug that freezes robots in place until you show their avatar?
Or could it just have been that
not sure since idk the settings or how it looked from my friends' perspective
but they didn't even see the portal that user opened
the user was also on quest and we were all on PC
Yesterday had a fun thing happen where tried to join off a friend, but got hit with eac saying cannot verify vrc files. Was able to invite them to another world just fine. Restarting didn't fix either

I lost the logs otherwise I'd post a canny
No sane person would probably want to sift through 3 days of uptime in an idle world logs before hitting the bug anyways
I would never 
Are you not able to use SendCustomNetworkEvent on a script attached to the same gameobject as the event sender?
I'm having a ton of issues trying to get that working, among those being this error: Unable to send network event 'LoadMazeData' as it does not exist on the target Udon behaviour 'Maze Generator'.
The function is set as [NetworkCallable] public void without arguments
and the script sending the event is a UdonSharpEventSender
are you calling it with [OtherScriptReference].SendCustomNetworkEvent(...)?
no, just SendCustomNetworkEvent(NetworkEventTarget.All, nameof(mazeLoader.LoadMazeData));
should it be in reference to the other script?
that would be the problem then
if you call it without a reference, then SendCustomNetworkEvent targets the current script
This is the same for any of the other CustomEvent functions too
does that also apply to RequestSerialization?
The error's gone, but the event isn't triggering for everyone
yes, kinda, I think a serialization syncs all scripts on the same GameObject
this makes what my scripts are doing rn more confusing, then
I am wanting to generate a random number, that is the same for every player in the instance. however I'm getting stuck with it being different for every player.
is there any documentation on doing this that i could read through? or examples other people have used?
seeding would probably be a good idea, you could initially sync the seed and receive the same outputs as a result
https://learn.microsoft.com/en-us/dotnet/api/system.random.-ctor?view=net-9.0#system-random-ctor(system-int32)
(constructing a random instance with a seed)
https://learn.microsoft.com/en-us/dotnet/api/system.random.next?view=net-9.0#system-random-next(system-int32-system-int32)
(using a random instance to generate a next number within a range)
you could also sync the array itself if it's easier and not especially large
yeah, not gonna sync the array, far too big, the seed sounds prefect. ill get to reading, thank you for the help
actually i didn't realize you were just randomizing the index, it'd only necessitate syncing the int length if it's only the length you need to sync
if you're filling it with random elements that you want to align across players seeding would still be relevant though
easy numbers is a hardcoded list of values. i just want to randomise what value i pull out of the array
ah okay, yeah that'd only necessitate syncing the index; it'd look something like this if you're unfamiliar with how to sync it
[UdonSynced] private int syncedIndex = -1;
// Only run this on the owner
private void OwnerRandomize()
{
syncedIndex = Random.Range(0, EasyNumbers.Length);
getPuzzle(syncedIndex); // The owner doesn't call OnDeserialization, so we have them enact the logic here
RequestSerialization();
}
private void getPuzzle(int index)
{
return EasyNumbers[index];
}
public override void OnDeserialization()
{
getPuzzle(syncedIndex);
}
the documentation on the relevant manual networking would probably be here
https://creators.vrchat.com/worlds/udon/networking/variables#2-manual-sync
and here
https://creators.vrchat.com/worlds/udon/networking/late-joiners/
ahh, semi working now. now the seed is hard coded it generates the same puzzle each time, but it is network synced. ill have some fun with this to work out what my options are
Woooo, working randomisation:
TY for the help Occala
for those who might be looking or interested in randomisation synced across other players so everyone gets the same random number:
You absolutely do not need to use SendCustomNetworkEvent here, you should just replace it with a normal method call.
I would also advise you to move the set owner logic to the line before you're assigning the random number to your synced variable.
Right. Because I don't need it to sync until I actually need it to sync, it doesn't have to be a network'd button.
also pls use nameof when calling events
You can also sync more than just a single random number if you use System.Random and sync a seed
[UdonSynced] private int mapSeed = -1;
GenerateMapFromSeed(mapSeed);
...
public void GenerateMapFromSeed(int mapSeed)
{
System.Random rng = new System.Random(seed);
//random map generation using rng
}
Do inactive scripts receive synced variable updates?
My scenario:
- There is an initially disabled object containing a script with some synced variables, there are 2 players in instance.
- Player#1 (owner) activates an object (gameObject.SetActive)
- Player#1 modifies some synced variables in the script's Start() function, calls RequestSerialization()
- Player#2 activates the same object a bit later
- Player#2 never receives OnDeserialization() event and the values of synced variables remain default, no changes from Player#1 are visible
Is this working as intended?
oh well
I'm assuming this applies not just to Udon events, but to UdonSynced variables too?
yep. by network events I'm also referring to things like OnDeserialization
@drowsy rover yes. the only way to have networked udon behaviour disabled is to make sure its state is synced for everyone as well through vrc pool or similar system. cant have it on for some and off for tohers and expect it to work later.
I see, thanks
disable other components of an object or move synced script somewhere outside
Ok, so it turned out I was stupid and didn't actually call RequestSerialization 🤡
And now that I do, I actually get all the changes even if the object is disabled at the moment when those changes were made... Huh?
I even get all the intermediate changes, if the variable was changed multiple times. As if all those network updates were queued and processed all at the same time when the object became active
Disabled and newly enabled Udon Networked Objects have unexpected, likely unpredictable behavior
Like others have already suggested it is generally best for your networked objects to be active at world load and never get disabled, instead using other means to "disable" the object (for example disabling the renderer and/or collider)
Yes, I will keep them active. Just trying to make sense of what is going on.
It's been awhile since I last tied messing around with disabled network objects, but iirc it's mainly an issue when you try to activate an object and then try to have it sync new value
But there might be other weird behavior that will randomly pop up in ways that won't throw any errors
In regards to this btw, I've been doing some research into it and I've found online that the randomness from System.Random won't produce the same results cross-platform due to the architecture difference
Do you have insights on this?
You’re supposed to be able to solve it by setting Random’s seed first but I was still getting differing results when I tried that and ended up having to sync all the random changes
That was the beginning of the end of my last project actually
Spent a week or two trying to get udon synced property calls on arrays then switched to strings instead, then my players weren’t syncing up after setting Random’s seed and ended up giving up
Too many hurdles and brain scratchers and not enough progress
Hmm, I currently use System.Random to do exactly that and it seemed fine to me. Although I have not gotten on a quest and pc at the same time to make absolutely sure. So who knows, it might have different results... I'll have to try it out once the login servers return.
But seems like it's relatively simple to just make your own random number generator if the builtin one doesn't work properly.
I see. That did also come up in my reasearch
Making your own engine, that is
I have to wonder why it'd be different anyways at that rate even with System.Random if it's just written as-is the same as making your own
i hadn't considered this and haven't tested it, hopefully puppet can find out because i'm kind of curious (i might end up using my phone to check eventually)
My world that uses a synced seed and System.Random to procedurally create a level, it works perfectly fine cross platform. I've had an account on pc and an account on android in the same world and they saw the same generated level
If there are variations it might be very slight. Might be worth making a testing instance where one player produces a long series of ints from a seed, syncs the seed and ints, and then the receiver of the sync also generates a series of ints from the seed and sees if there is any difference
Yeah it seems to work fine cross platform for me too.
Seems like floating point errors could cause issues theoretically, but the chance seems low? It's definitely something that should be tested for more serious projects.
are doubles actually reduced at that level? i thought it was more the rendering or maybe unity level (transform positions), but i'd be sorta surprised personally if floats or doubles in data land were reduced
thank you for testing btw
I wouldn't know for sure. All I can do is ask AI™️which claims that the Next Method is actually accurate and that only NextDouble can differ because it multiplies by a floating point constant internally.
Which I guess makes sense?
spooky 👻
can cheaters forcibly make themselves instance master and/or object owner? are there ways to do checks and prevent that? in udonsharp
you can prevent object ownership transfer like this
public override bool OnOwnershipRequest(VRCPlayerApi requester, VRCPlayerApi newOwner) {
return false;
}```
(^ this code will only allow instance master ownership of an object, return true to grant ownership)
if you want to prevent instance master transfer, youll need to come up with your own system to work without it, vrchat handles those
Definitely seems like a prevention to me
that seems so counter-intuitive though. like you would think if it's always false then the owner cannot change but what happens if the master leaves? apparently the new master will take ownership. I do this to make it cleaner:
public override bool OnOwnershipRequest(VRCPlayerApi requestingPlayer, VRCPlayerApi requestedOwner)
{
return requestedOwner.isMaster;
}
that event is just run on clients, the server transferring ownership from a leaver is kinda just implicit, like this doesn't even run afaik, so it's not like it's unclear if you don't denote it imo
That’s what I was thinking too
The server's photon layer is primarily where ownership is handled. There's the default behavior which is that the master owns all objects, with hooks and callbacks that can be used to facilitate ownership transfers. I believe generally speaking any client can make an ownership request, and if not rejected either by photon or the Udon ownership request callback, then photon will just inform all clients of the transfer and start regarding changes by the new owner as authoritative. So yeah, someone leaving is just implicitly handled by photon as well.
so i got a pretty complex and unorganized script and im running into an issue. Can someone tell me where its messing up (and no you don't want to see it😭 )
-
Object synced pickup enables bool in udonbehavior 2 when collide with udonbehavior 1.
-
Player1 puts in one object, player 2 puts in a different one. Two bools.
-
Any player interacts with UB 2 to "try combination" Auto fail every time for everyone.
-
Player 1 puts in same combination and "trys" with success but player2 doesn't get the same result.
-
UB 1 is not synced because thought the object synced items would do it.
-
UB 2 is synced.
I'll show a fraction of UB1 to get the idea.
This is ub2, the only difference between win and fail is the audioclip. (I know its cursed)
I meant code readability. so imagine someone doesn't know or forgot about what you said
the intent is better conveyed to a human
to play devil's advocate though, imagine someone reads this and thinks it is the reason the ownership transfer happens on leave, like i think understanding the underlying reason is better than injecting something that somewhat conveys that to someone who would need to know the case anyway
if it makes it more clear to you that's very valid though
hrm true if they don't know when that method is called
Any clue what I'm doing wrong here?
Wait, I forgot network callable, testing again
YEP that fixed it
Do you have any examples of what sort of behaviour? I've been doing this for years in multiple worlds without issue.
I recall a GameObject's networking straight up not working for a few frames after it is first set active in the scene
And I also recall VRCObjectSync having a tendency to teleport back to its previous location if you try to move it close to the same frame it was set active.
Would the first be a problem? A few frames it very brief and it'd then snap to the correct postion anyway wouldn't it? I've seen the "snap back" issue but never in one my worlds where I'm doing this so I suspect something else also required for that issue to trigger...
It's been awhile so I don't remember the specific details, but I'm pretty sure "networking straight up not working" was me trying to change a value and then RequestSerialization() and the updated values will never going out.
so i have a button that turns on and off a animation bool. it works but its only local.. idk what i need to do to make it global for everyone
does anyone know whats the issue?
replace both sendcustom networked event with just send custom event. Make sure State is marked as synced.
originally, it was both just send custom event but it was still local. when i press the button, it worked but only i saw it
Is the State variable set to be synced, and what is the sync type that your UdonBehaviour is currently set to?
i think its synced and the other is manual
That all looks fine then
make sure to check "send change" on set state as well
ok so- object follow player script. But delayed like a follower. Which node?
like a follower? instead of instantly setting the position, you can use something like Vector3.MoveTowards called every frame
https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Vector3.MoveTowards.html
yeah I tried something like that and it wasn't exactily what i needed. Maybe im doin it wrong.
this method just does the math, you'd then take its output and assign it back to the follower's position
thank you
here's an example, this will make one object follow another position. You could modify this graph slightly to make the player the target instead, if needed
Is it possible to sync a DataDictionary?
kind of. You can convert it to a JSON string, sync that, and deserialize it back into the DataDictionary
https://creators.vrchat.com/worlds/udon/data-containers/data-dictionaries#syncing-a-data-dictionary-with-other-players-over-the-network
Data Dictionaries store Data Tokens by key-value pair, similarly to C# Dictionaries. Most Data Dictionary functions are just wrappers for the underlying C# dictionary, so the C# dictionary documentation also applies if you are looking for more specific details.
just note that the keys must be strings (this is more of a json limitation from what i understand, not exactly vrc's issue)
and that numbers will be represented as doubles when deserialized from json
I have always used strings for keys. Anything else is just weird and non-standardized. Like if you wanna make numbered keys, just do "1" instead of 1 for example.
Using objects in the scene as keys unlocks some pretty cool stuff 
Wait, what? Doesn't that just make referencing certain things a pain?
It means if you have a reference to an object in the scene, you can have a bundle of data associated with it. Useful for registering objects with central systems - my favorite way to do hitboxes, for example.
also, dealing with large amounts of json data is a pain. (each of these is really messy and long)
I suggest making static functions to navigate through your datatoken structure and pull out the parameters you want into a strict-typed return value, so your working code which does things with that data doesn't have to constantly be casting stuff around
You get to design your own interface for the data as you want it - datatokens are just the base container
Also, will support for static fields ever be added? Not sure if that's possible.
Also, could you please take a look at this? I believe this is something that wouldn't require much work (I may be wrong though): https://feedback.vrchat.com/feature-requests/p/discord-activity-proxy-discordsayscom-in-url-allowlist
whats the best way to sync an int value for late joiners?
Put it in the synced parameters list?
👁️ 👄 👁️ in udon

Learn how to use Udon to make a slider that shows its value and syncs to everyone else in the world.
0:00 Building a Slider
2:54 Adding a Text Field to the Slider
5:04 Showing Slider Value in Text Field Using Udon
9:52 Syncing the Slider Value over the Network
13:42 Restricting Variable Updates to Owner
14:46 Switching to Manual Sync
16:26 Rest...
not working with a slider, more like an interact time change.
Its the same concept though, just different trigger.
Theres a few things you need to do in order to hook into all the right pieces. For something as simple as just a toggle it may feel cumbersome but each part is something important that plays a role in larger systems. In general, the flow is: set up a synced variable and use manual sync. When a user interacts you need to take ownership, set the variable, requestserialization. Then on the receiver side, respond to ondeserialization.
The exact way you do all that depends on you, but a good practice is to create your own function which applies the state of the variable onto the world, and then you can call that anywhere to ensure that whatever the variable says, the world is in sync with that. "anywhere" in this case would be after you have called requestserialization and another after receiving ondeserialization
i mean, thats kind of what I have already, yet the late joiners dont get the updated int value no matter what I do
That's because you're using a network event to transmit the interact. That's not truly syncing a state, it's just telling everyone around to increase the int by 1. Late joiners dont get old network events
right but it does the same even with a custom event + req steralization, the desteralization send event.
Probably didnt have that set up correctly then
is there a correct way other than this?
You do have selectedmaterial listening for a change, which is great. That is an alternative method which does simplify q bit - you dont need to listen for ondeserialization or call an application method manually
maybe thats what's messing with me. I dont usually work with ischange stuff.
Intlogic is the part of your code which changes the value. You dont want late joiners attempting to change the value, you want them listening for and applying the value to the world when they receive it
correct 
Which will happen automatically if this is manual sync behavior, synced variable, and not disabled by default
I would suggest removing the ondeserialization so that the onvariablechanged can work alone
I will try that
this worked thank you sm 🫡
Please upvote this critical issue: https://feedback.vrchat.com/bug-reports/p/vrcobjectsyncrespawn-no-longer-properly-works
ooh so that's why stuff was respawning weirdly in my world, in which i had changed nothing
i noticed stuff was respawning where it last was, not where it started
that's so weird
we have a fix that will ship in the next major release
https://feedback.vrchat.com/udon/p/vrcobjectsync-respawn-does-not-work 🥳
When will it be updated? There was a hit to Game World operations yesterday, so I hope it gets fixed soon.
Well I've found the niche use case where PlayerObject's locked ownership causes issues with my use case.
I want to create a system with two networked elements: a data element and a visual element. The data element will be solely controlled by the player so that is fine, but I want the visual element to be a pickup that other players can pick up. As a result I'll have to create my own implementation of PlayerObject... :/
lol I am glad I use my own variables for respawn functionality
I do too, except for synced items lmao. Made more sense to use the official method... but of course they break that.
I never ended up using it myself because it lacked the ability to change the respawn point
I made this flashlight script and I wonder....it's switching on/off when someone uses it and then playing a click and the animation turns on the light...but I have one weird problem...
The instance owner has everything normal, but remote players coming in telling me it clicks nonstop and if they grab it and use it, it stops non-stop clicking, but then clicks for me endlessly. What's causing that?
(and no, I checked, the AudioSource is not on loop)
don't combine flows, you're sending RequestSerialization and OnDeserialization to the same place, which can break the compiler and cause unintended behavior, possibly could be the cause of your issue
separate those nodes into its own function, and have each flow call the function instead
also, is your script set to Manual, or Continuous sync?
Continuous, because it bitches, when I put it to manual, what would be enough. But it's on a Pickup Item. Should I set the script to an object below the pickup to turn it to manual?
like so? >x>
Hm I mean the flashlight click is an event that's needed ONCE in that moment, independent from the state of it. I make it a NetworkEvent and get it out of the networking bool! >->
thought about putting the Udon Nodes on an object below the flashlight, but then I'd have 2 boxes...one for the pickup-hold and one for the on/off script. If I wanna one box collider for both scripts, I need to keep the on/off thing continuous, otherwise it moans at me on upload, that I can't on an Object with ObjectSync having a script that's manual =w="
ok I think I know where your bug is coming from then. RequestSerialization and OnDeserialization are meant for Manual sync, but OnDeserialization still fires when you have a script in Continuous sync
This means that, rather than only firing once like OnDeserialization usually does, with Continuous sync it fires several times a second as its variables are continuously synced
hence why the sound was playing over an over for other players. The sending player never runs OnDeserialization
Yes I got it too and I solved with putting the click as a network event after the button is clicked, so just the "looking for current state" of Flashlight is in the networked thing now. It'd be more performant to keep it manual, because such button is not clicked often, but welp...VRC forcing it continuous when it's on an object with pickup + sync~
whyever the one script is forcing the sync of another one shrugs just 'because'
it's because scripts on the same GameObject are all network synced together, so they can't have mixed sync types
Mhm...having states on synced objects that need to be networked seems to always be a little costy for performance if states of scripts on one object are forced to be the same. Ofc you need continous sync for an ObjectSync...but not for state changes
noooooot flexible! ^^
using a Network Event is good, that's what I was going to suggest for the sound, but for the light you'll probably want to use a synced bool and use its Change event to update the animator
So that's kinda the solution :x
Would the var Change thing also fire when e new player joins like the OnDeserilization does?
yes
oh convenient, am learning things. A few months ago I did everything with OnPlayerJoin check is master if yes check state and send to all to switch to that state, cuz I didn't know about networking vars and YouTube tutorials told weird stuff x333
So whenever someone joined, all scripts on everything for everyone checked multiple things and yelling all through each other. I feel more tidy since I dropped it and use vars xD
best way to learn is to mess around and come up with solutions like that, ones that make you look back and go "why the hell did I do it that way.... but at least it worked"
I have a project....nearly 2 years not looked into...it's a hell cuz it was my very first map back when I knew nothing...I am a bit afraid to come back cuz I can redo most scripts cuz it's bad practice and slow...but I will...not now...I'm fixing my current projects first...but this big building ground...will be tackled, cuz it hurts my soul xD
Can you imagine that I needed a full workday of time just to find out how to make a door be synced among players if it's open or closed? Gosh x'D
I'm sure we were all at that point somewhere in the past lol
what seemed impossibly difficult before is now trivial, a sign that you've learned and gained more skill
Fixed VRCObjectSync transforms not respawning in their original location.
#open-beta-announcements message
hiiiiiii
so im working on a system for my world wherein each player needs their own thing - so, VRCPlayerObjects is working great for that
But, im having some trouble working out how to get a remote visual copy of the object to appear for remote users, like ah, let's say the playerobject is a hammer, everyone does see their own hammer, but then nobody can see other player's hammers.
during runtime i take a visual-only copy of the hammer and unparent it from the playerobject root, assign the original as a source for a parent constraint so that visual copy stays where that players hammer is, and also it has VRCObjectSync on it. however, still nothing, it seems like, because the object started as a child of the playerobject root, that VRCObjectSync isn't working. am i right? Is this the wrong way to go about this? do I need to script my own system using network events to maintain a visual copy of the hammer in each remote players hand
i could only find one video that might be helpful but it uses the visual scripting, i really prefer to stick to manual scripting but some of the scripting api seems lackluster for the vrchat specific components
on what object specifically is the VRCObjectSync component located? The pole or the hammer?
ah im sorry, by pole i meant hammer
heres the heirarchy
also i lied its not a hammer its a fishing pole lol
so, im taking "visualpole" and unparenting it with visualPole.transform.SetParent(null);
are you replicating the unparenting for all players?
in play mode it seems to work fine but i cant actually test what the remote player sees until in game
ohhhhh hmmmmmm
i should put the unparenting in a network event
or something like that
i guess i just need to have a central script to manage recieving and sending the network events to keep everything where it should be
so, theres no reason objectsync wouldnt work as expected with an object that started as a playerobject?
it would be healthier to sync it with a variable to track its state instead
could you give an example? im very new to udon networking
no not particularly
remember that VRCObjectSync only syncs the position and rotation, so anything else that you change about the object (like its hierarchy position) won't be automatically changed; so having the object at different parts of the hierarchy across players might confuse it
yeah give me a sec
tytyty! yes that makes sense
actually I think there's an example in the docs
All supported attributes in UdonSharp
for any kind of "on or off" or any sort of true/false states, I like to do what's described in the example here
you can sync a bool, so in your case whether or not the object is parented.
Normally, you'd have to edit the value, RequestSerialization, then check in OnDeserialization what the value is, which works for some things but then starts to get convoluted
so instead you can use FieldChangeCallback. This is the same as using a Change node in Udon Graph.
what this setup does is automatically run some code whenever the value of the variable is changed to a different value
This is especially useful for networked things, because your variables can essentially automatically change stuff without you needing to shove it in OnDeserialization. Even more useful in cases where you need to use Continuous sync, like if you have a script that's on a synced pickup
i see i see hmmmmmmmmmmm
ok, i think i can wrap my head around this, barely, thank you
it does look a bit complicated, but once you get it set up, it's life-changing for networked stuff
the reason why you don't what to use network events is because they aren't replicated for late joiners
so doing this approach will also automatically work for late joiners too
so maybe something like ```
[UdonSynced, FieldChangeCallback(nameof(SyncedToggle))]
private bool _visualParentState;
public bool VisualParentState
{
set
{
_visualParentState = value;
visualPole.SetParent(_visualParentState ? null : original)
}
get => _visualParentState;
}```
am i on the right track?
I think the ternary operator uses a colon instead of ||
yeah idea should work, in theory
so then in the rest of the code, all you have to is just change the value of VisualParentState. and then this code will take care of the rest
okok
even if im setting that value from the script attached to the playerobject? neat neat neat
i'll give it a shot
so player joins > i set their visual parent state to true in onplayerrestored > another player joins > the unparented object will be unparented for them??
somehow i feel like theres a gap of logic there, but i dont know what all is happening under the hood so, idk
thank you thank you
yeah you've got it right
this will also run if the value is changed via a network sync. So other players will be sent the networked variables, they'll see "ok this variable has a FieldChangeCallback, so I'm going to run this other code too"
yes yes, that does make sense
its so hard to imagine it in my head though like, OnPlayerRestored happens , then another player joins, it would seem like the code in the first player's OnPlayerRestored would have to happen again
its mind boggling in a "surely im doing something else wrong" kind of way
ahhhh i get it i think ok, yes i'll report back if it works :3
its syncing that variable and the actual stuff isnt happening in onplayerrestored, its happening in the boolean thingy itself
yeah you can think of it that way
the variable's purpose is to track a state, and you've made it so the variable also sets the state too
is just sometimes convenient to keep it all in one place
The flashlight does the click and changes the bool on click, but the state Change is not firing, not doing anything. Any ideas? w_w
make sure you check the "sendChange" box in the "Set state" node
gosh! You're ma hero, I was biting my pillow already xD
I have a variable that will count up if a part of a mission is completed and whoever triggers or interacts to fulfill that, will cause for everyone to make it impossible to cause this again - otherwise for every player on the map it would +1.
But what's if the trigger itself is an object carried into a trigger. Then I can't catch the player entering the trigger but an object that's synced will enter the trigger for everyone.
How can I prevent the int going +1 for each player on the map in that moment? x_x
i'd probably base it on the player that owns the script tracking the +1, so if the player seeing the object enter the trigger is the owner of the variable counter script, they run the logic. alternatively you could run it on the owner of the object being brought into the trigger, or the master
I've built out a VRChat world menu builder! It should make creating custom menus for your toggles, settings, and most everything else, with a lot of room to create add-ons and custom versions.
Happy to take any feedback you might have!
https://youtu.be/PJduLxL-sDM
Get WorldUI Here!
- Patreon: https://www.patreon.com/collection/1842038
- Jinxxy: https://jinxxy.com/Vowgan/WorldUI
- Gumroad: https://vowgan.gumroad.com/l/WorldUI
- Booth: https://vowgan.booth.pm/items/7652053
WorldUI Documentation:
https://github.com/VirtualVisions/WorldUI-Documentation/wiki
Join my Discord!
https://discord.gg/nS3x5Pr
Patreo...
Looks really cool!
I know this is very late, but I was reading thru this and got curious
If you're saying things like opening hatches resolve for late joiners, why is there an event for it?
Hatch open states don't resolve for late joiners, and the game is actually designed around that. While I could sync an array of 17,576 bytes with 6 of the bits in each byte representing the hatch state on each side of each of the rooms, that's not terribly efficient, especially given how frequently hatches are interacted with by a variety of different people.
Instead, players can only join when a new round starts. Each "round" lasts 3 minutes, and at the end of those 3 minutes all the hatches force close and lock, the rooms do some reshuffling, and the next round starts with the new players being spawned into a starting room and all the hatches unlocking.
This means late-joiners can only ever enter the game with all the hatches in a known closed state. From there I just use the events system for players to indicate to everyone else when a hatch is being opened or closed. This is efficient network-wise (just an int (room id), a byte (direction), and a bool (attempting to open or close), sent to all players), and I've found quite robust in practice.
The only scenario where it could potentially lead to some minor strangeness is in the observation room, where you can watch the current game in progress. If the late joiner goes there before a new round starts, they might observe someone go through a hatch they don't see as open. But this doesn't affect gameplay, and hatches auto-close themselves after a certain cooldown of no one in an adjacent room anyways, so it's still not super common.
Whoops, meant that as a reply
if you have a way to index into the hatches it'd probably be more practical to only file hatches that are open (putting their index in an array), but you'd need a way to resolve an entry being removed from the local perspective to close it again (or otherwise store their state if they were interacted with at all)
in the worst case that's a lot more data though (idk if it's possible/likely for people to open every hatch)
events are good for round-based things though (so i agree with your handling of it)
though with all of them being in one array it'd be a pain to sync often. if it were absolutely necessary to late sync them i'd probably spread them into clusters
At smaller scales I'd just make each room its own networked object and have it manage its own hatch state. But with 17,576 rooms, this approach was far more practical haha.
The rooms still each have their own script that run locally and handle the internal logic, they just communicate upward through a singleton controller that listens for network events, and that then propagates the request back down to the specific room on each client. It's integrated with the dynamic room loading/unloading system as well.
If I were to late sync them, I would probably just have the master run a function on player join to query all the currently loaded rooms with open hatches, get their hatch states, and bitpack an int array with the data. 15 bits to index the room, and 6 bits to indicate the state of each hatch. Far less than the total number of rooms are loaded up at any given time, so it wouldn't be too expensive an operation to run every so often like that, and it would still keep the size down.
17.5k network objects on upload sounds really sad yeah ):
I wonder if someone has an insight to what's happening with vrc networking rn (the "unusual client behavior" error). people say it's cloudflare but what I observed is the missing object reference in the console. even in my world when I regenerated ids I would get booted from the world right as I join with this exact error, which didn't make sense to me until I realized all networked worlds are affected to varying degrees
what are network ids even used for? do they get uploaded to some networking server and photon uses them when communicating? if that is how it works it would make sense why the regenerated ids caused errors, maybe they don't get uploaded due to cloudflare issues
Network IDs are required for a client to know which GameObject a serialization is associated with. Network IDs are stored in the world descriptor component and are uploaded along with the world's asset bundle. As far as I know Photon does not care about Udon network IDs.
hm I wonder why I get an instant error when I regenerate network ids then...
it's not exactly easy to debug since all it pretty much says is: "shit happened"
previously I thought that putting udon behaviors to childs of other udon behaviors causes this issue, but then I noticed I have such objects in the scene right now and they work fine
all the references are also good as far as I see
Please ensure all the GameObjects in your scene have unique names
nope, there are no issues. there are gameobjects with the same name, but under different paths so it should be fine. I do notice that my last ID was 301 previously, and after regen it 's 283, also quite a lot of objects are reordered
Interesting
oookay this is veeeery peculiar. I have 21 objects with the same 4 serialized components. they are numbered in order and it's all good, but when I regenerate IDs the network utility of course goes down the hierarchy adding these objects in order. and when there is more than 20 (I have 21) it causes that "unusual client behavior" right at the world join. but if I move that network ID of the 21th object to the end of the list, then it's all good.
wtf vrchat how does this make sense
something must've changed on the backend. for example slashco got broken for no reason with the same error on round start
they've mitigated it by regenerating network IDs and it's better now, although some people still report issues, but in my case regenerating made things even worse as I've explained above
my world only recently started seeing people disconnect with "Unusual client behaviour" when certain events trigger on certain behaviours
it hasn't been updated in at least a month, but only started happening a couple days ago
it seems to be triggered by networked events?
but only a couple network ids in my world are affected
tried regenerating network ids but the same object is affected
can confirm though that the disconnect is happening when a local player fires an event on the behaviour itself
its strange because i have 17 other identical scripts in the world and only 1 causes this
(editing later on to say i was able to mitigate it by deleting the broken script and copying another one)
very interesting, I'd really like to know the root cause
looks like it's fixed now after the latest update
Im also getting unusual client behavior again in reproducible circumstance (that are sadly very complex and hard to narrow down why exactly to a single cause). We need better ways of logging for why. Couldn’t it at least give us the network id in the log of the object that triggers the client to panic disconnect?
Last time this happened it was a random game object that needed to be deleted and recreated with the exact same components and asset paths (no udon was changed, it was the same asset). I already did network ID troubleshooting and it didn’t help that time. Only just raw recreating the game object fixed it as if it was corrupted internally in unity.
Someone had the same issue with a script too, the script stopped working as expected and they had to delete the old one and copy its code to a new one to beat the error
Udon seems to be struggling a bit with false positive errors lately
I tried removing serialized udon assets to "clear the cache" so to say because I've had an issue with the serializer before, but that did not work in case of networking
so far so good though, I do not see the error anymore after recent update
yeah I fetched 'is master' and the master then does the +1 that will sync with everyone :0
#udon-showoff really should be at the bottom...
Yea this is how I’m handling situations like these too
deathing. help. something's messing up here.
the system should
getting the position reference of an object
turning that into a force applied to the world in reverse, giving the illusion of the world moving around you
but right now
when someone else grabs the control object, it stops referencing the position or something and it all just stays still
rotation is still perfectly fine, it's just position
I mostly work with UdonSharp but Imma take a look because I have nothing better to do. 😛
Trying to figure out why you're setting owner twice.
Oh I see. It setting the owner of two different objects?
There are a few things here that might be the cause.
The main thing is, likely, its not properly requesting ownership or setting ownership of all the appropriate objects? Perhaps?
One thing I want to mention though is, when it comes to simulating motion, I'd recommend using lerp to procedurally animate instead of physics simulations.
The latter is bound to be janky. This is specifically under the condition that you're simulating motion.
If you're actually developing a vehicle that moves, a rigid body will do just fine in this case.
no idea what lerp is
Its just, a way to move something from A to B over a period of time (t).
this hypothetical 'something' can be anything, typically. It could be a number that you gradually set.
But, in your case, the value would be two vector3 the first one, taking in the transform. The result is movement.
Oh, I almost forgot, transform.translate might also work well.
If you want, I can look into testing it in speghetti for you. 🙂
Lemme know, yo.
I actually wrote a procedural animation script a while ago if you'd like to use it.
It has a loop mode too.
Its a UdonSharp behavior, but I can teach you how to use it through screenies if you'd like.
yeah, that'd be helpful
No problem. Just trying to find it. 🙂
Found it. Just trying to wrap it up in a unity package for you.
Just writing headers so its easier to understand in the editor.
Would you prefer if I made a short video? Might be easier.
that would be helpful, thanks
And all of a sudden, my mouses right click button isn't working. Ffs. lol.
I'll try to send it, but now I'm struggling to even record.
Should do the trick.
There are two scripts : Enabler & Object_Animation
The enabler simply turns on the Object_Animation script.
Once its turned on, the object that Object_Animation script is attached to, will animate how you set it.
I'll show a short video.
I wanted to explain through voice but my damn audio interface is broken.
The 'Requires Interaction' toggle will require the player to press it to launch the animation.
Setting 'Is_Full_Animation' toggle and 'Toggle_Mode' makes it automatic in the 'Object_Animation' editor script.
If thats not working, make sure that the enabler referances the Object_Animation script by dragging and droppingit.
Also, on the enabler script, make sure to set the 'Auto_interaction_time' higher than 0. That will also enforce the looping.
I'll have to re-do this system in the future in a more optimal way as I'd much rather handle networking manually instead of continuous.
I only made it continuous as a cheap way to allow animations to sync for now. lol.
But yeah, animating is just a matter of adding to the 'Position_To' variable in the editor.
I believe its in world space - not local space. But you can get around that through clever object parenting.
It also loops through the values. So, like, you can have different location points and it will circle back around once it reached the last indice.
did you need help with udon networking? What are you working on?
oh hey techa, no i'm fine, it's just my clients ping arrrrre so high and idk why
quick question: does OnPostSerialization() fire once all variables have successfully been set for remote users? or just immediately after sending the data regardless of whether or not any remote users received it?
and if the latter, is result.success literally just "was the owner able to connect to the server to send the data"?
the latter. yes
unfortunate, but okay tysm !
the former is what OnDeserialization is for, when the client has received data and it's ready to use
right but the owner has no way of knowing who needs them to reserialize unless i clog the network to tell them with unreliable networkevents, unless I'm missing some other event/method
and more importantly, the owner doesn't know when to send the next batch of data in a list, since they don't know when everyone has gotten the previous one
there's no reason the reserialize if the PostSerializaiton reports it as successful. the data will eventually arrive for other players
and if i send new data before it arrives, will those players receive both serializations in sequence reliably and in the correct order every time ?
not for manual sync serializations, especially if you're sending it very quickly
you'd only get that reliability with Network Events, which aren't viable if you need this data to be synced for late joiners too
you're treading a two generals problem here
i don't, but i just need the reliability of synced variables in the sense that they don't go "oh, u hit the rate limit? we don't need to send any more events ever, no worries"
like, in my experience, network events don't queue properly ever, so i don't necessarily trust them
the network being clogged doesn't mean the data gets thrown away, it just means there's a delay in sending it out as the sender processes their queue
network events don't queue properly ever? that sounds like an implementation problem
I've had them queue just fine
right but i mean like, if i need to send 16 events OnPlayerJoined(), and 10 players joined, then all of a sudden those extra events get messed up
this is possible tho; i was trying to fix a VRCObjectSync bug, so it could've just been a symptom of that bug
i suppose i could try using NetworkEvents again for something more normal lol
you're already hitting the Network Event limit with 16 x 10 events
but this limit is 100 per-second, with a single network event limited to 16KB of data.
so assuming you're under that data limit, 160 events should take only 2 seconds to process
as in, 100 events will be sent immediately, the remaining 60 events will be sent 1 second later
oh, but that's if you actually raise the limit. the default is 5 per second
right yea, the example assumed a rate limit of 100
but so after those 100 get sent, the others are guaranteed to send eventually ?
yea ik, okay well tysm! i'll try looking into that again then
if they fire out, they will actually receive both and in order
the biggest nuance is that you can't request it serialize multiple times prior to it serializing as it will only send the latest state when it goes to serialize
yeah i knew this part; there have been a couple instances where i've set multiple variables and RequestSerialization() after each one is set in the same frame, and they serialize together
but as for this part, now i have two people telling me different things 😭
if you get two successes from PostSerialization, they'll send in order
I was thinking that you'd be calling RequestSerialization too quickly; then they'll get smushed into one Serialization as occala is referring to
oH, yeah no the idea was RequestSerialization(), check success in OnPostSerialization(), call RequestSerialization() if successful
i think you got this already, but success doesn't mean it was received, post serialize is called immediately following pre serialize and just means it was serialized and is going out
success doesn't typically fail in any meaningful way related to networking, it can fail if you try to serialize too much data or if the data is invalid in some way
with that said it will be received by other clients unless you're dropping packets and/or in the process of disconnecting
okay, ty guys for the thorough explanations !
So I have a lobby system. What's the proper way to beat raceconditions via networking? The current issue is that the lobby immediately starts, which serialization takes a few ticks to complete on clients, meaning they error out due to missing data.
most of the time you want just 1 player managing important things, or at least being the mediator in network timing
if they're erroring due to missing data, then you aren't waiting properly for every player to be ready
Right, I'm unsure about how to ask the clients if we're good to go.
I have one client acting as a psuedo-authority currently, the instance owner handles the logic and tells others about it.
But I'm unsure as to how to properly 'wait' until everyone's ready.
there's a lot of ways
when you send the signal to start the game, the client can check if they've received the data they need yet. If not, you could SendCustomDelayedEvent and check again a little later, and put them in the game once the data has been detected to have synced
so you could have a flag that flips in OnDeserialization or something
or you could store a value for every player, a bool that says "yes I'm ready and have the data", once they have the data synced they send an event to the Owner to flip their value; once all bools are flipped, the game is ready to start
or they should already have the data in the first place before the host even has the chance to start the game
What method would be good to make a habit of?
eh.... I think that purely depends on specifically what you're doing, and what fits best
I have a lobby system.
On game start, the master checks who is in the lobby zone and then tells them to teleport in-game, there is a brief grace period for roles to be distributed to everyone's PlayerObject that is involved in the game
for me, I'd do something akin to option 3? the game shouldn't depend on if they've recieved a specific network sync yet or not
yeah probably some kind of intermediate state would be best
you could just Start Game -> fade everyone to black -> send them into the actual game once they have the data they need
I randomize spawns, which is the current issue. But the fade to black is a good idea. I could do a little animation during the sequence as the game starts.
if you can get away with events for whatever you're sending out, events would be strictly ordered. the data meant to be given prior to start would simply be before the start event, which is much more simple imo
I know some lobby systems are like an elevator, the doors close after the button is pressed. Now no one can leave or join the zone.
Combined with occalas suggestions, it works pretty well
The current pattern I'm using is this. This is where the race condition starts. Are you suggesting that I use an Owner event to request serialization as one would have to come before the other?
what you're doing there is like, the most prime recipe for Udon networking race conditions
Serialization + Network Event
Right. I did think so.
First VRC game, so I'm learning as I go with networking.
I expected a racecondition but just laid it out since I didn't have a better answer at the time.
Since I had to implement the rest of the logic.
i'm suggesting you don't use variables for the data they need upon game start, but it might not be easy to convert to events
outside of that you're going to have to wait an arbitrary amount of time or try to infer that everyone's ready somehow
based on what you've got, it might be easiest to just modify PaperMasterStartGame to put the player in a waiting loop and wait for the serialization to arrive
Ayyy! It all works.
Simple timer and fadeout gives plenty of time, everything works just as expected. Tasks assigned, roles assigned, Task Instances tick on clients that they are instantiated on and communicate to other clients what task stage and ID they are, and other clients can infer it all via a method of Task ID and Task Stage.
Hey all! I'm having an issue using TryingToSpawn on my VRCObjectPool, moving their position and then setting velocity on their rigidbodies.
Objects lag/stutter for a short moments before moving on players who aren't master/owner. Has anyone experienced this problem before or know a fix?
its because of the transform lerping (made up term, idk how its called)
I think you can fix this with VRCObjectSync.Teleport ?
theres potentially an overload to remove the lerping i think, or a flag you can set
like flag discontinuity or something close to that
vrc object pool + vrc object sync has quite a few issues, you'd also find that late joiners would probably fail to see the objects in the correct spot if they'd settled prior to their join
I like the term "lerping" 🙂
It seems I'm still having the same issue with this change.
Maybe I'm using it in the wrong place (this code being in OnEnable as I just want it to happen once), or maybe there's a problem with using AddForce in this context?
sync.FlagDiscontinuity();
sync.TeleportTo(target.transform);
rb.AddForce(direction * 10, ForceMode.Impulse);
The objects appear in the correct place without delay, but move with AddForce after a delay and stutter.
Perhaps I need to take things in another direction to account for network delays? Like if the VRCObjectSync's teleportation is instant, perhaps the force on the rigidbody (from other players view) is not?
I also tried using rb.velocity and experienced the same effect.
doesnt vrcobjectsync also have an addforce method?
but yeah make sure its the owner of the object that runs this code
I don't think so. I also tried to mess with the sync's transform (sync.transform.position += new Vector3(0.1f, 0, 0);) and also have the same effect.
Even with the animator and rigidbody turned off as well
hmm yeah if its only a problem on the pov of non owners, then its due to the vrcobjectsync's lerping.
I dont think theres a way to solve this so long as you keep vrcobjectsync on the object, but if you find a way by all means lmk
are you ensuring that the Owner of the object is the one doing the moving?
Yes, this is wrapped in a Networking.IsOwner check
have you tried messing with FlagDiscontinuity?
i think you usually need to keep the gameobject with object sync warm (active) for what you're trying to do, which doesn't play well with vrc's pool. you'd probably need to use a custom pool if you want to fix the underlying issue, or do custom transform sync
more specifically a pool that only disables the visuals is probably the easiest, but you'd have to consider how to handle the object sitting around somewhere "inactive" with a rigidbody and collider still on it
put 'em in a jostler that keeps them constantly moving
ideally they'd just be kinematic and the collider disabled, but you'd have to ensure you undo that on the owner when pulling it. discontinuity should handle it teleporting to the intended spot from the remote perspective
I would have assumed it'd have correct late join support, even while asleep. Have you tested this?
not recently, but yes. it's more to do with the pool starting the objects disabled. by the time they get enabled from the pool sync, they don't receive or apply their location
Oh I see what you're saying! Yah they do make a bad combo. In fact combining using vrc object pool on any networked object can result in issues.
Wish it would be possible to send a custom event to a singular player instead of sending to all, with the playerID/name, and checking it on arrival.
Feels weird to have to send as many custom events as there are players for specific global interactions haha
cant help but feel like its inefficient network calls wise
As long as you aren't calling them every frame, those simple checks at the start aren't too bad
yeah im not, more like 5 per second, for a few seconds at a time during specific moments 😅
hoping i wont run into clogging issues, especially with 20-30 players in the instance + a bunch of pickups active
Oh yeah that should be fine as long as nothing crazy is happening on the other side
That could be a lil different
I can't speak on that
its to spawn a projectile, with a random angle to it
and i need them to be synced quite accurately
every x seconds, lets say something like 20 projectiles spawn with a random int attached to them
actually i quite diverged from the original problem i presented lol, yeah this isnt about sending an event to a singular player anymore
any way to test this specific scenario, or at least get more documented on it?
Without having to summon as many players to test
I'd say make alt accounts so you can have multiple "players" at once
I believe you can open multiple instances at once if you open the exe directly (or so I've heard)
havent tried with the exe directly. Atm i can get up to 3 players at once with VCC quick launcher
but theres no way im able to launch vrc 20 times on my pc, it would explode lol
That's valid
As for stress testing, not much ya can do other than get a bunch of people
Just work to make it as optimized as you can and call it a day
(that's what I do at least lol)
guess so yeah.
This specific thing im making is supposed to be a secret so i cant show it to many haha
Hmm
Well if you're making a synced particle with random angles
Why not have one person determine the angles then send them as one big string to be decoded
Then it's one network event
Then just cycle through and fire really fast
if they're random, you could just sync the seed and have everyone locally calculate the randomization
considered it, but then realized that its probably the same networking cost than sending multiple smaller ones anyway
clever! i think ill do that 👍
So true
In regards to the alt talk.
You can quickly boot multiple profiles.
oh wait yall talked about this mb
You can send an event to an individual player by using NetworkEventTarget.Owner (optionally you can combine this with a PlayerObject)
But it should make no change to the network usage on the sender's end. If I understand correctly, both NetworkEventTarget.Owner and NetworkEventTarget.All involve sending a singular event to the VRC server.
meow im seeking advice for the best way to get owner of an object for syncing its pos/rot
like some playerobjects that attach to their owners, and everyone sees the appropriate object "attached" to the appropriate player (the owner)
specifically, a way that works without a networking.getowner() every postlateupdate 😅
getowner is not networked function, ie it doesnt request anything from server/owner, so i recon it doesnt make much overhead. in order to move object with player you do need an update event anyway
oh thats not networked?
it just tells you who your client thinks the current owner is
you could cache the VRCPlayerApi of the owner into a variable when OnOwnershipTransferred fires, and refer to that variable in Update instead
I think GetOwner() is still an extern? while reading a variable wouldn't be
but yeah, ownership of every object is stored locally, so it doesn't take a network request to check it
What is your guys' standard for getting players from lobby to game when latency exists?
For example, if I have a game that operates on the win condition of "1 player remaining" well, the data has to reach them and back in order to put them into the match from the instance master's perspective, so ..
Is it common just to include a buffer for players that are loading into said match?
EDIT: If you're searching for this in the future, this was the answer I was seeking: #udon-networking message
Tried this.
This doesnt work well with late joiners, as they will start iterating on the seed from the beggining
Any idea on how to solve this problem?
Ive tried using the latest joiner's playerID as a seed, but it breaks if youre the one rejoining (it calls onplayerjoined on everyone in random order)
hey matt, not sure if i understand your question
hmm, you'd have to resync the seed periodically
aaah, so the master is the one handling the seed as a udonsynced int
makes sense
yeah that would probably solve it
the master, or the owner of the script?
sorry owner of the object yeah
alright yeah
I think any form of it resyncing periodically should work, maybe whenever a new player joins, or at a set interval of time
actually it wouldn't even need to be networked I don't think. Just when a player joins, just InitState again with the same seed so the sequence starts at the "beginning" again, which should work for your case
oooh, yeah true.
It would lead to predictable patterns though no?
every time a player rejoins, the same numbers would pop up?
precisely
you could then, on top of that, have the owner generate a new seed every so often and sync it
It might be something I'm overthinking
But if I have a scenario ..
Hmm let's say "last player standing" for this example
I want to ensure all players load/sync fully before starting the actual match
Well, that's where the variability starts because now, each "I'm ready" acknowledgement from player to master is tied in with latency
That's where I'm wondering about how others have approached it
Not to mention drops, if they take to long do I make the choice to drop them or just stall out the loading for others, etc
EDIT: If you're searching for this in the future, this was the answer I was seeking: #udon-networking message
i dont understand your problem
load/sync what?
Anything really
The main crux is that I'd want to ensure they send "I have finished loading" to the master so that the match can then start
theres no such thing as "finished loading" in general terms
Yea, the issue isn't about what's being loaded tho
The issue is the acknowledgement that I've finished loading and am ready to start the match and how that would work over the variability of networking
Hmm, if I had an easy example it'd be something like Udon Beatsaber
We want players to start at the same time so they must load the track and agree on a future sever time to start at per-client
EDIT: If you're searching for this in the future, this was the answer I was seeking: #udon-networking message
When I change a synced variable, shouldn't the Change of that variable also work on everyone else then?
Because it's just doing the Change for the one running into the event...that's so weird...
it should just locally for the trigger person change the variable, but change it for everyone, so it's synced and sent the Change, but it's just not working for others...
Problem is that the variable seems to change for others, so they can't do it again, but still they don't get the "variable Change > stuff", so not getting the other event + sound x_x
OK I tested around...it seems to only work if the Udon is set to CONTINUOUS and not on manually....I thought cuz not much change I need to set them to manual to save computation power if just rarely something changes...but seems in the case of manual stuff I'd need to set it not on variable change, but do a Serialization? So either
• Deserialidation + Manual Synchronization
• variable Change + Continuous Synchronization
So it's sending the change, but not dropping the On Changed event, cuz that's just for the person doing the change...how not intuitive...
is it more performant to keep everything manual but request serialization after change and OnDeserialization checking if variable true and firing the event? And if a LateJOiner joins without the request sent while the person is on the map, will the person still get the Change? Cuz variablo Change events seem not to fire for people not actively changing them...
the Change event is not a networked event. It will only fire when the value of that variable changes.
That's why you're encountering it only working when you send Serialization, or when it's set to Continuous; the Change event will fire if the variable is changed via a network sync. But it will not fire for other players if the variable's value has only been changed locally
Mhm I thought the send change checkbox would do the networking automatically when changed, cuz I "sent" the change...and indeed it seemed to send that change to others, cuz the event was then not longer available for them, even tho their value Change didn't fire x3
So is it noticably unperformant to use Continuous and the objects with the bool that needs to be delivered? Or would it be way better to keep it manual, but then request serialisation after the Change and use On Deserialization instead of State Change?
always use manual. its not about performance its just better in every way when applicable
So varChange better locally stuff and Deserialization magic for networking magic~
Change is best for variables that you're using to track some sort of state; the Change event lets you instantly update whatever you need to once that variable changes, don't have to worry about juggling OnDeserialization and also running the code for the sender
(as long as that variable is also synced that is)
I started using field change callbacks for non networked events too because it’s nice to have the code for the variable right there so you remember why that variable matters 😂
But networking is what turned me onto it in the first place 💜
Continuous does have the advantage of having non-guaranteed delivery which decreases network load on the receiving end. Also it doesn't have the send rate limit that manual sync has. But you're unable to turn it off on the sending end and that's why it is hard to work with...
This is what makes VRC Object Sync the most performant way to sync arbitrary movement, it has all the benefits of continuous but it also shuts itself off when not needed.
Doing some log cleanup and I saw this gem, a great example of what I mentioned earlier ❤️
For my "end of game" screen
Just to be clear, isn't that analogous with just using the property setter?
i noticed that networked events can send to just the owner of an object. would this reduce network traffic v sending to everyone.
because if it will, setting up a player object for per person event sending could be good
cap is applied to out traffic. so doesn't matter if you send to one or to many, its one event going to server
Do manually updated networked variables not get sent to new joiners in OnDeserialization unless I actively request a new serialization when they join? I had to kludge a line doing that into OnPlayerJoined to get a bool sent to new joiners.
new joiners also run OnDeserialization with synced network data
it's one of the main points of even using network variables
That's what I thought but it didn't seem to be working. I guess I'll do more testing.
how did i not know you can use => inside of a getter/setter
i only knew about that when it's formatted like:
public int Count => count; or public void SomeMethod() => something.text = "";
I have no clue what => means in c#, I learned that format from a smartypants that taught me properties and have done it forever ever since lmao
pretty sure it's just a shorthand way to write things
like, instead of
public int Count {
get { return count; }
}
you only need to write
public int Count => count;
does the same thing
as well as
public void SomeMethod() {
something.text = "";
}
vs
public void SomeMethod() => something.text = "";
Interesting, so it means “place this single argument in brackets for this element” but it infers based on type
Idk how to talk programmer but I’m trying to learn as you can see lmfao
Thanks for learning me a thing! 😂
i dont know if you can do as much with it in udon but it's at least nice for making some things shorter
I’ve tried many times but for some reason can barely retain C# knowledge written by Ms
Unity’s docs are perfectly fine
MS I never fully understand
Assumed I need an actual degree to learn all the vocab I’m missing there 😂
that's fair
yea lambdas are just methods without a name
in python there is literally a lambda keyword lol
Never been a fan of MS docs
I have a Drink UdonSharpBehavior,
Some drinks will have a flat alcoholLevel, some drinks will have a modifiable alcoholLevel that is Udonsynced.
Is it smarter to have a class that inherits my initial drink script and changes the alcoholLevel to UdonSynced? Or would I just be fine have Udonsynced on all the drinks, even if the alcoholLevel won't change on all of them?
Yeah, I've often said I like C# as a language, just don't like MS' libraries or docs or influence on it.
they're very technical minded, yah
And they seem to try to explain everything as it comes up, rather than separating core aspects from more niche applications.
On a side note, lambda expressions are, in my experience, almost entirely unsupported by Udon so you don't need to worry about them. Also lambda expressions are, in my experience, something that is hard to wrap your mind around in a theoretical sense and requires practical application to really understand... which you can't really do in Udon.
I think lambdas are quite simple imo. Though I come from languages where callback functions are common. It certainly enables much higher control over data especially filtering instead of having to define a function to iterate over specific properties with a specific compare fn
Using callbacks as a form of "async" of some sorts would also be pretty cool
If an owner changes a synced variable on a manual synced script and requests serialization, does Event "Variable" Change play with the updated value for all other players when it's received?
I've been trying to wrap my head around networking for a long time, but the more I read and watch I feel like I just get more confused.
Yes
I'd think the answer is yes, thinking on a whim, but I can't find any documentation that confirms this. I will just test it, I'm just on a reading spr- Thank you.
If you want a more specific definition:
A variable's Change event fires whenever its value is changed to a different value.
This includes if it's changed locally, or if a player gets a new value from the variable being network synced
Amazing. That's more clear than anything I've gotten before.
Emphasis on the "different value" part btw; it does NOT fire if you set the variable to the same value that it already is
@crimson flicker #udon-networking message
A treasure trove of knowledge. Explained just what I needed.
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
using VRC.SDK3.UdonNetworkCalling;
using VRC.Udon.Common.Interfaces;
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class StartGame : UdonSharpBehaviour
{
public GameObject gameSelector;
public GameObject buttons;
[UdonSynced] public bool objectsToggled = false;
public override void Interact()
{
SendCustomNetworkEvent(NetworkEventTarget.Owner,nameof(OwnerToggleVariable));
}
public override void OnDeserialization()
{
UpdateObjects();
}
public void OwnerToggleVariable()
{
if (Networking.IsOwner(gameObject))
{
objectsToggled = true;
RequestSerialization();
}
UpdateObjects();
}
public void UpdateObjects()
{
gameSelector.SetActive(!objectsToggled);
buttons.SetActive(objectsToggled);
}
}
Hi. If I'm testing a world build with two clients, and they're on the same account, are both clients technically considered the owner?
I'm asking because OnDeserialization is not running, even though I'm fairly sure it should be.
My thought process is if both clients are considered the owner, then OnDeserialization won't run for the opposite one because it thinks both are the same client in a way
no; only one player will be the owner, even if you're doing build & test with two clients.
The Owner (the player that sends the serialization) does not run OnDeserialization. This is probably the behavior you're currently experiencing.
your UpdateObjects function should also be called in OnPostSerialization, so that the sending player also updates objects
That's good to note. Thank you.
Also, forgive me, but why would calling UpdateObjects in OnPostSerialization update the objects for the sending player? Wouldn't OnDeserialization do that already, if the one calling isn't the owner?
OnDeserialization won't do that for the sending player because it doesn't run for the sending player
you call it in OwnerTogglevariable though, which is fine, but it's a bit better to run update functions like that in OnPostSerialization
also, the player running OwnerToggleVariable never will not be the owner, since your SendCustomNetworkEvent only targets the Owner
So, if a player presses the button and sends the event to the owner, it wouldn't update the objects for them because OnDeserialization doesn't run for them? That's what's happening right now for me,
it won't immediately, but they should get the update once the Owner runs the function
Yeah, it's not lol
I don't see why it wouldn't
I truly don't either
add some Debug Log statements to verify if the functions are successfully running, like in OnDeserialization and in UpdateObjects
I guess Debug.Log() doesn't work with test builds?
Okay so OnDeserialization is not running for the sender
that is normal. as I've explained
You did in fact say that
only the receiving players will run OnDeserialization
To clarify what I mean by the sender, I mean the person who presses the button in the first place. Like it doesn't run for the owner, because they're the owner.
That is normal?
the person that presses the button isn't guaranteed to be the owner
Yeah
Where in the documentation does it say that OnDeserialization doesn't run for the person who sends a network event to the owner?
a network event is different from a serialization
any player can send a network event
you've currently configured your SendCustomNetworkEvent so that only the Owner of the script runs that event
I have
I think trying to explain my logic would lead us in circles. Is there a clear solution you can think of here so I can see what I'm missing?
that's the thing, I don't see anything obvious that would cause this script to not work.... The owner changes a synced variable, requests serialization, after this happens then other players should run OnDeserialization
you could use OnPostSerialization to test of the serialization is actually successful
but you're syncing a single bool, I don't see why that would be causing problems
you've used debug logs to confirm that each player is successfully running the expected functions?
you should be seeing the player that presses the button run Interact, then the owner run OwnerToggleVariable and then UpdateObjects, then the other player(s) run OnDeserialization and then UpdateObjects
I'll double check real quick
run OnPostSerialization and have it spit out result.success as well
Not sure why this is happening
Is this the right way to use OnPostSerialization?
nope
it's its own event/function, like how you have OnDeserialization
like this?
also how would I get its return
lol
please forgive me if I seem like I don't know some concepts as I'm coming into learning c# from gamemaker
it's public override void OnPostSerialization(SerializationResult result)
if your IDE is set up correctly, you should be getting autocomplete suggestions, they'll tell you how to write the function formats for Unity and the VRChat SDK
Some things show up, and others don't. It's actually really annoying
Also it keeps trying to import things for me and it keeps messing up my code
I've tried to fix that but to no avail
Also
It's telling me that SerializationResult doesn't exist
Not sure how to fix that
add using VRC.Udon.Common;
you're cursed
I guess man
I'm gonna try to update the sdk and unity
Guys
Alright calling it a night that's enough of that
Okay. I got it to recognize that SerializationResult exists. It never runs the debug log for OnPostSerialization. Meaning somehow RequestSerialization doesn't run.
Why in the world is this happening
uhh I haven't seen the code and lack a bit of context, but are you taking ownership before requesting serialization?
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
using VRC.Udon.Common;
using VRC.SDK3.UdonNetworkCalling;
using VRC.Udon.Common.Interfaces;
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class StartGame : UdonSharpBehaviour
{
public GameObject gameSelector;
public GameObject buttons;
[UdonSynced] public bool objectsToggled = false;
public override void Interact()
{
Debug.Log("interact is working");
SendCustomNetworkEvent(NetworkEventTarget.Owner,nameof(OwnerToggleVariable));
}
public override void OnDeserialization()
{
Debug.Log("ondeserialization is working");
UpdateObjects();
}
public override void OnPostSerialization(SerializationResult result)
{
Debug.Log("onpostserialization is working");
Debug.Log(result.success);
}
public void OwnerToggleVariable()
{
Debug.Log("ownertogglevariable is working");
if (Networking.IsOwner(gameObject))
{
Debug.Log("networking is owner");
objectsToggled = true;
RequestSerialization();
}
UpdateObjects();
}
public void UpdateObjects()
{
Debug.Log("updateobjects is working");
gameSelector.SetActive(!objectsToggled);
buttons.SetActive(objectsToggled);
}
}
Instead of taking ownership, I'm sending the event to the owner already.
But yeah as said RequestSerialization just isn't running
And I just checked the owner does in fact run OwnerToggleVariable
because you're only sending the event to owner
the custom network event
it needs to go to target.all
not .owner
ohhh wait hmm
ok so whoever presses the button it sends a request to the owner who then syncs the change
blind moment lol
ye lol
yea you still need to set owner to be able to run the event afaik, but I don't work with events too often
so before the sendcustomnetworkevent put a Networking.SetOwner(gameobject, localPlayer); (I think that's proper syntax)
network events do not require being owner
whaaat
well the rest of this code seems perfeclty fine
that's where I'd start for troubleshooting I guess xD
better to try everything cuz who knows, if it works after then you've found a bug to report
so you're seeing it log "networking is owner", but then don't see it log "onpostserialization is working"?
correct
and this is happening in-game?
indeed
what the gunk
in game as in I clicked build and test for two clients
that makes zero sense
And I'm dumping that script into my current project xD
does OnPreSerialization run either
this button isn't instantiated, is it?
it couldn't be, then the networking event wouldn't work either...
...unless it's the owner pressing the button. then the event wouldn't require networking
even if it were the owner pressing the button, the problem is that it effects the other client, and not the one pressing the button.
so that doesn't make sense either
nope
Do you have any other scripts on the same object?
Is the GameObject active in hierarchy for all players?
FWIW I'm not on the latest SDK
No other scripts. And I haven't done anything to my knowledge that would make it active in hierarchy for some players and not others.
This is in a brand new project. I just made a new script and nothing seems out of the ordinary. My unity is cursed.
Tried it on the new project. Same issue in game.
Even though I haven't tested specifically that OnPostSerialization doesn't work, I can assume as much.
Gonna try it with the example world
Okay so the buttons you can press and stuff pretty much works
I think "become owner" should let you press the "owner" button and increment it but it's not working there for some reason
But the rest works
Now I've tried to transfer ownership like zon said. It still didn't run RequestSerialization()
Now, I've downgraded my SDK to zon's version and it still doesn't work
Like guys
What are we doing here
geniunely no idea what's going on
Zon demonstrated that the code itself works fine
so the issue is likely due to some way you've set up the GameObject
I didn’t even use any owner changes, I used your code exactly
Really wonk that yours doesn’t work
incredibly
Where should I submit a bug report?
for what, vrchat? https://vrchat.canny.io/
Give feedback to the VRChat team so we can make more informed product decisions. Powered by Canny.
chat, i keep struggling.
i keep trying to have the list of player requests show up if at least one person requests, but no matter what i do it just seems to not work. what am i doing wrong?
public class PrivateRoomAccessManager : UdonSharpBehaviour
{
[UdonSynced] public int requestingPlayersCount = 0;
[UdonSynced] public int[] allRequestingPlayers = new int[80];
[Header("UI References")]
public GameObject playerRequestListUI; // the parent ui that houses the content
public GameObject playerRequestListContent; // content containing all request uis
public GameObject basePlayerRequestUI; // base UI that contains the username, accept and reject buttons
public TextMeshProUGUI accessText;
[Header("Required References")]
public PrivateRoomManager privateRoomManager;
[Header("Debug")]
public bool hasRoomAccess = false; // whether or not the local player has access to the room
[UdonSynced] public int lastAcceptedPlayerID = -1;
[UdonSynced] public int lastRejectedPlayerID = -1;
public void Start()
{
UpdateRequestListUI();
}
// ... some code here ...
// gets called via a button click
public void RequestRoomAccess()
{
if (LocalPlayerHasRequested()) return;
if (hasRoomAccess) return;
AddLocalPlayerToRequests();
}
public void AddLocalPlayerToRequests()
{
if (LocalPlayerHasRequested()) return;
if (!Networking.IsOwner(gameObject)) Networking.SetOwner(Networking.LocalPlayer, gameObject);
if (requestingPlayersCount + 1 >= 81) // its too big, have to remove the first requested player
{
Debug.Log("Number of requests are too big. Removing first request");
for (int i = 0; i < requestingPlayersCount - 1; i++)
{
allRequestingPlayers[i] = allRequestingPlayers[i + 1];
}
requestingPlayersCount = 79;
}
allRequestingPlayers[requestingPlayersCount++] = Networking.LocalPlayer.playerId;
RequestSerialization();
SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.All, nameof(UpdateRequestListUI));
}
public bool LocalPlayerHasRequested()
{
if (allRequestingPlayers.Length == 0) return false;
foreach (int playerID in allRequestingPlayers)
{
if (playerID == Networking.LocalPlayer.playerId) return true;
}
return false;
}
public void UpdateRequestListUI()
{
if (allRequestingPlayers.Length == 0)
{
playerRequestListUI.SetActive(false);
}
else
{
playerRequestListUI.SetActive(true);
}
// clear playerlist
for (int i = 1; i < playerRequestListContent.transform.childCount; i++)
{
Destroy(playerRequestListContent.transform.GetChild(i).gameObject);
}
foreach (int playerID in allRequestingPlayers)
{
GameObject playerRequestUI = Instantiate(basePlayerRequestUI, playerRequestListContent.transform);
PlayerRequestEntry entry = playerRequestUI.GetComponent<PlayerRequestEntry>();
entry.playerId = playerID;
SetUIUsername(playerRequestUI, IDToUsername(playerID));
// TODO: move UI down
playerRequestUI.SetActive(true);
}
}
void SetUIUsername(GameObject ui, string username)
{
Transform uiTransform = ui.transform;
GameObject usernameGameObject = uiTransform.GetChild(0).gameObject;
TextMeshProUGUI usernameTMPro = usernameGameObject.GetComponent<TextMeshProUGUI>();
usernameTMPro.text = username;
}
}
wait i just read the docs, int arrays cant be synced
int arrays can be synced
wait then
what am i doing wrong?
wait wait wait
if (allRequestingPlayers.Length == 0)
man im dtupid
Hey i have a question. Im making a horror game where the AI entity can move around the map and when he sees you he targets you but im having trouble understanding how to get the AI to do that in Udon but also how to network all of that to make it so a player can see another player getting chased or have the AI target a player than switch to targeting another player. (sorry im sending this on Christmas as well i have just been trying to do this for a few days now)
unity navmesh enabled on owner/disabled on others+setting target to current target player location+switching owner on some condition+objectsync to send everyone else npc coords, smth like that
tho i remember someone had issues with navmesh+objectsync, idk. personally did such ncps only being local.
ok thanks ill try doing somthing like that but first i need to make this smart ai
hi, a thing that was not clear from the documentation is, are the network callable method synchronized? they say that the event order is guaranteed, and that multiple players sending events at the same time are not, but if player A sends event C and player B sends event C at the same time, does the second C call waits the first execution to finish or they are put on a different thread?
its whatever arrives first to listener
Is there any examples of a vrchat world pvp combat system? Mainly trying to figure out how to properly do damage when an object collides with a player, how would one update that player's health without changing ownership of their health gameobject, or changing ownership properly without accidently removing the ownership from the player when they could be in use of it?
place generic collider onto each player, since you cannot access their native colliders. then you can easily get collisions with that. damage nowadays probly should be done via networked events with parameters, ie attacker sends "i shot player 5 with a 9mm" (where player id and ammo type are parameters) and player 5 is upon recieving doing the rest ie calculating actual damage and subtracting it from health
if you want damage to be based on what attacker sees. i mean, ping is real.
i guess i gotta look into network events with parameters, but i also thought you could detect collision with a player's collider,
you technically can, but any component of the player is restricted; so if you try to actually get the component, it'll return null.
So you can detect the player through means like the functions specifically designed to detect the player, like OnPlayerTriggerEnter. There is also OnPlayerParticleCollision, but this event may not be viable for all scenarios
@ember urchin it is protected object and returns null on generic checks like raycasting/particles
and having your collider stuck to a player is like a couple lines of code anyway
especially with persistence-brought playerobjects
is there any good basic examples of such? My main objective is re-creating the CTF world for learning but i feel like every step forward makes 3 steps erased
there are very few tutorials besides very basic examples
I'd imagine there's at least one for PlayerObjects at least
hey so like when you build and test the game and you select the 2 client thing. does that make it a so both are in the same synced local world or does it work if for example that other client is another human on another device. im working on syncing stuff globaly in my world and just want so clarification
It opens 2 clients, on your own computer
They'll load into the same instance (locally)
ok so im asuming then i have to go in my code and add networking stuff right im aussuming i cant just put on VRC object sync
VRCObjectSync will only sync position and rotation
So if you want to sync more than that, then yes
ok then i have this door and key system where the key opens the door and you know basic key door lodgic. that changes position of the door when the key is in the lock which means i dont have to do anything with that. but would that matter for something like a flashlight or does having an animation on the door to change position need code for networking. im just trying to understand.
Animations are not inherently synced
So when one player opens the door, they need to tell other players to open it too
but i can add my own code to avoid this im assuming
alright well i just made code that make sure the door lock is networked and made it late joined protected but my main question was answered
Is using GetProgramVariable on a synced variable something that happens over the network or is that only SetProgramVariable? My world has an insane amount of network bandwidth despite only having 4 synced variables and I'm trying to track down the cause.
neither of those functions will inherently send data over the network
Is there documentation somewhere I can look at that goes over what specific cases cause data to be sent/received via the network (specific to udon ofc)? I'm having so much trouble figuring out what's causing my problem
I have a good bit of persistence in my world but it makes no sense that everyone is sitting at ~10KB/s sent when I have a save interval of like 15 seconds and it only updates persistent variables if they've been changed...
when data is sent over the network depends on the sync type
https://creators.vrchat.com/worlds/udon/networking/network-details/
Networking in Udon can be challenging! Try to keep things simple until you're more experienced.
but also, with persistence, whenever a player updates their data, that data is then also sent to all players
So the sent is the amount of data/sec * number of players?
Why would all the players need to be sent the persistent data unless they explicitly were using PlayerData.Get?
just down to the design of it
Seems silly imo
if you need a value from another player's persistent data, it'll be available immediately
versus, there would be a delay in getting it if it only pulled it when you called Get
that would be worse imo
Is getting a variable through playerdata more efficient than just using a normal synced variable?
I don't really see the use case tbh
I mean, that depends on what you're using it for
The way I see it, persistent data is only relevant to the player whose data it is
but it doesn't have to be
Otherwise you use synced variables
then it wouldn't be persistent
Persistent data isn't saved locally right?
It's saved to some server on VRChat's end
right
I'll have to test what you're saying by joining a near empty instance and a full one
Even if what you say is true, there's no way I should be sending over 10KB/s
Max players is 16 and recommended is 8 and even with a few it's up at like 10/sec
oh also, when you save a persistent variable, it sends all of that player's persistent data
not just that one variable that you save
And again, 15 second save interval for like 20-30 KB
you can use the debug windows to see what scripts are sending what amount of data
It only shows the player object itself and the one script I have with synced variables
is the network actually getting clogged though?
wait, are you using PlayerObjects that are storing data in PlayerData?
No I am not using the component
I have player objects but I'm using the normal persistence stuff
I just read through the player object docs to make sure and it says you need the component on it for it to use the player object persistence
is there a reason why you aren't using it?
I've never tested it and I'm not 100% sure how to set it up lol
That's the main reason
you just add the component. that's it. then any synced variables on the object are also saved persistently, automatically
Right but only 4 of the variables I have are synced
And those are changes very infrequently
It's definitely not the problem
are they set to Continuous sync?
that doesn't sound right for 10KB/s
That's what I'm saying lol
I've looked through my scripts up and down and I can't figure it out
is it actually causing any networking issues though?
pickups lagging? IsClogged showing?
Right now, no. It does say suffering but there's not much network stuff in the world atm
But in the future I plan to add more network stuff
For some context, the synced variables are like what pet is equipped and stuff
So there's a slight delay in pets changing when people equip different ones and such, but that's such a minor issue
I've not tested isClogged because I haven't updated the world just yet but I may mess around with it sometime later
so you aren't using ObjectSync or anything to update the position of the pets?
That is fully local
Each player individually sets the pet locations based on the owners
I try not to use objectsyncs, those things use a lot
yeah this kbytes in/out are rising significantly faster than I would expect with a manual sync script
manual can sync as fast as continuous though. purely depends on how often you are requesting serialization
not reached any suffering yet though
Yeah i almost never RequestSerialization because again the few synced variables I have are very rarely changed
It has to be the persistence, that's the only thing I can think of
possibly
you could test by removing it, then uploading the world under a different ID, and test the networking then
If what you said about all persistent data being sent at once even if you only update 1 variable is true, that would explain my issue
I'm at 10 KBytes in and not getting any suffering yet though
well, suffering is only out anyway
Because on top of the 15 second save, I also have various other persistent variables updating under very specific circumstances where it's relevant
yeah, and that will cause it to upload all data
That's stupid, just gonna say that
I might be able to fix it by just putting all my persistent variables in one save event
But then people could lose progress and that sucks
I can understand why it has to update everything
It's the way it's being saved
It's efficient for the server's end but limits us quite a lot
PlayerData is designed to save data that doesn't need to be saved very often, so when it does need to be saved often, the cracks start to show
but that's why there is the alternative, saving with PlayerObjects. those are better suited for things that need to be saved often
Hmm so I should sync all the other things and use player object persistence is what you're saying?
How does that differ exactly
you'd have to figure out what's being saved too often, and move those to being saved by PlayerObjects instead
Ah yeah that's no problem at all
as I said, PlayerObjects work by just persistently saving the last networked state, so you have better control of when it saves
and you could skip needing to upload all of the PlayerData. just what's on the PlayerObject
So it doesn't send all variables when it saves one?
If so that's the solution to my problem most likely
I'm not 100% on it but I don't think it sends all PlayerObject's data all at once
so if you have 5 objects, and do a network sync on just 1, just that 1 object's data will be sent
Universally, networking in VRchat is bundled per network ID. Everything in the same network ID must be sent at the same time, and they can't be split up further. But if you have multiple network IDs, they can be sent independently. Playerdata only has one network ID, but player objects can have be split across many.
That makes sense
neat so my assumption is right
Thank you, I will switch to player object persistence for the more frequent changes then :)
Thank you too, finally know what my issue is lol
best of luck to you
I'm glad it's an easy fix
it was funny you mentioned pets and I was like "wait I have a world on my check out list that sounds familiar...." lol
Haha yeah
Make sure you dont override the network id of your player object, otherwise all your data will be deleted afaik
Override the network ID? How would you even do that
you can edit them in the "Network IDs" section of your VRC Scene Descriptor
Ahhh
You probably wanna be using Bobby's tool to safeguard it resetting persistent data
I don't think I'd ever have a reason to even mess with that stuff so unless it somehow changes on its own it should be fine
the most common way this happens is, randomly, you'll get an error saying something along the lines of an issue with Network IDs.
The fastest way to fix these problems is by regenerating the Network IDs; this normally doesn't cause problems, but if your world has persistence, it will wipe and reset the IDs, causing any data associated with a PlayerObject be lost, since the Network ID is now different
it is, hence why BobyStar made a tool that handles network IDs a little smarter
https://github.com/BobyStar/OpenNID
Sounds like vrchat should like, do something about that
it's technically not random, it happens when you do some certain things in Unity, but it feels random
again, this all usually is a non-issue if you aren't using persistence, which most world creators aren't
you normally never have to care too much about what the IDs are, but for persistence it's important
When you're first building you world, you'll hit this a lot, but once the major work is done you probably won't be changing large parts around much, and so it probably won't be a big deal
well if you build like I do, with not a ton of plan up front 🙂
I've never come across this issue and I've played around with network stuff in a few worlds now
yeah, and any issues that do arise don't have to be solved by nuking the IDs and regenerating them, you can fix them manually
This is the furthest I've taken persistence though by far
it's easy to just nuke and regenerate before it's released - after, probably don't 🙂
it's all just something to be aware of when you have a project that heavily relies on persistence
Yeah fair enough
just to be safe, you probably want to take note of what the IDs are for your PlayerObjects
Will do 100%
I should have asked this at the time but now that I'm aware of how the network ID thing works, I should probably create multiple PlayerObjects to split up my persistence so it can save in smaller chunks, right? Since if I have all my persistence on the one PlayerObject it's still one network ID and won't solve my problem lol
At least based on what I understand from what I've been told here
correct
Alright cool ty
I’ve never heard of splitting persistence between scripts, is that a good and performant way to reduce lag from saving large amounts of data without any drawbacks?
I can't really think of any drawbacks
besides it maybe being a bit more work to set up
The biggest concern I'd have is to be careful on things where the state depends on data from two different persistence bundles. But as long as you either avoid that by ensuring all state is independent or account for it by ensuring that you join the two sources together before loading it onto the world, should be fine
Can someone actually give more info with the technical details surrounding that?
The main point is that if you have two pieces of data networked in two separate places, you need to be careful about how you combine it together.
For example, maybe you have a bool on one udonbehaviour and an int on the other udonbehaviour. Let's say the bool enables an object, and the int sets the color of the object. When these variables are synced separately, it would be possible for you to receive the bool first, before the int. This could cause you to enable the object but with a default, incorrect color.
It may only be like that for a couple frames before it receives the int and snaps into the correct color, but the fact that's possible at all is a problem. Consider if this default, incorrect state was something more problematic like game state which kicked off an entire flow that teleported players, it could be a lot more problematic.
The solution to this problem is to either split across logical boundaries that don't conflict, or account for it by joining the data together reliably.
Splitting across logical boundaries means that as long as you're talking about the same object or system, you'd need to ensure that it is synced in the same bundle. For example, two separate objects with their own toggle and color would be fine to split apart, but so long as there is a relationship between the bool and the int (in this case, the color int is dependent on the bool) you would need to keep them synced together.
Joining the data together is another option, which is simply ensuring that you don't apply the state to the world until you are certain that you have received both states. This can be a useful tool when your networking needs get large enough that you must split things apart, but they still must be logically connected. For example, pick one of the two to be the manager - lets say the int manages the state of this object, but it receives additional variables from the udonbehaviour that syncs the bool. In this case, you would store a local variable which tracks whether it has received the bool, and another which stores whether it has received the int. When the other behaviour receives the bool, it passes it on to the manager. The manager stores it, and remembers that it has been stored. When it receives the int, it stores it and remembers that it has been stored. After each of these operations, it additionally checks to make sure that all variables have been received, and it only ever applies the state to the world when all are received.
It is worth mentioning that as far as persistence goes, this is exactly why we have OnPlayerRestored - it fires when everything for that player has been received. That is an example of vrchat joining it together for you, making persistence easier. But that doesn't help for regular networking data, where OnPlayerRestored won't happen
Highly appreciate it.
On the topic of using multiple networked objects for persistence and that being more performant(?)
How does that check out?
Yeah, splitting your data apart can help a lot if it allows you to reduce how much gets serialized at once. If you still end up serializing both at the same time, it's pointless. If you have large data that can be chunked up into separate segments, and each one can be serialized independently, then that can be good savings.
If you have a situation where some data needs to be really big and other data needs to be sent frequently, putting them on one object can conflict because the big data prevents it from sending often. Splitting your big data from your fast data so that they can serialize separately works very well, but this is the situation where you need to be careful about joining the state back together
@sturdy saffron so why hasn’t this been done for argus yet 😂 or is this the only reason the criminal system can work?
Or maybe @jaunty ice is better 👀
It has been done already
In various forms
Most networked is already fragmented on a per object basis to a reasonably fine degree outside of player save data anyways
Player save data is annoying because we don’t want the save data itself to be fragmented into parts where some are more updated than others, and with the current version of persistance, having multiple objects and fields of data comes with all sorts of issues that made us not want to use it at all and use the more global dictionary instead to save stuff
(Instead if individual playerobjects that is)
Thanks for quick response! :o
haha i saw the ping so was like ill respond
Was asleep but bocu beat and has already stated what we do at project aincrad for that issue. I am hope once soba becomes a thing maybe VRC Team allow us to have more control over networked objects and more persistence data stuff opens up as soba is meant to be faster then udon. but how knows how fast.
our team would love to test it and get our hands on it to break it to bits haha
so i'm setting these networked variables in my timer methods, and reading them from my UI controller script using the getter/setter, however remote players are not seeing the image's fill change. why is this? does reading from the thing not work even if it uses a FieldChangeCallback?
Duck Spawn Controller (where the variables get set and serialized)
[UdonSynced, FieldChangeCallback(nameof(TargetTime))] private float targetTime;
[UdonSynced, FieldChangeCallback(nameof(TimeLeft))] private float timeLeft;
[UdonSynced, FieldChangeCallback(nameof(TimerActivated))] private bool timerActivated = true;
public float TargetTime {
private set => targetTime = value;
get => targetTime;
}
public float TimeLeft {
private set {
timeLeft = value;
uiController._UpdateTimerText();
}
get => timeLeft;
}
public bool TimerActivated {
private set {
timerActivated = value;
uiController._CheckPoolState();
uiController._UpdateCountText();
}
get => timerActivated;
}
public float TimerLength {
private set => spawnTimerLength = value;
get => spawnTimerLength;
}
public void SetTimer() {
// if (!TimerActivated) return;
TimerActivated = false;
TargetTime = Time.time + spawnTimerLength;
RequestSerialization();
RunTimer();
}
public void RunTimer() {
TimeLeft = TargetTime - Time.time;
TimerActivated = TimeLeft < 0;
RequestSerialization();
if (TimerActivated) { // Time ran out, run code that should happen at the end of the timer
pools[PoolIndex].TryToSpawn(spawnLocation.position);
SetTimer();
}
else { // Timer is still going
bool poolExhausted = pools[PoolIndex].PoolExhausted;
if (poolExhausted) {
return;
}
SendCustomEventDelayedSeconds(nameof(RunTimer), TimeLeft % 1);
}
}
public override void OnDeserialization() {
uiController._UpdateCountText();
}
UIController (where the variables are read)
public void _UpdateCountText() {
int total = spawnController.Pool.TotalCount;
countText.text = $"{spawnController.Pool.ActiveCount}/{total}";
countTextHUD.text = $"{spawnController.Pool.ActiveCount}/{total}";
}
public void _UpdateTimerText() {
if (complete) {
timerText.text = "";
timerTextHUD.text = "";
return;
}
int totalSeconds = Mathf.FloorToInt(spawnController.TimeLeft);
int minutes = totalSeconds / 60;
int seconds = totalSeconds % 60;
timerText.text = $"{minutes}:{seconds:D2}";
timerTextHUD.text = $"{minutes}:{seconds:D2}";
}
public void _CheckPoolState() {
complete = spawnController.Pool.PoolExhausted;
timerFill.enabled = !complete;
timerComplete.enabled = complete;
timerFillHUD.enabled = !complete;
timerCompleteHUD.enabled = complete;
}
private void Update() {
if (spawnController.TimerActivated || complete) {
timerFill.fillAmount = 0f;
timerFillHUD.fillAmount = 0f;
return;
}
float timeLeft = spawnController.TargetTime - Time.time;
if (timeLeft < 0f) timeLeft = 0f;
float fill = 1f - (timeLeft / spawnController.TimerLength);
timerFill.fillAmount = fill;
timerFillHUD.fillAmount = fill;
}
when i test locally with two clients, the timer fill works completely fine. when i test with a friend, they say the timer does not fill at all
so it's like when remote players run Update in the UIController, the values from the getter/setter are just not synced at all
i've been meaning to get someone to test with me and have their debug console open, but i've yet to get someone for that
but, the other thing is, the timer text and count text are perfectly fine, and they also use FieldChangeCallbacks and getter/setters
those parts are synced just fine
so is it because i'm not doing anything with the time related variables in OnDeserialization or the setters?
correct me if I'm wrong, but it seems like your use of FieldChangeCallback makes these values get processed individually from each other when in practice they affect each other and should be processed together
timeLeft having its value updated triggers a method that replies on the complete value to be accurate, but does itself not make sure that complete is accurate; that value seems to be made accurate by the completely separate act of FieldChangeCallback being triggered for timerActivated
I'm not sure if it is causing the issues you are encountering, but this design approach seems like it'd be extremely suspectable to errors
From what i understand from phase earlier, synced variable changes are received in the order they’re declared, so just putting the callback on the last declared variable of the bunch might fix it 👀 but in my experience fieldchangecallbacks never notice other udon synced variable changes so I put them in a one frame delayed method
yeah, that makes sense. i’ll try not using a callback on everything, and just on like, timer active maybe
i just kinda threw callbacks on the variables and saw it work with two local clients and thought it was fine
Just use OnDeserialization
sigh...
oh i remember why i used FieldChangeCallbacks now. its because i thought that, if i wanted to have a synced variable that also uses an getter/setter (to be read from other scripts), that it needed to have a FieldChangeCallback
if it's just a regular UdonSynced variable, that i set and use RequestSerialization on, if i read that variable from another script using a getter, do remote clients still read the synced value?
My personal style often has my networked variable and getter variable separate so I can't say for certain, but it should probably work
i would think so too, i'll just do it and find out
cause regardless, having a FieldChangeCallback for every variable isn't working
it's funny how one incorrectly-cased letter can cause something to not work
i thought i broke my timer text, but it's because i set timeLeft and not TimeLeft
dang, putting some of my stuff in a one-frame delayed method actually just fixed an issue i've been trying to fix for a week now
(not the timer, something else)
There is no separation between the "real" value and the "synced" value except for execution order. There is only one place that the variable is stored, and when you receive a serialization that one place will be updated. If your getter property reads from that one place, it will give you whatever is stored in that variable in that moment.
The confusion here comes from the execution order, because when you are receiving synced variables they will be inserted into the udonbehaviour one at a time. After each one has been inserted, the field change callback is ran. Then it sets the next variable, and it triggers the field change callback for that in turn. Only after all of these variables have been set and their callbacks ran, then does it do OnDeserialization.
The reason why it worked in one situation and not the other is because you created a race condition where it works if one variable is set first, but it doesn't work if that variable is set after. You should not rely on any particular order, as it could change with different compilation or environments.
The reason why ondeserialization will work is because it always fires after all variables have been received and set, at least in the same network ID. That allows you to run some code at a point in time that you can be confident all the synced variables are up to date.
If you are asking if an udonbehaviour can get the synced variables from another udonbehaviour, then the answer is yes - but it will always be grabbing it at a very specific point in time. If that point in time ends up being before the corresponding ondeserialization of the udonbehaviour that the variable belongs to, then you could get an outdated value. It's up to you to account for that however makes sense in your organizational structure
that makes sense. i ended up changing the structure to be non-reliant on FieldChangeCallbacks, and use regular UdonSynced variables instead. new code below
Duck Spawn Controller:
[UdonSynced] private float targetTime;
[UdonSynced, FieldChangeCallback(nameof(TimeLeft))] private float timeLeft;
[UdonSynced] private bool timerActivated = true;
public float TimeLeft {
private set {
timeLeft = value;
UpdateUI();
}
get => timeLeft;
}
public float TargetTime => targetTime;
public bool TimerActivated => timerActivated;
public float TimerLength => spawnTimerLength;
#region Spawn Timer
public void SetTimer() {
if (logs) zLogger.Log(name, "[SetTimer] called", LogColor.Yellow);
// if (!TimerActivated) return;
timerActivated = false;
targetTime = Time.time + spawnTimerLength;
RequestSerialization();
RunTimer();
if (logs) zLogger.Log(name, "[SetTimer] calling RunTimer", LogColor.Orange);
}
public void RunTimer() {
if (PoolIndex != noDucksIndex) {
TimeLeft = targetTime - Time.time;
timerActivated = TimeLeft < 0;
}
else {
TimeLeft = 0f;
timerActivated = true;
RequestSerialization();
return;
}
RequestSerialization();
if (timerActivated) { // Time ran out, run code that should happen at the end of the timer
pools[PoolIndex].TryToSpawn(spawnLocation.position);
SetTimer();
}
else { // Timer is still going
bool poolExhausted = pools[PoolIndex].PoolExhausted;
if (poolExhausted) {
if (logs) zLogger.Log(name, $"[RunTimer] Pool was exhausted, stopping timer.", LogColor.Yellow, true);
return;
}
SendCustomEventDelayedSeconds(nameof(RunTimer), TimeLeft % 1);
// if (logs) zLogger.Log(name, $"[RunTimer] {TimeLeft} seconds until next duck.", LogColor.Purple, false);
}
}
#endregion
public override void OnDeserialization() {
UpdateUI();
uiController.UpdatePackButtons();
}
private void UpdateUI() {
uiController._CheckPoolState();
uiController._UpdateTimerText();
uiController._UpdateCountText();
}
UIController:
#region Duck Timer
public void _UpdateCountText() {
int total = spawnController.Pool.TotalCount;
countText.text = $"{spawnController.Pool.ActiveCount}/{total}";
countTextHUD.text = $"{spawnController.Pool.ActiveCount}/{total}";
}
public void _UpdateTimerText() {
if (complete) {
timerText.text = string.Empty;
timerTextHUD.text = string.Empty;
return;
}
int totalSeconds = Mathf.FloorToInt(spawnController.TimeLeft);
int minutes = totalSeconds / 60;
int seconds = totalSeconds % 60;
timerText.text = $"{minutes}:{seconds:D2}";
timerTextHUD.text = $"{minutes}:{seconds:D2}";
}
public void _CheckPoolState() {
complete = spawnController.Pool.PoolExhausted;
if (logs) zLogger.Log(name, $"[CheckPoolState] complete = {complete}", LogColor.Orange);
timerFill.enabled = !complete;
timerComplete.enabled = complete;
timerFillHUD.enabled = !complete;
timerCompleteHUD.enabled = complete;
}
private void Update() {
if (spawnController.TimerActivated || complete) {
if (logs) zLogger.Log(name, $"[Update] Returning. Conditions: TimerActive = {spawnController.TimerActivated}, complete = {complete}", LogColor.Yellow);
timerFill.fillAmount = 0f;
timerFillHUD.fillAmount = 0f;
return;
}
float timeLeft = spawnController.TargetTime - Time.time;
if (timeLeft < 0f) timeLeft = 0f;
float fill = 1f - (timeLeft / spawnController.TimerLength);
timerFill.fillAmount = fill;
timerFillHUD.fillAmount = fill;
if (logs) zLogger.Log(name, $"[Update End] timeLeft = {timeLeft} | targetTime = {spawnController.TargetTime} | fill = {fill}", LogColor.Blue);
}
#endregion
Generally, best practice: only use field change callback when the thing you want to do is dependent on that singular variable, without any outside reference. If you need to depend on multiple synced variables to apply them to the world, use ondeserialization
the new code feels better and still works with two local clients, i'll just have to try with other people
and with that info, it seems i'm doing it better now because i'm using OnDeserialization instead of the FieldChangeCallbacks
basically batch applying the changes instead of individually
I gotchu b!
Iron sharpens iron! :D
the best part of being in this server
i was having an issue where:
- i reset my object pools which share some of the same objects
- the local client did it in the right order
- the remote players would reset one pool before the other, causing the states to be all enabled, then all disabled because the old pool got reset to all disabled after the new pool
but i put the resetting of the pool in one half, then the spawning of the first object in the other half separated by 2 frames, and then the remote client did it in the right order for once
I'm in the process of trying to wrap my head around some networking for ball physics here and I want some help...
Essentially, when the player swings their "weapon" into the ball, it should calculate a new angle for the ball and sync these changes so other players know the ball was hit and needs to update its trajectory to the new info, but I'm assuming that just throwing these in an UdonSynced FieldChangeCallback property wouldn't work because the hitter will see the ball fling way sooner than other players will due to network delay, which will cause some desync since players are essentially chasing something that's at a different position than the last player to hit the ball... I'm trying to research how to make up for this but I'm struggling where to start e.e
I want to add a hitlag that freezes the hitter for a moment & increases based on ball speed... is there a variable I can use to check your network delay so I can reduce the hitlag by that amount when you receive the network update?
That would sync it, but I haven't been able to even fully formulate that idea till I wrote the question here xD
Time.realTimeSinceStartup - SimulationTime(player), got it :D
Are you doing two separate networking events where one is reliant on the other?
i was just doing them too quickly together i think
i dont get the whole concept of multiple pools sharing the same object, like what
two cars sharing some wheels sounds weird
it’s like, i have packs of ducks, and the object pools are subsets of those ducks. so i have one for all of them, one for just the basic ones, one for custom ducks, etc
so some of them share the same ducks, and the “all ducks” one shares all of them with the other pools
the system works fine, as long as the previous pool resets before the new one spawns the first duck
which, speaking of, apparently this didn’t fix it. another thing where it works perfectly fine testing with multiple local clients, but testing with other people in-game it breaks
i don’t understand that because any other time i’ve tested my networked scripts, the behavior with local clients was the same as testing with other players in-game
makes it really annoying to debug, because i haveto get other people to test with me, so i’m unable to fix things as often as i could if the behavior was just consistent with my local tests
i suppose instead of multiple pools, i could give each duck tags for which pack it’s part of (with a script), then have my object pool only spawn ducks with tags based on whichever pool is selected
but again, the system i have works just fine in unity and in local tests, it’s only with other players that it breaks, so i can’t even tell why
what could possibly be happening here? why is the timeLeft value so wrong on the remote client?
(i fixed my other issue with the object pools)
seems like you have ducks everywhere
time is different
on each client
time.time is not the same but target time is
no wonder
well shoot how do i make that work then?
I forget- does requesting serialization on an object also automatically serialize all its child objects' variables too?
anybody? ;w;
no, only on the same object
Thanks!!
Selfishly transferring huge (260x260) images over udon as Color32 array...
The max manual sync limit is fine... but the b/s hurts
Any techniques I should know to get more traffic? or tricks I can use to pack a bunch of Color32's into a float or something?
how can i use server time for this properly? because it's a weird number that can go extremely high or negative.
i found this message, but i do not know what they mean, or how to do it #udon-general message
@timber ferry well ye it can be anything but its the same for everyone in instance. so if you need smth to expire in 1000 second you just add 1000 to current time 🤷♂️
actually, i think i understood what occala’s message meant right as i sent that
save the server time in a synced variable on start for the owner, then use that calculate server delta time method when i need to access it
if you need past events/time passed then ye
You dont need to sync server time, because every client already has access to it via Networking.GetServerTimeInSeconds()
yeah but i can’t just use that directly as a replacement in my code, because replacing Time.time with GetServerTimeInSeconds does not work
i have to do something else with it to make it work with my current code, but i’m not sure what exactly
There’s that thing, (time.realtimesincestartup - networking.simulationtime(localplayer))
It gives the current client’s calculated network delay in ms
That may help here? 👀
I’m subtracting that from a synced time for each remote client so they properly “release” at the same time as the person that networked it
(This is also untested but I swear it makes so much sense in my head, that’s gotta be how you use it?)
time and networking are hard
what i'm doing now is:
Spawn Controller [Set Timer]:
targetTime = Time.time + spawnTimerLength;
UIController [Update]:
float timeLeft = spawnController.TargetTime - Time.time;
float fill = 1f - (timeLeft / spawnController.TimerLength);
timerFill.fillAmount = fill;
targetTime is synced, but Update runs for everyone locally
so maybe instead of using Time.time in the spawn controller, i use the server time on both?
seems easy enough to use server time there, yeah
would it still work if it's negative too? i suppose i can use Mathf.Abs if not
I'd think that "time left" would always be >= 0
i suppose i'll try replacing it with server time when i get home and see what happens then
Me slowly realising you can't just break up a huge int into chunks of 0-255, 256-512, etc. and get a gazillion bytes
I hope this is a universal experience you have to get through when learning programming lol
server time can help yah; in some use cases, this might help as well: https://creators.vrchat.com/worlds/udon/networking/network-components/#ondeserializationdeserializationresult
This doc covers Networking Components, Properties and Events you can use in your Udon Programs.
I haven't gone through all your code, but not sure why you'd sync both time left and target time; shouldn't you be able to use one to determine the other?
time left is synced for the clock which displays the time left in minutes/seconds, whereas targetTime is used for the image component’s fill
i synced timeLeft separately because the clock only updates once a second and the fill updates every frame
are these two variables entirely unrelated to each other?
what happens locally every frame shouldn't matter for a networked sync value, unless you're synching that value every frame which manual sync does not really support
well, i didn’t know how else i could calculate the time left every frame for a smooth animation from 0-1 fill using the timer variables from my spawn controller script, besides reading the one synced value as a reference point for the time calculation. and it made sense to me, because i’m reading the time left in seconds directly to display it on text, and i’m reading the target time separately in update to figure out the fill
if there’s a better way to display the time left as a clock on text, and smoothly as fill on an image component, i’d be willing to try it out that way, but this is just the way i thought of doing it and what worked (until it needed syncing)
Well whenever it comes time in your program to update the visual clock, are you not able to look at the targetTime variable and determine the time left to display from that?
oh i see what you mean
because if i’m doing targettime - server time to get time left anyway, i could just do that to update the clock
well, i guess i’m doing Time.time in my RunTimer method, but i get what you mean i think
as an example, if I wanted to sync a timer that would end at almost the exact same time in realtime for everyone regardless of latency I'd likely do it something like this:
public void OwnerStartTimer(float time)
{
ownerStartTimerTime = Time.realtimeSinceStartup;
syncTimerDuration = time;
StartTimer(time);
RequestSerialization();
}
public void StartTimer(float time)
{
targetTime = Time.realtimeSinceStartup + time;
//do active timer stuff
}
public void OnPreSerialization()
{
syncTimerDuration = syncTimerDuration - (Time.realtimeSinceStartup - ownerStartTimerTime);
}
public override void OnDeserialization(DeserializationResult result)
{
float adjustedTime = syncTimerDuration - (Time.realtimeSinceStartup - sendTime);
StartTimer(adjustedTime);
}
yeah so, it seems like replacing Time.time with GetServerTimeInSeconds works just fine
and i also made timeLeft non-synced, and just calculate the time left in the UpdateTimerText method, which also works
int totalSeconds = Mathf.FloorToInt(spawnController.TargetTime - (float)Networking.GetServerTimeInSeconds());
surprisingly, 32 bits only give you 4 bytes.🫠
It's a transparent image, right? If not, could remove the alpha channel and send 3 bytes per pixel.
Otherwise:
- Could limit the color palette. Literally throw away the least significant bits and just send less bits per pixel.
- Data compression algorithms? Would be a whole project itself to implement in Udon, though.
Great minds 🌸✨👏🫶 I've already been working to break up HSV, then do some jpegy stuff where Value is the only one that's full resolution.
ye, 4:2:0
oo yeah that's good too! Chroma subsampling (had to google to remind myself of the term lol)
Its so cruel how limiting things are at low sizes... I thought about some smart ways of fitting two pixels into one byte... But if you cut 255 in half you only get two sets of 16?! Rude.
you can read on how gpus handle (and in the end, we see in any 3d app) texture compressions, unlike jpgs those are small chunks based with fixed bits per pixel. but i dont think it really goes lower that one byte per pixel, which still impressive since it results in 4 rgba bytes. https://www.reedbeta.com/blog/understanding-bcn-texture-compression-formats/
Pixels and polygons and shaders, oh my!
i have alot of scripts going on
how do i stop certain scripts to stop sending data to the network?
put them to "sleep"
If the script doesn’t need to network at all, you can set the drop down for sync mode to “none”
well i need them to activate eventually
i have a hundred ai
and well they occupy stuff over then network
128b
how do i stop them from doing that?
is there just a theoretical hard cap to how much networking stuff you can put on the network?
It's not theoretical, there is a limit
If it's continuous sync, no
The limit for outgoing data is about 11kb/s
If you've got a hundred AI agents, they probably shouldn't be continuous sync, or should be sending data out less often if manual sync
how do you "turn off the script" or put them to sleep so thhhey don't clog up the network?
For continuous sync, you can't
oh yeah talking abt manual