#udon-networking
1 messages Ā· Page 23 of 1
Yup. Thanks guys this helped a lot!
with synced bools I like to set them up callback variables
so as soon as it syncs to a new value, it does whatever you meant to do with it, automatically
rather than juggling OnDeserialization
which actually you'll have to use Continous won't you? since you'll be using ObjectSync
No idea with that kind of stuff, but I assume so since I want it to be moving around a lot?
continuous vs manual still stresses me out a bit lol
mostly the wording on continuous saying that things will be "tweened"
yeah that just means you don't really care about in-between values, so the literal values that get synced may not be the exact same numbers on the other side
which in a lot of use cases is perfectly fine
and best part about bools. What are ya gonna tween? they're either on or off
think of ObjectSync. You throw a pickup, you don't really care if the arc the player threw it at is precisely replicated, just that the end result is the same
so the pickup might fly in a slightly different way on other people's screens, but the spot it lands will be the same
I did have this to track lights stuff as players can upgrade the flashlight as well, but it's specific numbers so I assume that the tweening wont effect that much? Simple as a += .5 or 1
note that the tweening will only happen if the value is constantly changing
p much
if the value is staying the same, continous sync will eventually (pretty quickly) catch up and then be the same
if this was on like a slider that the player can change, it'll be tweened if the player is constantly moving the slider, but as soon as they stop it'll sync up
When I had just the light itself working, there was a delay when the players moved. I assumed that was from network lag, but was it actually the tweening then?
if you were using ObjectSync to make it follow the player, then kinda
that's the delay of it syncing to their position
ObjectSync following a player will always be a bit behind, unless it's in their hand
hence why it's usually recommended to have it follow the remote player positions instead
instead of objectsync? Like having synced transforms in the script and using GetOwner?
you wouldn't sync the transform at all, you'd have the local player find where it's supposed to be
you have the flashlight that you know belongs to player X; if you want it to follow them exactly, you just tell the local player "find the position (or head bone, etc.) of player X, and set the flashlight's position to it"
having the local player set it will prevent the ObjectSync delay effect
though if the flashlight mesh is invisible, you might not really notice it being behind
I think I get it. Local player just tracks the position based on the owners location, no networking other than getting the owner but that's w/e, just local setting. All I need to do is just make sure the network knows the specific state of the flashlight, and I don't actually require objectSync all the time
yeah that's what I think Uzer was mentioning earlier: you disable the ObjectSync component if the player is in VR, and set it up so that the local player figures out where it's supposed to be at that point
or if they're in desktop disable it. I forget how you wanted it. w/e
Yup. Thanks both of you, been bashing my head at this for like a week just trying things
Well let's check the VRC docs:
They might not know either, lol
Will have to test it yourself
This is my code to get the time and I display it in a UI, I get when the players join and also their current time. I can only assume it's based on your PC clock settings
DateTime.now does return your local PCs time.
So whatever you set for your PC is what you'd get. Sorry if this isn't at all what you were asking
I'm under the impression that DateTime is typically a universal time that typically auto coverts itself into the time zone of your local machine. So if you pass it from one machine in one time zone to the next it will display a different time that represents the same real time
But I only have passing knowledge on this stuff
Yeah, I was slightly wrong. Is purely just DateTime.Now that returns the current local PC time.
This is the time it's returning. I'm EST so my normal time is at the top
yeah just testing now, it seems it is getting a UTC from the server
Yeah, did a build and test, the top is servertimeinseconds. (Probs not helpful but wanted to try helping)
yeah i purposefully made my local time inaccurate to see if it makes a difference, and it does, meaning it is a supposedly synchronized server time, which is good
(im also testing the timing issues of delay events to make it change number on the dot)
So when I try build and test my world it throws a "network ID error" which I'm pretty sure is from me trying to disable the ObjectSync component
#world-development message do this
Keep in mind if you're using persistence this will nuke all the saved data for everyone
ty!
Wait, it'll break persistence data of uploaded worlds?
Is there any way to prevent that? Because it seems random when it breaks.
You'll need to program your own network id utility and make it like, not dumb.
regenerating IDs is just the "wipe everything and start over" approach, which breaks persistence. But most errors can be solved just by clicking select on whatever the network ID utility is warning you about
like if you add a new udonbehaviour, it wants you to manually accept that change. You don't need to regenerate IDs for the whole thing to do so
"wipe everything and start over" approach, which breaks persistence
Why does this popup not say those words yet?
good to know!
because we haven't gotten around to it 
if I'm not mistaken, worst case you just need to have the network ID for the affected PlayerObject to be the same after doing the regeneration
Then you should get around to it. I refuse to believe that it'll take more than 5 minutes.
so ideally you record its ID before regenerating
but it would be maybe a nice QOL if PlayerObjects' IDs never changed during a regeneration? as this is a really common issue for people to run into
Imo. the net id utility should do this too, but that will take more than 5 minutes for them to fix.
I'll add it. put me in coach
How do you manually assign the ID?
The IDs reside in the VRC Scene Descriptor component on the VRC World GameObject in your scene.
found them; thanks
How exactly does this break persistence when it's just a JSON payload being read on an individual-variable basis? It shouldn't matter who calls what, so long as the call is made?
maybe it is the PlayerObjects that gets nuked, idk
This only applies to PlayerObjects, which save the network state persistently. PlayerData is not affected when regenerating network IDs
Ahh, gotcha, that makes sense.
Does that mean data saved directly to playerData is maintained through regenerating IDs, it's only playerObject persistance that's borked?
precisely
Phew
I'm going to guess the reason PlayerObjects breaks is because the VRC servers no longer know what packets of saved data go to which PlayerObjects objects in the scene
I haven't had my tea yet u.u
understandable, but yeah that's the reason why
And because world creators are likely going to have an expectation that they can freely add, remove, and modify PlayerObjects at will I don't think there is an easy solution to this
I'm under the impression that the real issue is the way that network ids need to be assigned, but I'm also under the impression that the chance to change that has long since sailed.
yeah I read it as this is the path of least resistance
it's super convenient to have persistence basically magically work by just doing what you were doing before, if you already understood how Synced variables work. It's waaay more effort to rework everything that would be necessary to avoid this issue vs. "hey if you have PlayerObjects just be a little careful if you regenerate network IDs"
wait what broke? or is it because someones persistence broke?
A PlayerObject's persisted data gets wiped if its network id changes from one version of the uploaded world to the next
oh yea i mean that has been a thing forever if i recall?
not everybody knows about it
I just remember seeing someone say "use PlayerData for the actual data, use PlayerObject for in-world things that every player should get"
You can do data through the PlayerObject, but I assumed it was like, the less optimal way to do it,
I think it just depends pretty heavily on what you're actually using persistent data for
both have pros and cons and different mechanics on how they work, so sometimes one solves a problem better than the other one does
the last paragraph here is the most relevant part of choosing playerdata or playerobjects
https://creators.vrchat.com/worlds/udon/persistence/player-data#networking
despite playerdata being more logical to interact with as a key/value store, any change to playerdata requires sending all playerdata for your world; this is prohibitive on bandwidth in some cases
playerobjects allow chunking your persisted data into sections, but the main criticism i'd say is that it requires interacting with playerobjects for something that would otherwise not need an object per player and as people were noting above your data can be wiped if the network id changes on the playerobject
That's a fantastic point. I need to move my "last known position" logic to a playerobject, then
I care much less about that getting nuked by regening IDs
it's been suggested before that PlayerData is good for your big data, PlayerObjects are good for your fast data
it seems PlayerData is really just a wrapper for a virtual PlayerObject that is automatically referenced in all Udon scripts, and will virtually request serialization on value change therefore has no timing control like a PlayerObject, and is not network ID specific, and uses its own workflow to get and set data... it just seems there is no special functions that cannot be replicated with PlayerObjects, i mean there is nothing stopping me if I were to store everything in my own PlayerObject, and manual sync request serialization everytime a value is changed... it would behave the same, without all the weird special methods
the word you're looking for is probably singleton and it doesn't act like a playerobject, it's more like a cache of all the keys and values, it has additional ways to check for change events and context that don't exist on playerobjects
im trying to think of a situation where you have to use PlayerData or it wouldn't work, but I cant think of anything
-if you have multiple scripts that all need to reference the PlayerData, and you don't want to go through the hassle of setting the PlayerObject to it
-when you don't need a new object in the world for every single player, just something to track the local player's PlayerData
-if you want to check if the saved data already exists or not, and do logic based on that (not as easy to tell with PlayerObjects)
-you run out of available memory in PlayerObjects
hmm
PlayerData is basically just a PlayerObject pre setup for you with Singleton support
if you want to say you can do similar things i'd agree, but saying a per player instantiated thing is the same as a cache of all key/values isn't really true
you can't dynamically add keys to playerobjects, you can with playerdata
You can if you're using a DataDictionary, right?
if you want to make some custom way to refer to values within a byte array or a string you json out yeah you could make any number of keys, but if you're comparing them directly you can't add a new persistent variable in the same way that you could add a new key programatically
you can sync a DataDictionary of arbitrary size and key amount by serializing to a single persisted variable, can you not?
yes, a string/json in that case unless you make your own way to read and write the dictionary with bytes
you can
I'd assume that PlayerData is doing the same, although they might be using a proper Dictionary instead
The biggest difference is that PlayerData is very permanent, so much so that you cannot delete keys that have been created
If you have the ability to serialize all the main core data of your world into a single variable, it's probably best to safeguard that in PlayerData
PlayerObjects are also permanent I'd assume? you'd just be losing the keys to access them forever?
I mean if they are nuked, the data is still there, right?
I presume the server side data allocation matches your in-world allocation. So anything removed in world gets removed server side as well
Wait, that's kinda horrifying. Messing up your network IDs is relatively easy and that could make all of people's data just disappear?
I'm glad I use PlayerData for everything...
I thought PlayerObject would also have dynamic IDs, regardless of what it is on the template.
Apparently if you go into the the network id manager, it will often tell you the issue so you don't need to regen ids
and if you write down the ids of your PlayerObjects you can reassign them manually
We both know how unity can be.
And I had one particular issue which was annoying. It would bring back some IDs even though the objects were deleted already. I think it was because my VRCWorld component was a prefab, so the values didn't persist properly when the manager tried to clean them up.
So yeah, anything can happen.
Writing IDs down is also not exactly a user friendly thing. Not to mention how slow the UI for the manager gets when you have many objects.
Well, I should definitely export the ids just in case.
Quick question, as i could not find any documentation for it, Who is the instance owner when the instance is a group instance
theres no suchš¤·āāļø so you just ignore it in your logic
group instance owner is the group
Ok, Thank you
Dont know If this ist right in here, but how woud you go about finding per script the profilepictures of Players in the current world for use in a Playerlist Display
I'm not certain, but I think you'd need to use the actual API for that, which you can't do from Udon.
currently we have a c# script using the player API to display Names but are not certin how to get the profilepicture data
I didn't realize you could make those API calls from U#
anyone knows how to network animation sync? It's not just something like putting vrc object sync on an animated object yes?
Because stuff in my worlds is animated and I want it to stay in sync for late joiners. It's easy for static objects just to activate or deactive them. But how can late joiners catch up with all animations in a world?
You'll need to sync it per Animator.
This can actually be a bit complicated, so if you can provide more information about the setup of your animations and animator controllers it will be much easier to help you.
for example here just super simple animator on an empty with some platforms below and a loop that makes them always move, so people can go over those like in a platformer - but if someone joins at second 4 of those 8 seconds animation, the're completely offset while doing the course, what's just...distracting xD
You'll want to set the Animator's Update Mode from Normal to Unscaled Time.
Unscaled Time keeps animations running at real-time speed, unaffected by time scaling.
We'll be using an U# script to initialize the animator to the right position in the animation once on Start.
The script looks like this:
[SerializeField] private Animator animator;
private void Start()
{
AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0);
float animationLength = stateInfo.length;
float normalizedTime = Mathf.Repeat((float)Networking.GetServerTimeInSeconds(), animationLength) / animationLength;
animator.Play(0, -1, normalizedTime);
}
This does not need UdonSync! We're relying on Networking.GetServerTimeInSeconds() as the time source, which will probably be precise enough for our purposes here.
Oh? Is that possible with Udon Graph? So far everything I did with Udon Graph before, because it's so visually accessible for a non-coding person š
It's probably possible in Udon Graph too.
hah I just found something like that
Here is a .unitypackage containing the U# script and an example prefab.
if I drag and drop it into my scene, I get this :0
Wonderful
Wait, holdon, this isn't my script causing the errors
Lol
Find the AnimatorUpdater.asset file in your project and delete it
So if one dead script lays somewhere in Unity, everything stops working? xD I found and deleted my try to create something like that and it's working now
so if I put your script on an object with an animator...the loop animations there will stay sync, doesn't matter on which layer? :0
Only the first layer on the animator will sync! :(
And it will only work if that layer only contains a single looping animation clip.
I see, but I can at least sync those loops...whenver a state changes via a bool I can send it in Udon Graph as a networking event and everything that just does movement on start I can sync with your script when putting stuff on base layer in default state...that is at least solving my problem! So thankya! >x<
whenver a state changes via a bool I can send it in Udon Graph as a networking event
Please do not sync bools with network events, you should make aUdonSyncedbool variable and useRequestSerializationandOnDeserialization.
I meant something like that. Someone interacts with a lever, and the sound and opening animation will happen for everyone :3
Yes, this is what I'm specifically saying not to do.
If you click it once, and then someone joins, it will be desynced for the late joiner.
Yep! That I know. So I looked for dozens of videos on YouTube and the best ones kinda agreed that you should just let the master send that everyone checks something's state to sync it to the master :0
so it would redo all the scripts that're already done in an instance at once for a late joiner :0
:(
A variable that's synced means...after changing that thing in a world you request serialization so everyone has the same? And on start for a new player if would automatically onDeserialization do the same?
None of this is required. VRC already has a system that will handle all of this for you.
Means just the one joined person would get that Deserialization on start because for everyone else nothing changes and not every single person would get the question "are you master" and "did it already happen"? :0
The fact that there are tutorials out there that tell you to misuse SendCustomNetworkEvent in this way makes me sad.
If I search for tutorials about more advanced stuff than just creating a pretty scene, kinda every single tutorial tells you to blast Network events for everything in every case :0
Tragic
the only networking guide you need (at least used to, before persistence/events with params) #udon-networking message
So I could not use Network event...and just create a synced bool that is set on using a button and instead of custom event that was sent, I use OnDeserialization and it does the same, plus I can save all that OnPlayerJoin > ask questions to everyone blah bla? o.o
oh wait...probably I need to ask first if that bool now is true to do the action then...
because in future there may be more than one bool if the scrift develops
check the vid, you need to set the owner, and you need to play same event for him (interacting person) since ondeserialization wont fire for the owner
will look into that!
okies, yeah I remember years ago I made a door with a key and some friends tried to help me with Deserialization via stream and we couldn'T make that happen in 1-2 hours and then they told me 'screw that stuff and just fire network events and no more thoughts needed and I didn't touch it since that time end of 2022 >w<
I think what went wrong back then...was the declaration of ownership about an object. Network events kinda force something to happen without any ownership transfer, that's why everyone (out of here) tell to use NetworkEvent, because it seems simpler on the first glare
tho creates more problems later
yeah that last point is the important takeaway
the NetworkEvents make it fairly easy to just, in general do networked things, but their drawbacks aren't as obvious; at first everything seems like it's working perfectly, so why consider doing something different?
mhm. Well I try to learn that sync variable magic with that new lil map project to start clean with that world >x<
a secret third option that is useful when networking is a variable's Change event, which you can create by holding ALT while dragging the variable onto the graph
this is an event that fires whenever the variable has been updated to have a new value
which includes if it was changed via network syncing :)
so that kinda is a specified OnDeserialization? :0
One note about variable change events though! They fire immediately when the variable is changed, so if you have multiple synced variables you want to check you should use OnDeserialization instead of the variable change event since the other variables might not be ready yet. But variable change events can be great if you're only going to check that one synced variable.
I see...something might need more than one information and that extra information could differ if you just ask that one variable to push a button for everyone instantly~
you can optionally choose for the variable to not send its change event if you need it to, I think it's disabled by default (in graph)
but yeah its usefulness all depends on if you need it for other things. I like to use this event for bools that I'm using to track its own state and change stuff based on it, when it doesn't have to care about other things
I blush a bit. I already did stuff like creating empty gameobjects and setting them active or not active to save something happened or not and do the "onJoin ask every not master to copy the state of the master" - what kinda is just what a synced bool does hides x'''D
I've seen that done before lol. It's still a boolean, in spirit
I feel tracking a boolean like that makes sense for those that are more analog-minded
Mhm it's just - whenever I asked friends, they told me they don't know, because they just puzzle together a nice lighted showcase for VRC and done. No code. If I asked heavy world creators with all fancy stuff, they told me "eeeeeeeew you're not using U#, then you're a pawn and should not bother with problems". But I wanted to make fancy stuff with Udon Graph too T_T
So I just puzzled around and followed YT tutorials. Definitely learned something tonight tho >x<
Well⦠U# is a lot more⦠How do you put it⦠Friendly I guess. A lot of developers are text based which in itself is valid. Doesn't exclude being able to convert U# to graph, but U# does a lot of things for developer convenience which would add a lot of boilerplate in graph
I never learned how to program, but many programs use some kinds of visual programming. Blender also has nodes for everything. So I of course started with visual programming in Unity. Also all the tutorials on YT use nodes, because they target people with not much knowledge. I learn more and more about how programming kinda "thinks" and works, but I couldn't produce code from scratch now.
I used to believe the same thing, U# being more friendly, but I ended up finding out that this is because I already have many years of traditional programming experience, even primarily in C#, so U# comes completely naturally to me.
But for anyone I've run across that don't know how to code, this ends up being the opposite. I often see people finding a way easier time understanding nodes vs. trying to learn the C# syntax.
U# is definitely worlds easier to work with "big", complicated stuff though. Those 1000 line U# scripts literally wouldn't even fit in a Graph node before hitting the node limit.
U# allows you to write almost C# while using modern IDEs which have been refined for literal decades. This is a massive advantage.
I think the barrier to entry mostly comes with the assumption that you need to learn the words rather than understanding the underlying types. Like, once you understand that something always has a type and you can only do so much with a specific type then the rest becomes naturally, what something is named doesn't really matter. Just some ways methods are named matter if they're out of your control like with built in types
I certainly wouldn't be interested in Udon if the Graph was the only option lol
yeah I agree. It just tends to be quite challenging to teach how to code, imo. I feel like most tutorials for coding tends to scare people off, rather than trying more to foster an interest in it
they often devolve into the technical jargon a smidge too much
I wonder what the bottleneck in the current pipeline is
most people I went through school with described as coding at some point just "clicks", and you just get it from that point on
I feel the same way which is why I decided the best way for me to get into it was to just dive head first into some project and try to make sense of it as I go. I think it helped me a lot that I used Visual Studio Code as well for my first language of choice (JavaScript). That first class integration and telling me when an error may occur was monumental in getting some early hints in what I can and can't do
what causes the click? ĀÆ_(ć)_/ĀÆ
@twin portal @heavy spindle I can tell you exactly what the problem is:
Lesson 1:
-what is a function?
-what is a variable?
-basic "hello world"
Lesson 2:
-backend architecture of mainframes
-Algorithmic Solutions to Obscure Math Concepts
-Advanced Raytracing to discover god
literally
You may have a point
lmao
I have some future plans to try and solve this problem...
That would be like me anding you a pencil. Showing you had to draw a circle. Then pointing to a stack of paper and say "ok were drawing bugs bunny on model today. I need this scene finished by later this week." Hands you the scene folder with layout charts and all.
y e a h
You also have to get a working competitor with ChatGPT done by the end of the month
In my senario... You are left with a stack of paper, an animation disk, pencils, erasers, scene folder, and a lamp. THERE IS NO COMPUTER IN THE ROOM. I've shut the door.
> light lamp
i ended up finding a 1.5hr C# course on youtube, watched that and made the same scripts as he did, and just kinda understood coding
iāve definitely had to learn the syntaxes and common practices over time, but initially learning wasnāt too hard for me. maybe because C# is a pretty high level language, and maybe because i already knew graph really well
a lot of things ājust clickā for me though. like getters and setters, overflows in methods, return types⦠a lot of those i didnāt really understand well at first, but at some point i just realized, ooooh THATāS what those do
Unity does offer a free online 'course' for teaching yourself how to use Unity and C#. I had an education in programming but nothing for C#, game design, or Unity, so I went through that to get myself a good foundation. This might be it: https://learn.unity.com/pathway/junior-programmer
Free tutorials, courses, and guided pathways for mastering real-time 3D development skills to make video games, VR, AR, and more.
Also as of last night finished rewriting my world's networking to use a state based, eventually reliable approach. It... took a bit of work (that is a 1620 line file and there are a handful of other files that also got touched, replaced, or created as well)
What is that image im looking at? @north thistle
it marks the changes in the current version of the file since it's last git commit
to simply it, it shows what I've done since starting work on the network rewrite where blue = changes, red = deletions, and green = additions
oh, also it's the scroll bar for the file
So I changed my stuff to using changing variables now instead of firing network events for events. OnDeserialization was not working, but pressed Change works! But why does the Deserialization not work? I Requested Serialization before :0
Have you tested it in-game? Was doing some tests earlier today and I noticed all of the serialization events don't fire in ClientSim
Oh, also worth noting that OnDeserialization does not fire for the sender (the player that runs RequestSerializaton)
Yea, I was typing out what legos said. The workflow when using OnDeserialization is usually to set owner, request serialization and then apply changes to the local player. "Apply changes" meaning your sound effect and animator update, etc. And then OnDeserialization fires for remote players and apply the same changes there.
so kinda like that? Deserialization for remote players and variable change for me?
they mean something more like this (using an event here isn't necessary, but it's probably more clean in graph)
hmmmm I see
so instead of on Variable Change, I fire a local event...kinda the same then :0
But what if there are more variables that use Serialization?
in the resulting event, assuming you mean multiple bools, you'd set whatever states the variables correspond to
you could do it on variable change instead if that's easier in some way, it has other implications but if the variables aren't tied to each other in any way it's not the worst thing to do
i think you're working with animators, but the idea would be the same; you could use a block if it's more organized that way or just run them in sequence directly
what is the advantage to fire an event after Serialization instead of putting Deserialization and the changed Variable directly into the stuff that happens? Is it for making the nodes cleaner? :0
I don't know if you can do this with graphs, but with U# you can get the OnPreSerialization() to work in client sim via
RequestSerialization();
#if UNITY_EDITOR
OnPreSerialization();
#endif
if you're asking why the thing i'm showing is preferred to your picture here, it's because the variable change event fires for remote and owner (the owner being the one who changes the variable)
deserialization only fires for non-owners
as a result, non-owners are enacting the logic twice because they'd run both deserialization and variable changed. if the states are absolute (true or false here) and simply set something, it's not the end of the world if they run the logic twice, but it's not logically correct
if you're asking why i call a send custom event, it's because it's more clean in graph (and code) to run the same logic on both owner and remote if they're simple/absolute states and it's more clean than flowing directly into the resulting changes
just to be clear the event is a local event, it's for organizing logic here
Also note that "Built & Test" is more-or-less "in-game"; it is not a local test but actually a world uploaded to the VRC servers with your clients connected to said server
This dropdown is from the VRC Quick Launcher tool. Is there a way to locate and/or clear the references that appear there? Oddly (it seems to me) some of the old ones still work. Can't figure out where is it keeping these or how to make it forget them
Try OnPreSerialization() for the local player. That event gets fired just before the variables get sent out over the internet
But note that that event will not get triggered in ClientSim unless you do something that I think is U# only
OnDeserialization is mostly helpful when you have multiple synced variables that are related to each other. And the order you apply changes might be important. Like you have to check one variable and do something different with the second variable.
But if your variables are unrelated to each other, then changes events are fine.
It's probably somewhere is the Library folder considering how much room it consumes
If the order of changes matter, don't have them as manual sync variables. Manual is eventually reliable, not strictly reliable.
the example i made is a bit contrived, but i think they mean within the context of the send; doing this same logic within variable change would be incorrect if the bool wasn't deserialized prior to the change event
Although I might be misunderstanding what you meant. To clarify a bit better, for any manual sync variable, all the logic that is affected by a change in them should most probably be contained in OnPre/PostSerialization on the host side and OnDeserialization on the receiver side to maintain consistent behavior
got it, thankies! am new to that x33
One message removed from a suspended account.
have you designated it as network callable and setup the input parameter? here's an example of what a network event might look like when called to self
One message removed from a suspended account.
One message removed from a suspended account.
do you have the networkcallable attribute above the function? i think that is actually required when using params with network events, unlike ones without params that just require it being public with no underscore
One message removed from a suspended account.
One message removed from a suspended account.
not really sure, have you tried it on something that isn't an overridden method?
One message removed from a suspended account.
if you want to target another behaviour i think the typical way would be to reference it and call it like this
public UdonSharpBehaviour otherBehaviour;
otherBehaviour.SendCustomNetworkEvent
i don't know if targeting through the first argument works the way you were trying
One message removed from a suspended account.
NetworkEventTarget.Self skips sending the event over the network.
One message removed from a suspended account.
Sorry, I should've read more into the context of what you're doing.
One message removed from a suspended account.
One message removed from a suspended account.
One message removed from a suspended account.
One message removed from a suspended account.
Unrelated, but why aren't you using
nameof(OnPermissionsReloadedSuccessTest)
Instead of a hard-coded string?
One message removed from a suspended account.
is the base class method [NetworkCallable] too?
One message removed from a suspended account.
One message removed from a suspended account.
you're overriding the method so i'm assuming it's an inherited class, like it's defining a virtual function somewhere?
One message removed from a suspended account.
One message removed from a suspended account.
One message removed from a suspended account.
And you've tried with having the attribute only on the overridden version of the method, right?
One message removed from a suspended account.
One message removed from a suspended account.
nameof(OtherBehaviourType.Method)
One message removed from a suspended account.
I mean remove the [NetworkCallable] attribute from the abstract methods and only keep it on the override methods.
One message removed from a suspended account.
It's a wild shot in the dark, but, who knows! š«
One message removed from a suspended account.
y e ah
One message removed from a suspended account.
I mean, you could look at the U# compiler source code
does it work in-client though?
Or have you only been testing in client sim
One message removed from a suspended account.
One message removed from a suspended account.
I tried to create a door that can change their state from open to close with a simple animation. I made it like that and locally everything works fine, but if I create two clients to test it multiplayer, nothing happens for the player that is not using that door. Where is my brain-twist? :0
You need to use the OnDeserialization event.
But then it will automatically fire if it is enabled as a GameObject? Because there is a closed door before and if you use the key, it's interchanged with the door that can be opened (without a big Lock on it) and if I activate the door that is able to be opened...will it then automatically fire the OnDeserilization? >.<
No
One message removed from a suspended account.
One message removed from a suspended account.
Occala please save me
One message removed from a suspended account.
One message removed from a suspended account.
One message removed from a suspended account.
One message removed from a suspended account.
One message removed from a suspended account.
Network callable method 'DataReloadUdonSharpBehavior.OnDataLoadReloadedFailed(string)' cannot be virtual or abstract
oh
i get this error if it's not an abstract class
it can't flag that at compile time because abstracts don't have an equivalent udonsharp program asset
going to guess that's the issue at least
I think you might need to use
One message removed from a suspended account.
Replace this with a reference to the behaviour you want to call it on.
One message removed from a suspended account.
One message removed from a suspended account.
One message removed from a suspended account.
One message removed from a suspended account.
One message removed from a suspended account.
One message removed from a suspended account.
One message removed from a suspended account.
is DuSelbst the object with this graph program on it? the set owner call is going to that object specifically (which isn't necessarily the object with this script on it)
One message removed from a suspended account.
One message removed from a suspended account.
not U# behaviour?
One message removed from a suspended account.
š
One message removed from a suspended account.
if you inherit from it in a non-abstract script with a program asset it should flag it at compile-time
that's why we suspicious cast to IUdonEventReceiver..
It's in the docs
One message removed from a suspended account.
Duselbst is German for YourSelf and yes it's the door object with the UdonBehavior on it, which has the two door wings below that're animated.
One message removed from a suspended account.
Do you actually need the type to be UdonBehaviour for anything else? Or could you be fine with declaring the type as your base-class DataReloadUdonSharpBehavior?
image 1 compiles, image 2 does not, which seems sus to me
oh
One message removed from a suspended account.
One message removed from a suspended account.
One message removed from a suspended account.
One message removed from a suspended account.
just to double check, openState is set to synced in the variable list?
openState is public and sync and default off, it is to check if the door is opened or not and it starts closed in animation and state. BlockerCOL is just an invisible cube so the player can't go through closed door.
Here, from the U# example I wrote https://creators.vrchat.com/worlds/udon/networking/events/#sending-events-with-parameters
One message removed from a suspended account.
i think the set owner call should be before the bool set from a technical point, but i'm not sure why it's not working otherwise
All I'm saying is that you don't need to constrain yourself to making the reference UdonBehaviour.
You can even make he reference whatever class inherits from UdonSharpBehaviour. This will hopefully make your life easier.
I heavily suspect that it in fact does not work for abstract methods either. The compiler just doesn't throw an error for some reason, even though it should.
The solution in that case would unfortunately be to not use abstract or virtual for the networked method.
One message removed from a suspended account.
One message removed from a suspended account.
without params it might handle the call differently, it might only search for a public method with that name
so that should kinda worky? :0
for instance this compiles, but not if they're tagged with the networkcallable attribute
and i wouldn't be surprised personally if these could be called through legacy network events
it looks correct from what i can tell, though if the blocker collider's active state is meant to be based on the door state i think it'd be preferable to set it directly from the synced bool instead of flipping the active state
SendCustomNetworkEvent (legacy) and NetworkCalling.SendCustomNetworkEvent are not the same, and they are not handled the same.
One message removed from a suspended account.
yeee can do like that...open = gone and closed = on
Why are you even using local network events for this though
You're in U#
You don't need SendCustomEvent with parameters
One message removed from a suspended account.
One message removed from a suspended account.
You're making an interface
Okay
can you try yeeting the abstract class for a moment and go back to hard-coded strings
so no override methods
One message removed from a suspended account.
One message removed from a suspended account.
Okay, but they're all extending your abstract class??
Just call the method on the base class like normal?
One message removed from a suspended account.
You can literally do this
public class MyBehaviour : UdonSharpBehaviour
{
[SerializeField] private DataReloadUdonSharpBehavior[] broadcastTargets;
private void Start()
{
foreach(var target in broadcastTargets)
{
if (target) target.OnDataLoadReloadedSuccess("Permissions");
}
}
}
And if you want to save yourself the effort of fetching all the scripts that inherit from your base class you should consider using VRRefAssist
In which case you could do:
using UdonSharp;
using VRRefAssist;
public class MyBehaviour : UdonSharpBehaviour
{
[SerializeField, HideInInspector, FindObjectsOfType] private DataReloadUdonSharpBehavior[] broadcastTargets;
private void Start()
{
foreach(var target in broadcastTargets)
{
if (target) target.OnDataLoadReloadedSuccess("Permissions");
}
}
}
One message removed from a suspended account.
One message removed from a suspended account.
One message removed from a suspended account.
The video that was shown yesterday like official VRC network tutorial showed it like that and so I just made it like that and thought it's true, but yes...making Ownership BEFORE changing variable helped.
hmmm a difference...I checked sendChange, but the person in that video didn't
also no obj linked to the slot, weird...also I wonder what instance in RequestSerialization means~
sendChange just allows you to use the changed event for the variable
if no object is set for something like SetOwner it will just call on itself which is what you want a lot of times
the instance in RequestSerialization means you can call RequestSerialization on a program that isn't the one you're currently in i believe
i didn't totally understand the setup here, did you mean that there are multiple doors with programs on them?
I will turn it into a prefab. Once my door works, I can use it more often as a prefab xD But I am since 3, 4 hours on that door...you can find a key that should remember if it's grabbed...then you have a locked door that can be opened once someone found the key...coop...and then it changes to an unlocked door, that can be opened and closed ^^
is the unlocked door a different object entirely? just want to inform you that if it is another object that starts disabled in the scene it might not receive network updates until it's been set active; if it's not a separate object then don't worry about that
It is disabled at start, yes, because the opened door is another fbx as the closed door and has own collider with own logic :0 it changes place (and locally works fine). I will test again now, I really can't find any issue anymore and it should work. I whine
The object which has the synced graph script on it should never be disabled.
That is the key that can be grabbed and it's always on, but will need to be disabled later, to not be grabbed anymore x_x that key thing works in network!
Then the closed door (active) and the opened door (inactive on start) both not working for network but perfectly fine locally T_T For other players the door stays locked and closed, whatever the local one does...
so in that first program, the locked door, i think you should consider storing the mesh renderer (or skinned mesh renderer) and possibly the collider of the DoorNoLock instead of using setactive, you could instead enable/disable those 2 things, that way the door without a lock can always be ready for networking
I mean I could create a trigger that gets access to the objects then...not putting the code on objects that will disappear...otherwise I'd have to manually switch mesh renderer and trigger collider and blah and need to workaround more and more and more...
I see I see, so kinda what I just typed. Creating an own trigger for the logic OR accessing collider and mesh renderer instead
you could also make a little hierarchy for DoorNoLock, where the parent is the program and below that is a child with the collider and visuals under it, that way you could interact with the visuals and collider through one setactive call on ActiveStateObject without needing to disable the synced program object, that'd be another way to accomplish it if it's easier
OKay I will not switch the ActiveStates, I will now switch Mesh and Collider and try
I die, it finally works...the problem was....I am not allowed to deactive a synced Udon script ever...didn't know that...I always went for the GameObjects, cuz I always asket ten times each script on every Join if everything works like it should xDDD
If I want something to happen 6 times...do I need to make six events that hand over each other...I want to make a for loop, but I also need those events to always wait a bit and not brrrrrrt fire all 6 in a millisecond...there is no sleep/wait function in noodles mhm? oxo
If you use a loop they'll all execute in the same frame
You can alternatively use SendCustomEventDelayedFrames or SendCustomEventDelayedSeconds for an easy method to delay an event firing until later
But in a For loop? :0
So if I need something for effect to do it 6 times, but with a slight delay...I need to copy over the same "play sound and kill object" code 6 times in a waterfall with EventDelayedSeconds and can't make it tidy with a for loop mhm? Cuz when coding with text you'd just say Sleep.Time(750) for 750 milliseconds or so in the loop
you could try something like this, where you define a delay per loop (DelayPerCall is a float variable); you may want to add 1 to the index prior to casting it to a float though if you want the first call to have a delay too
which value is DelayPerCall? Isn't it rising cuz the index rises? :0
DelayPerCall is just a float variable i added, i made it 0.5 in my case, so each event would fire with a ~0.5s interval
i assumed you wanted them to be spaced out, or is it that you want all 6 to fire at once after a delay?
aaaaaa I got it, makes sense...you cast all at once, but every next one with a longer delay cuz the *
that's right, the delays would look something like this when called in the for-loop (0, 0.5, 1, 1.5, 2, 2.5)
I do that! Seems not important when 6 things...but when I do it like you showed, I have it modular and could reuse that thing later cuz it always can grab the length of the array to do the thing °0°
aaaaaaaaaaa vibrates
thankies~
I learn things today, but I also whine that I needed 5 hours for a frickin door
Guys, who knows how you can add your group to the map in the form of a label?
all you do is have a counter for it.
if you are using UdonSharp you can do it like this. otherwise it should be fairly easy to copy it for UdonGraph @solar crescent
I really hope they give us coroutines or at least local events with parameters
On a side note, does Udon support any async stuff?
there's a readback they provide here
https://creators.vrchat.com/worlds/udon/vrc-graphics/asyncgpureadback/
string loading is probably async
https://github.com/chiugame/udon-task
this package technically runs things on like an audio callback unity provides but it's probably not ideal to actually do that
you can make something coroutine/task esque that runs on the main thread by spacing the logic over multiple frames and caching the state, something like tracking how long it's been running before deciding to defer to the next frame
yeee I found a solution that works with a counter that gets the array length :3
Hm for changing states via ownership that's all kinda easy now, but I wonder what if you want to switch a state back after 60 seconds. If you just cast an eventDelayedSeconds after 60sec nothing would happen. I bet because who knoes who the owner would be. Noone. Noone did something, it's automatically...even if I pick the master as owner after 60sec to change the object's state back to active...nothing happens...
passing back to the original owner isn't really necessary unless you need/prefer that for a specific reason
you could have the new owner change it back after 60 seconds, that'd be the easiest way by the sound of it, but it wouldn't change back if they left during the interim
to make it leave resistant you could record the server time it changed and listen for it exceeding 60s since then if you wanted a way that was safe through leavers
oofff sounds not so easy
i think for a simple attempt you could add a delayed event call targeting 60s after you do the initial change, like after request serialization for example
in that event that fires after 60 seconds, you could change the state back and request serialize again
that didn't work so far either
could you show the graph?
i don't think coin visibility should be a network call, just a local event
and after the request serialize in coinback, the owner should go to the event CoinVisibility again
I just want the coin to be gone when someone gets it but not active for someone late joining when there is no coin for the others for some more seconds :0
the ondeserialization will handle that for late joins, but it shouldn't be a network event imo
oh I see....it's not Deserializing for toe Owner automatically if they does the change...
yeah the owner doesn't actually fire ondeserialization
you could use the value change event if it's easier in this case to automatically go into the coinvisibility logic
but without networking that, it couldn't be that the coin's state is always the same for all, mhm? :0
OnDeserialization will fire for non-owners after the owner requests serialization and the data gets to them
so at that point they just want to go into CoinVisibility to check the synced bool
it being a network event probably doesn't technically make it perform incorrect logic but it will fire multiple times per player
ah so 'OnDeserialization' could just be named 'ListenMaster', and 'RequestSerialization' could be named 'TellPeeps'...would be easier to understand x3
yeah the request serialization just informs udon that you want to send the synced variables, if you're the owner it will take those variables and send them to the server, which gives them to all the other players in OnDeserialization
Am for a language pack in which SetOwner is called 'GibGib' and PlayOneShot 'Yell' and stuff. Would be funny xD
But how would that Graph do the same without being a networky thing telling all players stuff? :0
do you mean without the custom network event?
oh that is my bad! That was from the old code before I learned the variable stuff during the last days....I missed changing it to a SendCustomEvent x_x
Sometimes my eyes are not seeing an obvious thing, sinking in all those noodles and things and bwah x'D
das better x_x
yeah i think that looks like it'd work
And Network Events I just use for single few-second shots of stuff that's fired from time to time but not important and not changing a state...like an enemy always rolling down stairs a few sec if a person gets up x3
network events are good for things that are much more temporary, like maybe someone firing a firework
before I did all with Network Events cuz I didn't know the Serialization stuff and Network Event was the only tool I had in my box x'D
yeah I think a few seconds an enemy doing a move is also okay. If someone late joins, the person will not reach enemies in the blink of a second x3
yah you can get close to coroutines by storing the in-between data in global variables, but besides being jank to set up and cluttering your global variable space, it still doesn't support full arbitrary amounts of the method being run. And the more you try running at once the more complicated your code gets.
for example, I wanted to do bullet trails in my game world. Looking it up online this is how I saw someone doing it with coroutines
And here's the U# code I had to write to get something equivalent
private void RenderShootingLine()
{
//LogDebug($"Starting {nameof(RenderShootingLine)}");
_isRenderingLineArray[RenderingLineIndex] = true;
_trailArray[RenderingLineIndex].transform.position = _fireStartPositionArray[RenderingLineIndex];
_trailArray[RenderingLineIndex].Clear();
_trailArray[RenderingLineIndex].emitting = true;
//LogDebug($"_trail stats; enabled: {_trailArray[RenderingLineIndex].enabled}, emtting: {_trailArray[RenderingLineIndex].emitting}");
_renderLineTimeArray[RenderingLineIndex] = 0;
_renderLineEndPointArray[RenderingLineIndex] = _fireStartPositionArray[RenderingLineIndex] + _fireStartForward * _maxRange;
if (_hasHitArray[RenderingLineIndex])
{
_renderLineTargetArray[RenderingLineIndex] = _hitPointArray[RenderingLineIndex];
}
else
{
_renderLineTargetArray[RenderingLineIndex] = _renderLineEndPointArray[RenderingLineIndex];
}
_renderLineTargetDistanceArray[RenderingLineIndex] = Vector3.Distance(_fireStartPositionArray[RenderingLineIndex], _renderLineTargetArray[RenderingLineIndex]);
_renderLineTravelDistanceArray[RenderingLineIndex] = 0;
RenderingLineIndex++;
if (!_isRunningRenderShootingLineLoop)
{
RenderShootingLineLoop();
}
}
public void RenderShootingLineLoop()
{
//LogDebug("Entering _RenderShootingLineLoop");
_isRunningRenderShootingLineLoop = true;
bool hasLinesItIsRendering = false;
for (int i = 0; i < _maxTrails; i++)
{
if (_isRenderingLineArray[i] == true)
{
hasLinesItIsRendering = true;
//LogDebug($"_renderLineTime: {_renderLineTimeArray[i]}");
if (_renderLineTimeArray[i] < 1f && _renderLineTravelDistanceArray[i] < _renderLineTargetDistanceArray[i])
{
Vector3 nextPosition = Vector3.Lerp(_fireStartPositionArray[i], _renderLineEndPointArray[i], _renderLineTimeArray[i]);
_renderLineTravelDistanceArray[i] = Vector3.Distance(_fireStartPositionArray[i], nextPosition);
if (_renderLineTravelDistanceArray[i] > _renderLineTargetDistanceArray[i]) { nextPosition = _renderLineTargetArray[i]; }
_trailArray[i].transform.position = nextPosition;
_renderLineTimeArray[i] += Time.deltaTime / _trailArray[i].time;
//LogDebug($"_renderLineTime: {_renderLineTimeArray[i]}");
//LogDebug($"Continuing {nameof(RenderShootingLineLoop)}");
}
else
{
//LogDebug($"Ending {nameof(RenderShootingLineLoop)}");
_isRenderingLineArray[i] = false;
_trailArray[i].emitting = false;
}
}
}
if (hasLinesItIsRendering)
{
SendCustomEventDelayedSeconds(nameof(RenderShootingLineLoop), 0.1f, EventTiming.LateUpdate);
}
else { _isRunningRenderShootingLineLoop = false; }
}
you're right yeah, it would be more convenient
i think you're going for something a bit more visual over time than what i normally do and mine are more for debug than aesthetics, but have you considered using particles instead if it's visual alone; or running the lifetime logic on the object itself? not to say one approach is better, but i find it's relatively easy to manage a pool of line renderers and particles for a shot effect, where each line has a script that manages its own lifetime
it's more object level, but only active lines run logic
i think if i were doing it at manager level i'd probably prefer to use binarysearch to insert and maintain a sorted list of trails to lifetime, where the earliest entries are the closest to expiry; it's more complex, but it works well in practice for lifetimes imo. it doesn't require iterating over every trail while any are active would be the advantage
hmm. do we know how Hitches Per Net Tick affects the world?
I only learned recently that you can do trail renderers with particles, so yah that would likely be a better option
So I could be wrong, but it appears that when a remote client leaves, the master gets that player's OnPlayerLeft event before OnOwnershipTransfered events.
But when the master leaves, the new master gets those events in the opposite order....
How annoying.
I wasn't paying close attention beacuse it does not affect me rn but I think i noticed that too
I'm curious though what problem is that creating for you.
My code has a list of players in world that gets depopulated by the OnPlayerLeft event. OnOwnershipTransfered uses this list to know who is actually in the world to 1) know if this transfer was due to the last owner of the object leaving and 2) know who to redistribute ownership of objects to
If the instance master leaves the world in on player left would he still show up as master inside of that callback if you ran an if check?
from my testing, Networking.Master gets updated first
So are you saying that by the time on player left has been called, the player who was master would no longer return true on a is master check
If it is the case we could detect a master leaving by keeping the network id of the last master in a variable. It wouldn't need to he synced because all clients could easily ask for it
So you could have an alternate branch of code to handle that edge case
thats is a very odd way to do it.. considering you get a player reference OnPlayerLeft
the issue is that when the master leaves, the new master 1) doesn't know that the objects transferred to them was due to a player leaving because that player is still in the "in world" list of players, and 2) will try to incorrectly assign ownership to that leaving master because they show up in the "in world" list
uh its not exactly a good idea to have Reliance on a Master etc.
There are ways to get around this (I used the OnPlayerTransfered event on a high priority singleton script on an object that will only transfer ownership on master leaving to force OnPlayerLeft to trigger for the leaving master), but the fact that there is inconsistent behavior like this for no obvious reason is legit annoying and a problem. And it's something I'll likely make a canny post about.
OnPlayerLeft is called for all no matter what you cant exactly change who it calls for or not .
and it will always tell you who left and if that person was the master or not can be checked.
so you should honestly validate it on someone leaving and then run whatever you need to run.
You can kinda do it in a fake way.
You can have a custom function in place of the real callback. Only use the real callback in a specific script and use that script to call your custom functions for all fhe other scripts
well that is an odd thing to do. considering its still gonna call everything in the back. all you are doing is potentially causing issues
or having delayed information that is incorrect
Didn't say it was a good thing to do but it's an option
there is an event that fires when master is transferred if that helps in any way
yea OnMasterTransffered or whatever its called
"OnOwnershipTransfered relies on logic that runs in OnPlayerLeft so when the master leaves I need to manually make OnPlayerLeft run first" is a pretty straightforward concept, right?
I would try to find a way to do what you need to do in just one of those events
considering that vrchat is verifying you actually left etc
These are being run across different scripts and different objects
And the player variable in OnPlayerLeft contains the information that I need
Perhaps you can store that data in a value on a script so your other methods can get at it.
You might need to also create a method for your other functions to know if it's too soon to grab the value
That's what I'm doing...
And the fact that the order of events arbitrarily flips when it is the master leaving is the issue that creates an annoying edge case to work around
Thst seems like the only visible route with the structure you have
well i mean it makes Sense Vincil.
Just to clarify, I have designed a solution to the issue it was causing. My issue is that I even needed to do that in the first play.
?
wait so give me the order again
someone leaves it calls OnPlayerLeft as always
but you are saying OnOwnerTransffered happens before that?
It's an unintuitive gotcha that happens semi-invisibly that requires log printing variables to discover and requires you to create edge cases in your code which adds inconsistency to your own code
In anycase thanks for discussing it beacuse i may have to deal with a similar thing soon
if a non-master leaves, the master gets the events OnPlayerLeft followed by OnOwnerTransfered
if a master leaves, the new master gets those events in the reverse order
could be a bug or not. but then again it shouldn't pose a problem.
you're free to think that I suppose, lol
well. Considering that when a Master leaves it needs to change the master relatively fast and before anything else. hence why it most likely calls OnOwnerTransfered first and perhaps even OnMasterTransfered before that. and before OnPlayerLeft.
atleast i see it like this The networking layer may have different event queues or priorities based on player roles:
Normal player:
OnPlayerLeft -> OnOwnerTransferred
Master player:
Ownership is critical ā OnOwnerTransferred is given priority, so it fires first.
also its likely something that happens to prevent objects from ever not being owned. as that is fairly critical within networking
I have my own theories on why this happens. But my point still remains.
you may have have so. but in reality im pretty sure every single Networking API does this. for that specific reason.
and then again you can just have code that triggers seperate for when its a master and a non master and no extra changes or other things should pose issues.
this would work fine right? since i'm returning if the player isn't local, using the VRCPlayerApi from OnTriggerEnter should be fine?
if the player is valid, at the point of setowner that is the local player yes
i realized i need a reference to the localplayer anyway because i have a differnt method that needs it, but good to know
Networking.LocalPlayer works for that
Also correct me if I'm wrong, but I believe SetOwner can be used by anyone, even by a non-owner transferring ownership to someone else. Still for this use case only letting the player to take ownership do it is a good idea to prevent everyone from making redundant SetOwner calls
setowner can be called by others yes, in their code it can only be called by the local player, but i would agree that for readability it makes sense to pass it more explicitly
in the context of just the code they shared here, this event (OnPlayerTriggerEnter) will be called by everyone; unless the trigger is on a layer that doesn't interact with non-local players
they do an early return there
if (!player.isLocal)
only the player entering the trigger will continue beyond that
Yes
i'm not sure i understand what you mean, it checks if the player entering the trigger is local, while everyone will see this call, only the player actually entering the trigger will proceed to the setowner calls
I think I just misunderstand what you mean by "for readability it makes sense to pass it more explicitly"
by readability, i mean option 2 here makes it more apparent at a glance to anyone writing/reading the code that it's intended to set ownership on the local player. option 1 isn't wrong, but it is slightly less apparent what the intent is
// Option 1
OnPlayerTriggerEnter(VRCPlayerApi player)
{
if (!player.isLocal) return;
Networking.SetOwner(player, gameObject);
}
// Option 2
OnPlayerTriggerEnter(VRCPlayerApi player)
{
if (!player.isLocal) return;
Networking.SetOwner(Networking.LocalPlayer, gameObject);
}
Ahh I see what you mean. Personally I feel that is pretty minor, but readability best practices is an area where I wouldn't consider myself too knowledgeable
Hey Skull, just wanted to shoot you a message in the public chat first but I'm having an issue with your SlipperyFloor Script, it works super well except this one issue, would you mind if I dropped you a message about it privately?
sure
I'm trying to play an audiosource when a player presses a button but I'm having trouble implementing it right now.
I have a player object with a "manager" script referencing an audio source child attached to it and I'm trying to play a sound clip referencing that players specific audio source on an interact. Having trouble getting the audio to play for everyone else. If anyone is willing to help again I'd appreciate it. I can show code or w/e
Sounds like you want a network event.
This'll let you trigger an event for every player in the instance, not just the local player
Yeah I'm trying that but it's not working for some reason and I've been stuck on this for 2 weeks also just trying to figure it out. Doing something wrong just can't see it
Hi
public class SpawnConsumableFromPool : UdonSharpBehaviour
{
public VRCObjectReference objectReference;
public string itemType;
public Transform spawnPoint;
public int cost = 0;
private PlayerManagerScript playerManagerScript;
public ConsumablePoolManager poolManager;
public override void Interact()
{
if (Networking.LocalPlayer.isLocal)
{
playerManagerScript = objectReference.GetPlayerManager(Networking.LocalPlayer);
Networking.SetOwner(Networking.LocalPlayer, gameObject);
}
SendCustomNetworkEvent(NetworkEventTarget.All, nameof(PlayAudio));
if ((cost == 0) || (cost <= playerManagerScript.GetPlayerWallet()))
{
poolManager.SpawnItem(itemType, spawnPoint.transform.position, spawnPoint.rotation);
ChangeCurrency(playerManagerScript);
}
}
public void PlayVending()
{
playerManagerScript.PlayOneShotSFX("VendingMachineItem");
}
public void PlayButton()
{
playerManagerScript.PlayOneShotSFX("ButtonClick");
}
public void PlayAudio()
{
if ((cost == 0) || (cost <= playerManagerScript.GetPlayerWallet()))
{
if (poolManager == null || spawnPoint == null || string.IsNullOrWhiteSpace(itemType)) return;
SendCustomNetworkEvent(NetworkEventTarget.All, nameof(PlayVending));
}
else
{
SendCustomNetworkEvent(NetworkEventTarget.All, nameof(PlayButton));
}
}
public void ChangeCurrency(PlayerManagerScript script)
{
if (Networking.IsOwner(script.gameObject))
{
playerManagerScript.PlayerCurrencyChange(cost, false);
}
}
}
is my current code on the button
I think I've figured it out, needed to figure out who pressed the button and assign the script using an event also so then it would know who to play the audio on
are you using playerobjects to play audio at the interacting player's position?
The audio source is a child of a player object that I have following their respective owner and then play audio either globally or locally depending on what I want. In this case is when they press an Interact button I want it to play globally from that specific players audio source that pressed the button
unless you have different audio per player somehow, i think it'd be much more straightforward to specify the audio clips on this script and pass the play one shot + position over the network event. it looks like you're using the playerobjects for persistent currency possibly, so if it's working well already i don't think it's imperative to change
as an aside, this check in interact isn't really necessary
if (Networking.LocalPlayer.isLocal)
interact only triggers locally and the check itself always returns true (the local player is always local)
I have a bunch of global bool/float settings that change very infrequently. I currently have them set to sync continuously.
Should I switch these over to manual sync?
My only concern is that i'll spam serialization requests while a user is dragging a slider around.
I see that each of my float values is constantly taking up around 0.25kB per second (which seems kinda high given that the size is 20 bytes and the update rate is 4Hz)
it looks like it's working after switching the floats to manual
i see network traffic flowing when i'm wiggling the slider around
you can make some internal throttling if you're concerned about the slider itself, but even just calling request serialize every time the slider event fires is arguably better than using continuous for a value that changes very infrequently and then in bursts
i'd also point out that calling request serialize doesn't necessarily serialize that frame
I have a comment like // TODO: make this manual sync before release at the top of one of my scripts that's a backend for a UI thing š
it'd be nice if i could make these things default to manual sync
I can't force manual sync because some of my FloatValue/BoolValue/etc. components are meant to be unsynced
e.g. values for graphics settings
what do you mean by default to manual sync?
the synchronization method on the udon sharp behaviour
above your class you can attribute with [UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
which will enforce the sync type
I don't want to force it to be on
i have a mix of manual sync and no sync
are you referring to individual variables? only variables flagged with [UdonSynced] are synced
I'm talking about the entire UdonSharpBehaviour.
oh you mean sometimes you use FloatValue for synced things and sometimes for unsynced things
Correct.
e.g. this is a graphical setting that is not synced
It's not a huge deal; i'll just need to remember to set them to Manual as I create them
personally i think i'd move that to a serialized bool isSynced that branches into using sync or not and enforce manual sync; if i was mixing their usage often
would it use two completely different variables for synced vs. unsynced?
or would it just never call RequestSerialization
i don't actually know if you can even use udonsynced variables on None
but it sounds like you're doing that currently?
yeah, here's the relevant script
most of it can be ignored
i realized that I can just do Networking.SetOwner(gameObject) if I'm making the local player the owner
...er, no, I can't
I must be thinking of something else
i'm getting 'someSyncedVar' cannot be synced on an UdonBehaviour with sync mode None in my own compile
i suppose it's because i'm enforcing it to none on the class
but i'm not sure if it will be happy with udonsynced vars on a none script in practice
I get that if I use [UdonBehaviourSyncMode(BehaviourSyncMode.None)]
which makes sense, since that means that the behaviour should never be synced
it's nonsense to have an UdonSynced attribute in it
yeah probably use manual in that case, right?
Currently, I don't use the UdonBehaviourSyncMode attribute at all
Some FloatValue instances are set to Manual, and some instances are set to None
and that's fine
i think going back to how i'd branch, i'd probably enforce manual but early return on the deserialize event and never call request serialize or ownership related things
then again i don't know if i'd trust udon to not set the synced variable back, maybe using a different var would be valid
I'll have to see:
- what happens if a non-owner tries to set an UdonSynced variable on a behavoiur with a sync mode of None
- what happens if a late-joiner shows up -- do they wind up receiving the instance master's values, even though RequestSerialization never got called?
for the 2nd part, no, a value that hasn't been serialized will never propagate like that in manual
Currently, you always take ownership of the object, even if the behaviour's sync mode is none
for the 1st thing, i believe non-owners can set synced variables, but i don't know if udon will at some point try to set it back (on manual) though you're asking about none, i don't think i've tried [UdonSynced] on None, but if it compiles and works i assume it'd be okay
I'll give this a whirl
I presume I can just do build-and-test and spawn two clients
yeah that should let you test it, i'd mostly be concerned with the behaviour just halting if there's UdonSynced vars on something you set to none, but maybe it doesn't matter
That part has worked fine in-game
oh if it works then i think you're fine
this has me curious
and allows you to use the behaviours on GameObjects that use either Manual or Continuous sync.
does that mean that the entire game object has a sync mode?
yes, for instance if you place multiple behaviours on a gameobject they must share the same sync mode
requesting serialize on one manual behaviour will end up serializing all of them on the object
interesting.
so having an udon behaviour with a sync mode of anything other than "None" will cause that object to become networked
If there's a disagreement it uses the last syncmode
Aka. the bottom component in the inspector.
I'm wondering if I'll be creating any overhead by having objects that have an UdonSharpBehaviour set to manual sync mode
(when those behaviours don't actually sync anything)
it sounds like VRChat will have to start keeping track of the owner of those objects
my instinct would be yes on it introducing some overhead, but if it's easier to not separate your types, i don't think it will matter much in practice unless you're making thousands of them
https://creators.vrchat.com/worlds/udon/networking/network-details#prioritization-of-visible-objects
this specifically is something i'd be concerned about on synced objects
that part isn't free
Manual synchronization is good for variables that are updated frequently, but quickly. It is intended for data that changes infrequently and where intermediary values matter; like the positions of pieces on a chess board.
oops
manual is still best for a slider imo unless you don't want to handle the interpolation yourself, but in terms of bandwidth i wouldn't use continuous personally
i'm not too concerned about interpolation
most of these things are either instantaneous (e.g. turning off lightning) or just controls for a process that takes a while anyway (e.g. setting a target rainfall rate)
are you noting a specific part in that quote?
this text is contradictory
it's good for variables that are updated frequently [...] it is intended for data that changes infrequently
"but quickly" is also mysterious
that might be referring to how far back they buffer the data, for reference this is how far in the past a packet from each type is on desktop
So far so good
Everything's on manual sync now
although, hm, one thing I just realized...
public float Value
{
get => value;
set
{
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (this.value != value)
{
if (synced)
Networking.SetOwner(Networking.LocalPlayer, gameObject);
this.value = value;
if (synced)
RequestSerialization();
Notify();
}
}
}
this would cause remote users to try to take ownership and request serialization
yes
ah ):
I personally don't like field change callback
fieldchangecallback is kinda sad compared to the actual deserialize event imo
yeah that thing annoys me the way it's designed
I'll have to split it up
I really wanted to like it, but I don't.
Now that I'm using manual sync, though, I can just react to deserialization
You can use OnDeserialization with Continuous sync too.
i think they might just mean relative to none
oh
oh, you can?
but yeah you can use deserialize on continuous too
i guess that makes sense
It calls all the same events
So I'll do it like this now...
public override void OnDeserialization(DeserializationResult result)
{
Notify();
}
public float Value
{
get => value;
set
{
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (this.value != value)
{
if (synced)
Networking.SetOwner(Networking.LocalPlayer, gameObject);
this.value = value;
if (synced)
RequestSerialization();
}
}
}
although, this would make it less smooth looking locally
why's that?
since it'll only send out events (that's the job of the Notify method) each time serialization is performed
This won't work locally
Because Notify() doesn't gett called locally
does OnDeserialization not get called for the owner?
I was going off of the example in this video
Oh!
I missed the SendCustomEvent at the end of the "Send variables" block
Okay, that makes sense.
I was worried I'd wind up notifying twice.
public float Value
{
get => value;
set
{
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (this.value != value)
{
this.value = value;
if (!synced) return;
Networking.SetOwner(Networking.LocalPlayer, gameObject);
RequestSerialization();
Notify();
}
}
}
public override void OnDeserialization() => Notify();
Perfect, that'll work
I was also wondering if I needed to take ownership before setting the value
but I imagine that only matters when it's time to actually sync
it would be a bit silly if udon aggressively checked that the moment you set the variable
as long as it's the same frame it doesn't really matter afaik
i don't think it actually matters, but logically i think it's better to put it before the set
in terms of reading it
If you set a synced value as a non-owner without changing the ownership the value will still change.
Your RequestSerialization will fail ofc. though.
yeah, and then the value will eventually get replaced (either by continuous or manual sync)
I don't remember if the value gets fixed the next network tick or if it gets fixed the next time you receive sync.
this means i can move the sync logic into my base Value type
less duplication, yay
base class:
public override void OnDeserialization(DeserializationResult result)
{
Notify();
}
protected void TrySync()
{
if (!synced)
return;
Networking.SetOwner(Networking.LocalPlayer, gameObject);
RequestSerialization();
}
derived class:
public float Value
{
get => value;
set
{
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (this.value != value)
{
this.value = value;
Notify();
TrySync();
}
}
}
and i tossed the callback attribute
what
Would you be able to use U# 1.2? I think generic classes would be a good fit for this.
I am already using that!
I hadnāt considered using generics
They do make handling the base Value type more annoying
Perfect!
Thank you to @minor gale for testing this for me
public abstract class SyncedValue<T> : UdonSharpBehaviour
{
[SerializeField] private bool synced;
[UdonSynced] private T _value;
public T Value
{
get => _value;
set
{
_value = value;
TrySerialize();
}
}
private void TrySerialize()
{
if (!synced) return;
RequestSerialization();
HandleSerialization();
}
public override void OnDeserialization() => HandleSerialization();
protected abstract void HandleSerialization();
}
public class SyncedBool : SyncedValue<bool>
{
protected override void HandleSerialization()
{
// SetValueWithoutNotify on your ui elements here or something
Debug.Log("Bool was serialized! Value: " + Value);
}
}
Instead of making the class SyncedBool I would instead make classes like CheckboxField, FloatSlider, IntSlider, etc.
I separated the actual value from the UI control
š
There could be many ways to set the same value (notably, a portable menu)
I personally don't think that will be necessary with this approach.
I generate a menu at runtime based on an array of values
Nice, that's neat
Itās very close to what Iāve done in standalone games
Why would a portable menu be different?
Oh, you have a centralized value and multiple UIs that change and read it
Thereās only one value. There can be many menus that control the value
I see
Yeah
I may have fun physical controls, too
at like 7s
the Weather Machine has been set to 44
scary
Can I send networked events with parameters in U#?
I see that they're available now in Udon Graph
yeah, the namespace is
VRC.SDK3.UdonNetworkCalling;
you attribute a function with [NetworkCallable]
I think "annotate" is the word there
Unity refers to those doohickeys in brackets as attributes though right
i think it's technically an attribute
Ah, neat
I'm using the U# beta, and I figured that would need to be updated to allow for this
Correct.
Would you like to hear me recite the entire section of the C# standard
haha
I'm not really from the C# world
"attribute" seems very much the wrong word, but hey, whatever.
They can be applied to many different things
you can attribute something to someone, something can also have an attribute, they don't really have the same meaning
and they describe an attribute of the thing, yes
...actually, why does this work?
https://github.com/MerlinVR/UdonSharp/releases/tag/v1.2.0-b1
this is from November of last year
like did you get u#1.2 from releases or latest?
I just saw that the NetworkCallable attribute exists; i haven't actually tried to do anyhthing yet
do you actually have access to the new network calls?
i.e.
NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)this, NetworkEventTarget.All, nameof(PrintMessage), someParam
I do
does it compile š¤ ?
I'm guessing that the U# code won't actually work out correctly
here's the one with support for network events with params
https://github.com/MerlinVR/UdonSharp/commit/e28dbc50d56302562d32050affe918ee7983b2ee
yeah, this kind of thing compiles
I presume that's part of the World SDK, and not provided by U#
I'm just a little confused about what part is U#'s job
it compiles u# to udon assembly afaik
right
and NetworkCallable exists, since it's part of the VRC SDK
But I presume that U# will not correctly convert a NetworkCallable method with parameters into udon assembly
(until I update it)
no, any custom event that does not have NetworkCallable will be considered a "legacy" event, and will only work how the old events do (with no params)
right, but I can currently slap NetworkCallable onto a C# method (with parameters)
it probably doesn't interpret the method itself any differently without support for it
like u# probably ignores the attribute if it doesn't recognize it
actually, wait -- do I have to use NetworkCallabke.SendCustomNetworkEvent to invoke a method on an UdonSharpBehaviour over the network?
i use the old syntax normally because it's less verbose
you just add params after the method name
SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.All, nameof(SomeMethod), someParam);
If so, then this makes more sense to me
it's not like U# has to magically turn this.Foo(123) into a network event
But I'm just really fuzzy on what's going on here
you can either use NetworkCalling.SendCustomNetworkEvent or just the normal SendCustomEvent
(as the normal one just calls NetworkCalling.SendCustomNetworkEvent under the hood anyway)
EXTERN, "VRCUdonCommonInterfacesIUdonEventReceiver.__SendCustomNetworkEvent__VRCUdonCommonInterfacesNetworkEventTarget_SystemString_SystemObject_SystemObject__SystemVoid"
this is what it's doing with 2 params passed in, it passes them by object
Oh yeah, I understand that much
I think I'm getting it. UdonSharp doesn't actually have to do that much to "support" network events with parameters
Most of this commit is just adding convenience methods to UdonSharpBehaviour
It's not UdonSharp's job to even understand that networked events exist, really
this really says a lot about society
I've been treating U# as a very mysterious magic box
and it seemed natural that it'd need to do a lot of work to support custom networked events
either way, i can still update U# to the latest commit
I'm pretty sure U# doesn't do anything more than convert C# into the udon assembly that runs on the Udon virtual machine
updates to U# are not updates that what Udon can actually do, for example
Yeah
U# does need to update to correctly allow NetworkCallable methods to be invoked (by emitting the appropriate metadata), but that's about it
and just to clarify, VRChat's version of U# auto updates with the SDK update, so you don't need to worry about that in that area
i'm thinking a bit about security
suppose I have an admin panel that lets you control synced map values, like time of day. I only want to let certain users control these things.
The most obvious option is to hide the panel unless you've been granted permissions (by having an event sent to you by someone who already has permissions).
Of course, someone with a modified client could just turn that variable on and then interact with the panel.
But, at that point, is there anything I can do? Surely they can just take ownership of whatever objects they want and update synced values.
So, is there any point to doing anything beyond hiding the controls?
If there isn't, it will save me a lot of work!
This world is going to be used for group events where this is a non-issue, but I do want to try to design my systems more correctly if I can
since this release they include signatures to stop modified scripts when joining at least, in terms of networking and someone maliciously calling your permission sets it might be possible if they were persistent enough. i don't think it's something i'd design around
I'm unclear what that even protects against
I guess it makes it more annoying to load a modified version of a world?
yes, they can't check the bool beforehand and load in with it already set
I guess loading a modified world is easier than getting around the anti-cheat and modifying your client
One alternative I thought of was for players to send a networked event to the instance master requesting to change a value
the instance master would decide if that was acceptable and then update the value + request serialization
imo that might be best practice anyway to prevent race conditions that come from setting owner + vars
I would use a one way hash so somone with a modded client would at least need to try to break that first
yeah, it does feel like a more coherent way to handle it anyway
I want to be able to tag players with specific roles and then check if they have those roles later
there are cases where the master won't be responsive (suspended player or in the process of disconnecting), vrc somewhat recommends against doing logic through the master because of it, but i think it's an acceptable tradeoff depending on what you're doing
https://creators.vrchat.com/worlds/udon/players/#get-issuspended
https://creators.vrchat.com/worlds/udon/networking/ownership#the-instance-master
hm, so synchronization can still happen even if the instance master isn't responding?
there's a backing server/relay yes
in vrc's ideal world you'd use ownership acquisition to allow your logic even if the master isn't responsive, but you can have race conditions as a result
like if Player A decides they want to give permissions to Player C
if at a similar time, Player B decides they want to give permissions to Player D
they both request ownership and update the synced array
one of those hits the server later and ultimately wins, if we imagine that B's change "wins"
Player D now has permissions, but C does not because the change from Player A was dropped
maybe dropped isn't the correct word, but Player B's winning synced array didn't include the changes from Player A
ownership acquisition doesn't make much sense to me
you instantly acquire it without having to wait
without this callback (OnOwnershipRequest) it's not possible for it to fail, so it's assumed that you gain it
though even with the callback, it's assumed you win if you return true on the request locally and the only indicator you lost is it firing back ownership transfer to the original owner. it's not great
https://creators.vrchat.com/worlds/udon/networking/ownership/#transfer-events-diagram
I guess I'm trying to create a "server-authoritative" system here
central logic is great for centrally held information
i'm unclear if vrchat's networking architecture makes that any more secure than just winging it
i think at the point of what you're worried about they could gain access in either case (via a faulty ownership call and variable update or a faulty network event)
though you could reject their ownership and the server wouldn't respect it if you were using OnOwnershipRequest to deny it on the original owner's side
cant figure out why my gameobject wont disable if another player walks through it... im trying to make multiplayer collectibles
// Collectible.cs
// this isn't doing its job properly!!!!
public override void OnPlayerTriggerEnter(VRCPlayerApi player)
{
int index = transform.GetSiblingIndex();
collectibleManager.Collect(index);
}
// CollectibleManager.cs
public void Collect(int index)
{
if (((collectedMask >> index) & 1) == 1) return;
if (!Networking.IsOwner(gameObject))
Networking.SetOwner(Networking.LocalPlayer, gameObject);
collectedMask |= (1 << index);
count++;
if (count >= collectibles.Length) lockedObject.SetActive(true);
RequestSerialization();
UpdateAllCollectibles();
}
public override void OnDeserialization()
{
UpdateAllCollectibles();
}
private void UpdateAllCollectibles()
{
for (int i = 0; i < collectibles.Length; i++)
{
bool isCollected = ((collectedMask >> i) & 1) == 1;
if (collectibles[i] != null)
collectibles[i].SetActive(!isCollected);
}
if (count >= collectibles.Length) lockedObject.SetActive(true);
}
the trigger enter event will fire for everyone who sees a player walk into it which probably isn't what you want; it looks like you're trying to unlock some object when count meets the collectible amount?
i think using what i assume is an active state bitmask is a bit overkill while you're debugging, if you know that part is working for sure then fair enough, but i'd probably go for a synced bool array to ensure it was working as intended
Yah, it works a bit like schrodinger's ownership, lol
You'll have to view it as an eventually reliable system, which tbh is probably the best way to view your networking systems in general
Does anyone know why Object Sync is bugged all of the sudden? Started noticing it on my props and I tested it by adding an obj sync to this random sphere. Locally the object is fine, as soon as someone else grabs it however it lags. Has anyone else encountered the same issue?
is your world overloading the network limit?
I've also witnessed this happen for prints in another world I was just in (ZZZ New Eridu) when this wasn't an issue previously. There may be something going on with pickup syncing once released. Further testing may be required
That is because whoever grabs it becomes owner, starts simulating physics and syncing its position to other players... in other words, you no longer simulate the physics and rely on the new owner to tell you where the ball is supposed to be.
And judging by the Hz of that ball, it could be the other player is being throttled because they are sending out more than ~9 KB/s
I don't know why it isnt being interpolated tho
which it should
Weird thing is I tested it with other friends too and they all witnessed the same thing happening
can u post your vrc pickup in inspector?
I will when I get home
do you have Allow Collision Ownership Transfer? it should be off
but it doesnt fully explain why
Yeah think that's on by default too, I'll try disabling that once I return
but turning it off will rule out any ownership issues
you might also have Force Kinematic On Remote turned on by accident
I remember that one being off so it's probably not that
Where do I see that?
These are the tools you can use to debug your worlds in-game.
if suffering is anything over 0 for the current owner of the object, everything synched they send out will be laggy
Yeah
suffering remains at 0
https://gyazo.com/c7ec159952238167ddb32c22379780bc btw this is the pickup in the inspector, literally just a sphere I added and it lags like the other objects
when you open debug menu 4 how many bps does the object have?
or is it menu 6 I forgot
and what is your global Kb/s output?
around 230
on which debug menu is that visible lol
oh wait I think I found it, around 1.700
its KBytes Out right?
yea, and total output throughput is good reference, 0.5 is 50% which is what serializations are capped
for me it's 0.05
Does this mean that if I try to change ownership, but the original owner rejects the change, any changes I make will eventually be lost?
(possibly after appearing for other players?)
in this case they won't be corrected locally unless another deserialize fires, but their rejected change won't be applied for other players
so, if the owner of an object is currently non-responsive, taking ownership and setting a value won't propagate to other users until the original owner becomes responsive again?
i'm unclear on how the data is moving around here
this is only the case if you override OnOwnershipRequest, that explicitly requires the original owner to authorize the transfer afaik (by returning true); i don't know if there's a timeout
outside of that, the server just allows the transfer of ownership. you can even set ownership on other players
Having the same issue. Im making a car system and all of a sudden the networking is jittery even with interpolation. I worked fine before, now it dose not. I suspect something might have broken on VRchats end because I'm struggling to fix it as well.
Yeah cuz for me the problem literally appeared out of nowhere, all I did was add new models to the world and boom the jittery suddenly became an issue
Did you update your SDK to the latest version?
yeah with creator companion
Yah I do so as well. I believe that's is when I noticed the issue.
It might be wise to uninstall the latest version and install that last one. Ill do that to see if it fixes it.
I might try that too then yea
just tried it, sadly doesnt seem to fix it
I'm sure there's some explanation, we def can't be the only ones dealing with that
It happens in my other worlds as well. I still think this is a VRchat wide thing.
That would suck because it any world used a item or phys object it wont look great.
If the ownership transfer gets rejected, the person who failed to claim ownership will received on "OnOwnershipTransfered" even indicating as much; does receiving said event not also force update to that owner's latest state?
i tested it a bit earlier and no, unfortunately it doesn't revert the value

I just want to make cool car now it broke! Love vrchat. Hope it gets fixed soon.
All we can to is wait.
I suppose if it's really important you'll need to save the old value, apply your changes, and then revert to the old values if "OnOwnershipTransfered" is triggered
Your cars are pickupable?
No but they use the VRC object sync. Thats whats broken.
here let me load up my project where I can have over 100 object synced objects and see if anything has changed within the last day or two
I would expect that to only happen when it syncs again, yea
Load up a 2 client build and test you should see the jitter on remote clients.
Because what you are describing is different from what that canny is describing
Yep... it is broken...
Well every pickup, that you want synced, uses object sync so its the same issue im just not using the pickup part.
You can use player pickup without object sync if you want.
The actual issue I suspect is triggered by an object sync changing ownership.
Actually I have a way to test this!
wait... that was some unexpected results! I'm suffering at levels that were previously significantly within VRC's limits
Hopefully they fix it soon because I was hoping to playtest my world with a friend tonight
OK so I have a weird one today. I have a bug that I only see when I play my game with one of my friends in Australia. at first I thought this was a bug related to ping but science proven that is not the case. I used clumsy to bump my ping with vrchat up to 900.
and while playing with 900 ping with other people the game worked flawlessly with no bugs. if played with other NA players.
a bit about the nature of the bug itself. in my game a to make a move local client side validation is done and then the move is sent via the new beta networking calling function with parameters. The receiver then runs the exact same validation check as a redundancy. if the check is valid it accepts the move if the check fails it drops the move.
Sometimes specifically on an Australian network regardless of witch player is the sender or the receiver. the client side validation will pass. it will move to the receiver, and the receiver will get the correct data, no race conditions. no functions are firing too soon. but somehow will make the incorrect choice in the if statement even though it made the same calculation with the same parameter as the other client.
I have no clue how network conditions can change the result of an if operation but what im mainly looking for is ideas on how to reproduce it without needing a player from Australia. Any ideas to what the trigger is if not packet loss or ping the only other info I have is that the other player was also running on linux at the time.
bit of a shot in the dark, but you aren't using a player's local time anywhere, are you?
from a networking standpoint, if a client considers a move valid and enacts it, you shouldn't reject it on other clients at that point imo unless you wait on authorization on the initial client (more like a pending move)
don't know why it would be different, but float precision comes to mind
the reason is that, its a 4 player game where each players move could change the validity of other players moves. so there are rare cases when there are moves that where invalid at the time of click but only beacuse the data that would have told thier client hasint arrived yet. the client can think the move is valid in that moment
but this was not one of those cases in the test run
i really think you should consider running all the moves through a central source (like the master) if that's the case
thats exactly whats happening just not using master
i thought you were describing something where Client A enacts a valid move, but Client B rejects it, leading to desync?
unless you have Client A revert the move
Did you try your game with the other settings on clumsy enabled? Mainly the "Drop" one although the "out of order" and "duplicate" ones might also be worth it.
well in my senario one of those two clients is the owner of the game. and the other is just a player
but even when the owenr is making moves it follows the same structure for consistancy it just sends the request to itself
oh so you make the request to the owner and they adjust the game state?
exactly and then the owener seralizes it back to everyone
so this is a log from when I caught the bug in action
and ill paste some relavant code in a sec
this is the one thats returning false when it should not those extra logs at the bottom are not present in the above screenshot I added them after the fact so I can get a clearer view the next time I see the bug
when it says card played 14 4
14 refers to the calculated value of the card and 3 refers to the suit diamonds in this case
the log card not accepted would run if the above function returns false essentialy. and right after that it prints the values of what went into the function
is the suit an enum?
yes
and as a reminder this is a case where 1 client got true and another client got false runing the same check
Are you making sure that every single value you receive from the networked event/sync is correct, even those derived indirectly?
it's hard to tell without seeing where you deserialize* the data to the resulting card i think
this bug occurs far before we get that far but I can zoom out a bit and show some more for sure
it occurs before you get the data? sorry i should clarify, i didn't mean OnDeserialization, i meant where you get the synced data over the event and construct the card
this is where the request gets sent
this is where the owner gets it
ive collapsed a non relavent if statment that we do not traverse though when we are going down the bugged route
i think i'd use and pass them as ints instead of bytes while debugging
this casting makes me uncertain, but i can't tell
I dont think any of us will figure this out unless we find out what the reproduction conditions are. I know if we were to play it right now the chances of us hitting this bug are close to none. but when I play with my friend from austrailia its garenteed to occur about 80% of the time.
Id like to focus on how to cause it so I can farm more data to learn how to fix it
are you sure they aren't just making a move as a remote client without the correct picture of the game state? you were describing using clumsy to inflate your latency, but if you were the owner you would never reject your own moves
I thought about the casting but logs confirmed both clients casted the same ressult and got differant answers anyway
exactly the issue only occurs when the non owner player makes the move however it does not matter witch one of us was the owner as I tested with us in both positions
I have a feeling it has somthing to do with the network calling system specificaly. beacuse before I had a differant method to do the same thing. and this same player was testing for me back then too. we never had this issue. but there were many others
Id would most like to know the netowrk conditions nessisary for the bug so I can more freely reproduce it and adjust logging to get the info I need
but as it stands right now I cant reproduce the bug whenever that player is not around. so I litteraly cant even test if ive fixed it or not. thats why Im more intrested in reproducing it rather then fixing it right now
do you know that the move they made was valid at the time of the owner receiving the event?
yea they sent me their logs. and I also expeerianced the bug from both positions
but from the owner's perspective
their move could be valid but behind by latency if a game state update was in flight
yes I played several rounds from the owners perspective and several from the clients perspective
I cant imagine the owner being behind the client
its the owner that makes the wrong decsion
i'm saying if some client makes a move they believe to be valid, but the owner receives an update or changes the game state before that request hits them, the requested move may no longer be valid; which sounds like what your system is meant to stop, but i'm probably misunderstanding if you're saying it's acting counter to that
yes thats a case that can happen in correct flow. but im talking about a case where that explicity did not happen.
move rejection is supposed to happen somtimes. but this is a case where its a misfire
like let me give you an example
lets say the defender has 1 slot remaining
and there are 3 players who all have cards that can fit in that slot. only one of them will be able to place their card
so if player a clicks and the 0.1 secconds later player B trys to click they may have not gotten the packets from player a's move yet so they will still think theres room left
but this is a case where that clearly did not happen
but like I said right now a solution means nothing without a means to reproduce as theres no way to test the solution otherwise
Can anyone think of what characteristic an aussie network might have talking to an NA network, other then ping or packet loss that I might be able to artificualy create?
again was unable to reproduce the bug with 900 ping and 70% packet loss. so its not beacuse the code is sensitive to those
was the owner accidently set somewhere else in the project?
is there any checks or loops anywhere that happens at a certain delay or frequency in the project?
no but if there were im fairly sure that would cause issues for all players regardless of specific network conditions
all this lag related issue points to something is being done wrong at a specific time
so finding "time" will narrow things down
ive allready tested the arival time and execution time of all the functions everything is happening at the correct times. Ive added more logs that would help me better understand where its going wrong but. I don't know how to reproduce the bug. is the main issue.
I dont think there's enough data to understand the issue yet. I intialy thought it was a timing issue however disrupting the timing of events seems to lead to correct execution regardless so thats led me to rule out timing as an issue
if I shoot the ping up to 900 the code will correctly wait before proceeding
(what was the original issue? i have goldfish memory, like gpt)
i have one play tester who plays on linux and is in australia. during my last test with him we descovered a bug that only happens if he is in the game.
If he is in the game it happens to all players regardless of who the owner of the table is.
if he is out of the game the bug does not occur
the bug only occurs when a non owner makes a move
the offending code is running on the owners machine
the bug is as follows
non owner preforms a validation check, it passes correctly, the owner then gets all data exactly as it should, then runs the exact same validation check but gets a differant result from the client despite preforming a seemingly identical calculation.
again the reason for two validation checks is somtimes the non owner can have a slightly out of date view but this test case is a reccorded case of the client having the correct up to date view at the time of pasing validation
other side notes I have a suspsion there is somthing happening under the hood with the network calling. as this bug emerged after introducing it to the system. but I cannot prove or disprove this. I am seeking to find the conditions that make this bug possible
I might just have to wait for that particular tester to become available again to collect more data
this is some serious "500 mile email" weirdness
yea my project is rather large and i regularly find and fix bugs quite often im fixing 4 other less serious ones as we speak. I only come in here mainly for the real big wtf ones XD
and I never expect to find an answer but somtimes somone says somthing that leads me to look at it another way
did u check if this was null?
no but if it was null we would crash when we try to do anything with the DurakPlayerV2 Object
I welcome and encourage you to keep speculating down that line. but evan if you landed on the correct reason. we would be unable to varify if we cant steadily reproduce the issue. we would litteraly have no idea if the fix worked or not
so this is the difference?
yes thats the one value that ends up opsite from what it should be
everything else including the values that go into getting that ressult are correct
basically what seems to be happening is a weird situation where 2 + 2 = 7 somtimes (as an analogy)
lets back trace it... i can see this being set to false, where does it get set to true?
again this is how that is decided
however weather its line 30 that is returning false is unknown to me at this time
I would like to add a log there and check but again I dont know the network conditions required for this to happen
if we ignore line 30 all the other numbers should pass all the other checks.
so there are two posiblities. theres a weird network state that causes the owner to do wrong math. or there is a weird network state that causes the owner to have a more out of date view of the value on line 30.
both posbilites seem very unlikely to me yet theres no evidence for anything else yet
is arkCards synced?
yes
and cards is totaly static never changes set at build time
i don't see a variable named cards in that screnshot; are you talking about atkCards?
in line two of tektons screenshot
oh, there it is
cards[atkcards[target]]
atkcards is just an array storing indexes that point to a card in cards
how and when is atkCards synced? I mean that seems to be the variable that can be different
that would not happen until after this incorect decsion is made.
the chain is pretty simple.
client validates -> if pass -> network call (card I want to play, where I want to play it, how I want to play it)
object owner then gets that -> validates again -> writes new data to sync vars -> requests serilization on all objects it modified.
the failure point is on the owner side validation
so the serlization shouldint be part of this problem. as its doing its job correctly
why would owner fail validation?
in a normal case. maybe the clients view was out of date and made a wrong calculation
so in that case the clients paramaters would differ from the owners
but this is a case where they agreed but still got a differant validation ressult
I can see other parameters being send (as part of the event parameters) but I dont see atkCards being sent
they dont need to be sent as they are exlusivly managed by the owner the client can has read only acsess
atk cards represent cards being placed againts the defenender.
as a defender you need to beat them one at a time
so lets say theres 3 cards infront of me so atkcards might look like [20,15,7]
and I want to play the card 2 againts that 20
so my request would send
2, 0, true
2 for card ID that I am playing
0 for index in atkcards to fight againts
and true to say it should be beating the card and not attemping a transfer (another mechanic we dont need to talk about for this bug)
the only difference in that code that can produce a different cardAccepted is atkCards, as far as i can see
correct
but if there was anything wrong with atkcards there would be way bigger script crash level issues
this is basicly the operation that is happening
if atk cards was wrong on ither end then we wouldint even get correct card textures showing
this screenshot is the game working as intended in the case of the bug the card wouldint be able to be played