#udon-networking
1 messages · Page 21 of 1
ye
unless you want to wait for the infinite loop to end before checking
hmm...
what if you just counted how many admins there are. and if the admin count equals the total player count, then it's kind of implied that all players are admins
my thought process is some kind of loop to grab the player ids from the api list and compare it to the ints inside the datalis
hmmm that is true, any risk of it being messed up?
I think that would be up to your adding and removing to the list being reliable
yea kinda
that and maybe editing the button to say "List Empty" or other related fields
i believe i should add some feedback logic and maybe reset the index
sounds like a good idea to me
alright ill try it
gonna test it now
dang
it broke
i think a infinite loop happened
awesomeee
did you see it log "No More Staff to Add"?
lemme check again
yea it def went through the infinite loop, that's usually how it behaves
it'll hang for a bit and go "welp you're clearly an infinite loop. zap" and crashes the script
added all 3 players and saw no more staff to add but the ui isnt consisnt with it
well
sorta
i see empty!
interesting
this time
it wont crash
but...
its being weird
its pasting the same name
ah. because that's the player at index 0
which we told it to set the index to whenever the list is full
also, it removes the names from the staff list as intended but not from the player list. (UI) wise
i also gotta update the button UI each time something is added or removed
because itll show empty or previous thing
wasn't the plan not to display players in the right-side list if they are an admin?
yes
it should take them off that UI if they are staff
i think i see why the duped names is happen
gonna dig into it and see
welp
iv just screwed up a lot :/
i did some changes to ideally fix the UI problem
and its working better because it actually shows the right name
heyo
not bad
but
if i use the staff arrow
and move it over say once
it removes 2 names then gets an out of bounds error
if i dont touch the arrow it doesntb reak
well that's good. tells us precisely where the issue is
yeah there is a mild issue too
with the arrows on the playerbutton side
where it doesnt appear to fully update
when i add max staff and press the right player button i get out of bounds
hm
public void RightArrow()
{
if (playersList.Length == staffList.Count)
{
playerButtonUI.text = "Empty!";
currentPlayerIndex = 0;
Debug.Log("Right Arrow Cannot continue (No more players to add!)");
}
bool isStaff = false;
VRCPlayerApi selectedPlayer;
do
{
//When triggered we will move the index forward once
currentPlayerIndex++;
if (currentPlayerIndex >= playersList.Length)
{
currentPlayerIndex = 0;
}
selectedPlayer = playersList[currentPlayerIndex];
if (staffList.Contains(selectedPlayer.playerId))
{
//If the player is staff, cycle once more and keep the loop going
currentPlayerIndex++;
UpdatePlayerButtonUI();
isStaff = true;
}
else
{
if (currentPlayerIndex >= playersList.Length)
{
currentPlayerIndex = 0;
}
//if the player is not staff end the loop
UpdatePlayerButtonUI();
isStaff = false;
}
}
while (isStaff);
}
did you remove return from the first check?
having trouble finding the pre release option for udon I could swore I saw it a few days ago in the UI but now that im looking for it I dont see it
if i spam the arrows and add random remote players to staff it just becomes out of bounds
it's in the VCC, under Settings > Packages
ty
gonna take a break so i can focus better
just gonna drop this here in the meantime
its the current version of my script
its got a lot of issues rn
out of bounds error
somehow trigger an infinite loop
sometimes the player button isnt updating even though i moved the player over (thats usually when it crashes)
will these new features only work for vrchat players who are on the beta client or will it work on all clients?
all clients, it's in the world
there is no beta client for this new stuff, it works on live
ok perfect just double checking beacuse ive been on other platforms where they push beta clients along with the beta sdk
sometimes VRC does that too, but not this time
persistence was its own beta, for example
that one makes more sence its kinda big
in these new custom network events can we idenfity the caller without passing their id as a param?
yes
https://vrc-beta-docs.netlify.app/worlds/udon/networking/events/#accessing-the-sender-of-an-event
Network events allow simple one-way network communication between your scripts. When a script executes a network event, it executes the event once for the target players currently in the instance.
oh I see
well that saves another 4 bytes
Would also be cool if in a future update we could make sync groups. so for objects with a ton of data that dont need to sync together. So we can sync a targetted set of values.
for example syntax could be somthing akin to
[UdonSynced, group = 1] int myValue;
RequestSerialization(1);
I guess you could just break that up into different scripts and just RequestSerialization on whatever group you need to sync
yea what I currently do, but it introduces an extra layer of engineering you wouldint otherwise have to worry about. making sure timings are right during the cases when you sync all groups etc
I wouldint call it a must have feature but it would speed allot of things up for sure
also just woundering what happens if you give a network callable a return type other then void. will it send the return as a reply?
beacuse that would be kinda cool
no, that isn't allowed
yea thought so
finally being able to send arguments though is pretty big
i can already think of a way to ditch using synced variables
for some use-cases, not all, but low-frequency non-important values could be done without syncing now
well, you can't fully ditch them
as the concept of an Event is still the same; this is a one-time event that all players currently in the instance should get, and not apply to late joiners
yeah, it's not a replacement
but i can think of a few places in my scripts i've wanted this feature
that and finally eventarget.others
lots of cool stuff besides event parameters that I wasn't expecting to see
yeah, just out of the blue
boi i can jsut imagine how esy it'll be to shoot yourself in the foot with the rate limiting and possible out of order events, lol
the event order is guaranteed
rate limit, a bit unavoidable, but should you really be sending more than 100 events per second?
i can forsee people using it in an update loop with no protection
well, it's going to be very obvious when syncing starts to slow down because of it lol
I wouldint ditch them dosint make sence for me to do so but, I have a particular use case these can be good for
Lets say im calling one of these new network events on object A but I want the event to go to a differant script on Object B. I allready can do this with the no paramater events by wraping the call. but how would I go about this with the new system.
previously, you needed two seperate methods, and you'd do the logic before you send
now you can have one method, and do the logic after you send
which is much mroe useful when it comes to timing
what do I have wrong so far here?
what does the error say if you hover over the red underlined bits?
Not sure what matches an IUdoneventReceiver
oh, I see where you got that from - docs usage of NetworkCalling.SendCustomNetworkEvent is wrong. If you use that version, you have to provide an udonbehaviour casted to IUdonEventReceiver like this: (IUdonEventReceiver)this which the docs don't explain.
If you instead just remove the NetworkCalling part and call SendCustomNetworkEvent directly, then that runs in the context of the udonbehaviour this code is running on so it doesn't need that part
lol, par for the course for the documentation
Idealy id like the call to go cross script like this but I just wanted to get the syntax right in the more simple case first to see how it works
if you want it to cross scripts then you would do either
otherBehaviour.SendCustomNetworkEvent(NetworkEventTarget.All, ....
or
NetworkCalling.SendCustomNetworkEvent((IUdonEventReceiver)otherBehaviour, NetworkEventTarget.All, ....
either way, need to specify where it goes
The only reason
SendCustomNetworkEvent(NetworkEventTarget.All, ...
works without specification is because that function inherently operates on this so it's already within the right context. But the version inside of NetworkCalling is static so it doesn't have that context
ah I see this appears to be what I needed. for my context
state.SendCustomNetworkEvent(NetworkEventTarget.Owner, "AttackerPlayCard", cardRef.id);
Is there a documentation page better then the one I was going off of that I can use for future referance or is it still wip?
that documentation page is the best source of truth. It's quite extensive, but that looks like just a minor slip
fair enough and it does make perfect sence after doing it once
honestly happy this syntax is allot better then what I saw in the doc
holy hek I just finished transplanting my first function to this new method and the response time is noticibly faster
before there was like this 100ms delay between click and action. even on local sim. now theres zero noticable delay
oh, were you waiting until OnPreserialization to apply synced data that you're sending? You don't need to, you know
you can apply sent variables immediately
prior I would request serlization on one side and have the server send one back from the other side
so I imagine the 100ms was that ping pong
theres still a return sync but it seems to start sooner
Client >> netoworkEvent >> tableMaster >> sync back to all clients on a differant object
thats what the new flow is now
could make it instant if you do rollback networking 
that's up to your implementation though
Im not familiar with the termanlogy but I may have done it before. im bad with the names of things. like for example I didint know I was doing somthing called "lock step" before but ive done it plenty of times.
eseentially, you let the client do whatever they want, and it applies locally isntantly, send to the server and sync, if the client's value has to change then you do that, if not, everything is good
oh yea thats a great idea
in my case i can just let the server blindly over write what the client does beacuse there will be no change if its the same anyway
for me to effectivly do that visualy I dont even need to modify the values theres just one function I need to call that would represent the action visualy but not change the data
Rollback can get super complicated for games involving positional data and hitboxes, but you're doing a card game so you don't have to worry about any of that. Just need a robust event record and undo system
I just need to worry about what slot the card lands on
its just a list of positions in memory
I just realized how much pain these events are going to save me beyond that one edge case too
I was going to use network events to send players notifications now instead of making dozens of them I can just have paramaters
so far initial testing shows im not hitting the bug Ive been getting all week anymore.
but ima need to test at least 100 times to be sure
is it againts TOS to write a python bot to play my own game?
if I do it with computer vision and not touch the client
you could do it through sending OSC events
oh ok nvm no need for that much testing the bug still exists exactly as it did before but now im getting actual useable info in the logs when it happens
what does the networkcallable attribute do?
i thought public methods were always callable anyway
lets you use the new beta versions and send with paramaters
ok yea now I have way more clues as to whats going wrong in my project. The network call was not the issue but it was some how blocking the logs I needed to see
im getting outputs for everything I wasint before
im getting so many logs now im gona have to de clutter them now lol
this is all good news tho
this new attribute has been sorta teased that it was gonna happen for a bit, though I wasn't expecting it to be a thing until Soba
Instead of "everything public by default", they're moving to the system kind of being the opposite, stuff can't go over the network by default. This attribute now lets you create public functions, that can also not be network-callable
previously you'd just add an underscore to stop it from being network callable, this way seems a bit neater to me
whats the timeline for soba and is it still going to be the c# interface?
so wait on the current sdk beta you cant call public methods over the network by default?
i wanna have the arguments events asap so i was gonna upgrade, but that may break my existing stuff
also where can you see the sdk patch notes, i cant find it
with Udon you still can though, it isn't required; public methods are still callable, but "it's not recommended" to use
here's an explanation for it
https://vrc-beta-docs.netlify.app/worlds/udon/networking/events/#legacy-events-and-security
Network events allow simple one-way network communication between your scripts. When a script executes a network event, it executes the event once for the target players currently in the instance.
essentially, the "old" way of calling events are now considered "legacy events", and will still work as long as they are parameter-less
kk
the rabbit hole I went down beacuse this log never got to see the light of day XD
lookin good so far
Can you see the cards of other players ?
Hey @twin portal I managed to get the script to an almost passable state, but.. there a couple issues
- (minor issue), sometimes a player who was moved to staff will linger on the player button ui until it is updated then itll realize they are gone
- (major issue) nothing is syncing over the network, as things are right now. everything is only working locally
do you or anyone else have any ideas why? also if you see any funky stuff in here let me know, i used a bit of AI to help with the UI issues
well we haven't added anything network related yet, right
so that would explain it not working on that part
the first one can be a timing thing. Maybe the lists need to just check if they've been updated every few seconds, or just have it update when you click the arrows maybe
hmm.. yeah
i thought we did with the ondeserialization calling UpdateChanges()
yeah
I don't see any variables with [UdonSynced] on it
i think its maybe something to do with json not being udon synced maybe?
maybe because its private?
it can be private and still be networked
does it need a udon sync?
yes
also, add [UdonBehaviourSyncMode(BehaviourSyncMode.Manual)] right above the class declaration, the public class PlayerListHandler : UdonSharpBehaviour line
oki
also they do update each time you press the arrows or button
ohhhhhh
xd
im so used to things being on the same line yk?
alright, im gonna test it rq
(i want this to be done and working already, im starting to die inside)
prolly still have a ways to go
idk how im gonna explain this in a video when i only understand like 60-70% of it
and you would be right, the ui doesnt update for player 2 and non host player can't move any names over now
the script has no calls to RequestSerialization(), so nothing is being synced yet
which is a little tricker because we need the owner to make this call
i have 2, one at the end of selectplayer and removeplayer
ah.. yeah..
ah I didn't catch them
though, you only need to sync if the adminList has been edited
hmm i see
here is overview of what this script should be doing
When you press the player or staff button arrows or button themselves, it should only update for you locally.
when you remove a player or add a player we want to send those changes across the network and have the players apply the changes on their end, we need to make sure that they also update their UI so that they aren't trying to add/remove someone incorrectly
our current issues are, the Ui does not update at all
and remote players can't seem to add anyone to staff, i can imagine they cant remove anyone either
when i start a build and test i see this though
maybe the script is crashing?
This is a very common issue. JSON does not have type hints and it also does not differentiate between numeric types. The JS in JSON stands for JavaScript and in JavaScript there only is a "number" type. So when serializing any number (int, float, double) and then consequently deserializing it again, the C# JSON deserializer (by default) has no idea what type it should use for "numbers" so it uses the broadest one: Double.
So when you serialize/deserialize an int you have to access it as (int)token.Double
VRChat devs could have just implemented it differently, for example by trying to cast it to an int automatically when you access it as an Int.
But they didnt.
That makes sense so I just need to change it to (int)token.Double
Thank you for help guys its always a pleasure to have help from this community, i wish I wasn't working today otherwise I'd dig deeper into this but I'm already out of time
But thank you for being patient helping me through each problem
I'm doing my best to solve as many as I can on my own too
This system quickly became a lot more complex than I expect but I'm learning from this which is good
I want to really document and comment this out well when it's fully working so I can use it as a good reference and share it with others who want to accomplish similar things
no but this will be a setting in the future.
default setting is no one can see a card face unless its on the table or in a hand they own.
but will add separate toggles to allow spectators or all other players to see
nonono don't remove cheating, this is realism lol
Hi, quick question - I'm a bit of a beginner, but I'm trying to send a custom network event with UdonSharp, but I get an error that it doesn't exist on the source object. I want object A to send a custom network event that ends up firing something on object B.
So it's not necessary for it to be on object A since it wouldn't do anything there - is it needed anyway? Can I just do an empty method on A?
can you show picture or code of what you currently trying?
It's pretty basic as I'm still learning:
Object A
Object B
You are not currently specifying object B, so it's trying to just call it on the ButtonForTheCube script and that doesn't have that event so it doesn't work.
In order to allow for it to trigger an event on an external script, you would add
public UdonBehaviour otherBehaviour on the ButtonForTheCube script outside of Interact, and then inside of interact you would do otherBehaviour.SendCustomNetworkEvent(NetworkEventTarget.All, etc...
That will make your script capable of sending to an outside behavior, and then in order to link it up to the correct outside behaviour, you would drag and drop it into the slot which should then be visible in the inspector for ButtonForTheCube
I see. Is there no way to just "broadcast" the network event out for other scripts to receive?
It's entirely possible to build a broadcast event system but that's not what SendCustomNetworkEvent is
I see. It's more "directed" then? Like sending a specific event to a specific UdonBehavior?
yes
Alright, I'll modify accordingly. Thank you for the pointers!
Hi all, working with VRCPlayerObjects. If I unparent the object during runtime, will it break stuff (the networking aspect)? Trying to make a flashlight system where VR users get a physical flashlight and desktop users get a light attached to their head.
I am unparenting from the instantiated version, not the original
TL;DR does unparenting a child from a vrcplayerobject parent break stuff
as far as I've seen, no, I didn't encounter any issues when I did that on my on project
(how do you parent to a players head?)
well I did encounter a slight issue but I don't think it was wholly an issue with PlayerObjects; I had about 8 pickups with ObjectSync on them, and because I also had persistence on this PlayerObject, it would persistently save the object's position. So as the object fell with gravity and such, its position was constantly changing and being saved
I attempted to fix this by setting the pickups as kinematic, but incorrectly set this on the rigidbody itself, rather than on the ObjectSync component
At least, that was my theory. I fixed it by just removing the ObjectSync. With it enabled, all players besides the player who owned the PlayerObject would freeze indefinitely, basically crashing the game
so the ones stuck to peoples heads, their object sync should be kept on rather than locally update position every frame?
to make something follow a remote player you would actually not want to use ObjectSync, because it'll lag behind their position of them that you're currently seeing
flashlights as pickups though, would need it
can object sync be individually turned off like this? for the same player object
I was just looking at that, I would assume disabling the component itself with the enabled property should let you turn it on and off
hmmm... can use this for magic tricks
alright, things are looking better, no more errors and things are mostly networking now, there is a weird issue
so, when either player 1 or 2 adds the names to staff, both players see the names get added to staff (good!) however, this is the bad part. the player who added them sees the names get removed as options from the player list (good!) but the player who didnt add them still sees them as options on the player ui and they are actually able to add those fake players and both player 1 and 2 see the fake players get added and it gets all messy. any ideas? my guess is we aren't properly syncing the fact that the players are no longer available, we are syncing that they got added but dont sync that they are no longer options? im unsure
would anyone be willing to look?
idk if i can pull this off @twin portal
i just seem to keep running into consisnt issues that i dont understand how to fix
the script has grown so large i doubt anyone actually wants to read through the mess iv created
I skimmed through it and didn't see anything obvious
theres a lot of issues
might be time to learn some debugging strats
debugging is half the game when it comes to making something semi-complicated
everything i tried just seems to not work
idk where to begin
i see stuff breaking when players leave, ui isnt accurate when i add players to staff for remote players
did i just really screw it all up
idk if its just the approach iv taken
usually first thing I test is; are my values actually what I'm expecting them to be, and at the right time?
All the values you want to "see", have it print the info on Update into a TextMeshPro object you place in the world somewhere
Since it's on Update, you know you'll always be looking at the current values of your variables
like your list, for example
if something's getting out of sync, we want to check if the player actually have the same or different values stored
so i should create a ui and paste the results there each time?
yep. just a TMP wall of text will do
set the text to whatever you need to see on Update
yes
the Unity event Update
that way it's updated every single frame
no worrying about "do I actually have the data or am I just not displaying it in the UI"
just make a temporary string variable, then loop through the array and paste all the names into it, then concatenate that into the final debug text
if you wanted to save space you could just output the Length of the array
oh
but might be useful to see the names
i was about to do this
that ideally should be the same as the playerList count, but you're technically not getting that array's length by just getting the player count from the VRCPlayerApi
you can do playersList.Length instead
alright
also, there's a trick with strings so you can put variables in them easier, without having to use the + to "add" strings together
if you add a $ to the front, you can use the variable name and put brackets around it, and it'll put the variable's value there
like this: $"Players List Count: {playersList.Length}"
oh yeah i forgot i can do tha
hmm im getting an error
it aint happy
with the line here
did you assign your TMP object in the Inspector?
what part of that line is column 69
how do i check that?
if you put your cursor anywhere on the line, you should see something on I think the bottom-right corner that tells you what line and column you're on
forget how it looks in VS
i only see numbers on the side
"Players List count: "playersList.Length is the issue
removing it removed the issue
i wish i had more time. but sigh gtg to work again
thanks for the help again
I can see why that is happening
playersList is not initialized to anything when you first declare it; it doesn't get a value until FindPlayers() runs
so just need to add something like private VRCPlayerApi[] playersList = new VRCPlayerApi[0]; to the variable declaration
Alright
Yea, can do this or put it in start(). I would also do a null check in update just in case
why do it in start when you can just set it at the declaration?
Donesnt work for everything.
Mostly just localplayer but i do a lot of setup in my scripts that happen on init
right... in this case it will work before Start
assigning the local player though, has to be done in Start, since the player does not exist prior to compilation of the script
yeah i think im at the giving up point. no matter what i do, the player list is always gonna be the problem. The player list UI doesn't update for remote players when someone else adds staff ,and its not even just the UI thats bugged, its the list itself because that remote player can add the same names to staff despite the fact that they are already on it.
somehow at the start i got my 2 clients, same name but as soon as i move them both to staff, the other remote player can add them again as if there is 4 clients.
I think this is just way out of my skill range at this point. unless someone knows what im doing wrong
Trying to confirm something that just absolutely drove me nuts: GetPlayerID returns an id associated with a given player in an instance, but those ID's aren't consistant across the network?
it is consistent inside the instance across all the players, the first person joined is 1, second person joined is 2, third person joined is 3, if second player leaves and re-join they become 4, so on
I'm not finding that as the case
?
It's probably just network shenanigans, I'm having issues keeping IDs straight
the ID will be the same for all players, within that instance
@twin portal should i just give up on it.
never give up. never surrender
im at a loss
ai. useless. me, not skilled enough
im terrible at explaining so i doubt anyone can help
assuming anyone wants to at this point
its just such a big script
i thought maybe just once i'd actually be able to create a useful system that would not only help bit help others
because there is 0 tutorials out there or assets that let you do this
the only one iv seen is $30
which, you know great for them they worked hard to make it
but it would hurt me inside to buy something i can learn to make
and id much rather learn to make it myself so i can show other people
the best way i can think of describing my issue is like this
The player list UI doesn't update for remote players when someone else adds staff, and its not even just the UI thats bugged, its the list itself because that remote player can add the same names to staff despite the fact that they are already on it.
somehow at the start i got my 2 clients, same name but as soon as i move them both to staff, the other remote player can add them again as if there is 4 clients.
for example when i start up a build and test i got 2 Koko's on the player list UI
any player can add them to staff as instead
when a player adds them, both players see the names get added to staff but only the player who added them sees the names taken away from the player list ui as options. its supposed to do this for all players. but since it doesnt do that, that means the other play can somehow add those 2 names to the staff list then there is magically 4 koko's despite the fact that the script has parts in it that check to see if that ID is already staff or not.
you have a 3 second delay from ondeserialization before it finds players and then another 3 second delay before it updates the UI. Have you tried doing all those things inside of ondeserialization at once?
those were just dumb ai suggestions, even without the 3 frame delay i still had the issue
i was worried about doing it in ondeserialzation
because iv seen things just outright get ignored in it
yeah, the root of it might be somewhere else but at the very least, that delay will make it difficult to be certain what's happening with sync
but i could try
like what?
it was in a past project, i remember doing some checks in there and it just never worked
it was awhile agp
so i just assumed i should put a method instead
so are you saying i should put the find players() and put refeshUI with no delay inside of ondeserialization
I think the AI suggestions may be hurting you more than they are helping
yeah, if the goal of RefreshUI is to take the current state of StaffList and PlayerList and display them on a board, then you want to do that immediately in ondeserialization
imma pull up my older version of the script before the ai suggestions
if FindPlayers is a dependency of that, then yes it needs to be ran in ondeserialization too. Or it could be put into a separate Initialize() function and called once if needed anywhere
the idea was to run find players each time a player leaves or joins or gets added/removed then that list is fully updated then i compare that list to the staff list to see if any of the players are in staff, if they are then they should not be displayed as options to become staff
ill try that rq
@frozen igloo this is what it looks like when i add a player
but for the other player it looks like this
once both are added it looks like this for player 2
you see (empty!)
but
i can click the empty button and still add the 2 names over anyways
now i magically have 4
this is with the changes
mmk, I'm looking at the selection code to see what you're doing there
alright
so is it intended that the UI is also synced, so everyone can see exactly what the person operating the panel sees?
so, the buttons and arrows are meant to be local, but they should only be effected by the network is a player got added or removed
for example lets say player 1 had player 1 on their button ready to select. same with player 2. this is local. when player 1 adds player 1 to the list, we need to shift the index for all players so that player 2 can't see player 1 as an option anymore
idk if this would be easier to just sync the index or not
i thought it wasnt needed and would be nicer to the network to do it locally
It seems like this revolving selector UI is making things a lot more complicated than it needs to be
It would be easier to navigate and more straightforward to build if it was a vertical list of players with buttons next to them which performed actions like setting to staff
how would you do that?
there's a handful of ways to build a UI like that. Some techniques are manual and involve a fixed list of UI buttons which get enabled/disabled as necessary. Some scripts can be used to automate the generation of that. Or the runtime scripts could be built to be capable of automatically instantiating/destroying objects to fit
sounds complicated 😅
not as complicated as dealing with off by one errors and navigating up and down a list that can change out from under you at any moment 😅
i mean fair but i got no idea how to do that, let alone where i can learn it
right here ^^
its just such a garbage feeling when i cant figure out stuff for myself and have to rely on ai, or asking people constantly
it just makes feel like im not independant at all
idk if that makes sense
like i have to have my hand held to doanything
This stuff is complicated, nobody knows how to do any of this just naturally
it honestly feels like everyone knows
but me
and i dont know what i have to do to get to a point where i can be like you guys
iv been doing this on and off since 2019, whats my excuse for not being at that point yet
I've been doing this since I was a child, and went to school for it 😅
i apologize for the negative attitude, im just feeling a bit overwhelmed and frustrated with my own incompetency
it's very understandable to need to ask people on how this stuff works
im applying for a game design college very soon
because i think i need to learn this stuff properly
but idk how much of that is actually gonna trasnfer
like the networking is just a nightmare to learn
i practiced with normal stuff and it did help but as soon as i reach anything networking wise, i just get slammed and stuck
Once people already know stuff, most of them don't hang out in question-answer forums. This isn't where the communities are, this is just a launchpad. But a handful of people that you see talking in this channel like hanging out here answering questions. We do that so that people like you have the resources you need to learn and grow to the point of being able to launch yourself.
This channel's entire purpose is for questions like that. That's an excellent beginner project that will teach you some key concepts. I'd be happy to dig into that if you're willing.
spot on. I'm mainly hanging out here so often because I see so many people get frustrated when it comes to learning how to code, and I do my best to help.
also once you do know stuff, teaching it can be a good way to reinforce the knowledge. It's fun, all around
Yep, all of that, and maybe, just maybe we can help people make better content 🙂
i honestly appreciate the hell out of you guys. if i was as knowledgeable as you guys i would be making videos and trying to help people do (which i actually did awhile back but my methods were bad so i deleted them)
i figure if i can learn useful stuff liek this i can at least start making videos for people so they can learn too and i can use the videos as refreshers too
i just dont wanna post
bad or inaccurate content
but yes, i would love if you would dig into this and help me
Also a willingness to learn goes a LONG way towards others wanting to actually help you 🙂
because i feel like there is a lot of ways i, and other can benefit from having a system like this around with a tutorial and public source code
im very willing, just lost on where to learn reliably and consistently
this is my only place i got so far
ok let me start a thread
Just double checking myself. When using the OnDeserialized event, do we need to include a base.OnDeserialized() inside it to avoid breaking it as an override?
public override void OnDeserialization()
{
base.OnDeserialization();
//My code would go here.
}```
no, base.OnDeserialization does nothing, if you are extending UdonSharpBehaviour directly
Good to hear! That's what I thought, but figured it didn't hurt to ask. Thank you for giving me confidence!
Been doing a lot of editor coding lately, so I started to doubt myself a bit with UdonSharp.
Is there a method I can use to pull all PlayerObjects for all players and get one specific script from all of them?
Guessing I might have to make use of Start() on the PlayerObject scripts and call a function on an isolated script that creates a new array and adds the PlayerObject script to it.
You can iterate through an array of all VRCPlayer's and use FindComponentInPlayerObjects. (And cache the result, updating the array as needed with OnPlayerJoined and OnPLayerLeft)
Thinking your suggestion is probably better than what I was thinking. Definitely fewer lines of code to run.
Given the mention of avoiding Destroy on objects with Networked UdonBehaviours here:
https://vrc-beta-docs.netlify.app/worlds/udon/networking/events/#legacy-events-and-security
There is also no mention of Player Objects here regarding Networked Variables:
https://vrc-beta-docs.netlify.app/worlds/udon/networking/variables
There is also no mention of using Networked UdonBehaviours or Networked Variables, but mention of object ownership as if it could apply:
https://vrc-beta-docs.netlify.app/worlds/udon/persistence/player-object/#ownership
Is it problematic to have Player Objects with Networked UdonBehaviours or Networked Variables? This could be a great security feature and might be intended as such, but since it's not documented as being supported or recommended, I'm asking here.
Not problematic. PlayerObjects have networking support
My own tests confirm this as well, but there's no documentation on it that I can find that it's actually supported or has any specific limits to be aware of.
I've had folks bring up limits of Unity's Photon Engine, since we cannot be aware of everything VRChat needs internally, that makes the limits of Networking with Player Objects very ambiguous.
There are worlds that have hit 300 members, which code I've written has been in without my intervention, would that make just four variables on a Player Object catastrophic or not? It's very hard to be sure if even one Synced Variable on a Player Object is something to avoid as a general practice since other scripts using them that anyone doesn't know about could be very problematic with this ambiguity.
Depends on the data type and if all users are syncing data at the same time
Does that mean VRChat is circumventing Photon Engine's limit of how many networked properties it can use? I'd imagine if VRChat were packing strings with JSON, this could be the case. Documentation would be ideal so this isn't an ambiguous limit to avoid.
Guess for now I'll YOLO the code since it's necessary for a current project. If it doesn't break then great ^.^
I'm personally uninitiated with any limitations of Photon or how VRC handles its networking backend. Would likely be something for Phasedragon to answer if and when they see this. Honestly just fuck it we ball
Though if you're hitting 300 members in a single instance, I think you have bigger problems to worry about such as CPU exhaustion/other performance problems
I'm in a similar boat. I've had other devs on projects be very up front with avoiding Photon's limits within VRChat. I'm not about to refute them without something to reference. Would love to avoid reinventing a wheel or handler if VRChat already has something internally that makes it so creating a JSON packing string system isn't necessary on a singular Synced Variable Player Object.
I'm not sure what made you think PlayerObjects potentially wouldn't have networking. Having networked variables is how persistence works on PlayerObjects
I've done testing, yes it works. My point is I'm not seeing documentation regarding it.
Also would love to know of any limits if folks have found any. That knowledge shapes best practices.
there are no worlds that have hit 300 members lol. simply said it really starts to struggle around 100s
Actually, there has. My code was used at Furality, which had instances with multiple hundreds of players in instances with reserved slots for staff that can override even the hard limits.
they'll have the same limits as any other networked behaviors
https://creators.vrchat.com/worlds/udon/networking/network-details/
Networking in Udon can be challenging! Try to keep things simple until you're more experienced.
eh? thats a first considering they never showed that at Furality.
oh yea that one. had forgotten that existed.
There isn't mention that I can see regarding a limit of how many synced variables there can be.
around 80 players Vrc typically struggles. with synced variables.
I still don't understand how that's possible. You had to test to know that PlayerObjects had networking? You get PlayerObjects through the Networking class. It's described in its own doc page that persistent data is saved via the last networked state.
like in my own creation we had a hard limit with 80 people that each had 4 synced variables. and only around 24 bytes per person. + another 16 so 40 per person and around 80 players it started to hit data limits.
these 4 synced variables also only changed when a player requested it. and that was limited to once per 30 seconds.
I'm not asking about capability, I'm asking about limits and best practice. It's not suggested or recommended in the documentation.
@twilit pewter thats what i have been telling you 😄
4x80 synced variables. and a semi regular SendCustomNetwork every 30 seconds would cause a fair bit of problems.
That's good to know! ^.^ Thank you
but it all depends on so many dam things.
like how fast it changes.
how many players etc
Exactly what brought me to ask ^.^
cause it does seem that each player adds another extra layer of complication to how much we can even nuse.
and with my own testing and checking it does seem like each player joining the world adds to the overall data output.
Right! That's great to know, but if those are the only limits, then that's awesome.
on average with 60 ish people it would often hit 7-11 kb/s per person
Thing is, if Photon has limits regarding how many Networked Properties there can be, if that applies to VRChat, that'd be good to have in documentation too.
even if 60x40 is only 2400 bytes
the new networked events also had some additional data about networking limits
https://vrc-beta-docs.netlify.app/worlds/udon/networking/events/
Network events allow simple one-way network communication between your scripts. When a script executes a network event, it executes the event once for the target players currently in the instance.
for example, the size of all parameters in a single networked event can't exceed 16KB
That page is what brought me here to inquire in the first place. Super hyped about the new things available!
If that's all that matters, I wouldn't be skeptical as I am. Awesome if that's legitimately the case. Bandwidth limits are all we I know about in terms of limits right now.
realisticly you cant exceed 8-10 kb/s at max no matter what
A string with JSON data would circumvent any limit on how many synced variables we can use. Issue comes with awareness of other packages that assume the same thing and may not be up front about using Player Objects with Synced Variables. The number of Synced Variables in total could be problematic if it is a factor, not in terms of bandwidth, but in terms of allocation.
For example: If you bit packed a byte, you'd be limited to 8 bits, that'd be 8 variables essentially. If you limited bandwidth to 1 bit per second it wouldn't matter overall if you wanted to try using a 9th bit.
well no. the Json would at some point be to big. you would still be limited to the max of 8-11 kb/s no matter what. a Json even has some overhead just to be created etc.
That's very much a factor with using JSON, which I absolutely agree isn't ideal. Say I had 4 booleans per player, which could be 4 separate systems using Player Objects. Even if the overall impact is as low as it could be per system, there's a lot of potential for bloat even on the smallest possible footprint.
It's easy to see that this could be a factor that makes it not recommended or a best practice to utilize. That said, it depends entirely on aspects of VRChat's internal handling that we can't know about unless we either hit those limits or there is documentation for it.
Chances are high that in the most extreme use case of a high capacity instance, what is best practice will be extremely good to know.
well tbh vrc isn't ment to handle alot of throughput. so we are very limited to what can be done
No argument here. There just should be documentation to reference on the subject. I asked here in case of the other possibility, which is that someone found there was a limit or success with any kind of metric. Very much appreciate that you shared what you've been able to push.
This info right here is gold!
how would someone make a local timer?
Probably just send custom event delayed seconds for one second, then remove 1 each time it runs and display the value
isnt that networked?
I never work with network events
All my networking is just field change callbacks (property callbacks) on variables
but player positions are synced
and OnPlayerTriggerEnter fires for ANY player that is seen colliding. Including remote players
OH
that IsValid should be a branch
Is this a timer you want displayed for players to see or handled purely in code?
code i got it
fuck ur so right
I'd recommend you use SendCustomEventDelayedSeconds
thanks guys
Depending on purpose, I would use Time.Time or Time.unscaledTime or even Time.realtimeSinceStartup
Time.Time for game logic
Time.unscaledTime for clocks and UI
Time.realtimeSinceStartup is good for benchmarks
Tfw there’s more helpers than askers on a Tuesday morning
Something I've learned regarding SendCustomEventDelayedSeconds is you cannot cancel the timed event once started. You can fake it though if you may want to.
Say you only want to have one event of the timer active at a time:
- Have a boolean default to true.
- Check if that boolean is true then:
- Set that boolean to false before SendCustomEventDelayedSeconds.
- Set the boolean to true in your target event.
Say you only want the last time the timed event is fired to actually run:
- Use an integer that you add one to right before SendCustomEventDelayedSeconds.
- Subtract one from that integer immediately in your target event.
- Immediately after that subtraction, check if that integer is zero before continuing to run your intended code.
If you want to be able to "cancel" the last event (imagine you cut power to a device that was charging up to fire something):
- Set a boolean to true immediately before SendCustomEventDelayedSeconds. (Could also be before an interger you're adding to, if you're also doing that.)
- In your target event, check if the boolean is still true before continuing to run your intended code. (Could also be combined with checking an integer if it is zero, if you're also doing that.
- Set that boolean to false wherever you may want to cancel that event for.
tysm I tried to do somethin like this but my brain short cricketed
Back in Jan 2021, it was one of my first scripts since I immediately ran into such a use case.
that is correct. its not an event that is within our control. which i do find abit odd. but its easy to maintain and cancel them when necessory. all it takes as you mentioned is a bool that controls the flow and if it should keep going or not.
It also takes a count unless you want it to fire every time the timer elapses, even while the timer is supposedly already active.
Oh wait, you meant if you wanted to ignore new instances of that timer. >.< Derp.
Like only have the timer active only once at a time.
I got to head to work, but I'd absolutely add that into my post if I had more time >.< Had to warm up the car, so added it in 🙂
Thank you again!
Added another concept to that post in case it helps you out.
My issue is I really like methods that call themselves a frame later and haven’t really worked out how to “cancel” one if I need to start another before the original finishes its task
I like your previous solution, do you have one in mind for that issue? >.>
My only thought has been somehow counting how long it’s been since the method was called and if the time value you’re comparing against updates it measures a smaller time interval than expected which means you called it again so the old one dies, but it’s just a theory with no real code put behind it
Yeah maybe save the Time.time as a float variable e.g. timeFired, and in the delayed event, check if time.time is greater or equal to timeFired by the delayed time, and only continue if true. This ensures only the latest firing will run
there is no real way to do so in Udon. as there is no way to exactly know when something is done. other then by the order of execution.
you can make use of execution orders to order things even within the same frame https://clientsim.docs.vrchat.com/systems/script-execution-order/
| Execution Order | System Name | Description |
All supported attributes in UdonSharp
thats not going to do anything sadly. thats just to order when the type of script will execute. has no impact on when you can expect it or not.
You can keep a variable around that holds the frame count of the previously executed event. At the start of your method just check if the frame count is the same as the previously executed event. If it is, just return early.
what exactly is the method that needs to be one frame late and maybe cancelled?
you cant cancel it ever. once u send it it has to execute. the only time you can ever stop it from doing it if its a self repeating one. like SendCustomEventDelayedFrames that is in a repeating method. like for instance instead of using the Update method do something like ``public void _OnUpdate ()
{
// then add a check here like a boolean to stop it from executing again.
SendCustomEventDelayedFrames(nameof(_OnUpdate),1)
}`
maybe u can cancel like this
[DefaultExecutionOrder(0)]
public class First : UdonSharpBehaviour
{
public bool canFire = true;
void CustomEvent()
{
canFire = false;
}
}
[DefaultExecutionOrder(1)]
public class Second : UdonSharpBehaviour
{
// if canFire true
// do stuff
}
That's not gonna do anything. The execution order doesn't mean it will execute right after each other on top of that it would mess with any other scripts executing as you tell unity that no matter what those two always have to execute before anything else
why wouldnt it execute in order? and why would it mess with other scripts?
all the scripts by default runs in 0
it says the order can be negative so maybe the first could be -1 to force it to run before any other behaviours?
Everything doesn't run at 0. And it can go negative too
Generally it's not recommended to mess with the order unless you know what you are doinh
...?
Even unity them self tells you this
so if iKnowWhatImDoing is true, then it works
hm... wanting to do networking coding in U# to send variables about related to my script...
How should I do so? Is there a video of doing in that manner (not graph)?
It's limited. In terms of tutorials. But you. Have to check the vrc documentation for udonsharp
the traditional way to sync variables is to request serialization, there is a good video from before:
#udon-networking message
i see...
I synch the variables with OnPreSerialization, then do "RequestSerialization()"?
other way around
The sender (needs to be owner) runs:
RequestSerialization
Then on next network tick, runs: OnPreSerialization -> OnPostSerialization
Then everyone else (the receivers) runs: OnDeserialization
this is all for Manual sync btw
a good analogy for Manual sync is like mailing a letter:
RequestSerialization is "I've got a letter ready to send. Please send it at the next available chance."
OnPreSerialization and OnPostSerialization are optional for your script to work with, but can be very helpful.
OnPreSerialization fires right before the serialization is about to be sent. It's basically "Hey I'm about to mail out this letter. Is there any last-second info you want to put into it?"
OnPostSerialization fires right after the serialization is sent. It's like the mailman coming back to you with "Hey, I tried to send your letter. It was successful and it took up X amount of space in my mail van." or "sorry, sending your letter failed."
you might also like the new NetworkEvents with parameters that are currently in beta, they're pretty neat (and in a lot of use-cases, can save you the effort of needing to juggle all this serialization mumbo-jumbo)
https://vrc-beta-docs.netlify.app/worlds/udon/networking/events/
Network events allow simple one-way network communication between your scripts. When a script executes a network event, it executes the event once for the target players currently in the instance.
I think the key difference is, serialization is for syncing variables, that is having variables that are persistent and consistent across the instance (and ready for late joiners). Like an HP bar.
Whereas the new events with parameters, is still an event, and is meant to be "sent and forget", and is not meant to persist or remembered (or available for late joiners). And especially suitable for non-owners to tell the owner of a variable something has happened and should be changed, without changing the variable directly (e.g. variables that cannot be changed owners like an HP bar stored on a Player Object). So the event can say "damage by 20 HP" for example.
So I suppose it depends what you are sending and for what purpose. The two methods are for different use cases.
definitely, all depends on what you want to do
though I did devise a little system that.... I think actually worked faster than continuous sync, with the new events
basically just sent the data to everyone, but whenever the owner received the data, they would save it to a synced variable
interesting, any risk of more desync? cus i had the impression that events are less reliable but not sure
i may be wrong, maybe events dont suffer more "packet loss" than serializations (so equally reliable)
I wanted to get a bigger sample size but I had about 5-6 people interacting with the (pretty simple) test to an alright degree
even if there was a desync, eventually the owner would sync their saved value with everyone so things would recover quite quickly
I imagined the system like how something would work with an FPS game, where everyone is shooting the same enemy very quickly and tried to design around that
So multiple people can all affect the same "health" of the enemy, but it would fluctuate a little. I think if the synced value was just in a healthbar without a visible number, it wouldn't be noticeable
ah interesting!
I plan on synching multiple variables from certain scripts, which will be used by other scripts for world visuals. Synchronizing them and persistent (for late joiners) will ensure that everyone sees the same (or very similar) thing.
VRChat's networking works really well for that kind of stuff, should be pretty doable
If what you're doing is so important that you're worrying about desync then you shouldn't be using network events for it.
What kind of desync are you worried about that would discourage using networked events? I've found using networked events (especially the new ones with parameters) to be incredibly powerful, personally.
You don't have the issue with continuous sync where it is always running and consuming your networking bandwidth and you avoid the risk of intermediate values being lost like with manual sync. Also events with parameters allows you to transmit data with more contextual meaning, reducing complexity and the amount of extra information you need to accompany with it
Also, does anyone know of a simple way to determine when the real time that an event was sent by a remote player?
I was thinking of having a single object whose only purpose is to manual sync once at instance creation and then when people load into the instance they read the sendTime in DeserializationResult to determine determine what their Time.realtimeSinceStartup is in relation to the instance time. And include the instance time the event was sent at as one of the parameters.
But maybe there is a simpler way of doing that?
Alternatively would Networking.SimulationTime(NetworkCalling.CallingPlayer) gives me a reliable idea of when the event was sent?
It's not that they're less reliable, it's that they are ill-suited to the task. You can't make any guarantees about state with them. But yeah, what you said about using them for send and forget stuff is great
No, simulation time is a whole other thing. It tells you how far back in time the player's sync is being simulated. But each object has its own independent simulation time dependent on a variety of factors, and network events don't even utilize simulation time.
huh, I could've sworn that network events with params came with a sendTime param you could access, but I guess not. In that case, sending the current time as an argument would work
I'm just not sure what my options are for sending a "current time" that is usable by remote clients. For example I assume Time.realtimeSinceStartup would not work. I think my manual sync idea would work, but I always like having more options.
I think DateTime.Now.ToBinary() and DateTime.FromBinary would work, but test it to be sure
I'll look into it, thanks!
Ahh it even takes into account different time zones; nice
Networking.GetServerTimeInSeconds() and Networking.GetServerTimeInMilliseconds() are also a thing
but you may have to deal with wraparound and/or precision loss with those
Is the server time unique to the instance or is it more global?
server time is a measure of how long the associated photon server has been open. Servers manage multiple instances and stay up for months, so these numbers may overflow and wrap around, sometimes being negative
I was worried that is what you meant by wraparound issues, lol
Thinking about it, as long as you accept that the instance being open for a significant amount of time will cause issues, it shouldn't be too hard to work around the wraparound. But at that point it probably isn't anymore elegant than the manual sync method.
(and if my math is correct, a "significant amount of time" is over 24 days)
I think this should work, right?
//returns true if the first network time is sooner than the second, taking into account wrapping
public static bool IsNetworkTimeSooner(uint firstTimeInMilliseconds, uint secondTimeInMilliseconds)
{
//check if a wraparound has occured
if (Math.Max(firstTimeInMilliseconds, secondTimeInMilliseconds) - Math.Min(firstTimeInMilliseconds, secondTimeInMilliseconds) > 2147483648)
{
return firstTimeInMilliseconds > secondTimeInMilliseconds;
}
else
{
return firstTimeInMilliseconds < secondTimeInMilliseconds;
}
}
The SDK video players use server time to sync their playback, but it is a simple calculation of local time offset from server time, there is no correction for wrapping or anything. I wonder if you really need it, do/can instances even stay longer than a day?
And is server time the only way of time measurement that is consistent across players (since their local clocks can be wrong)?
I mean how accurate is it, is it automatically accounting for network latency? Or is there any extra calculation needed for very precise syncs?
Networking.GetServerTime wrapping around to negatives is not caused by the current instance being open for a long time.
You can join a brand new instance and have the Networking.GetServerTime already be negative.
Wouldn't reading it as a uint make that irrelevant?
No?
Why not?
... instances even stay longer than a day?
Again, this wraparound does not require a long running instance.
So? I am already expecting that behavior
You need extra calculation if you want to account for network latency. @minor gale has played around with implementing Network Time Protocol (NTP) in UdonSharp for this purpose.
I'm assuming that Networking.GetServerTime that is running on the server is being treated as a uint that keeps counting up until it hits uint.MaxValues at which is flips over to 0; that is why when the instance connects to a server that has been running for awhile and you read it as a int you get a negative value and get confused
I think that is an incorrect assumption
it overflows because it's being treated as a signed int, no?
It would overflow regardless of what type of integer it gets treated as.
is overflows to negative because it's signed, that is
I'm a little curious why that would be signed
Well, yeah, but the problem isn't that it becomes negative. The problem is that it becomes smaller.
sounds like a proper headache to account for
Actually it's irrelevant if the bytes are being treated as signed or not as int.Max [2147483647] + 1 is the same value as uint(2147483647 + 1)
So just read the server time as an unsigned value and you'll be good
.
It would overflow from uint.MaxValue to 0; that's what my snippet of code above is for
It becoming negative is not the issue. It being unsigned or not doesn't matter.
You've fixed nothing.
What issues remain with my approach?
If the first timestamp is before the overflow happens, and the second timestamp is after. Then the second timestamp will be smaller than the first timestamp.
btw a uint maxes out at 4.294.967.295 signed int is half that.
does my snippet of code not address that?
Int32.MaxValue is 2147483647 not 2147483648 btw.
I typed 2147483647
Oh you mean in my code? I might be off by one, I wasn't super caring though as it doesn't really matter for what I'm doing
2147483647ms is almost 28 days; a single millisecond difference isn't going to matter at that point; and I'm not expecting to need to compare two timestamps from the same instance that are that far apart anyways
You have a fundamental misunderstanding of the problem.
In what instance would my code fail?
Can I not treat Networking.GetServerTimeInMilliseconds as a 32 bit array that increments by 1 bit every millisecond?
hell even if on server side it is a 64 bit array and we are only sent the lower half, it's effectively the same, right?
an array of bits I guess is kind of a way to imagine how an int is stored
not quite the same but I see the idea
I didn't know the correct technical words to describe it and array does get the idea across, yah
so what your code does is flip the comparison of the two server times depending on if one is larger than the other
I think the question of "does this work or not" comes down to if this statement is the correct way to do it
if (Math.Max(firstTimeInMilliseconds, secondTimeInMilliseconds) - Math.Min(firstTimeInMilliseconds, secondTimeInMilliseconds) > 2147483648)
since a value close to uint.MaxValue minus a value close to zero is going to be a massive value, if I do a subtraction between the larger of the two values and the smaller of the two values and get a massive number, then a rollover is presumed to have occurred
I see it now
how this would work in a practical example I'm not fully aware of, though
I feel like there are easier "timestamp" values to use than this one
Phasedragon's suggestion to use DateTime.Now.ToBinary() and DateTime.FromBinary would probably be the easiest, but would require you to either trust or not care if players change the local time on their computers
CalculateServerDeltaTime(double timeInSeconds, double previousTimeInSeconds) is wrap safe if you're working with the seconds version of the server times
the ms version is in int, but i don't think it hits negative numbers so if you cast them to a uint and compared they would wrap safely i think? (or even if they do hit negatives, casting to uint after subtraction is probably the correct thing to compare)
per photon
It can start with any positive value.
It will "wrap around" from 4294967.295 to 0!
Hmm, if that is the highest value then precision loss isn't going to be meaningful, right?
yeah precision loss in terms of the value precision isn't going to cause issues i imagine, precision in like accuracy terms is questionable because of how they poll the server time initially, but it's in the range of like +- 20ms and consistent
Thank you, thank you. I only have a vague understand of how precision loss works with doubles/floats so when it comes to that stuff I can only guess.
if someone had the game open for more than ~18 hours they would start to get precision loss when using stuff like SendTime (which is provided when you receive synced data) because vrc keeps them relative to a float (Time.RealtimeSinceStartup)
but the server times themselves shouldn't be in float territory if you used them for timestamping
Thanks for the info!
for posterity, this is probably how timestamp deltas should be checked when using the ms version of servertime
Also testing out some things, I think my code would crash in practice. As far as I can tell, C# will refuse to convert a negative int to a uint unless you use the unchecked statement; and if you've ever used U# you know where this is going... UdonSharp does not currently support node type 'UncheckedExpression'

there is also Time.timeAsDouble or Time.realtimeSinceStartupAsDouble etc
how much more precise are they idk
the issue is that vrc provides the sendtime in single precision relative to the latter
Also I'm specifically looking for a timestamp that can be sent over the network from one client to another and be understood by the receiving client
also i hadn't read what you were originally trying to do, i think if you want to figure out the datetime for someone doing an event, you should send the GetServerTimeInSeconds() as a timestamp
get the delta with delta = CalculateServerDeltaTime(GetServerTimeInSeconds(), syncedTimestamp)
so you have the seconds difference, you can make a deltaTimespan = TimeSpan.FromSeconds(delta)
DateTime.Now - deltaTimespan is when it happened
Yah to be more specific with what I am trying to do:
there is an item on the ground that can be added to an inventory by interacting with it; I want it to be seen by and interactable with by all players but I want only one player to be able to add it to their inventory
The idea is that interreacting with the item will send out a "I claim this item at this time" networked event and then immediately (or after a very short delay) the item is added to their inventory. Then if the person receives a "I claim this item at this time" for the same item from another player, it compares the time they claimed the item vs the remote user's claim. If the remote user claimed it first then any necessary rollbacks occur.
personally i think you should arbitrate that through the master or some central figure who can respond telling them they can claim it, to prevent race conditions like that
yeah i wouldnt do rollbacks, just having the owner decide (first come first served) who got it and response with an event, no need to mess with rollbacks and timestamps, let the lag be lag
you could also override the OnOwnershipRequest method, which would tangibly lock out people who come after (the owner rejecting their request); the "losers" would receive an OnOwnershipTransferred after the fact, where they could infer that they lost ownership of it despite clicking on it
doesn't feel as good to me though
for what you want to do with times, if you prefer sticking to timestamped events, you could just use their local utc.now, even if it's not technically correct which came first because of clock differences, both clients would agree that some tick time is before or after another
(DateTime.Ticks is probably what i'd send in that case)
wouldnt server time be more appropriate?
probably yeah, i don't really like server datetime because i've observed negative trip times from it, but it should be more aligned than pc clocks
just noticed server time is double, so should be good enough
https://udonsharp.docs.vrchat.com/vrchat-api/
oh the servertime? yeah you could directly compare the delta i guess with the method they provide, newer incoming stamps would be > 0
that's probably a lot easier
the logic for newer would become
OnPickup()
myPickupTime = Networking.GetServerTimeInSeconds()
OnEvent()
myTimeIsNewer = Networking.CalculateServerDeltaTime(myPickupTime, incomingTimestamp) > 0d;
if myTimeIsNewer, drop item
maybe that's not the thing you'd actually want to check, i guess they'd both be timestamps on the object
mmhm
interestingly, double should be plenty good enough for centuries, says gpt
| Elapsed Time | Seconds | `Time.time` (float) Precision | `double` Precision |
| -------------------- | -------------- | ------------------------------- | ------------------------- |
| **1 hour** | 3,600 | 0.429 ms | 0.0000000008 ms (0.8 ns) |
| **4.66 hours** | 16,777 | 2.00 ms | 0.0000000037 ms (3.7 ns) |
| **1 day** | 86,400 | 10.3 ms | 0.0000000192 ms (19.2 ns) |
| **3 days** | 259,200 | 30.9 ms | 0.0000000576 ms (57.6 ns) |
| **11.6 days** | 1,000,000 | 119.2 ms | 0.000000222 ms (222 ns) |
| **1 year** | 31,536,000 | 3,759.4 ms (3.76 s) | 0.000007 ms (7 µs) |
| **31.7 years** | 1,000,000,000 | 119,209 ms (119.2 s) | 0.000222 ms (222 ns) |
| **317 years** | 10,000,000,000 | 1,192,093 ms (1,192 s) | 0.00222 ms (2.22 µs) |
| **3.17 million yrs** | 1e15 | 1.19 × 10¹¹ ms (1.19 million s) | 222 ms |
| **317 million yrs** | 1e16 | 1.19 × 10¹² ms (11.9 million s) | 2,220 ms (2.22 s) |
And having the master resolve things, I was going to show reservation about allocating an extra task to the master in an already network constrained world (the main goal I'm attempting with this world is seeing how many networked agents I can get running within a single instance), but I'm already distributing the most network heavy tasks and the master appears to get extra bandwidth on top of that, so it would probably be fine
nav agents?
yep, each with an attached AI script
you can run a somewhat arbitrary amount if you only sync the destinations, but that can obviously desync more heavily than periodically syncing their position
mmhm; I have considered it a lot but always felt the concessions I'd need to make to avoid desync would be too great
i think if i was trying to run a large amount with periodic sync i'd use playerobjects and have each player control x amount, which scales pretty naturally with playerobjects
but i guess it depends if you want them to be able to change ownership
Best compromise I've found right now is to have a VRCObjectSync attached to my nav agent and then in the child put my AI script that sends out networked events as needed
Interestingly having VRCObjectSync and my script on the same object severely reduces network performance (my best guess is that it interferes with VRCObjectSync's ability to prioritize itself)
I'd use playerobjects to distribute them, but doing so would prevent ownership transfer and there are some circumstances where I want to transfer ownership
Instead I have the host distribute the agents as they are spawned in, with each player having a soft and hard cap of how many agents they can be assigned
i think you might benefit from using manual or events over vrc's object sync, like rotation can be inferred from travel direction (or as a single float for the yaw)
but it kinda depends and object sync is probably more convenient
I tried using events but was running into the max 100 events per second limit. Looking back, however, I might have messed something up with the implementation as I was distracted talking with someone when I implemented and then immediately reverted it.
With that said I am currently sitting at a max of 35 agents per player which seems pretty usable
for events i think you'd want to use a central manager to avoid the header per event, but it introduces more complexity than just sending them at object level
something like push new state to manager (locally, don't allow duplicates and only takes newest state); package all of those queued agent states to bytes every interval (sync rate)
ownership/state could also become a bit nebulous (people could push states to things they no longer own), but it sounds like they wouldn't be changing hands super often
I'll certainly keep that in mind for future networking optimizations. Right now the biggest networking contain is the about of networking capacity used by movement sync. The AI script itself only uses events and sends them out relatively frequently. Although in the future I'll likely change it into a queued system for damage events to prevent event spam.
if ai follows predictable path, you just need one event at the start to launch them
they do not, unfortunately
i more meant events for the movement, as object sync probably has overhead compared to doing your own movement sync (via manual or events, though manual should beat out both); doing damage events at object level sounds fine unless they happen really often (you could just pool them on the object at that point prior to sending the cumulative damage that occurred over some timeframe)
That might work, yah. Although over time I've suspected that vrcobjectsync has a privileged way of interacting with the networking limits... guess I'll find out if that's true
i do think manual scripts are limited to 40 objects per second, so object sync using continuous could interact a bit differently if you have a lot of objects
by manual scripts do you mean manual sync?
yeah
so to make sure I understand you correctly, if I am using manual sync to sync location data, I'll either be effectively limited to 40 active nav mesh agents or I'll have to group the location data together and sync them collectively?
having many independent objects will waste a lot of space on headers, so yeah you'll be able to get more by packing them together
What about the "Group" that each network object is put in? ("Grouping in this context is an internal networking system used to combine multiple objects together by distance so that their data can be sent together.") Would this interfere with that? Would it even matter?
I'm not sure if that "by distance" is arbitrary or serves some specific goal
if it's like other networking, that sounds more like it'd be related to users receiving the data (if an object is especially far away it might not prioritize sending them that data compared to an object directly next to them)
i don't think it really matters on your end
That is what I thought as well, in which case grouping the location data of multiple objects into some script only object sitting at 0,0,0 would remove that optimization, would it not?
any optimization related to grouping probably wouldn't really be user facing, it'd be from the server to non-owners and isn't related to the bandwidth send limits on a player
like if your client decides it wants to send some variables to other players, it sends every variable on the object to the server, the server handles distributing it around; udon side you only care about the first part where it sends to the server because that's all you interact with
the only caveat would be if they somehow affect how often the owner can serialize a script based on the grouping, but i don't believe that really changes things (if you update the synced vars before they go out, you'd still end up sending the latest)
my udon overlords
does anyone know how to sync something so it exactly happens on all clients at the same time
regardless of who triggered the event
depends on what u r syncing
i wonder if anyone had tried using RoundTripTime from VRC.SDK3.Network.Stats to delay the local event to happen at the "same time" as remote
https://vrc-beta-docs.netlify.app/worlds/udon/networking/network-stats
A number of networking stats are available to Udon via the VRC.SDK3.Network.Stats static class.
in a basic sense something like this should work, it doesn't really require a synced float if the delay is consistent/known to all users
this doesn't lock out the event or have any handling for it being clicked multiple times, it's not the best way to do it if you want high precision
[SerializeField] private float timeDelay = 2f;
[UdonSynced] private float syncedDelay;
public override void Interact()
{
CallDelayedEvent(timeDelay);
}
private void CallDelayedEvent(float timeFromNow)
{
Networking.SetOwner(Networking.LocalPlayer, gameObject);
syncedDelay = timeFromNow;
RequestSerialization();
SendCustomEventDelayedSeconds(nameof(_InvokeDelayedEvent), timeFromNow);
}
public void _InvokeDelayedEvent()
{
// Do something here
}
public override void OnDeserialization(DeserializationResult result)
{
float packetAge = Time.realtimeSinceStartup - result.sendTime;
float invokeDelay = syncedDelay - packetAge;
if (invokeDelay < 0f) return; // Discard events that have already fired
SendCustomEventDelayedSeconds(nameof(_InvokeDelayedEvent), invokeDelay);
}
what's the difference
the difference is, TakeOwnership is undocumented 😭 https://udonsharp.docs.vrchat.com/vrchat-api
a node doesn't exist for that function
either something deprecated, or something new?
ah, and this is what I expected to see
so I guess don't worry abt it
A follow up question (maybe for devs), how is DeserializationResult.sendTime calculated? Like how do the receiving player even know the time of the sender's? How did the sender know the time? Is it all just server time?
Would it be equally accurate if I use GetServerTimeInSeconds() as basis to trigger an event at a specific server time?
in my testing it's relative to server time and brought into the time space of the local player's time.realtimesincestartup
so yes you could use server time to trigger an event, but you'd still want to check delta imo rather than comparing absolute stamps (absolutes would be like checking if your current servertime >= target servertime)
check delta? why?
mostly because of wrapping, if the time was monotonic it wouldn't matter
if you were doing something like that, checking in update to see if it was time to fire an event, it's safer to calculate the time to fire and then increment or check against some local timespace than to check if the server time >= target time
it should be monotonic for all intent and purposes, because it is a double, so it should be fine
and why check again? would servertime drift at all relative to localtime (why would it drift)? just trying to think of what circumstance would this happen
because servertime wraps, even the double variant
when you say trigger an event at a specific servertime, i assume you mean checking if it's >= to see if it's time to do the action
i'm just saying that's not really wrap safe and you'd probably want to check the difference between target time and current time to see how long it will be before that event
- do they even wrap around?
- yes checking if >= in Update() seems most straightforward
GetServerTimeInSeconds()
and
GetServerTimeInMilliseconds()
these wrap, they should go from some high positive value to some negative value; it's not related to how long the instance has been up and can happen at any point, in practice it's not likely you'd encounter a ton of issues comparing absolutes, but it's not logically sound and if you're working in long delays it could be relevant
can happen at any point? interesting (did a dev confirm this?)
so how would check delta work? so if the wrap happened at all, we abandon? since we don't know when it can happen?
this here, it's related to the underlying server the instance is on, so it's a consistent timeframe, but you don't know when it will happen for an instance you load into
so if delta > original delay, a wrap is detected... but since we dont know the value the wrap happened, we cannot calculate the new offset? i guess
if you need to compare two timestamps for delta, you can use the method they provide for the seconds variant
CalculateServerDeltaTime(double timeInSeconds, double previousTimeInSeconds)
which should be wrap safe, or you can use the result.sendtime to calculate the delta assuming you're not using network events
Ok looking at the comment again, i think he meant the GetServerTimeInMilliseconds() which is an int will wrap (as expected, in 24.86 days to be precise) if it stays up for months.
However I don't think this applies to GetServerTimeInSeconds() which is a double, which does not wrap around as per IEEE 754, and even if so it will take a significantly long time (the precision will only exceed 1 second after 285 million years) so I think we are totally wrap safe if we always use GetServerTimeInSeconds()
(Why is there two options to begin with?)
they're directly related to these two calls in pun, so i would expect both to wrap
it doesn't wrap in a value sense, they make it wrap
probably because of the int wrapping
So the double is just converted from the int? lmao ok i see the problem
But at least we know how it wraps around
Use this value with care:
It can start with any positive value.
It will "wrap around" from 4294967.295 to 0!
Now just need to check if this is a constant, or if something else will make it wrap or jump to some random number...
i don't know that those numbers are directly equivalent, but you could probably check and see if you ever load into an instance with a negative servertimeinseconds (which would depart from photon's default)
you can also do something like this if you want to create a simple shared timespace, it's backed by servertime but wouldn't really wrap; you'd use syncedTime from that point on. not sure if my math is correct here, but it's this idea
[UdonSynced] private double startTime
private double offset;
private double syncedTime => Time.RealtimeSinceStartupAsDouble + offset;
void Start()
{
if (Networking.IsOwner(gameObject))
{
RequestSerialization();
}
}
public override OnPreserialization()
{
startTime = Time.RealtimeSinceStartupAsDouble;
}
public override void OnDeserialization(DeserializationResult result)
{
float packetAge = Time.realtimeSinceStartup - result.sendTime;
offset = startTime - Time.RealtimeSinceStartupAsDouble + packetAge;
}
Some digging around...
- Looking around the 4,294,967,295 (which is max value in an unsigned 32-bit int) should be from
networkingPeer.ServerTimeInMilliSecondsbut it is unknown if it really returns an unsigned int. - BUT
int ServerTimestampfrom Photon docs (https://documentation.help/Photon-Unity-Networking-2/class_photon_1_1_pun_1_1_photon_network.html#af32d375b092832d574b3258f4de4d13f) is anintmeaning it will wrap at 2,147,483,647 and this looks like latest docs. - And in Unity (and Udon) for our purposes this is also an
intmeaning it does not matter whether it was originally signed or unsigned, it will wrap at 2,147,483,647 to negative. - But it will still have 4,294,967,295 unique numbers, before actually "resetting".
- Although the Photon doc does says the double Time wraps around at 4294967.295
- The
doubleis literally converted withreturn ((double)(uint)networkingPeer.ServerTimeInMilliSeconds) / 1000.0f;, note the use ofuintbut not sure if this is most up to date (https://github.com/martindevans/SpaceGame/blob/master/Assets/Photon Unity Networking/Plugins/PhotonNetwork/PhotonNetwork.cs) but since it is cast to an uint, so it will go to +4,294,967,295 as expected, but should never wrap into negative.
- So the question is, can the VRChat
CalculateServerDeltaTime(double timeInSeconds, double previousTimeInSeconds)calculate wrap arounds, if yes, then problem solved.- Someone seems to have done some look-into here (https://phi16.hatenablog.com/entry/2025/01/08/023655) and it seems the code does take into account wrapping.
...
- Someone seems to have done some look-into here (https://phi16.hatenablog.com/entry/2025/01/08/023655) and it seems the code does take into account wrapping.
- Conclusion... it would seem we should always use the
doubleGetServerTimeInSeconds()(which will go to +4,294,967,295 and resets to 0), and useCalculateServerDeltaTimeto calculate the delta. To avoid the "first wrap around" of the int. Using this method we can safely go beyond the 24 days or so, until... - However, once the server exceeds 4294967.295 seconds i.e. 49 days, 17 hours, 2 minutes, and 47.295 seconds, there is a "second wrap around" which goes to zero. For both
intanddouble. Which we will have to...
... make the judgement of whether the delta is >49 days.
In other words, the probability that ServerTime will overflow after one hour is 0.083%. that's interesting, i'm glad someone's checked the wrap safety of their method
thank you for linking it. i actually use my own ntp scheme in my most recent worlds (clock syncing) because of some inaccuracy in servertime (this can be observed by measuring the one way trip times between two clients); though this usually doesn't exceed 20ms up or down and i haven't had many other people check the accuracy
Note to self: I think what he meant was, there is a chance (0.083%) to load into an instance that is within 1 hour of an overflow (at 4294967.296 seconds), not that the server time will randomly overflow in an hour by pure chance.
yeah it sounds like they were checking the probability you'd find an instance where that happened within an hour
how do you even do NTP? does udon allow that?
it's actually pretty simple, i use utc.now.ticks as the underlying space i exchange; you do two exchanges (an initial time from a remote client, a response from the host/source)
four timestamps are used
- initial send time
- receiver time
- receiver's response time
- initial sender's receive time
rtt and clock offset can be determined from these four stamps, i forget the formula, but it attempts to account for the time it takes for the source to respond (relative to the time they receive), which is why it records T2 and T3 instead of just T2 (response time)
(so it needs allow untrusted url?)
no i'm running this between clients with udonsynced variables
oh i thought utc.now.ticks is an url
it's actually a lot easier with custom network events, but you have less control over when those go out (as opposed to recording in preserialize)
it's like System.DateTime.UtcNow.Ticks
i forget the exact call
but i'm aligning system clocks
so you are measuring the ping, basically
yeah you can acquire the rtt from it very easily
so you are mesuring the p2p ping, not just the server ping
I thought you meant, NTP as in, sync the time with some known accurate public NTP server
p2p is the relevant part yeah, if you're doing something like extrapolation or need a very precise shared space it's more accurate than using servertime due to how they initially poll it once
i guess calling it a clock sync algorithm is more correct? it's the basis for ntp
that's how I read it too
- I guess yeah, when you say NTP I just think of syncing with an atomic clocks somewhere.
- Speaking of precision, looking at photon docs again (https://documentation.help/Photon-Unity-Networking-2/class_photon_1_1_pun_1_1_photon_network.html#af32d375b092832d574b3258f4de4d13f)
static void FetchServerTimestamp ( )
staticRefreshes the server timestamp (async operation, takes a roundtrip).
Can be useful if a bad connection made the timestamp unusable or imprecise.
(This is how clients get the server time I assume)
- If I understand correctly,
GetServerTimeInSeconds()gives you a timestamp that is about half a round trip time late. - In here https://vrc-beta-docs.netlify.app/worlds/udon/networking/network-stats/ says
VRC.SDK3.Network.Statscan give you aRoundTripTime. - If we use both, we can maybe do some high precision sync. Maybe
GetServerTimeInSeconds() + RoundTripTime / 2can give you a "real time server time" to sync your stuff.
- Or maybe "async operation, takes a roundtrip" means, the difference is literally a roundtrip. Idk.
that initial fetch is done once on join and is the basis for servertime being inaccurate (modern netcode libraries align this periodically)
the rtt provided is actually more accurate as it's polled periodically and not really related to servertime afaik, but i don't believe you should use that rtt for anything between players
and no i don't believe polling the server time would be behind the server, they should attempt to align it to the server (the exact value) based on how long it takes for the timestamp to arrive (compensating for latency)
i'd expect the initial servertime you get is timestamp + rtt / 2 (already accounts for the time it took to arrive)
if you can find what pun actually does on join it'd be interesting to see
that fetch is not how clients acquire server time in an udon sense, you can poll servertime at any point, it doesn't pause your code or execute async
oh yeah i meant, that is how the client gets it (and caches it) in the background, yeah we just do GetServerTimeInSeconds()
so yeah it seems GetServerTimeInSeconds() + rtt / 2 is solid, even gpt says it can understand the logic
interestingly .SDK3.Network.Stats is very new, released late april
you can actually get some of the outbound data rates and stuff to make your own throttling now which is nice
as opposed to clogged/not clogged
we can now shame people with bad ping
Decided to give this another crack.
//returns true if the first network time is sooner than the second, taking into account wrapping
public static bool IsNetworkTimeSooner(int firstTimeInMilliseconds, int secondTimeInMilliseconds)
{
//check if a wraparound has occured
if ((firstTimeInMilliseconds ^ secondTimeInMilliseconds) < 0)
{
return ((firstTimeInMilliseconds >> 30) & 1) > ((secondTimeInMilliseconds >> 30) & 1);
}
else
{
return firstTimeInMilliseconds < secondTimeInMilliseconds;
}
}
it is better to use GetServerTimeInSeconds() which virtually doubles the wrap around time to 49.7 days, and use CalculateServerDeltaTime(double timeInSeconds, double previousTimeInSeconds) to calculate difference
Well if you're planning to have an instance open for weeks and plan to compare two timestamps weeks across, yah
But rule of cool, what I came up with seems pretty cool, at least to me personally
are you just comparing the 30th bit, and is this intentional?
i think it won't work, say if the first is 1000 and second is -1000
it will return false
hmmm
what about first is 2147483647 and second is -2147483648, it will be false, but 2147483647 is sooner
I'm pretty sure you're off by one; shifting by 30 right will put the 31st bit into the 1st bit position, right?
also
LogError($"Is {-190} before {100}: {MyUtilMethods.IsNetworkTimeSooner(-190, 10)}");
LogError($"Is {19} before {-10}: {MyUtilMethods.IsNetworkTimeSooner(19, -10)}");
LogError($"Is {int.MinValue + 190} before {int.MaxValue - 100}: {MyUtilMethods.IsNetworkTimeSooner(int.MinValue + 190, int.MaxValue - 10)}");
LogError($"Is {int.MaxValue - 19} before {int.MinValue + 10}: {MyUtilMethods.IsNetworkTimeSooner(int.MaxValue - 19, int.MinValue + 10)}");
LogError($"Is {-1000} before {-10}: {MyUtilMethods.IsNetworkTimeSooner(-1000, -10)}");
LogError($"Is {1000} before {10}: {MyUtilMethods.IsNetworkTimeSooner(1000, 10)}");
outputs:
that's a time span that pushes the limits of what you can accurately track before lack of information makes it impossible to tell
but that's also almost 25 days of real time, so not really an issue for 99.9% of use cases
im still strugging to understand the 30 bit shift
numbers approaching 0 from a negative will have the two highest bits of 11; numbers moving away from 0 towards positive will have the highest bits of 00
in turn, approaching int.MaxValue are 01 and moving away from int.MinValue is 10
thus we find that for numbers around zero the number with the higher 31st bit will be negative while for int.min/max it will be the positive number
precision loss away from 0 only applies to floats. ints are fixed resolution, and that's why they wrap around
since for the int.min/max the positive number is the earlier time and for around 0 the earlier is negative, we can look at this 31st bit to see which occurred first
Phasedragon we are not talking precision at all, this is bit shifting, scroll up for the code
yeah I know the code is ints, I misunderstood what vincil was saying. Sorry about that
i think i grasp the concept, and it is cool, but maybe hard to read and understand for other people lmao
lol yah, that is the downside
just think of it like floats: powered by black magic
is it faster in performance, or is it pure playing with your bits
and btw, if you use the double you won't have to worry about signs, you will be forever in positive land
I suspect 95%-99% of the performance consumption is going to be the method call itself because Udon, lol
but these are probably fast enough that you wouldn't be able to tell a difference unless you are calling it many times per frame
check this
public static bool IsSooner(int a, int b)
{
return (a ^ b) < 0 ? (a >> 30) > (b >> 30) : a < b;
}
gpt made it
Clever; also not Udon compatible, lol
wait, why not udon compatible
i dont see anything illegal
public static bool IsSooner(int a, int b) => (a ^ b) < 0 ? (a >> 30) > (b >> 30) : a < b;
Alright, Im having a bit of an issue, my game works great solo, but whenever a player joins, the host experiences terrible lag.
I narrows it down to the players own hitbox as well as the enemies which is fine so what I did was change the logic in a way that it can work with manual sync instead of continuous, that helped a bit, but monsters using a manual sync dont really perform any networked events like attacks or anything, their position in the world is synced but thats it...
If I have the monsters work using a continuously synced Udon, it all works great, but the performance drop makes it unplayable (The joined players dont experience any lag by the way)
Was wondering if there is anything that I can do to improve performance while still having it continuous sync, or work with animations.
I set up the logic so that the host of the session does most of the legwork and all the attack ques would come from networked events, it works but it takes up alot more resources than I anticipated, unless Im just not optimised
can u recap the main architecture of your thing, so you have monsters walking and attacking? and the owner is doing the monster logic?
last time I tried to use the ternary conditional operator it complained that the c# version wasn't correct for that
also T_T
looks like it's time for yet another layer of DataDictionary or List...
the ternary conditional operator works
oh I see; the previous time I had it not working, it was specifically in a way where it complained about "target-typed conditional expression"
so something like this doesn't work:
areaInfo[(int)AreaInfoType.NavArea] = isUsingNavAreaBounds ? info.navArea : info.floorTransforms;
Yes, the host does all the legwork with the logic, the joined players just wait for the host to say "This monster performed its attack, run the code"
Whats confusing to me is even before the game starts, just waiting in the lobby area with nothing going on, when a player joins I go from 100fps to 70, like damn!
I found that the players persistent hitbox was causing some issues so I changed to logic around, changed it from a continuous sync to manual and its a bit better but still very odd
players persistent hitbox <--why it needs sync? can it not move locally?
what does it do exactly
Yes it moves locally, but it does more than just act as a hitbox, when a player gets downed itl spawn a "Revive" text above them, also tracks the player status to other players like "This player is dead"
And it spawns blood splats whenever a p[layer gets hit so networked events go off so people can see another player getting hurt etc
this all sounds they can all run locally and dont need networking, except the dying part
Oh I forgot to mention, the host depends on the hitboxes gameobject name to be case sensitive to detect if there are any alive players present else the game detects that it can start a mission failed scenario
So when a player is alive the hitbox is named "Activeplayer", a dead player is labaled "Deadplayer"
and the game does a "Isvalid" check to see if there are Activeplayers present in the world
it may be easier if you just post your code... how many codes have you got?
Just hitbox and a udon for each enemy type, they run using a very similar method
using object name as bool is expensive i guess
The performance is fine on the most part, and these issues only seem to affect the host, joined players get good FPS
i guess u can isolate what is only run on the host and go from there
can u post the code so i dont have to open unity lol
The hitbox is split into 2 parts because of me switching it to use manual sync in an attempt to aleviate host performance issues
These are graphs :L
oh...
Lol, should have mentioned that
I have organised every part of them so they should be easilly readable
hitbox 1 and 2 are on all players?
Yeah,
Hitbox 1 is on the root game object for tracking the owners position
and then the rest is all on 2
first problem is you are checking if owner is network local player every fixed update, multiple times... is this on a player object? so there will be one on every player?
Yeah, its player object, so thats to tell it only the host should perform the operations
wait no
the owner, player yeah the owner of the hitbox
ok that is the problem, every fixed update, many objects checking if local is owner, very very expensive
also, networking.get localplayer is expensive, best save it to a variable at start and only use the variable, (not that it will fix your problem, but is good practice)
So make a bool "Is owned" or something and just use that as the branch truefalse?
So hitbox 1 is just a follower to make it follow the player? can you name it as follower and/or add comment in the graph, to make it obvious what it does
if it is a player object, the owner will always be the player who owns it, so there should be zero checking who owns the object, unless you are doing something else
Oh yes now that you say it I guess there is no need to check for owner when by default the owner is det when it spawns
btw, in hitbox1, you dont even need a "SELF" variable, you can just leave any input unplugged and it will always be itself if unplugged
Im not a programmer you see so I dont know how "expensive" things are, I assumed a isowner check wouldnt be that taxing but if you say it does Il take your word for it, perhaps making an onstart, assinging a bool "Isownedbylocalplayer" or something to then use that as the branch truefalse will be better?
Ive had issues in the past where Unity would give a null reference exception, so just got in the habit of doing htat
thats ok i am just dumping all the improvements i can think of
Do you think that would improve the situation or be a fruitless change?
let me go through hitbox2 to see why u need owners for
does the player2 hitbox needs to follow player2, on player1's client?
its nothing specific, just in general
is the hitbox for hit detection or something
Everyones hitbox needs to be tracked onto themselves on everyones client yes
so that when a player goes down, an active player can walk up to them and revive them to get them back into the game
Interesting...
Eh?
missed a link
So youve set a variable for a fixed player, is that to reduce load finding the owner and then following the owner per update?
yes
I didnt think that would really improve the situation much but Il definitely try everything to make it faster
If only there was like a way you could see how much each node costs in power to run etc
similarly in hitbox 2... i am saving two very often used variable
I getchya, Il try that
I will do that for the monster scripts too
Have you had a look at the spider udon?
So for the monster udon logic I do tick cycles for the monster AI to run
Like once per second it goes through an AI cycle and every so often if the int is correct, itl perform a special attack etc
Its all host controlled so its great for joined players, not so great for the host lol
also for (your own) readability sake, please dont make spiders, make it send a bunch of custom events instead
and if you need the same variable in many places, just make copies of them (also indication you should save a variable for those)
you can use block --> a list of custom events
Oh yeah, its pretty messy due to me getting sick of scrolling back and forth lol
for example
yeah i cant even read hitbox2 so i would wait until you do those cleanup... opening spider...
And I avoid using duplicates of the same variable node due to udon slowing down the more nodes you have in the graph :L
Especially when Im getting near the 512 mark
Waiting 5 seconds per edit gets a bit much 😛
Luckily I can watch family guy or something while waiting inbetween 😛
if it is too big try to split into multiple files and have like
SpiderMainController
SpiderAttacks (store attack events)
SpiderMovement (store movement events) etc...
Been there, doing that 😛
SpiderSound ... etc
yeah please do that because if it is hard to read, it is hard to help
make it easy for yourself and others lol
Ye alot of the main features of the game use tons of objects to spread the load
yeah its too much reading for the spider
i mean you have some good grouping and you try to lay them out which is good
but try to clean up a bit first, and do the caching variables first
then we can go from there
i only recently started learning udon sharp and the good thing about codes, you can tell gpt to write it for you
and clean up for you
and optimize for you
its like cheating
I dont even trust chatGPT for basic stuff, like one time I asked it to help me balance my weapons in the game based on stats, give them a consistent power creep, I cant remember exactly what it did but it would constantly get things that I as a human would notice and be like "You cant use this number because you already declared it a different number in a previous sentence"
And then its like "You're totally right, it is incorrect, lets correct that for you..."
Only to get it wrong again
yea i get that too, sometimes u have to handhold it, if it gets too wrong i have to delete the conversation and try again, hoping it has better RNG in its... understanding.
I think Ai is good for suggestions but not for meticulous design
Using Ai is like watching a person with only 1 arm and no legs swim, itl do it, but cant really work.
@fallow mountainI cleaned it up and applied the boolean check instead of isowner
(im afk for a while)
No probs man, thanks for assisting so far
Would anyone happen to know how to sync cloned objects even with late joiners?
Or at least with people in the world when spawned
generally its what vrc object pool is for
Would need to be an array or just one
if you need to sync smth simpler than typical pickup, like idk, object that spawns once and then never to be moved, you may use some synced script to spawn it, where it just stores/syncs an array of transforms
array
in fact afaik the only unique thing pool does is allowing to have initially disabled networked objects without breaking
Standard instantiated objects are not able to sync, but there are several other options.
If you need the ability to just 'spawn' a new object and don't necessarily need a fully unlimited number of those - then the best way to do that would be an objectpool. Each object inside doesn't need to be instantiated, they can just be disabled most of the time and then enabled as needed.
Alternatively, a common pattern in game dev is to spawn an object every time a player joins the instance, assign it to them, and then let them manage it. Things like hitboxes, nameplates, personal vehicles, high score management systems, etc. If that doesn't sound like what you want, then just use a standard object pool or something else. If that does sound like what you're trying to do, you should use PlayerObjects, which are a dedicated sytem provided by VRChat for that specific use case
If I use it with instantiate and add the clones to the pool will the clones sync
if they're instantiated in editor, yeah. But the pool doesn't make them magically sync, it's the fact they exist during upload.
Ok
You may think that, but I can guarantee that the code you're ending up with is of extremely poor quality.
If you insist on using AI to learn programming then please never copy paste the generated output into your script. Read it, understand it, and then write it yourself.
There is no language model on earth that understands anything.
All they can do is predict what's likely to come next based on their training data.
UdonSharp optimization is an extremely niche topic that is extremely unlikely to be in the AI's dataset. And even if there is, it'll be so little compared to how much other programming related data there is.
its like autocomplete on steriods and saves me typing, i just tell it what i want to change
AI can only ever suggest extremely mediocre code.
if a code does what i want, how i want, and fast, and readable, it is arguably good quality, it is just writing what i want for me
and yes, it doesnt code automatically, i do hand hold a lot
it's another tool, and it can make good quality code, if you hand hold it
You're neglecting the most important aspect of software engineering. Maintainability.
AI code is very often all over the place when it comes to following standards and conventions.
It often includes completely unnecessary parts that at best do nothing, and at worst waste performance.
If you don't ensure you understand everything the AI generated for you every single time then you'll quickly end up with a codebase that "just works" if you don't poke it too hard.
Sure, if you have a very specific problem that a lot of people have solved before. And they have posted their answers online. And the problem can be solved in a handful of lines, then you can use a chatbot to probably get an okay output!
yeah you have to on top of it
and poke it from every angle
and make it do things for you how you want
Exactly, you have to already know what you want so that you can determine if the AI gave you what you want.
Which makes it a dangerous tool for less experienced devs.
If I hand you an F-35 fighter jet, would you be able to use it?
Sure, it might be "powerful" and "dangerous". But only in the right hands.
not like were building nuclear reactors here, worst that will suffer is dev's reputation
And the players of whatever you make.
i just tell it to explain what it is doing all the time, and why, and use it to research and present and interrogate itself, and i learn during the process... and if something smells off, i delete the conversation and restart with new RNG, and i reroll until it makes sense
it's f-35 that flies for you, in a way, you dont have to micro everything
and you stay high level and learning stuff, and let it do all the leg work, which works out suprisingly okay so far, granted i havnt made serious complicated stuff but that is my impression
you learn why and how it is flying and you actively monitor how it is flying, you can ask questions etc, you can step in any moment to tweak things, etc... i mean it went from looking jank to looking optimistic from my experience, so i guess it got better recently
but it is very jank at udon, yes, but even including debug it is quicker than if i write stuff myself
It's like hiring a Boston Dynamics robot to run on the treadmill for you so you can get fit. You ask it to explain what it's doing and how to get good at running and it'll go on and on for you while you sit and watch.
That's not a good sign.
Please just try to actually run on the treadmill alone every once in a while. Turn the bot off and see if you remember what to do.
i wouldnt compare it to running on treadmill for you because the work is not the goal here... the goal is just getting some code, maybe a self driving car is a better analogy lol, you dont manually drive it, but you can watch and direct it and push buttons to correct it etc
it is very good for people who dont type very fast like me, it types for me
it is a car, in a way, you lose some leg muscles because you don't walk anymore, but you could go faster and further, if you drive it and dont crash... a car or bicycle, i would say
dangerous, yes, potentially very
like real cars
but it's not all bad, is my experience
(scary to think we all traded muscles for machines in the past, and now, mental capacities for advanced calculators)
The treadmill analogy is great if you intend to actually learn programming.
If the goal is always "just getting some code" and not "learning how to code", then you will forever be stuck as a passenger of the self driving car.
on some level, that may be where things are headed, like how cars made walking obsolete (for long distances)... i dont disagree walking is great and good for you, but cars are great too even as a passenger in a self driving car
i guess it is just a matter of pros and cons
Again, if you only ever intend to go where others have already gone you'll be happy to know there's a massive highway built for you and your self driving car.
But if you want to actually explore you'll need to go off road.
walking is great and always will be the fallback yes i agree
(but im too lazy to train my muscles)
Then you will never be great.
i will never be fit, i agree
I'm glad we have reached a consensus.
yes, interesting conversation i have to say
actually made me even more excited, AI is jank now, but it has that funny smell similar to early cars that are wobbly and fragile... and looking at cars now, now it may be that weird historical window where we get to taste this janky AI, because it is evolving at quite dramatic speed... this could be some rare vintage AI experience😂
i like using GPT for some quick help with my small issues. i recently started using it as a tool for coding rather than seeing what it can make, and it’s really helpful for small snippets that i couldn’t quite remember how to make, it where i wasn’t quite sure how to write it.
for example, time. i find Time.time difficult for some reason, so if i explain to it what i want, eg. a timer that runs every second, it’ll give me a couple lines in an update method that reminds me “ooh right, yeah that works”
or if i have something i want a button to do, i can explain to it what that something is, and it gives me a starting point and i get some ideas off it on how i can write the functionality myself
all in all, i think it’s a really great tool, but i’d never use it to generate a whole script or method for me
I have used it to generate methods when I can’t wrap my head around what I’m trying to accomplish and it showing it’s work automatically teaches me but I guess that’s because I already understand so much myself I can just read the code and understand most of it 😅
I also used it to learn HLSL myself by generating methods and messing with them myself until I understood what was happening and could optimize the shader manually, really neat but it definitely did not make a working shader on its own lmao
So far anything I wanna learn is ez af with GPT since I get to see all the steps broken down super detailed
exactly! at this point i can actually read the code and understand what it’s doing, so rather than mindlessly copy pasting, i can look over what it generated and figure out how to implement it in my own script. it’s helpful knowing what you’re doing too, because it’s definitely wrong sometimes.
and yeah, it’s not very good at shaders. the only shader i could get it to write that worked and didn’t look too bad code-wise, was a shader that was literally just color and an emission strength value
its nice to automate routine stuff at least. i needed very simple vertex local wave shader, and while i def can do it manually (clueless), spending 10 seconds to type prompt and getting exactly what i want instead of debugging def feels nice. same with simple unity stuff, like idk, take three transforms and find their middle point. but i dont trust it with udon/vrc parts, not much to train on compared to unity/c#
honestly AI wont help you improve. since the solution is created for you. even if it's a very small part of it. you would get further by just sitting down and trying to run things by trial and error. or if you are the person who learns from reading then read a few programming books. there is too much AI garbage out there and it's a stain upon game developement and software development in general.
Yeah but not everyone has the brain capacity to just get into the nitty and gritty for hours to figure it out themselves
then i really do need to ask whats the purpose of using it then?
i dont need to worry about typing out every single line of the code, i can focus on the mechanics design and the general structure and implementation strategies of the code, and offload the writing part to gpt, so i dont have to think about indents and checking brackets and typos and all those tedious works
i can go in and micromanage something if needed, but for the most part, it is doing the physical writing, but im still the director and author, if it makes sense, it is really just like autocomplete, just a bit smarter
you have IDEs that can highlight stuff and spell check and suggest and autocomplete and all those fancy things... it is similar, just more automatic and less manual typing
and it can suggest methods, strategies, structures, it can comment code and analysis code and report to you, you learn and brainstorm and test faster, you write faster, you debug faster, etc
at least for me it is faster than me researching and typing it
the only pitfall is it makes mistakes, and you have to spot them
so you have to be the one driving the thing, and not let it run you around in circles
Ai makes alot of mistakes. And the problem is that people don't really proof read it nor may even understand what is going on.
, so i dont have to think about indents and checking brackets and typos and all those tedious works
Any modern IDE will do this for you without AI?
exactly. u can setup a auto checker that is build in to all IDE today
outside of udon you just import libraries/packages and run isOdd(1) (or app.listen, or torch.device('cuda:0')), you dont care how its doing it, you dont even care if its doing it correctly all the time or there are some edge cases and it just happens to work for your setup. So ye, ai will fail on global creative tasks and youd have to deconstruct them into smaller ones, and youd have to debug them, but its still faster and for most people will produce generally better code as well.
The only time I've explicitly asked an an AI "how do i do this?" I made sure to understand what it was doing and then rewrote the code in my own style.
Outside that I've only used it for its autocomplete feature; which is amazing when it works, I just wish it worked more often, lol
does everything in vrchat go though the server, including voice? or is voice p2p?
https://vrc-beta-docs.netlify.app/worlds/udon/networking/network-stats/
what are the data types of these stat numbers? float? int? how do i find out?
oh nevermind, it is in beta sdk
Are the VRCPickup methods like "OnDrop" network callable? They are public methods but they're also special methods.
i dont think so, it is "custom" network event only
but there is nothing stopping you from doing customevent->drop it
I'm mostly wanting to know if I need to manually add protections to it since adding a _ prefix isn't an option
protection from what exactly?
malicious network calls
i didnt even know there is such thing, i mean if people hack, they will find a way
