#udon-networking
1 messages · Page 27 of 1
^ Udon deleted that script yesterday for some reason.
I can set it back up its not hard
Just instead of it handling the roles itself have it send a customevent to the rolemanger?
that's right
so if I just assume that the script is a button that sets the role for the local player...
the beginning and end should be something like this
we know we start with Interact, which eventually leads to calling SendCustomEvent on an UdonBehaviour.
so how would these connect together to do what we want?
Forloop the getplayerobjects... then use something to pull the specific gameobject we want, run a bool if true send custom event if false let it end there?
you're on the right track
I think just finding the specific gameobject is the only task you haven't figured out yet, with that you'd basically be done
Objectpool?
naur
I'm scouring the wiki looking...
that might not help. This is more of a logic puzzle.
so our GetPlayerObjects array, it's a list of potentially multiple GameObjects. but one of them is the object we wanna mess with. The array could look like this:
[Cube_01, Cube_02, RoleManager, Cube_03, GunThing]
Or RoleManager could be at the start of the array. or the end. But we know it's in there somewhere.
Right
our For loop will, essentially, "look" at one GameObject at a time.
...so we'd need some sort of test, no? A test that only the GameObject RoleManager will pass.
Fucking christ.
sometimes happens when the graph fails to compile
it can get confused when that happens sometimes
Int?
what int could you get off of a GameObject?
Location?
hm... not sure if that would work.
what would be unique about RoleManager's location, compared to other PlayerObjects?
So the name? String?
bingo
Because thats the only thing that changes, how do you reference that everytime it changes?
loop through the PlayerObject array, use the String.Contains node. Find the GameObject that contains the name "RoleManager". Then we'll know that's the right object
note to use Contains, not check for the name to be exactly "RoleManager".
When the PlayerObjects are instantiated, their name will add a number to the end of it, so each player's object will have a unique name
So contains checks just for the name? If it matches the name (Even with extra characters) it'll let it through?
pretty much does what it says on the tin yeah
you give it a string, if that string contains the other string at any point within it, the bool will output true
so you need to plug the GameObject's name into instance, and "Rolemanager" into the value
(either that or the other way around. some nodes are a bit hard to parse compared to their original C# functions)
Its not liking the gameobject being an instance
Wait, do I need to convert the game object to a string also?
you need to get the "name" property from the GameObject
that's it
Gameobject?
right. What GameObject goes there?
The one were looking for. So we would get it from gameobjectarray.get
that's right
this should do something
well, once you actually have a Rolemanager PlayerObject.
but that should be easy to make for a test
Right, I put a log at the end to test it, so in this case we don't need a public variable for the object were checking for, right?
So problem, since the contains was a char it crashed, if I set it o string I lose the flow.
change the dropdown in Contains to (string)
that's fine. I'm actually not sure why the Char version even has a flow input
So its saying that the bool is sending a fail
in the Branch there? yeah, it's gonna fail for any PlayerObjects where the name doesn't match
Shouldn't it be matching here? Rolemanager is already made...
Its under the playerobjects, its duplicating when tested
should do
🤷♂️
So we just need to move on than?
Gameobject, Udonscript, and udon behavior is all present
Its not feeding anything
those events need to actually do something in order to compile
can have them just Debug Log
can you have the loop print out the name of the GameObject, to see which ones it's checking?
RoleManager is not a PlayerObject
PlayerObjects is your PlayerObject.
I thought children were affected by parent playerobjects?
The GameObject with the PlayerObject component, and all of it's children, will be one PlayerObject
Oh so I need to seperate them out
yes either give Rolemanager and Nametag Script its own PlayerObject component (and remove the one from PlayerObjects), or you'd have to rework the graph to search for "PlayerObjects" instead, then search through that object's children to then find the name "Rolemanager"
one of these sounds a bit easier than the other.
great. I think that's the hard part done. Now as long as you've got a VRCPlayerApi, you have a way to assign a role to them
🙂↕️
Now the rolemanager, unless your down to help me tomorrow with it
I lurk here and help whenever
Fair enough lol, I need some sleep after all this 🥱
I have a question about the 10Hz cap in VRC.
I was reading an article about eye and face tracking, particularly face tracking while talking. It’s recommended to keep visemes enabled because the face tracking is limited to 1-10Hz updates. In contrast, other VR platforms offer 20Hz and even higher update rates.
I’m curious to know the reason behind this low synchronization cap. Was it intended to prevent past abuses? Or is it aimed at ensuring that mobile standalone headsets can handle the workload? Perhaps it’s also meant to prevent overloading the P2P broker (man-in-the-middle VRC)?
I’m interested in understanding the purpose of this cap on synchronization rates if other systems are capable of higher ones.
I’m asking because eye tracking and face tracking are part of my 3-6 month upgrade plan, and I want to gain a better understanding of this aspect of VRC if possible.
i'd assume the bandwidth cost of sending twice as much data (20hz compared to 10hz)
By bandwidth cost do you just mean the financial cost of paying for the bandwidth?
(I feel like I could interpret bandwidth cost also as network / computational resource strain)
vrc runs instances on photon, so yes they likely pay photon on some usage basis; i don't believe it's a cpu consideration
VRC has hard limits on how much data a client can upload to the VRC servers per second, likely to keep the server costs down. And to a seemingly large degree (it's never fully explained, mostly just vaguely alluded to), that bandwidth limit is shared with world network usage. So the more networking avatars use the less networking a world has to use for its stuff.
that is correct. your avatar and what ever it has on it that is networked count towards the limit. Eye tracking, face tracking and FBT all 3 take alot of data about 7-8 kb/s
can there be a case where an event gets sent from the owner but not received on others? or vice versa, where for some reason the owner doesn't receive an event or doesn't even send an event when it should
not taking rate limiting into account
I'm thinking about network connectivity issues
I'm pretty sure the only time that should happen is if the sending player times out
I guess this is why there is a reset button in some worlds
that's the simplest way to recover
though it's also used as a way to cover one's ass too
Usually if worlds get in funny states as a result of the owner disconnecting and ownership is passed on to someone else, things can be recovered. Though it's usually better to just harden your logic to such things. Not always possible though
If two players ask for ownership of the object at the same time. At what point can one be sure that they own it? At what point can one be sure they don't own it?
I guess one is sure that they don't own it if they recive OnOwnershipTransferred(anyone else)
I feared that might be the case
the real solution would probably be more along the lines of arbitrating it with a player
from what i remember testing you can actually get serverside confirmation of ownership via serialization, in that if you override ownership request, request ownership and request serialization, the serialization won't actually go out until you become the owner in the server's eyes
it's very poor practice however as a failed ownership request leaves the serialization request flagged, i'd argue that the current situation for the ownership request override makes it very undesirable to use because of this side effect
Ahh do you get confirmation of serialization from the server when you request it? I was looking to see what the function for that is
there's a pre and post serialize event, it behaves differently when you override the on ownership requested function, but again i wouldn't recommend trying to use it for confirmation of ownership
Is the post serialize event triggered by the server responding to the serialize attempt?
it is not, it comes after you serialize the data (locally)
Oof.
So if two clients both
Set Owner (self)
Synced variable += 5
Request Serialization
They'll both set themselves as owner, and then send the data without actually knowing if they are the owner.
And the only sign that it failed is when one of the clients gets a OnOwnershipTransfered(the other client).
yes, a race condition, in a case where you're just incrementing something it'd be better to inform the owner to increment
Now I wonder, if youre the owner, would sending a networked event to owner only not use any network bandwidth? Since its effectively to self
well i say better, but i'd consider it safer personally; if you don't care about losing some data ownership transfer is fine
i think it'd call self in that case
not actually sure
a sort of distinction, both ownership requests are actually valid when you aren't overriding ownership request from what i understand, it's more that the resulting value is currentValue += 5 in both cases and ultimately one of those is later than the other
Data loss is actually the reason for the inquiry in the first place. If only one player performs those actions then the synced variable is more reliable and less network taxing than a networked event. Especially if that one player is performing a math's operation multiple times in a row.
I'm exploring the case where multiple players request to determine how much of a issue the race condition is and how much I need to engineer for it.
If needed I can have one intermediary per player which will have the most reliability, but that could have exponential scaling problems instead.
So I'm probably going to have some sort of middle ground.
Well it sounds like the solution to this would be what someone mentioned above: using a networked event to owner to update things instead of setting owner
events will not drop, if you actually need some way to verify the order of operations in some time critical manner you can resolve it with timestamping; processing it through a single player via events is the most straightforward way to interact with data in the scenario you're describing
just to be clear, you'd still use a synced variable in what i'm describing, but the interaction would not be at the whims of ownership transfer
with events there is the case of the owner/processing player being unresponsive, so i wouldn't say it's the perfect solution, though i consider it preferable* to the alternative
Again network events are the problem in the first place.
I guess I should have said more reliable OR less network taxing. As depending on the implementation it's one of the other, in VRC's case it seems to be taxing.
#udon-networking message
at what rate do you plan to operate on the value? i can't imagine a player driven scenario where it would clog the network
i.e. player interacts with some element
This endeavor is all about scalability, edge case stability and reliability.
Network events (and RPCs in general) are a magic bullet, they work well and do a lot for you, but they are also expensive.
they have some amount of overhead, that's certainly true, but i think you're severely overestimating the weight; if the case is sending an update to a value every frame i can't imagine advising someone to do that in manual sync either
That's a good point, I'll switch the intermediaries to continuous sync. I'm willing to sacrifice promptness.
the point i'd hope to make is that it's likely not necessary to send your update every frame, continuous will handle the rate for you, but you could handle it quite well in your own implementation on events or manual for that matter and control the rate yourself; if continuous works well for your case that's good though
To be clear I'm trying to build a data transfer framework that I can scale into a number of different games
How demanding would it be to have six players each sending a network event that updates an integer every second?
thats 0.036% of your bandwith
OK so I should be good
might be better to use a synced int for just doing that
Probably, but I wanna have all players contribute and I really don’t wanna mess with changing ownership.
I’ll do that later if it’s absolutely necessary
With networking you generally want to avoid situations where networked data needs to be accurate at a specific point in time. You want to aim for a design of eventual sync, where as long as the correct state eventually arrives your program will eventually do the correct thing with it.
And that is why my game start waits for all players to respond, if not everyone responds, it fails
My game, virtually every single networked object runs on a state machine, so that helps a lot
it's sadly way more then that. a byte cost 2 bytes to network, a int cost 8 bytes in udon. and so on. for instance a long cost 16 bytes. and since its also in Manual. each second that means each person is gonna output 20 bytes per second roughly. considering that just having manual mode on a script cost 12 bytes. and then there is cost of the handshake that is done in manual mode. which makes it cost even more.
not to mention you don't really have 11 kb/s or even close to that. there are also other factors such as the amount of players in an instance, the things on your own avatar, fbt, face tracking and eye tracking also counts towards that max.
even for my own creation which only has 24 bytes synced once per some amount of time. in manual mode. and then a more regular 20 byte event. combined around 44 bytes at worst case every 30 second. which in the case of my world with a limit of 48 players. was previously 80. which was not stable. which then was reduced to 60. which was stable to some degree but to many avatars so it was eventuelly reduced to 48. which has been proven to be very stable. but with a low chance of it running into some udon limit for 1 or 2 people.
and at 48 people even if you did not sync anything it would hover around 9 kb/s give or take. which makes no sense if it is only 44 bytes at worst per 30 second.
since 44 x 48 = 2112 bytes. and having 48 people in an instance seems to use a fairly large chunk of the available data. have still to confirm exactly how much it is per person. but i have somewhat estimated that each person joining cost roughly 145 bytes per so 48x145 is 7000 bytes
intresting, do u know the size of the evnt overhead. i might need to batch some of my event then
Events seems to have a overhead of 12 bytes or so. and so does it seem to be for just adding [UdonSynced] to any script all through thats a single time cost.
Respectfully, please don't peach design practices in response to a only tangentially related question.
If you read my other comments you would know I am fully on board the state based train. If I ask about a specific edge case it's because I want to know about the specific edge case.
The question you asked did imply this was something that needed to be pointed out.
While that's true, preaching by itself is unlikely to change my mind if that was the case.
Good form would be to answer the initial question first, explain why it's a bad idea, then talk about the alternative.
I saw occala had already responded to you. They know what they're talking about and I presumed the specific answer to your question had already been answered.
I was gonna hit you with the "Well you know what they say about assumptions"
But also assumptions are useful to saving time and I can see how the assumptions you made are often reasonable.
So chalk this as one data point towards "assume less" and come to your own conclusions I suppose.
Very fair
I understood some amount of this. But I think I’m just gonna set my world to be 10 players max. It’s not a hangout world and you really don’t need more than 10.
Thank you very much for the explanation
i apologize if i came across as pushing events too hard or if this is unsolicited. from my perspective it did sound like you want something resembling events without calling them events, but i definitely don't know the full context/intent
i put together a small tangible example of a simple value modification. it's how i might approach a state-based system where each player is responsible for their own intermediary as you called them, in a scenario where they want to advertise a change to some central value
continuous could be applied to this, removing the need for the timing and serialize request, but continuous will have inherent delay and broadcasts continuously, regardless of changes, so i'd prefer manual personally
there are real bandwidth benefits to using state over events, for bundling changes the benefit can be pretty large; though the boilerplate/routing of state is larger than events. i use both where applicable
[UdonSynced] private int requestedValueModification;
private bool runningSend;
private const float dataPushMinimumInterval = 0.25f;
private double lastPushTime;
public void _ModifySendValue(int delta)
{
// I initially forgot, but you'd want to check for the local player being the master/owner of the system at this point and branch if they do not need to advertise over net
requestedValueModification += delta;
if (runningSend) return;
runningSend = true;
double timeSinceLastPush = Time.timeAsDouble - lastPushTime;
lastPushTime = Time.timeAsDouble;
float delay = (timeSinceLastPush >= dataPushMinimumInterval)
? 0f
: (float)(dataPushMinimumInterval - timeSinceLastPush);
SendCustomEventDelayedSeconds(nameof(_PushData), delay);
}
public void _PushData()
{
RequestSerialization();
runningSend = false;
}
public override void OnPostSerialization(SerializationResult result)
{
requestedValueModification = 0;
}
public override void OnDeserialization(DeserializationResult result)
{
if (!Networking.IsMaster) return; // Assuming the master is the owner of some central system, an owner check is more applicable with a specific reference
// Handle the value at this point, likely passing the value to a central handler
}
Also in terms of networking performance, events might be slightly more performant than manual sync. At the very least, vrc lets you send out more events per second than manual syncs (maybe due to manual data needing to be saved on the vrc server while events can relatively pass through).
Continuous is more performant per sync, but the fact that it constantly, rapidly sends out syncs with no good way to control it makes its in-practice network cost prohibitive.
A good middle ground is VRCObjectSync but that only works for syncing transform and ribibody info
Did not realize SendCustomEventDelayedSeconds() was usable in using sharp. That'll be useful.
Is it's implementation more efficient than running a time comparison if statement every frame?
Id assume so
Note that delayedframes is also present
it's not free to run delayed events, but i haven't tested the specific case of time checks as noted in the thing i'm forwarding here (so i do not know); at the level of a single object i would take the thing that is easier to invoke or most readable
Very interesting
How long was the delay like?
Id assume delaying by 100 seconds would be cheaper, rather than incrementing each frame for 100 seconds, testing each frame if its been met
Maybe theres a sweet spot where the overhead for queuing an event is cheaper
Thats definitely overthinking it huh XD but thats fun
in that specific case i was emulating an update loop by calling a delayed frame event for the next frame every frame; instead of using update, i didn't expect it to be especially slow, but it was substantially slower in that test environment
Also Update() on its own has overhead even when empty
But senddelayed doesnt
Like, i mean, when not called or interacted with
To disable update() youd need to turn off the gameobject
yes if it's something you do sparsely it could be preferable to use over update
Yeah
As an avid user of many, many scripts that happen once in a blue moon i imagine overall performance would be much worse if they all had an update loop checking a bool in them
At least if its on singletons youre sure it wont be multiplied…
you can also use this.enabled = false to disable the update loop
rather than the gameobject
Oh, i thought you couldnt disable scripts in the inspector? But you can programmatically?
That and, well, it also turns the script off entirely, so you cant recieve events, sync stuff or nothin
they'll receive an inspector check box if you have an update loop, i'm not sure if that's the only case. it does not stop the script from receiving stimulus afaik
it may have sync implications
Then again its just me.
Im the freak taking hours setting up box colliders over every little thing instead of using mesh colliders like a normal person lol
from what i remember if the script has started* it will still be capable of receiving data
Very interesting
Maybe it is free after all, worth an experiment
Such specific behavior to recieve a check box, cant say ive ever paid attention
I'm kinda enjoying the fact we have to code these games like we're running them on the performance of a PlayStation 1.
Why does drinkManager.bartenderId not sync to remote players? It is marked as [UdonSynced] and DrinkManager is set to BehaviourSyncMode.Manual.
internal void SpawnDrink()
{
Networking.SetOwner(Networking.LocalPlayer, gameObject);
var drink = drinkPool.TryToSpawn();
Networking.SetOwner(Networking.LocalPlayer, drink);
var drinkManager = drink.GetComponent<DrinkManager>();
drinkManager.bartenderId = Networking.LocalPlayer.playerId;
drinkManager.RequestSerialization();
drinkManager.OnDeserialization();
}
What part is not synced? The location?
drinkManager.bartenderId
Is the drink gameobject disabled while it is in the pool?
yes, that's what the VRCObjectPool does
Last time I checked, networked game objects don't have fully working networking for a period of time after they are first enabled.
A workaround is to never disabled networked game objects and to disabled their individual components instead (for example the renderer and collider)
Or you can have all the stuff that handled networking be in a parent object and everything you want to disable be a child of that parent object
A workaround is to never disabled networked game objects and to disabled their individual components instead (for example the renderer and collider)
I have no interest in making my own pooling system, at least not right now
But I can try delaying the variable setting for a bit
You might run into issues with networking edge cases, but yah it'll be a lot simpler and will probably work most of the time
When checking playerobject ownership in unity runtime against a bot should it be returning you also as owner?
player object owner is always the player, so yes
This won't be the case with two seperate players though right?
clientsim remote players don’t act like real players
they can’t hold any sort of persistent data
so i wouldn’t be surprised if the player object ownership didn’t work accurately with them either
i’ve noticed you can’t teleport them either, don’t know if that’s my code not working or clientsim remote players not being able to be teleported
and fun fact, trying to immobilize a clientsim remote player crashes your udonbehaviour
Probably because you just cant teleport remote players period
You have to ask that player to teleport themselves
And since clientsim remotes cant execute code on their own, they cant ever run it
I don't understand. I even tried drinkManager.SendCustomNetworkEvent and just setting bartenderId locally for everyone like that, but that ALSO doesn't work. What in the world is going on with this networking?
Im having issues with Udon logic halting from working whenever a player joins the instance, it causes variables to change and things to stop acting as normal.
The logic isnt networked in any way at all so im wondering what on earth is going on
Show us
Will do
Alrighty sorry for late reply, here is whats happening, this issue causes a whole heap of other minor issues but this particular one causes people to not be able to complete the tutorial, they perceive the world as a buggy mes and I wanna jump off a 3 story building 😛
The value of 70 is being stored in this Udon which has no programmed way of changing outside of what its designed to do, I have no idea how a player joining is causing it to reset the value to 0
Ta da
is this on a player object?
No
could you show the (presumably) player restore section
at a glance, your player restore there isn't branching based on the local player; it will fire locally any time someone joins. it also doesn't guarantee that your own player has restored initially upon your own join as it may fire another player prior to your own
Uh?
Ohhh
so you're saying I need to check if its actually you loading in?
But... I always thought this would be just you, your local client loading in, why on earth would I want touse that node for whenever someone else joins? wouldnt you just use an on player joined node?
OnPlayerRestored runs for for every player that joins. The output for OnPlayerRestored is the VRCPlayerApi corresponding to the event.
Your video conveniently cuts off which player you're loading data from, but you should be using the output of the event, and checking for isLocal when relevant.
So when a new player joins, It runs that function, and attempts to load the data from that player again. Most importantly, if those keys that you are requesting for are not present in that player's persistent data, then they will return the default value for the associated data type. Which is often 0.
there are cases where it's ideal to start up in restore for other players (especially player objects) or intake their data initially
even easier to just call isLocal in the VRCPlayerApi player, this outputs a bool if that player is also the local player or not. You can then plug this result directly into the Branch
plug player into that
Oh yes my bad
Well that clears up a headache Ive had for weeks now
@twin portal@minor galeThank you very much for your time!
My internet sucks atm so i cannot play imbeds videos unfortunately
I can have a look at anything else tho
Either way looks like this was solved
Indeed onplayerrestored is a global event, you can sorta tell those because they have a vrcplayerapi output
There wouldnt be a need for such an output if it only fired for the local player 😌
quick question cause I cant find the answer on docs
Can I sync variables that aren't public using [UdonSynced]?
yes
fire
is it possible to network sync an instantiated prefab? the prefab has a vrchat pickup and object sync on it. for example pressing a button instantiates a ball prefab?
no; one of the major drawbacks of instantiated objects is that they cannot be networked.
There are alternatives, like using an Object Pool instead
So, I can't even sync the button press and have it instantiate a prefab locally for everyone in the world?
you can do that, yes. but ObjectSync still won't work on it
Ouch
This throws a wrench in my plans
So, no matter what, objectsync won't work on it unless the prefab is in the world already?
pretty much, the only exception being Player Objects
Player Objects?
Player Objects make a copy of a prefab for every player in the instance, automatically
despite the copies not existing in the world beforehand, they can be networked as usual
https://creators.vrchat.com/worlds/udon/persistence/player-object/
PlayerObjects allow you to automatically give each player who joins your world a copy of a GameObject, such as a flashlight, a health bar, or a sword.
they work best for this specific use case, of course, when you need exactly one object for every player
if you need a copy of an object for every time you press a button, rather than for every player, you could instead use an Object Pool. It's technically not an infinite amount of objects, and the objects will still be in the scene at the start, just disabled, and the pool manages the enabling & disabling of the objects
all depends on your use case and why you need copies of this object
You wont be able to use vrc object sync, but you can write your own script to sync transforms of locally instantiated objects, but i dont recommend it doing it
That sounds like too much of a workaround.
Just having objets in the scene and draw from them sounds like a better way, and have a limit so each player have a max of 30 "balls" they can spawn. Can I put these 30 balls on a PlayerObject to give each person his own pool of 30 balls to pull from when he joins the map @twin portal
What happens if a player requests ownership over an object, but the object owner doesn't send back a response because they are frozen or timing out? Is there some kind of automatic rejection or timeout?
sure that works. that can end up being a lotta objects though
ownership transfer is, in most cases, immediate and doesn't require authentication from the current owner
alright I feel like im bashing my head into the wall working with a coding helper to try to make something that can read audio data from a tv source like protv and move blend shapes with it, like an audio link of sorts, can this even be done before I sink more time into it?
audiolink is not some exclusive allowed script, you can check out what and how it does and do the same thing. and also thats not networking.
I dont see a udon code section, where would this be better posted then
I tried that but when I ran the errors through copilot and it told me my script cant read data from audio link because "AudioSource Isn’t Feeding Real outputData" and says the script can only interpret local audio like mp3s in thw word, which it did do
Can you post some of the code that is generating that error?
sure
using UdonSharp;
using UnityEngine;
public class AudioLinkAmplitudeReader : UdonSharpBehaviour
{
[Header("AudioLink Material")]
public Material audioLinkMaterial;
[Tooltip("0 = Full, 1 = Bass, 2 = Mid, 3 = Treble")]
public int amplitudeBand = 0;
[HideInInspector]
public float amplitude;
void Update()
{
if (audioLinkMaterial == null) return;
Vector4 data = audioLinkMaterial.GetVector("_AudioLinkData");
amplitude = amplitudeBand switch
{
1 => data.y,
2 => data.z,
3 => data.w,
_ => data.x
};
}
}
ah yeah, your problem is that copilot gave you nonsense
ugh
I may have already asked this before? I cant find it
If SendCustomNetworkedEvent has a limit of X calls per second, can I still, locally, call it more than X times per second? (sending to NetworkedEventTarget.Self)
i can't imagine it rate limiting as it'd process the call immediately
sick
at least i'd hope not, i'd find that odd
I have another question that has me stuck currently
Currently, I'm designing the game start system for my game, and there are several states it has to go through, some of these states are owner-only (only the owner runs them), and some of these states are synced (all players in game run them)
When the owner does a local state, and is moving to a local state, they should be able to continue instantly
When the owner does a local state, and is moving to a synced state, they need to tell everyone what state to move to, occasionally need to send data (might just use a synced variable, since order is guarenteed if I send it before), but, the owner should only be able to move to the next state when everyone responds
tl;dr, just wondering what would be a good method to do this, I don't need an exact solution
im also trying to do it in an organized way
with a synced variable you don't necessarily send every variable
i.e. you change variable to 1, you request serialize
you change it to 2 within the call (or even on a subsequent frame) and request serialize
it's possible for only the 2 to go out because the serialization is just a request for it to send it out when it combs through and sees it flagged as wanting to serialize
this probably wouldn't happen in practice if you had a send/response before needing to shift the variable, but it is possible
i think it'd be best if players could infer the current game state at any point, in like an absolute sense (from an int or something), but that's not always possible if you do things sequentially/rely on previous state (and that is valid too)
i don't have a ton of practice making games, so i don't know what i'd do exactly, other than running through a central handler that ensures i'm enacting game state in a way that isn't reliant upon other objects/order
i think most vrc creators probably do not explicitly rely on responses for game starts, they have some owner/master player enact the game start and inform everyone that the game has started. if the master needs to pass out info/roles they'd likely just do that themselves and rely on data they can interpret on any eligible players (players who are in a trigger that opts them in for example)
maybe someone with more experience making games can provide better insight though
Trying to think of why I did this on the old platform
Because, I probably could have the owner do everything they need to do, then tell everyone to do what they need to do
yeah you know what, thinking about it, doing all this waiting is pretty pointless
like i think it could be valid to process it through events on eligible players, but you could get into odd situations with an unresponsive player or leaver i think; if players advertised their preferences on variables it'd be possible to infer it at any point, but it may be a little harder to route around
I really could just have the owner do everything at the start, and then later, when done, tell everyone else do to everything else
Oh, I would only ever wait for players during game start, so if anyone is frozen/crashing during game start, it fails; However, it shouldn't really matter, since game start code should be really fast, unlike the old platform, and when the game starts, it pings all players, and only players who respond can be put in
alright sick, coding time
oh this is actually pretty easy since I should just be able to tell everyone "run game start, dont do [thing] if you arent the owner"
are you making like a pvp game?
social deduction
need everyone to locally ready the game, and need owner to randomize roles based off weights and assign them to everyone
My idea currently is to do it in 2 stages, the "Everyone do this to ready the game" and then "Alright, you have your roles now, do the rest"
Personally I wouldn't fret too much about making sure everyone is there and good to go before starting a round and then assigning roles. Any cases of people dcing or freezing can happen just as easily after the role has been assigned and you'll probably want to handle it the same way anyways.
Also they can still freeze/dc right after confirming that they're there and ready to go, so you'll want to make a system that can handle that anyways
Yeah, I was thinking that
I do still want to ping players before the game starts, because from previous experience (the old game on the old platform), its very common where a player freezes (eg. quest player AFK) for long periods of time before the game start- But, as for during-game start, im just gonna not care
i'm also going to try and make it less time-dependent, having a bunch of data where, if its already received by the time you get there, you continue, but if its not already received, you wait until you receive it, this way you can locally continue when you receive the data, rather than the owner waiting for confirmation from everyone and continuing to the next step
you're telling me
i can just
get when a player is suspended or not?
oh my god
goodbye entire ping system
it can only happen to quest/mobile players i think, but yeah i think the server informs you of their suspended state
this is specifically for them like minimizing the app, not whether they're frozen
whoever worked on udon is my goat
the master can also be suspended
thats fine, game start runs through master, if they're suspended the game cant start at all
does this look right for a network synced state?
My IDE is telling me I should be using a different style of method
had a question, while typing, found the solution, awesome
StartAsync isn't exposed to udon, I switch that around, but my question still stands.
Looks alright to me, my guess is it's Rider assuming you should do something a certain way that doesn't really work with Udon
classic
"Expression can be converted to a foreach loop" -> I'm iterating over a DataList
"Expression can be turned into a LINQ call" (or wtv it says idr) -> I'm using for each to check for a condition or smth
another good one is not being able to do Array[^0] for the last item, got do Array[Array.Length - 1]
one reason why i stopped using rider soon after i tried it out
i know you can probably disable it, but it felt like it handholds way too much
and it doesn't understand udon, so the suggestions don't even make sense every time
Rider A) isn't aware of Udon limitations and B) assumes a higher .NET version than we're using too. So you have to be careful about the recommendations.
You can disable specific recommendations for your project so I just do that on an ad-hoc basis when I find it recommend something VRC doesn't support.
"where All contains Self"
do you mean when the instance only has a single player?
You're still using 1 event usage per All regardless?
VRC isn't pure p2p, it uses a proxy server...
You send the event once to the proxy server and the proxy server splits it to everyone else in the instance
event splitting will not split the event into multiple events, it's referring to splitting and reconstructing the data if it's large enough to warrant fragmenting it
it does note that the split will affect your rate limits internally though, which is interesting to note at least
Where are you getting this info, Ophura?
This sentence is very confusing to me:
"when invoking a custom networked event with an event target of All; the event will run at least once for each player in the instance, which eventually executes for the Self player; this invocation won't be affected by any rate-limits (neither set by NetworkCallable nor internal rate-limits)."
It seems to claim that events running on remote clients will count against the local rate limit.
Well just to clarify it for everyone:
Self has no rate-limit cost
All has a rate-limit cost of 1 (before Event Splitting is applied)
Owner almost certainly has an identical rare-limit cost to All when the owner is another player; it most probably is identical to Self when the owner is local, but nothing in the docs makes that explicit
So, if the local player sends to All 10 times, and the rate limit is 5/s, the local player will see all 10, and the remote players will see 10 over a second? (5 'instantly', the other 5 over 1 second?)
And then, when sending to owner, if you ARE NOT the owner, it counts towards rate limit, but if you ARE the owner, it does not count towards rate limit?
I don't believe that the docs are super precise about the exact timings, but in your example you can generally expect all 10 of those events to be sent out by the local player to the proxy server within 2 seconds.
But yes, events that can execute locally do so instantly regardless of what is going on with rate-limiting.
got it, sick
about what i expected, if i ever run into unexpected behaivor ill test it
i actually wasn't sure if it'd queue them when calling all, it calls all 10 instantly (locally) when i tested just now and remote received them over time
Also just now trying it out in Build & Test, I can confirm that when the target is Owner and the owner is the local player, it does not appear to contribute to any rate-limiting
for the owner of the object spamming the event:
for the non-owner spamming the same event:
Hiii
I am storing a list of player ids in a DataList that I serialize via json over the network but for some reason when I try to check if the id of a player is in there (using Contains()) it always returns false even though the id is indeed in the list, what's up with this?
testing locally in a vrc client
for the owner it works fine but not for others
bruh, turns out owner had ints and others doubles
I need to remember to explicitly cast when deserializing...
feels so nice when you realize how to fix this stuff and it works
I love network encoding
Trying to call a simple networked event, but setting up the event causes this error to appear, and when trying to manually enter the networking library, it returns another error that it doesn't exist. Any idea of what to do?
it's this namespace using VRC.SDK3.UdonNetworkCalling;
Unfortunately adding that returns this:
My IDE doesn't seem to recognize that it exists?
are you on 3.8.2? (the sdk)
How would I check? Sorry I'm new to using VRC
in the VCC you can check in Manage Project
you should close your project if you try to upgrade the sdk
It was on 3.7.6, upgrading rn
that seems pretty recent, but i don't remember exactly which version added the new network events
Still returning the same error...
Found the problem
I set the script to automatically update instead of manually
Who would've thought
Thanks for the help though
What type of arrays can be synced using manual events?
I was told that only Byte Arrays can be synced, but looking online doesn't confirm this at all.
i think it's any of the sync types here
https://udonsharp.docs.vrchat.com/vrchat-api#synced-variables
byte arrays are the only array type in playerdata persistence, but that doesn't extend to playerobject persistence
so i finally got dropdowns to play an animation, now how do i go about makeing it sync for players and late joiners
- you have a synced variable that is going to sync e.g.
DropdownAValue - Owner change
DropdownAValue(if not owner, set owner prior) - Owner request serialization, and then send custom event
DropdownAUpdateAnimationPlay - You need to have a OnDeserialization node (for non-owners) to send custom event
DropdownAUpdateAnimationPlay - Make the
DropdownAUpdateAnimationPlayuseDropdownAValueto update the dropdown and do animation
Hello! I have been trying to learn how to make a UI menu that allows players to flip through pages of options for my world. I cannot find any tutorial that shows how to setup such scripting. I prefer to use Udon Graph. Any help would be so greatly appreciated - Please reply to this message so it pings me >w<
UI components such as Buttons have a little section in them for UI Events; ideally you set these up to then send a Custom Event to an UdonBehaviour to do whatever you need
In your case the script would probably just need to toggle GameObjects on & off
the bits that look like this
Yes this is how I have it setup, but for some reason it turns off all the pages when clicking a button instead of cycling through the page its meant to be
Feel like I am missing something small
you loop through the entire array of Pages, enabling all GameObjects in the array with this Interact event
this won't cycle one at a time
Ohhh okay! So I need to go into the graph and change so it cycles through one at a time, what do I need to add and/or move around?
though cycling doesn't make sense if you have a dedicated button for every page
it would be easier to have the button first disable all pages, then enable the page the button is for
so for the button on Btn_Credit, Interact > disable all GameObjects in the Pages array > enable the current page
Where would I make these changes (I have basic/minimal Udon Graph skills, so sorry)
uncheck the box in SetActive
then make another SetActive node, and connect it to the Exit of the For loop
The "instance" you'd plug into this second node should be the page you want to enable
something along these lines should work
with the currentPage variable being public
you could also have just one script to handle it all rather than having a copy for every button. either works
This helped a bunch!!! It allows me to cycle thru the pages. But the issue that came now is once I clicked through all the pages, when I click on the pages again, nothing changes. As if I can only flip through the pages once. Maybe I set this up wrong for the buttons?
looks fine. check your console for any errors, see if the script is crashing
Everything is okay in the console regarding this script. I just noticed when I am toggling the pages in play mode, it is turning the pages on but not turning the others off and vice versa
So theyre all turning on as intended, but no off function
is the little checkbox in the SetActive nodes the correct value?
should be unchecked to disable, checked to enable
Omg i dont have a noodle on one
you have the nodes set backwards compared to my example
the SetActive connected to Body should be false, the one you have now connected to Ex it should be true
You are my biggest blessing
It works
Thank you so so so so much!!!
no problem
So I'm trying to make a teleporter pad that plays an animation for everyone during the teleport, that DOESN'T teleport everyone in the instance. How can this be changed to fix that?
OnPlayerTriggerEnter/Exit will also fire when a remote player collides with it.
So if you watch another player touch the trigger, it will also fire on your end as well.
If you only want the player that touched it to run the event, check if the colliding player is also the Local Player; you can get what player caused the event with the VRCPlayerApi player output of the OnPlayerTriggerEnter/Exit nodes
just plug it into a Branch
then connect TriggerEnter - > Branch -> true part branch -> the rest of your code
And that should be all I need to fix it?
yes, that will make it so the code will only run if the local player touches it
Now the animation plays only locally and it doesn't teleport anyone.
I'm giving up on trying to delay the teleport using a script. Will this be local or will this teleport everyone?
this would request all players that witness the TriggerEnter to teleport the player that touched the trigger
Which I think... should work? Since only the local player can teleport themselves
hi ✨
may i ask for some help with udon and network please 🤍
i just started to work on my first world project, maybe it’s a month at this point. A lot of things are done, but networking makes me crazy. no google or ai can give me answer i’m sure about.
Huge part of my world is collaboration - anyone can change variables or what ever and everyone must be in sync. And i just don’t get it, how to make robust system to even pass 1 variable if a lot of people try to interact at the same time. setowner, success no success, is there a que or no que… so could anyone explain to me what will be the proper way to make just one robust synced slider, which anyone can move around and keep everyone in sync? my code flow is like this:
- onsliderchanged fires
- small throttle for only 10 times a second
- if enough time passed
- setOwner
- wait if owner or not. if not return and wait for ownershipTransfered and wait that it’s me
- if an owner or got ownership - update synced variable from local one, request serialization
- on post serialization if success apply changes to yourself if not, just dump changes.
- on deserialization others update the synced value of the slider using setWithoutNotify
But, is setOwner instantaneous? I can’t find how it works at all. will it wait for ownership for 10 years? what if i drag slider non stop and cause 10 changes a second, are changes qued? do i even need postDeserialization? what if i have two people requesting ownership at the same time? changes qued by time order or only one gets chance to sync and other who tried at the same time got nothing?
i will be happy to get any information. these small things makes me crazy because i need some how robust system where i understand when it fails and can decide it’s fine, but i just dont understand how much i have to do for protection or maybe data just dissapears and behave not the way i though and now people are desynced T__T
i have much more things done with network, but example with slider would help me a lot, it’s simple but gives kind of a perfect example.
- SetOwner is instant. As soon as a player sets themselves as the Owner, they can immediately edit synced variables and RequestSerialization.
The docs detail additional methods to actually negotiate ownership, but this is optional. "Waiting for ownership" is not something you are required to do. - How this data gets synced depends on what sync type your script is using. If you're syncing a slider's value for example, Continuous sync will sync the value at any time when the value is updated, with some interpolation. Manual sync will only send the values to other players if you call RequestSerialization; then whatever the other players need to do with this data (like set the slider's position, etc.) you would run in OnDeserialization. OnDeserialization does not run for the sender, however, so you need to either do the setting immediately after you call RequestSerialization, or call the functions in OnPostSerialization.
- If multiple players are requesting ownership simultaneously, they'll fight over it, and the server decides who wins. Anyone who loses will have their data ignored, so you generally want to avoid situations where you are changing ownership frequently.
ok, so setOwner is instant, got it.
my mode is manual and i do take care of updating for myself and others as you said, so i guess that one is fine.
i don’t want to fire ownership hell, but i would prefer to understand what will happen. So, i will get ownership, then i’m allowed to call serialization right away, but will it be synced or not depends on latency and what ever chance - only one right?
and stupid question. i do scripts, should i use udon graph or does not matter - what ever i prefer?
pretty much up to your own preference, they both have their pros and cons
whichever makes more sense to you
and, what about ownership transfer and postSerialization? those were mentioned to me as important so i never push buffered changes before get approval that its fine
So if i want to change a variable or a vector i have a sequence where i buffer my chnages, then try to get ownership, if not - wait for ownership transferd. when that one fires and i’m the owner - i call serialization but do nothing yet. only when post fires i check if it was done and apply to myself buffered changes - is this stupid, not needed?
I wouldn't call it stupid, but as I said, waiting to confirm ownership isn't necessary
do people wait for post? to see if it went through? what if it failed and i applied changes to myself, which others did not get it?
curious how you're "waiting" though. Are you just using OnOwnershipTransferred?
yes. if i’m not an owner i keep my changes inside buffer and flag changes needed, so it wait untill transfered comes to me.
this is why I usually suggest for people to run their setters in OnPostSerialization for the sender; in this function, you can also check if the serialization was actually successful or not, and potentially cancel making changes, that way even if something fails, everyone is still synced
and drops if after one second i did not get ownership
yes yes yes, that what i meant. so you do use it, great, i will keep it then.
so, basic proper way to sync with people then looks like this:
- get new variable state you want to sync
- buffer it to network synced variable, do not apply to yourself yet
- setOwnership
- do not wait, request serialization right away
- wait for OnPostSerialization
- if done - call function which uses synced buffer variable to apply local changes
- other players OnDesirialization - use same function as master to pull synced variable to local
this way we know that we transferred data and we have just one function which apply buffered/synced variable to local for both slave and master
something like that?
setting the variable and setOwnership needs to be the other way around; you have to be the owner before you can edit synced variables
Legos, I think I remember you saying that on trigger enter and on trigger exit run for everybody is that right? In that case I’ll just make them run for the local player.
wait, i thiough you can put inside synced variable what ever you want, it’s just never synced if you dont call serialization
btw, i dont want to bother anyone too much - are there well known projects on git or whatever which i can use as “well known good implementation” examples? So i can go and read and not spam here :3
nobody writes good code

in seriousness the only thing I can think of is some of the VRChat examples
being a programmer is coming back to your code a week later and saying "this code sucks"
you could always look at the code of a random prefab, but those tend to be very complex and hard to follow
if you're trying to learn something simple from them, that is
dont even start, i had 3 refactors of all my functions at this point
I can give you all of the code I used for Infinium if you want.
the networking for that crap was surprisingly never a problem
can we get one :3, but it’s also fine, i think i’ve got all answers i need ✨
thanks a lot for the help ✨
maybe I can find a small one.... lol
exactly my point that it's multiple scripts all working together, so just one by itself may not make any sense and not have anything useful to learn from
it’s ok, i’m already happy with info :3
rather than give the code, I've now realized why the networking was never a problem: I never change ownership of anything
Infinium runs mainly on PlayerObjects, so I just take advantage of that, and additionally the game doesn't really have any reasons to change ownership
just sort of further showing that it's better to not transfer ownership if you can help it, I guess. If you need multiple players to be able to adjust a value frequently, instead of changing owner, you can just send an event to the current Owner to do the calculating, rather than fight over ownership
I used this in a demo to make a value that can be updated by anyone, and updates quickly without data loss
unfortunately i cant. some parts where you hold an object - yes, those are fine. but i have a control timeline for setting frames and time and adding edits and that one i cant separate. and if everyone wants to jump to the same frame, or some one else added a frame or changed timing - it’s like working on project all-together in powerpoint or what ever similar like devinci
i have small values to take care of here and there and slider is the one, where if people decided to work in synced - anyone should be able to pull it and force all other people to jump on the same time slot
you still wouldn't have to change owner for that
you can have one player handling setting the values, made especially easy with the netowork events with parameters now
i mean i can send to master and then to everyone
master is not guaranteed to be the owner of the script
but that's the idea essentially
send to one player, that player sends to everyone else
but… how to guaranty everyone gets the event and stay in sync?
same way it would sync normally. Only by doing this, you're not fighting over ownership anymore
possible lost of data?
not anymore, since ownership isn't being changed.
i mean are events as save as serialization?
yes, both are referred to as "eventually reliable"; if you know they've been sent, they will, eventually, arrive to their target(s)
but are almost always immediate.
what you are using as synced variables then? what would be for sure not a custom event?
i’m actually supa curious now how furality and club events are taking care of network
there is probably only one owner and everything is supa strict, but still.
synced events smell with checksums and version number of a change now :3
a bit more complex, but if it works… maybe i should used it
you're still using synced variables, just only one player is allowed to edit the "real" ones and send them out to other players
ohhhh
hmmm
so if not an owner send custom event if an owner do serialization - sounds good
i feel stupid i didn’t thought about that
yes, you SendCustomEvent, target the owner, and can even include the value you want as part of the event's parameters
i need to pull ping to server and make owner with fastest connection :3
that's usually already figured out via Master
i’ve learned it today, but know it kind of make sense to use
VRChat does a bit of calculating to decide the best player to become the Master, if it's been transferred
so, i just need a check if master or not and that is it?
i dont even have to select?
no, don't check for master, check for the owner; they will often be the same player, but it's safer to use Owner.
is there a function send custom event to an owner or just to everyone and only owner does the job?
yes, the SendCustomNetworkEvent allows you to specify the targets, and one of the options is Owner
ok, small refactor it is :3
basically:
Player1 is the Owner of the slider. All other players, inherently, are not.
- Any player updates the slider (including Player1). The slider calls SendCustomNetworkEvent, targeting the Owner, and sends along the value of the slider as a parameter.
- basically "Hey Owner, I want to change the slider's value to X now."
- Owner receives the NetworkEvent. Takes the value from the parameter and sets it to the synced slider variable. Owner also requests serialization.
- Handle the network update as usual; other players receive the changes in OnDeserialization, the sender makes changes in OnPostSerialization
so if 10 players change the slider at the same time, they'll all go through the Owner instead of being lost in the process of ownership transfer
sounds ok, latency tho… hmmm, and how to buffer changes too.
because i want to move slider, and it should move nicely for me and not being affected by owner telling me onDeserealization
i guess i need to find a way for that
that may not end up being a problem, I'd test it first
but there are also some features with NetworkEvents where you can limit how many events are being sent per second, and can add some logic so that you don't send more events if there's already too many waiting to be sent
i have limits for updates already. i do want to get smooth transitions but it’s probably impossible, so it’s ok.
probably some magic you can do with lerping to make it transition nicely
ok, i guess there is no way around it. ether get owner fights or one owner controlling everyone
lepring?
interpolating?
yeah
i can get speed of slider change and transfer speed and end point
ok
i was thinking about that but lets see will implement xD
it will look much better, but there is a chance i will fuck up myself… ohhh, wait, i wont, i have slider as a separate object now, so i just need time output and that is it.
hmmmm
or maybe not… i will think about it.
thanks again ✨
interesting read, I also came to the conclusion that it's more straightforward to ask the owner for the changes others wanna make. there will be latency but for my use case it doesn't matter
if something like a slider is used one could just set the value locally and ask the owner to change it as well, while the slider is being held buffer the change of synced value received from the owner and when the slider is released make the update on the player. that way it stays in sync and the network updates won't be sporadically changing the slider while you're editing it
really basic question, but for the IsOpen14 variable, does anyone know how i get the sync changed from none to automatic from here? followed the steps from a YT video on how to create doors, and it said that this was good for global settings, but after digging around, i found the sync was still set to none, when i believe it should be automatic.
on the gameobject where you add the udon behaviour it should have a drop down for the sync type, you probably want continuous if you want something automatic. you could also use manual with a few additions to the graph (for reference, continuous variables constantly broadcast)
is there a good way to get the fact that a slider is held?
i think you'd just have to infer it from the value being changed recently via events, i think there are real ways to do it that aren't possible in udon
maybe via this or smth
awesome! i remembered seeing that drop down somewhere, but honestly i had implemented this all about 3 months ago, and had since been working on building furniture mmodels since... i kinda forgot literally everything i learned then XD
much appreciated
make sure you set ownership somewhere if it's possible for any player to change the bool, i'm not sure if the guide you looked at covered that, but i'd assume so
thats what i am looking around for right now, attempting to find the Set OS on Int checkbox -_- unfortunately in this first year of unity, i have found that apparently no 2 people run the same version of unity, and every guide you find has the same final house being built, but is using completely different building materials so to speak...
and after doing some more investigating, it seems the option might not be available in my version..
first of all, if you want to detect when a UI element is held, you have to use the EventTrigger component, we cannot use OnMouseUp and OnMouseDown directly in udon. however, note that due to a bug, these events will fire twice in the same frame, so you need to account for that (related conversation here: #world-ui-design message)
second of all, why do you have a VRCUiShape AND collider on your handle? that is completely redundant. what the VRCUiShape does, is add a collider to your canvas if it doesn't have one, and allows for focus view on mobile. that's it. the only part of your UI that needs a collider is the root object with the canvas component, and you can either add one yourself or let VRCUiShape do it for you. do not add colliders or VRCUiShape components to any UI elements that are a child of your canvas
also, you'll want to use OnPointerUp and OnPointerDown, not the Mouse variant. i believe this will also account for VR
i'm getting good results with the OnDrag events (all 3) unless there's some issue i'm unaware of with them
Is it possible to send an int[][] with SendCustomNetworkEvent?
searching for it further, because discord search sucks, looks like it does not
time to play with OnPreSerialization()
You can try making a datalist of datalists and using VRCJson to serialize it over the network https://creators.vrchat.com/worlds/udon/data-containers/vrcjson
Data Dictionaries and Data Lists include functions to convert to and from JSON. A Data List is equivalent to a JSON array, and a JSON object is equivalent to a Data Dictionary with string keys.
yeah but its most likely going to be a very large send
its supposed to be object data for custom maps, assuming players can only use the current GenericMapObject, its about 13 bytes per object
im fine having it save as json, my idea for sending it over the net is just turning the byte[][] into byte[], with the first item of the second array containing the length of the array
(working on 2 projects currently, this is unrelated to the other one, one im working on as a solo side project)
if it's an int[][] it's probably even easier to pack into a single int[] array, like deserializing it could be the outer length, then the inner length, followed by every element for that inner array, repeat for each inner array
though if you expect using other types it'd be better to pack to bytes i think
yeah that is the plan
theres gonna be 2 types, the json that can get exported/imported, easy to read and manipulate, and then the data that gets stored/synced, which is a very compact and optimized byte[]
either way, when you import the json, it just gets stored/synced as the byte[]
its mostly for me lol, the preset maps for the game are going to be made with the same map editor i'm giving to the players
i just realised i dont really need to make my own json format thing lmao, I should just be able to create and export a data dictionary
Is there a way to get this ping value? I tried VRC.SDK3.NetworkStats.GroupDelay(owner) or other values of the Stats-class. But I can't figure it out - or at least no value resembles this ping value 🤔
I try to predict a manual synced position, so that two players, each riding their dragon, can fly next to each other. If I set "latency" manually to the value of the ping, they fly on exact the same position in both instances (maybe only a coincidence?).
float latency = VRC.SDK3.Network.Stats.GroupDelay(owner); // <-- this value is too high - but ping would fit
// Calculate the time since the last deserialization
float deltaTime = Time.time - lastDeserializationTime;
// Predict position based on last known velocity
Vector3 predictedPosition = lastDeserializedPosition + velocity * (deltaTime + latency);
// Smoothly move to the predicted position
transform.position = Vector3.SmoothDamp(transform.position, predictedPosition, ref smoothDampVelocity, smoothTime);
(velocity is calculated in last OnDeserialize event)
In OnDeserialization, you receive DeserializationResult which contains SendTime. You can compare SendTime to ReceiveTime to calculate how long it took to reach you, and by calculating or receiving their velocity, you can calculate where they would be now if they continued on that trajectory for that time
Oh, never remarked that there is an OnDeserialization event with this additional infos 😮 Thanks - that helps a lot!
Jagged array? No. Nor multi dimensional array
Pretty sure we can only do primitives
Like bytes. Strings . Longs etc
the syncable types are listed here
https://creators.vrchat.com/worlds/udon/networking/network-details#synced-variables
Networking in Udon can be challenging! Try to keep things simple until you're more experienced.
you can sync these, and arrays of these types
What's the proper way to go about this:
- Someone takes ownership of a
GameManagerscript and changes a synced value. - Different people own different
Playerobjects, each that reference that synced value in their ownOnDeserialization.
I currently try:
foreach (var player in players)
{
player.SendCustomNetworkEvent(NetworkEventTarget.Owner, nameof(Player.Serialize));
}
in GameManager.OnDeserialization, but it doesn't seem to properly work.
Player.Serialize is the following:
public void Serialize()
{
if (!NetworkCalling.CallingPlayer.IsOwner(gameManager.gameObject)) return;
RequestSerialization();
OnDeserialization();
}
Regardless, this seems like the wrong way to go about it. But (I think) I need Player.OnDeserialization to reference that synced value from GameManager because it enables something locally based on it and a synced value from Player itself.
wha
gamemanager script receives ondeserialization event locally
now everyone knows and can reference that value
GameManager has a currentQuestion synced variable.
Player changes the text of something based on whether currentQuestion != -1 in its own OnDeserialization.
I know this is a stupid way of doing it but I can't think of another for some reason lol.
huh. make a player an owner of a current question, what
what is even player.serialize
Player.Serializeis the following:
public void Serialize()
{
if (!NetworkCalling.CallingPlayer.IsOwner(gameManager.gameObject)) return;
RequestSerialization();
OnDeserialization();
}
In my original message
Each Player script (each owned by a different player) needs to do something when GameManager.currentQuestion gets changed.
How's this:
How can one script get notified when a synced variable changes in a different script?
ondeserialization -> targetUdonBehaviour.SetProgramVariable("VariableName", newValue);
given othet script had on change, or to be sure just fire custom event after it as well
I don't want to keep a separate variable in Player. I really just need a way for Player.OnDeserialization to be called when a synced variable of GameManager gets called, as some logic in Player.OnDeserialization relies on synced variables in both Player and GameManager.
I really don't understand what you're not getting.
Player is a script, not a literal player
Perhaps that's leading to the confusion
(although if a word is in a code block, it's probably code or a class/field name)
ye, comprehensive naming my beloved. what to you mean by ondeserialization being called when x, it gets called only on deserialization of its own script, if you need same to happen on other conditions than add some other way for the same function to be called
Why does this not work?
GameManager.cs
public override void OnDeserialization()
{
foreach (var player in players)
{
player.SendCustomNetworkEvent(NetworkEventTarget.Owner, nameof(Player.Serialize));
}
}
Player.cs
public void Serialize()
{
if (!NetworkCalling.CallingPlayer.IsOwner(gameManager.gameObject)) return;
RequestSerialization();
OnDeserialization();
}
Hopefully this should make my question clear(er)?
you csnnot call ondeserialization
k i get it
just make ondeserialization: do my stuff
on custom event 'Serialialze:' do my stuff
do my stuff being custom event/function whatever you call it
meaning, don't actually request serialization in Serialize, rather call a method that does the same thing?
But I'm explicitly calling RequestSerialization. Why doesn't it work? Because no synced data was actually changed?
normal hardoded events happen only when normal hardcoded stuff happend
oh
anyway, you def csnnot call
Ondeserialization();
Out of (bad) habit, I call both RequestSerialization and OnDeserialization, but the main question here is where does the issue with my code lie:
in
foreach (var player in players)
{
player.SendCustomNetworkEvent(NetworkEventTarget.Owner, nameof(Player.Serialize));
}
or
public void Serialize()
{
if (!NetworkCalling.CallingPlayer.IsOwner(gameManager.gameObject)) return;
RequestSerialization();
}
ie you can only react to it, not invoke it other way than request serialization
Why do you even use NetworkCalling.CallingPlayer.IsOwner?
Client safety. Helps prevent functions from being called by client users.
No but the standard function to call there is just Networking.IsOwner
That's not at all the logic I want though.
I'm checking to make sure that the player telling me to run this function is the player I want telling me to run it, not someone else.
Hmm in any case, if youre not actually serializing anything, then it wont ever run OnDeserialize
so yeah you have to change some synced var
Even if I call RequestSerialization?
yes
i think request serialization does implicilty send all vars to remote users
RequestSerialization sets a flag, that the next time the networking handler looks at the object it serializes all data.
Ohh actually
you dont need to change anything, but you have to have at least 1 synced var
Because I'm trying to call OnDeserialize. I'm tired and didn't think of extracting a common function for some reason lol
i do, hence why i have OnDeserialization in this script as well
owner gets no ondeserialization at all. other users do. then both run some custom event (function) based on that, thats gist of it
Ohh
dont call your script Player
Unity has some builtin stuff that collides with it
Thats not what Im referring to
If it was interferring, the entire script wouldn't work.
I use custom namespaces (as every dev should).
Hmm good
lol yup. But yeah, as a rule, don't name your scripts the same thing as other scripts without namespaces lol
Well yeah I mean that should be obvious, but what isnt obvious is that Unity has builtin things like Player
Ohh
Youre RequestingSerialization on Player.cs but the OnDeserialization is on GameManager.cs
Thats not how it works. Serialization is per-object. You have to do something like gameManager.RequestSerialization()
Youre also calling OnDeserialization on the Player instance
I have OnDeserialization in both scripts. I'm trying to call Player.OnDeserialization FROM GameManager.OnDeserialization.
yeah then you need a reference
That's what i'm TRYING to do lol
I'm trying to call Player.OnDeserialization.
foreach (var player in players)
I think we're going backwards here 😅
The solution was extracting logic into a function that both OnDeserialize AND this networked event call. I guess RequestSerialization only calls OnDeserialize if data changed? Or maybe something else was happening. Regardless, it works now.
Thanks y'all.
Have ondeserialization call another method, and call that method from your other script
yeah, that's what I ended up doing, thanks
What are some of the recommended paradigms for a dynamic number of synced objects per player that aren't exclusively ownable by that player?
I'm working on a project where each player would start with 2 objects that need to be network synced for position/rotation, and able to be picked up by anyone. What these objects are would be chosen from among maybe ~10 different types of objects, a player could double up on a single type of object, and there could be up to ~32 players.
The need for others' to be able to pick up and take ownership of the objects naturally rules out PlayerObjects. The most straightforward solution I can think of right now would be to use multiple VRC Object Pools for each object type with a pool of up to the maximum number there could exist at a given point based on the max player count, together with a VRC Object Sync on each object. But that would mean a minimum of 8 * 32 + 2 * 64 = 384 pooled objects, which feels like a lot when the absolute maximum number of spawned objects at a given time would be 32 * 2 = 64.
Ideally it would be nice if there were a way to dynamically allocate network ids to objects as the player count goes up, but outside of PlayerObjects I don't believe that to be possible. I could also theoretically write my own networking layer to handle object movements especially since they would be moving rather infrequently otherwise (or have situations/states that would avoid the need to sync them at times), but that sounds like it would be prone to syncing issues, slower to update, and a pain to write and manage in general.
uh, just have 64 vrc sync objects and control their properties (type etc) externally via manual sync
Just a generic object that can exhibit the different object properties? That had come to mind, but I hadn't looked into the feasibility of it yet with the other states the object could have
like i have a rather buggy due to my limited knowledge back then but mostly working world of 200 pickups each can be anything from like 70 types with different meshes, colliders, scripts, interactions etc
Perfect, I'll look into that path further then, thanks!
@wintry bear also as a headsup, with pool there are always problems of spawning in right position and need for delay on that with owner change etc. so since vrcsync sleeps and has no networking cost being idle, id suggest to just go with vrcsync object being empty/invisible when not needed, rather than trying to use pool and ever disabling them
Good to know. I'm not terribly experienced with either so that saves me some time and headache learning the hard way 😅 I'll see about make a collection of generic synced objects that are assigned their properties when activated and placed with whom they belong.
Thanks again @tulip sphinx 🙂
Since VRC Object Sync objects sleep during periods of inactivity, having a lot of them in the scene doesn't cause much issue in my experience. The biggest issue I've observed is that all those VRC Object Sync objects will cause the first person in a brand new instance to have a clogged network for ~30 seconds until the objects start sleeping and any backlog gets cleared.
Also yah, if you're pooling networked objects and doing more advanced stuff them with them, I'd advise making your own pool instead of using VRC Object Pool. VRC Object Pool disables the entire GameObject of the items in its pool and disabled/newly enabled networked objects tend to have undesirable behavior. You'll want to make a pool that "disables" them in a way that isn't via disabling the entire GameObject.
Writing your own network layer would be subject to more problems then just using what vrc gives. And you would still be limited by what vrc gives us. And you would most likely just drain resources more then necessory to do basicly nothing
Which is why I considered that a last resort. The scenarios in which I actually need to sync things are pretty limited so if nothing else I could make it work, but I'm aware of the performance cost overhead among other issues it would introduce.
i wouldn't say it does nothing, you can support an arbitrary amount of networked objects if you run it yourself, but it's not trivial
pooling doesn't work super well at some scale (like if you were doing an rpg/crafting system)
I mean you can do 1000s of networked objects. But it just becomes a nightmare to manage under udons constriant. And beside that is it even possible to go around vrcs current inplementation? Like around manual or cont.
you'd still use vrc's networking for passing data, there's not much way around that; i use network events in my own example. you have to define what can be spawned and how to pass data about those objects. supporting late joins is more involved, but it is possible
i think in prismic's case i'd probably define a way to instantiate the visuals on an object when the synced type changes, with a pooled object as the base
i highly recommend nobody should try to create a spawn system with late joiner support, speaking from experience (but its also fun)
Definitely the right way of going about it. Each item would be "bloated" with the data for every possible item type, but the network will heavily thank you haha
you def dont need every object to have every thing. you only care about network ids on vrc sync, since cant duplicate those, the rest can be easily instantiated from a single (for each kind of item) prototype.
By designing my own network layer I've crated a world that can support 200+ VRC Object Sync objects + 100s of more other networked objects, and it all has worked flawlessly so far, even with people on the other side of the globe. I wouldn't call that "nothing."
Dont know if i would call that your own network layer. You could potentially have more then that. Its just a question about how many each player can have active at a given time. Considering that the limit data wise last i checked was 45 or so. But that also leaves you with nothing left
Like my own world i have a little over 250+ networked objects when the instance is full at 48 people and its all manual sync. No issues there either
With another 100+ being added soon or later.
Follow up question: I assume VRCObjectSync won't synchronize it's transform's parent changes from the owner? In other words, if I have the owner move the object from one parent to another, it will still be under the original parent to others unless I also perform the same reparenting action locally for them?
That's correct
Thanks, figured as much but wanted to verify before I went down the route of accounting for that.
speaking of VRCObjectSync, if I have an UdonBehavior on the same object and its set to manual, does VRCObjectSync also become manual and only syncs when I do RequestSerialization()?
wtf is this
you might be trying to do something related to ownership on a player object, they specifically have fixed views (ownership)
oh so that's what a fixed view means
no, the other way around, all scripts sync along with vrcsync. which doesnt break manual, but make it send excessive data. and does break continuous.
so, all and all, you dont place other networked scripts on the same object as vrcsync
got it
Becomes a bit of a nuisance though when you want to ObjectSync a Pickup, since then you also need those on the same object as the Rigidbody and Collider, which is where you also have to have the Udon script if you want to detect the pickup/interaction events and do anything network related with them. As far as I'm aware it means if I want to manual sync any related data I have to create another object to hold the manual sync udon script and forward the pickup/interact events to it.
@wintry bear sure, dummy functions on non-networked script on pickup itself and child object with manual sync to receive them and do the rest.
iirc my hierarchy was vrcsync - manual sync - intance of prototype object. i also read collider types on prototype and applied them to existing colliders (like, disable cube, enable capsule, set length/radius) on vrcsync object tho now when i think about it, probly couldve just copy the whole component or smth
More or less what I'm implementing now. The amount of extra data I am syncing is fairly small so mostly worked fine with continuous sync on the same object, but because the instance master is also holding ownership of all the other unowned items, it can be enough to network clog their continuous sync. The data changes infrequently enough I'm gonna just offload it to a manual sync on a separate object.
i tried continuous sync at first, wasnt good for my goal of 200 and again, it can break with 'vrcsync type of sync' taking over
i hand only 2 shorts and 2 bools synced so i think it was more about the latter than the amount of data. but i didnt knew back then
Yup, that's what I'm running into. I also need to leverage some kind of disabling for the inactive objects. I sleep and kinematic their rigidbodies, but it appears they're still continuous syncing stuff anyways. I still need to debug to be sure of what's causing the clog.
Mine is a byte and an int, but I can probably fit it into 2 bytes. It's just that plus the 28 bytes for the position/rotation, multiplied by up to 64 objects.
hm🤔 . use overlay for network objects, idle pickups should go to sleep in a few seconds normally
Which overlay?
i dont remember, the one that not a console but floating plaques near networked objects
Right, I think that's one of the higher ones. Yeah, I need to check there and see what exactly is going on with them.
setkinematic on the objectsync, not the rigidbody. When you have objectsync, it takes over that property so setting it manually will desync it from the internal state of the objectsync
That's probably where I went wrong. I'm setting kinematic on the Rigidbody. I'll give that a try and see if that fixes it.
Nope, even when using ObjectSync's SetKinematic, and even checking the overlay to verify the other objects are properly asleep, it's still too much.
With 64 objects (4 of which are in use, the other 60 are kinematic and asleep as confirmed by the debug console), the overhead still seems high enough that interactions from the instance master behave as though clogged. If other players interact with an object such as throwing it, the instance master sees the changes update quickly as expected. But if the instance master interacts by throwing the object, the remote player sees the updates occur roughly once every half second or so, jumping positions without the simulation.
To confirm this is tied to the number of objects, when I reduce the number of networked objects to 4 in the build and change nothing else (basically just removing the objects that weren't being used and left asleep in the background), the problem goes away. Both the instance master and the other player see objects behaving as you would expect.
Guess it's time to do more digging, maybe experiment with disabling those unused objects instead.
are you running continuous variables at the same level as the object sync? not sure what stage you're at with the swapping to manual part
Presently yes, hadn't fully switched over to the second behavior for the manual sync approach. But my understanding of continuous sync udon was that it wont sync any data unless changed, and the values are 2 bytes set once and then mostly left alone until the item is recycled. Is that not the case for continuous sync udon?
if you're running continuous vars on a script at the same level as object sync, the sleep doesn't really work the same way
i'll have to test it real quick, but from what i remember it will continue to advertise the object, but remote players will not deserialize them
That would likely track with the behavior I'm seeing then. I was under the impression they wouldn't advertise in general if nothing was changing for either the ObjectSync or the Udon behavior, but it seems they may just be doing so at a reduced rate even while asleep.
continuous itself just broadcasts continuously, object sync does some specific thing that's sort of continuous unless sleeping, but mixing them hasn't worked well in my experience
Does changing the owner of a parent object like a pickup apply to a child object's udon behavior as well, or will I need to trigger an ownership change on the behavior manually?
you'll need to do it manually, you could listen for the ownership transfer event on the parent object to handle it more implicitly though if that's desirable
Did vrc change the way ObjectSync Respawn works? Testing using ClientSim and the console says its teleporting but the object itself doesn't respawn.
I think my problem is I want it to override respawn when a player is still holding it.
Reporting back that splitting the synchronized udon behavior to it's own object for manual sync has solved the clogging issues 🙂
Any chance there's an annotation or something that can be added to a method so that I can avoid adding the following code repeatedly to the beginning of some methods on synced objects?
if (!Networking.IsOwner(gameObject))
{
return;
}
Actually perhaps I need to be sending events to just object owners more often
I wonder if Udon allows for custom annotations, might be helpful to represent the scope of an event listener. Naturally just calling the events on the owned objects would be the same thing but it's good practice to have the method itself indicate it's scope so other devs don't have to wonder about side effects, etc.
annotations? just name your things well and its not an issue. annotations are not for that either. its for a very different thing.
Yeah looks like I meant "attributes", I was thinking annotations in a java sense.
As for naming, are you saying that you name your events based on whether they are called for all clients or just the owner of the component?
i name events based on what they do. or what they will handle generally. all through we dont really have events in Udon. per say either soo
Attributes are a way to add additional functionality to events, fields, or classes. It sounds like you're looking for XML comments. You can start an XML comment by typing out /// before an event and if your IDE is set up correctly, it should automatically add the <summary> section and also sections for each of the variables for the events. Inside of those sections you can put your comments, and then when you hover over that event somewhere else it should show you a pop-up with the information you filled in
Actually I had intended to use the annotation to insert a ownership check and throw a warning/error if the event was called in an incorrect scope (like if you call an event scoped for only the owner from a different client). Looking into it however, there aren't any easy ways to do this before compilation in unity and Roslyn seems a bit complex for this.
you're probably thinking of something like a decorator in python, there's no such thing in C# afaik
Not really possible. Considering ownership is only ever a thing in runtime. And you always know who the owner is. Even when you change its obvious as to who the owner will be. And honestly if you have things that are ownership only running you just need to use the api to check if someone is the owner of an object
if someone is the owner of an object or needs to send data to players that currently have a player blocked will it not send out the data?
no, the data will still send
okay cool slay
can I change ownership of objects with vrc object sync? for some reason when I do this on my object with AI agent it teleports close to the destination
I do transform.position = Agent.nextPosition on Update as well, but it only runs on the owner, maybe something goes wrong during the ownership change
all players have the destination set ofc and it's synced with a variable
okay disabling agent altogether for everyone except the owner works
on a side note, VRCObjectSync doesn't sync rotation when rigidbody is present for some reason
it should, are you sure it's not nav agent related?
could be, not sure at all
but the rotation is alright on the owner
as soon as I deleted rigidbody others started displaying proper rotation too
that was back where I did updatePosition to off on both and updateRotation to off only on others
rn I am just doing agent.enable = false on others
Wow thank you, this is absolutely brilliant and exactly what I was picturing. Thanks for putting in all that effort, I really appreciate it!
Hey Does anyone know how to set up world generation using an object array, i'm making a backrooms world and in need of a way of randomly generating the world
What is the structure and purpose of the object array?
i was trying to use the object array ro make it seem more random and the ytying yo make it a square area
I see
You should post some reference code
Also, is this networking related?
when a value is continuously synched, is there a specific amount it has to change for it to be networked?
I have a world with a variable that ranges 0-6.28 over 16 seconds, and while I see it as smooth, everyone sees it as changing just twice a second?
Does anyone know what a good inbound data rate limit to shoot for is? I know outbound per player is throttled at around 11kb/s, but I'm working on a world that will have entities that generate between 64kb/s and 256kb/s of inbound traffic (between all players). I tried going to a youtube/movie world to get a sense of how much data they generate, but they surprisingly (according to debug menu #4) only generate like ~50kb/s. Not sure how they're doing that, but if it's some kind of side channel that they're streaming video through (and not saved files), then maybe my upper network activity of 256kb/s is nothing to worry about
Video players don't go through Vrchat networking so they don't count towards that number. Also, vrchat networking is primarily measured and limited by outbound. I'm not aware of any situations where inbound is limited in a similar manner
Ok! Thank you very much. I'll just see what happens when I push towards 256kb/s. Will share results.
Spread across how many players?
Aiming for 32 players.
So each player's outbound would be 8?
Each with a capacity of ~24 object synced entities. I've observed each entity generating like 0.25kb/s, so 6kb/s per player if saturated.
but you're probably more accurate with it being higher. There's much more going on than just those entities
8 KB/s is heavy but doable. It's more likely to reach that speed with a small number of large serializations. If it's a ton of small serializations don't expect 8KB/s, you'll likely have to optimize your data
Noticed a issue where if 2 players "fight" over a objects ownership it completely breaks so that both players locally have ownership and don't sync with each other until a 3rd person requests and takes.
Also noticed this behaviour extends to the default object sync and pickups.
At first my solution was a simple "on drop" disable pickupable for 1s but alas the issue still sometimes persisted.
My dirty solution is
public override void OnPickup()
{
NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)this, NetworkEventTarget.All, nameof(ForceOwnership),
Networking.LocalPlayer.playerId);
}```
Mixed with
```csharp
[NetworkCallable] public void ForceOwnership(int id)
{
VRCPlayerApi forcedplayer = VRCPlayerApi.GetPlayerById(id);
if (forcedplayer != null)
{
Networking.SetOwner(forcedplayer, gameObject);
}
}
I also kept the delay of 1s.
This feels really dirty but it works even without the delay. Anyone else ran into better solutions?
Heck even works with autoclickers with the delay disabled but uno the events pileup hence keeping the delay.
Used case is I have interactable pullable levers that sync via a single byte 0-200 and single "isheld" bool instead of floats with a custom sync frequency while held to reduce network traffic.
I know ownership is auto transferred on pickup but was still breaking on occasion when 2 people "fought" over the lever.
I Dont like dirty.
@verbal pebble
I haven't really tested it yet, but I have a similar scenario where I have to resolve competing ownership claims. My implementation is to use OnOwnershipRequest() (https://creators.vrchat.com/worlds/udon/networking/ownership/) to reject whoever attempted to set ownership second. Original object owner would have a local bool/flag (isBeingClaimed). First non-owning player to call SetOwner will result in OnOwnershipRequest() being called, evaluated for if (!isBeingClaimed) return true and then approved by the original owner. Both Original owner and player making claim flip isBeingClaimed to True, so when the slower player attempts the claim it gets rejected by the faster player who claimed it first (or rejected by original owner if transfer not completed yet). Eventually flip isBeingClaimed back to false on both original and new owner.
Might try it. Need to look if there is a canny for this as its a issue with the default object sync aswell.
As more people join the world, the outbound usage is going to be higher than you expect. It appears that whenever you send out sync data via manual sync or events, everyone else in the world has to send back confirmation data to the server that they correctly received this data.
I also think that handling avatar sync data of other avatars will also use up your outbound limit.
I think you're right. I'll likely have to either dial down per-player entity capacity (24 -> 16) or try to do custom object sync if basic avatar overhead is that bad at higher world population. Or lower max world pop. Yeah. obviously something would have to give.
if you're doing positional sync, VRC Object Sync does not have this issue; right now it is the most efficient way to sync arbitrary movement
Oh, duh. Sorry. I just re-read your first sentence. Yeah, thankfully the bulk of the data is positional via VRC Object Sync. Fish swimming in water.
If you're concerned about network usage there might be ways to make the movement deterministic, which would allow for much less network data needing to be synced.
That's a good idea, but I think CPU is the bottleneck in my case. I'm trying to do boid-nav as cheaply as I can, but I'm still worried about the CPU cost, so I spread the ownership amongst players to distribute CPU load.
Sadly the resulted in the same issue tho took a lil more spamming than normal with both players having isBeingClaimed stuck on so when a 3rd player stepped in to claim they were permanently locked out of ownership. Might be worth testing your direct implementation on your end.
Guess for now i'll stick with dirty.
You can try doing some benchmarking to see if you need to optimize in that way.
Thanks. Will do, eventually. It's just hard to replicate in my case. Two players trying to catch the same fish in an aggressive manner that's a little random.
I used the power of a friend and me both with a autoclicker.. works well for breaking stuff.
Also I design out the possibility of competing ownership whenever possible. My systems tend to have the world master be the sole arbiter of ownership with players needing to send a request to the owner to either get something done or take ownership of something.
Kinda hard to do with a "Physical sliding" system even more heavy when it'll come to doors and I want players to be able to "steal" the door off other players. Could just not sync I guess but thats icky.
Seems theres also a number of cannys for this issue already aswell. Dirty works for now I guess until theres a fix on vrc's end. Makes me feel better that even the default object sync has the same issue.
https://feedback.vrchat.com/udon/p/network-ownership-desyncs
have you tried just returning true without using a bool to lock out the request event?
Odly already that in your canny and reason I dident try was because of your wording "some success mitigating" while my jank fully stops it from what I can tell.
Or does your solution fully stop it?
Also if you're using OnOwnershipRequest at all, that changes the transfer method to something that is more susceptible to this bug
Just using the auto request from pickup
in my experience it changed the flow of ownership entirely to completely await the request before serializing
i haven't experienced this, though i get the impression that this could happen if the original owner was unresponsive?
It really shouldn't be common enough to need a workaround on something that is user-input driven, that's disappointing :/
i found it very common in testing between two of my own clients, it seemed latency bound
Was common enough when I let some "must touch everything" people in my test world that I had to find a fix.
I know three players spamming transfer can definitely trigger it reliably, but between two players and especially without spamming is weird
Yha 3rd person tends to fix the break. But just 2 and its over.
in my case it was just a transfer and serialize upon receiving data, which still shouldn't really happen
In my case even debug overlay was locally displaying the wrong owners. User A would show user A as the owner and user B would show user B as the owner. User C would be syncing off one of the 2 randomly but those 2 users were out of sync.
All fixed when user C took ownership but would still rather avoid dync situations.
can you describe in what case the request event can cause the bug? as i'm not familiar
you might be able to turn off the automatic ownership transfer by returning early in request (locally) after the pickup, but use a network event to explicitly request ownership that the original owner has to hand off, it'd be a pain though and i'm not sure if it'd work well
i've also used proxy objects with no networking to act in that way (a pickup with a more strict ownership flow), but it's also a pain
it's a shame that the automatic ownership transfer of pickups can't be turned off in a direct way
when i said "seems to mitigate" it completely stopped the issue in my case, so i think it'd be worth trying
notably it does keep serialize requests in limbo and i only tested it with manual, the implication of the serialize flag staying on is that when you receive ownership by other means it will try to push your latest state even if you're not actively interacting with it (potentially minutes or hours later)
though that might not be the case with continuous (i.e. object sync)
Might try it as its was less icky than what I did.. What I did works even with auto clicker spam.. But just feels like the wrong way to handle it.
Or to be honest it shouldn't even be a problem in the first place but meh? I cant fix that.
As for continuous thats not a issue for my usecase.
i would say that your original solution probably spams transfers as it targets all players, any player can set the ownership in that way so it does look a bit "dirty" to me, but if it works
i think targeting the owner explicitly would be a little more clean in a way, but it might not result in the same behaviour
the fix in that case might even arise from some 3rd player handling the set owner
Tried just owner at first and it didn't work. The other bugged user stayed bugged.
ah okay
might be able to limit it to the requester and the owner will toy tomorrow when I have other people to spam click it
Conceptually, my theory of it is:
- Request transfer makes the transfer take longer.
- Ownership desync is due to a race condition somewhere.
- By making it take longer, it expands the time period where the issue could happen
I'm not 100% sure though
so it's something you've experienced when overriding request? in my tests at the time it seemed to eliminate the desynced ownership state entirely
people at high enough latency couldn't reproduce my case at all, while it was very consistent for me in my own region and others with low ping
without being able to see the networking code, i can only posit that the underlying issue for the desync is related to serialize arriving before the ownership update
You likely have more recent and relevant tests on that than me, then. I haven't been able to do first-hand networking investigation stuff for most of this year
i think the desync you mentioned is here, i don't think i've really tested the event prior to that time period
in general it's unfortunate that there isn't an explicit way to transfer ownership more authoritatively if desired (like a more explicit ownership gained via request event) as the current behaviour expects ownership to succeed if true locally, the only indication that it failed being that it eventually fires back ownership transfer to the original owner
though with network events it's a bit more possible to explicitly control the flow (barring things directly on a pickup)
a bubble gun with ammo is a good example of the pitfalls of ownership only logic
after picking up a bubble gun, the player begins firing and detracting from the ammo count, despite not actually having ownership (perhaps they were denied an ownership request)
their bubbles send network events if they hit other players, but there's not a good way to tell if you actually have ownership without using pre serialize in a way it's not meant for
other players could probably discard events if the player isn't the owner in their eyes, but this isn't an especially sound or good way to handle it imo
it's very hard to branch into a correct situation there without arbitrary delays (probably the most correct/simple way to handle this exact case would be to delay firing allowance for some timeframe, which is a shame)
I've got a weird one here I've seen popping up occasionally - how can this possibly happen, this code: Networking.SetOwner(player, gameObject); (specifically the gameObject) throws Object reference not set to an instance of an object. Now given that gameObject means this gameobject, how could this happen?
you're positive it's the gameObject and not the player?
Yeah, IDE highlight from the error in console jumps specifically to the gameObject.
The player would make sense if they'd just left and it was triggered when exiting the world, but that's apparenty not what's throwing it.
i can't say that i've seen that happen in relation to the gameobject, i would still suspect the player reference first personally
I'll disregard the pointer and just harden the player checks and see if that does infact negate it then.
public override void OnMasterTransferred(VRCPlayerApi newMaster)
{
masterPlayer = newMaster;
}
this would guard against that pretty heavily i think if it's actually always the master that you want
or just using
Networking.Master
some would still suggest safety checking that with a utilities.isvalid though
Oh interesting, wasn't aware that existed.
i will also say that my own reference exception points to this spot (denoted by a pipe symbol after collider2)
Physics.IgnoreCollision(collider1, collider2|);
where collider1 is null and collider2 is a real reference
so i'm not sure it's pointing to the exact argument that failed, i think it's just the end of the line
though i wasn't able to trigger an exception while setting owner with an invalid player or a null player, so i'm not sure exactly what's going on in your situation
Hmmm, could be the case. I'll keep an eye open in future for other failures and see what they do.
Waiting for owner to confirm transfer is how it initially worked..... Every time you wanted to "set owner, set variables, request serialization" you had to build in a delay to wait until you actually have ownership. It was probably the third worst thing about udon networking 1.0, behind everything being continuous and ownership being almost entirely unreliable for late joiners
It's frustrating to see things now that are still obvious, painful problems but yeesh, it has come a long way...
that's why i said "if desired," it's not reasonable to accomplish in a normal or standard way if it's preferable for some use case
the ownership request function to someone reading through the functions may expect it allows a more authoritative stance on ownership, but it ultimately doesn't allow the type of control over it that would be necessary i feel
a callback akin to "on ownership request response" would be much more in line
I think at that point, network events serve the purpose better anyway
the issue is that pickups circumvent the ownership route if you plan to handle it through network events
Ah
it's possible, but it's not especially straightforward as you have to proxy the pickup
like i think the assumption of ownership is preferable in a lot of casual cases
linking people the ownership flow, when i tell them about the ownership request function
i eventually have to explain the fact or misunderstanding that they can't actually know that they're the owner in that flow, it's unexpected in a network context i think
so branching logic on that is odd (which is what i tried to highlight in the bubble gun example where someone was effectively dealing damage to people without owning the bubble gun; but prior to receiving notification that they lost ownership)
i think the most intuitive thing to most people, or at least me, is that ownership transfer would not fire until ownership was actually transferred if overriding ownership request
Just to make sure im not goofing it.
Ive got a hashset of a custom class with a bunch of data in it related to a player (ID, name, ect...)
And i want to network a bool thats inside this class (but dont need to network everything within that entry, only the bool)
How should I do this?
Im thinking just a separate udonsynced int[]
But itll be annoying to make sure each entry in the hashset is at the same position as the udonsynced int[]
otherwise im not sure how im supposed to cross reference these two arrays
In code format if its easier to understand:
internal class PlayerEntry
{
internal GameObject entry;
internal UIButtonSender button;
internal VRCPlayerApi playerAPI;
internal int ID;
internal string displayname;
internal bool isToggled;
}
public class PlayerList : UdonSharpBehaviour
{
HashSet<PlayerEntry> playerList;
void Toggle(int i)
{
playerList[i].isToggled = !playerList[i].isToggled //This needs to be synced for everyone
}
}
hmmm actually, maybe i could send a customnetworkedevent with the ID of the entry to identify it
But then syncing it to late joiners could be a hassle
might be hard to do without a ton of networked events directed at a new joiners so that they recieve the info
how many of these entries do you anticipate?
as many players in the instance, so from 1 to 80 really
if i'm understanding it correctly, i think the easiest thing you could do is an int[] and a bool[] array, where you work on them in tandem
[UdonSynced] private int[] elementsLookup;
[UdonSynced] private bool[] elementsState;
private void FillSyncStuff()
{
elementsLookup = new int[playerList.Count];
elementsState = new bool[playerList.Count];
// Iterate here
int i = 0;
foreach (kvp something something)
{
int key = kvp.Key;
elementsLookup[i] = key;
elementsState[i] = playerList[key].isToggled;
i++;
}
RequestSerialization();
}
i don't know the syntax you need for iterating over the hashset to fill it, but i think something like this should work?
making your own byte[] serialize and deserialize would also work well, where each element is preceded by its identifying int
and if your hash set is derived locally (from join/leave events and on start or something?) i think you'd want to safety check that the element was actually there when you deserialize and look into the hashset, or just use tryget, not sure how you're generating entries though
hmmm that makes sense, could optimize this a bit by not using a bool array and just have toggled ints inside the synced array
can you udonsync byte[] ?
i thought it was only int[]
you can udonsync pretty much all of the primitive array types
oh awesome
what do you mean by storing the toggled int inside the synced array?
correct
im instanciating them
like if the hashset contains ID 1 2 and 3, and 2 gets toggled, then add 2 to the udonsynced int[]
even if we dont find better, this feels robust either way, thank you occala :)
so you'd only send the identifiers of those actively toggled; toggling them off if found locally, but not found in the synced array?
yepp
that could work
better than two synced arrays right?
better is subjective, i think the two array way is the most simple and readable to start, but you can save on data if you pack it differently, i'd probably use a byte array personally
your idea for only sending active ids would work though if you need to save on data
i suppose i got lucky having IDs here.
If i wanted to sync more properties, like lets say a string, per entry, that would be harder with a hashset and i might have to resort to a more ordered data type
ye true, i always have the optimization mindset first but maybe it doesnt matter too much
having more properties you'd want to move to a byte array i think
it being ordered doesn't necessarily matter
hmm oh yeah, because the lookup of which value is being modified is just based off of where it is in the original byte array
no, you'd preface each entry in the byte array with the identifier
more like a header
followed by the string length then string value, which means the read length for that element is known and you can parse as many entries as the length of the byte array
it's a bit more advanced and you should look into packing in relation to serialization/deserialization if that's actually something you need to do
hmm how so? can you show me a brief mockup?
// Writer side
int writeIndex = 0;
byte[] packedData = new byte[playerList.Count * 4]; // Each identifier is 4 bytes
foreach kvp -
int identifier = kvp.key;
byte[] identifierBytes = System.BitConverter.GetBytes(identifier);
System.Buffer.BlockCopy(identifierBytes, 0, packedData, writeIndex, 4);
writeIndex += 4;
// Reader side, being passed the packed data (we'll call the bytes packedData again)
int readIndex = 0;
while (readIndex < packedData.Length)
int identifier = System.BitConverter.ToInt32(packedData, readIndex);
readIndex += 4;
this does nothing other than pack each identifier sequentially, but it's an example of how you might write the header, on both sides you'd sequentially read or write more data within the same entry if you had properties you needed to pack
strings are slightly more involved, but there's an example of how to pack and unpack a string on one of the vrc data docs, this is probably good to look at in general if you're interested in manually packing things
https://creators.vrchat.com/worlds/udon/data-containers/byte-and-bit-operations/
hmmm i see, yeah im pretty unfamilliar with byte shifting and packing, i touched it once like last year
so basically youre turning each int into a more compact and performant version via bytes?
thought just the simple act of having a byte array would already be a performance gain
it's more like a rigid structure, you can think of it like a data entry
- int
- int
- int
if you were to pack a string after each entry, you'd know about a string related to each identifier when you pack or unpack
- int
- string
- int
- string
- int
- string
mhmm sensical
and is that done all within the byte array? im havign trouble seeing it in the mockup
Ngl im having trouble understanding some of it either way
Whats kvp? Whats kvp.key?
i was operating on it like a dictionary, you can just imagine it's iterating through a list or an array if that's easier
they also do a pre pass kinda thing in the doc example, where they don't know the length in advance because strings are variable, so they get the bytes first and calculate the size before assembling their final byte array they utilize
it might not make as much sense because i only included one data point per entry
hmmm yeah i dont really understand this, i think ill review it after i took a look at the docs you sent
i might be bad at explaining it, it is a little different if you're not familiar maybe; basically with a consistent write and read format, someone can deserialize the bytes to the resulting properties of the entry
like if you were packing some properties in series, you could use 3 arrays like this, but it becomes kind of difficult to interact with if you add more types
[UdonSynced] private int[] someIntArray;
[UdonSynced] private float[] someFloatArray;
[UdonSynced] private bool[] someBoolArray;
with bytes you can just define how to read and write each entry, in this case you'd read an int, then a float, then a bool and if you're not at the end of the byte array after reading those, you know there's another entry; so you again read another int, float and bool
[UdonSynced] private byte[] someBytes;
yeah i got the principle i think :) and this explanation makes a lot of sense
Its just the syntax im unfamilliar with
but youre essentially packing each data type into a single byte entry
efficient
in udon land it's probably more expensive to both pack and unpack in that manner, but it's more versatile and good to know how to do compared to rigid explicitly typed arrays
For sure, ill do some reading on it when ive got the chance
Prob useful outside of udon
I was wondering, is it possible that nobody has ownership of an object? assuming both sides always agree on receiving the ownership at all times.
in my testing sometimes things my udon behavior is supposed to do on owner just get to a state where they are never done
so I assume the IsOwner() check fails for everyone somehow
there is also a possibility the owner will try to set the ownership to two different players at the same time, that is also not good
no one has to be. owner
so what I should do is make a failsafe on master where if nobody is the owner then master gets the ownership
eh. no cause ownership always transfers it self if the one that had it before dc/leaves etc. you cannot rely or should rely on master ever
I am not relying on master
I just want to prevent a situation where no one owns the object
since that breaks my game world very heavily
and a new instance has to be made
however I am not 100% sure it's the issue of not having ownership, exploring the causes rn
Pretty sure there will always be an owner
that's what I believe too
There cant be a situation where no one owns it. Its simply not possible
that's good to know
any idea what's happening when I try to test the world? this started happening after I regenned my network IDs but they look alright. I have the world uploaded to VRC as well, I think it gets confused somehow
there's a period of time during ownership handoff where someone will be unaware of their ownership status, especially after a leaver or if the ownership is passed by a player other than themselves, so it would be possible for no one in the instance to process something you do in udon logic on the owner's end (in their local view they would not yet be aware of their ownership), despite there always being an underlying owner in the server's eyes
whether that's what you were experiencing previously i can't say, that case should be pretty rare (though it should happen if you were doing owner-only logic in update for example)
(this is unrelated to the issue you posted about just now, it's more in response to ownership)
though it should happen if you were doing owner-only logic in update for example
yeah I am doing that exactly
AI state machine
it chases the player, each time a new player gets chased the ownership is passed to that player for them to do all the logic
it worked fine when all of it was done without changing ownership from master but due to severe delay between the master and other players it wasn't acceptable
as I also do raycasts when the player is near and decide whether the player should be damaged
if the current owner is passing ownership there would be some timeframe where both the original owner would believe they are no longer the owner and the new owner would not be aware until they received the ownership update
whether that breaks your logic i can't say (ideally it wouldn't break your logic though)
it's alright if it lags a bit, sadly even after the ownership is transferred everything gets to a broken state
so there's an issue somewhere in my logic
sucks that it's hard to reproduce
I am adding a lot of debug logs to get a better picture
okay I figured it out, never put udon behaviors as childs of other udon behaviors
also one thing I figured out recently is that if a gameObject is disabled the udon behavior will stop serializing
I was doing a simple synced on and off script and was putting it on the same object that was being toggled, that's a big no-no
it works with events but not variables
@warm steeple you should never disable networked objects in udon. As it will cause unwanted effects and potentially. Breaking everything
yeah
figured as much by now
sadly I have a thing I can interact with that also needs to be disabled
and there are 71 of them
so for now I just decided to move it really far away instead
a workaround but good enough for me lol
By not aware, do you mean they are functionally the owner but their client cant see the assignment yet? Or is there truly just no valid owner for x amount of frames and it ceases to function until it has one
the former yes, in a local sense they are not yet aware of their ownership
did some testing and finally nothing is breaking so far, I did notice the AI agent get duper fast for some time on rare occasions, I think that was VRCObjectSync catching up after ownership transfer but I won't bother fixing that, it's funny actually
Just be aware of vrc object sync using about 120 bytes per one.
is okay I am only using one AI
is it just me or is there no official documentation on how to use the manual sync mode?
this describes the sort of event-driven flow you'd use for the enacting player (OpenDoor) and remote players or late joins (OnDeserialization)
https://creators.vrchat.com/worlds/udon/networking/late-joiners/#1-use-synced-variables-instead-of-events-for-state
i'd note that the doc here doesn't showcase how the setting player should derive the door state from the variable change though (as they do not fire OnDeserialization); nor does it show setting ownership
UdonBehaviours on GameObject 'Glow Stick' are using manual sync
while VRCObjectSync is on the GameObject, this can cause sync to work unexpectedly.
grrr 😡
That's to be expected, you can't mix sync types on the same object
what ended up being the issue here? i am having an identical error message but the command works yet it still crashes
I'm having issues networking NPC locomotion animations, for the host, NPC's walk around and do their walking animation in sync with their current velocity that their nav mesh agent is, but for joined players they just stay in their standing idle animation while moving, I tried to fix it using different methods of calculating speed such as measuring distance between ticks but it's being stubborn and just not utilising the animator walk cycle floats for joined players
Are your nav agents setting a destination locally, or are you using continous sync and vrc object sync?
cont and object sync yes
I even totally redesigned it and made it so that it takes a vector 3 snapshot of its current and previous positions in the world to ditermine speed and even that didnt work networked, not to mention it didnt look very good locally for the host so i reverted back to trying to get velocity from nav mesh agent
i think your issue is, nav agents arent doing anything on the client, therefor your velocity magnitude is always 0, it might look like they are moving but its object sync interpolating their position
personally i use manual sync and serialize the destination (i.e. player id or vector3), then set destination on deserialization, which will also save you a lot of bandwidth
interesting, I'll see what I can do
my own system, I really wanted nav agents to be able to have inter-avoidance which created a chaotic enough system that only synching the destination would produce too much desync
so instead I had the walk animation triggered by the synched npc state
with that said, on the remote player's end, if the npc has a rigibody, you might be able to pull velocity from that
i don't think kinematic rigidbodies or those moving under object sync will provide useful velocity, but calculating it yourself remotely is easy enough
ye they def wont cause a rigidbody is only enabled on the owner afaik🤷♂️
oh wait, a vrc object sync won't impart physics on the rigibody so you're likely right
at least I'm going to presume it will not
im struggling to understand how TeleportPlayersToGame() is not being called twice
when i have two players in the scene, the remote player [1] and the local player [2], i expect it to be called twice, but apparently not..
am i doing something wrong?
private void Update()
{
// ... some code ...
if (GetGameState() == GameState.Ongoing && !gameStarted)
{
// lock in decision for whoever is playing
playerManager.SetPlayersPlayingIDs(lobbyManager.GetPlayersInsideIDs());
// TODO: Set player teams and teleport
playerManager.AssignPlayerTeams();
SendCustomNetworkEvent(NetworkEventTarget.All, "TeleportPlayersToGame"); // only calls it once, yet there's two players. what????
gameStarted = true;
}
}
// ... some code ...
public void TeleportPlayersToGame()
{
Debug.Log("Test"); // only gets printed once for some reason i cannot understand
VRCPlayerApi localPlayer = Networking.LocalPlayer;
if (!playerManager.IsPlayerPlaying(localPlayer))
{
Debug.Log($"Player {localPlayer.displayName} isn't playing the game and will be left behind...");
return;
}
PlayerTeam team = playerManager.GetTeam(localPlayer);
Debug.Log($"Player {localPlayer.displayName} is on team {team}");
// ... teleportation code ...
}
only the local player's being teleported, should be teleporting the remote player as well
it gets called once, for each player
each player will locally run that function once, so you'll only see the output log once if you're only viewing the logs from one client
so how can i log it from other clients
you'd have to send another event to everyone saying that they've ran it
though, is doing that necessary?
you could have a custom network event with 1 parameter that takes a string, that then outputs that string to the console
if thats the case, then
public void TeleportPlayersToGame()
{
VRCPlayerApi localPlayer = Networking.LocalPlayer;
PlayerTeam team = PlayerTeam.Wanderer; // changed to Wanderer as a test
Debug.Log($"Player {localPlayer.displayName} is on team {team}");
switch (team)
{
case PlayerTeam.Wanderer:
{
localPlayer.TeleportTo(wandererSpawn.position, wandererSpawn.rotation);
break;
}
case PlayerTeam.Entity:
{
localPlayer.TeleportTo(entitySpawn.position, entitySpawn.rotation);
break;
}
default:
{
Debug.Log($"Player {localPlayer.displayName} is on team {team}, which isn't valid for this game.");
break;
}
}
}
how come doing just this, and calling it via SendCustomNetworkEvent(NetworkEventTarget.All, "TeleportPlayersToGame"); still only teleports the local player?
im so confusedd
the local player as in, the player sending the event?
you can only teleport the local player anyway
the local player as in "non-remote" players
if you know what i mean
you did say it should run on each client locally, so the remote player should be teleported as well because of localPlayer.TeleportTo(...)
that's right
...so why is that not happening?
as the event should send to every player, who would then teleport themselves
what sync type is your Udon Behaviour set to?
it should still send if the type is manual, as long as it isn't None
so you aren't seeing any logs on remote clients when the sender calls the event to teleport?
oh. lol. it's not going to work in ClientSim
remote players in ClientSim aren't real players, they won't execute network events that have been sent to them
oh?
you'll need to test with 2 real clients
so i have to actually open up vrchat
you can build & test with 2 clients
I did with 6 since I was having really bad networking issues past 5 players lmao
it's cuz of default event send rate of 5
ended up using synced vars, made much more sense for what I was doing anyway
If you're doing a lot of network testing, can you get temp blacklisted by VRC? It's happened before and now it's happened again, where after doing a lot of testing I've suddenly been hit with "cannot authenticate with the realtime server" errors.
yes that happened to me once
I was temp banned
and I bet it's a measure out of vrc control since they use photon service
If there's a way that people collect analytics, what's the common approach?
For example, if I have 3 different options for starting a gamemode and I want to see which option people choose the most globally would that be feasible and possible?
I figure it might be an untrusted URLs thing which isn't optimal, but it works
yes afaik untrusted url is the only way to send some collectible data outside
I'd make it opt in even if we were allowed to use untrusted urls at all times, better for privacy
you could make it so that if the player agrees to the collection but doesn't have untrusted urls enabled you catch the error and don't let people in unless they opt out or enable untrusted urls
its anonymous by design, people can not enable untrusted urls if they want. trying to link any data to an actual user is punishable, no harm in a simple counter that doesnt even reflect total amount but rather ratio
Does anyone have a good example for syncing an integer w u#? I’ve been struggling very hard on this.
This is with manual sync.
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class IntSetting : UdonSharpBehaviour
{
[SerializeField, UdonSynced] private int valueSync;
public void SetValue(int newValue)
{
Networking.SetOwner(Networking.LocalPlayer, gameObject);
valueSync = newValue;
RequestSerialization();
ApplyValue();
}
public override void OnDeserialization()
{
ApplyValue();
}
private void ApplyValue()
{
// React to the value changing here!
// Like if the number were a score, you'd update the scoreboard here.
}
}
Would I need recursive callable if I recursively call a function that decrements the int by one every time it is called?
Yes? Although, why are you recursively updating a synced variable?
I was doing that, and then I realized that was a bad idea, so now I update the variable send an event to everyone to update a non-synced variable to be the same as the synced variable. Then decrement that. The problem I’m having is that everything gets de synced after a bit.
Would labeling my recursive functions as recursive fix it?
I don't think I'd use a recursive function at all. But I also don't know what problem you're solving or what the number is
I’m trying to generate a random int, synchronize it between everyone set it equal to another non-synced int then I call a function every second and use the int to control it as a timer. If you have any better ideas to suggest, please do Im not super experienced.
Also, should I be setting owner every time I sync something. In the past, I’ve just checked if they’ve been the owner of a specific object. And since I don’t change object ownership only one player ever runs the code.
You don't need to set owner every time, no. They just need to be owner to change a synced variable. But if one player always owns it, then no need.
If someone goes afk or their tab goes to sleep will networking get screwed up?
thats not a thing on pc. on quest it is
they once again rewrote the docs so theres nothing on the subject now
if your code has any risk of breaking due to a suspended player (which is kinda rare), you can use OnPlayerSuspendChanged to react to the player becoming suspended and make any necessary changes
https://creators.vrchat.com/worlds/udon/graph/event-nodes/#onplayersuspendchanged
This is a list of Udon Nodes that are considered "Events".
I may just run import stuff on the owner instead of letting the clients run it. I think it’ll be cleaner.
the what
I have never heard this occurring on Quest either; has anyone with a Quest actually tested this?
I strongly suspect, but haven't tested, that this is a thing oh the mobile version of VRC, however. Of note in the docs: "The master player may also change if they're on Android and kept VRChat in the background for too long." - https://creators.vrchat.com/worlds/udon/networking/ownership/#the-instance-master
Introduction
I think I'm missing what you mean by synching a variable then setting it to an unsynced one. Do you mean setting an unsynced one based on the value of the synced one? What's the reason for not synching the other value if you need it to be in sync with all players?
Yeah I’m setting a synced variable then setting an unsynced variable equal to the synced variable. After that I decrement the unsynced variable. It is a very scuffed system now that I really think about it. I think I’m going to have the owner run more stuff in future instead of having the client do things the owner can. (I’m new to all this)
You can still mesh client and server logic, but if you need certain points to be "in sync" with everyone, then yeah, having those points be coordinated by the master is the way to go. I think you mentioned this value was associated with a timer? How are you ticking down that timer?
Send custom event delayed seconds.
I call it recursively but I haven’t tagged it as recursive. Could that be a problem?
if you're using delayed seconds, then it isn't recursive and doesn't require the attribute
Personally I'd use a fixed update loop that decrements the timer value for something that's recurring like that. For more accuracy you could also pass the local time the owner sent it at, compare it to local time when received, and do a little math to get it lined up with the master. Does assume that both machine's clocks are relatively up to date, though there's other ways to account for even that. All depends on the precision you need.
I just realized I have been running client stuff when I set it up as an object owner thing. Man I’m bad at explaining this.
I think I can just have the owner run the timer and send a network event when needed.
Keep in mind that network events don't late sync state, and if an event gets missed it just gets dropped.
I don't know all the details of what you're making, but my recent Cube world does something similar. I sync a random seed value and the start time in milliseconds once at the start of a game. From those values players run their own generation and timer logic locally that determines the structure of the cube, what round it is, when rounds change over, etc. one-off events are triggered by network requests, such as requesting networked item spawns from the master, opening hatches, etc. But those states all resolve functionally well with late joiners, and don't break the core game structure or loop if they're missed.
Maybe one day I’ll get good enough to make something like this. As for syncing the network events everything should be fine late joiners would just have to wait for the current round to end
No, "Android" refers to both Quest stand-alone and the literal mobile phone version.
If a Quest user takes off their headset but keeps it powered on then the VRChat application will be suspended. The player is still in the instance and may still be the network owner of objects or be the current instance master.
However, they'll be unresponsive. This can break systems such as Cyan's Player Object Pool that rely on the instance master to behave as a server.
Please don't use network events to synchronize game state.*
-# * Unless you know what you're doing. (I'm looking at you Teal)
😞 I was really hoping everyone reading my statement would read it carefully enough to realize what I wrote.
My statement on the quest and my statement on the mobile version are separate statements that have different logic to justify them.
So I'll ask again, has anyone with a Quest actually tested this?
Do you have any good tutorials or guides, most of networking is voodoo black wizardry to me
VRChat's Udon networking paradigm is inherently very "peer to peer like". For situations that are well suited to that structure you'll have a much easier time if you lean into that.
If you can define your entire game state as UdonSynced variables then that would be optimal as you won't need separate logic to handle late joiners.
That's of course only if your game state isn't too big™
Hmmm coding communism, well with enough trial and error. I’ll figure everything out eventually.