#udon-networking
1 messages · Page 29 of 1
Hm. Okay. Thank you.
if there is a small amount of AI it makes sense to sync their position continuously, but in case of hundreds I'd sync only the destination
there will be desync eventually so it's not as simple to deal with
and for late joiners you need to sync the start position anyway
Yeah you don't need to sync anything that can be calculated locally, if the movements are deterministic, you can just sync what determines the movement
In the most extreme case you dont need to sync anything at all, and use Networking.GetNetworkDateTime() (for example) as the timer and random seed, and generate random fireworks at certain intervals. Millions of particles, maximum chaos, zero sync needed.
I've gotten hundreds of AI agents with ObjectSync working, but it requires distributing ownership to players as they join (and handling ownership when they leave)
I wanted to wait until I had actually done anything with it, but I've been obsessed with this all week! I never knew BCN compression was so clever. I love the idea of putting a little palette together with vectors and math, and sampling only from that. I watched so many videos on the topic.
I made a huge script of my world that takes a texture2d, breaks it out into color[], then packs 4 pixels into every byte in array, given a 00, 01, 10 or 11 based it's value in reference to the 4 current pallet colors of that group.
Every group of 16 pixels has 1 extra byte for the minimum value, and 1 byte for the maximum which determine the palette. So 6 bytes for 16 pixels overall, .375 bytes per pixel~
There are still some quirks to work out, and this is only the Luma component of the YCbCr, but this was definitely some of the largest code I've written 😅 and I'm really impressed with the results! It's crazy what you can do with 4 values when you're smart with them~
Welcome to GB/NES graphics coding
I've noticed that sometimes players will "exist" even when they don't seem to be around anymore
As in, they're surely disconnected or crashed a long time ago, but their form still remains—frozen in time
It seems problematic because their ghost won't be there for new joiners and if it's a game world, they still seem to trigger certain functions/projectiles on them
Does anyone know what this is or how to work around it?
sounds like vrchat lol
I didn't realize new joiners wouldn't see them tho?
I was under the assumption that if someone is frozen (about to leave), they'd be visible to new joiners
It's less so them being strictly in a state of "about to leave" I suppose
It seems like a completely local phenomenon
Their frozen corpse can remain for quite some time
If there's any local hit detection for weapons or magic they'll still block it
sounds like a bug where the player is not being removed from the world properly when they leave the instance sometimes
logs when it happens might be useful to submit a bug report with
Do Pickups w/ Object sync break when the script on it is set to manual?
I noticed you get a warning about that
It popped up recently for me as I need to sync a variable on the pickup.
If it is the case that it breaks, do I just have this script further down the pickup and have a relay script to handle the interact?
as long as the scripts are not on the same exact GameObject, it should be fine
oh right - I got the warning 'cause mine were the same object, good point
Gotcha, thanks 👍
So I was under the impression that I could subtract Time.realTimeSinceStartup - Networking.SimulationTime(LocalPlayer) from a delayed event in order to trigger it for remote players at the same time as the sender, but I’m noticing vastly different event times across clients when testing with friends
I thought maybe I just also needed to run the same calculation against the player that sent the event, but that timing didn’t appear good either 🤔
Am I supposed to subtract both players’ simulation times from a single time variable, or is it just more complicated than this?
Same time? What do you mean
If I send a custom network event that triggers a delayed event, in terms of real life time, the delayed event occurs for each player slightly delayed based on both the sender’s serialization speed and each receiver’s
I wanted to try to minimize that by subtracting the “networking delay” in ms from the delayed event for each remote player when they receive the networked event
So in real life time, the actual event for all players ends up happening as simultaneously as possible
This doc covers Networking Components, Properties and Events you can use in your Udon Programs.
double currentRealTimeServerTime = Networking.GetServerTimeInSeconds() + (Stats.RoundTripTime * 0.001) * 0.5; is the theoretical way but, Networking.GetServerTimeInSeconds() itself is just gotten at start of joining an instance and then continually updated with computer clock, so its included latency is out of date when you try to compensate for RTT with the current RTT at any moment
Meanwhile simulation time is just the size of smoothing window, it wont give you an accurate sense of network delay
But double currentRealTimeServerTime = Networking.GetServerTimeInSeconds() + (Stats.RoundTripTime * 0.001) * 0.5; is the theoretical best estimate we have access to, I think
And at the end of the day, almost everything in VRChat happens in a mess of unsyncd time so I wonder if it is really necessary for such accurate real time, so I think for pretty much all intent and purpose Networking.GetServerTimeInSeconds() is all you need if you want to sync anything
And if all fails, pick a DateTime and pray the player's computer clock has internet time turned on and recently synced so it is accurate lol
Thanks!
Hey hey would anyone be down for a call to ask some questions ? 🙂
you could just type them here
Since when have you been doing this? How is the learning curve? Is it possible to make money? How does the monetization of worlds work? Do you do freelancing? What price ranges can you get? What are the pros and cons of Udon? Things like that.
sounds like you want to interview someone
Thats what I said. haha
A friend of mine is good with blender and I do coding for a while now and we would like to start something in 6 hours. Like a first "test-project", but we would like to know if its even worth it. Do go deep into that. We just see VR as a big importunity
a) a while b) I'm the wrong person to ask about that c) sure, a little d) patreon or similar seems to work for some e) a little occasionally f) meaningless without detail g) pro = you can script stuff. con = it's extremely limited and annoying
I see the monetization of the worlds is not as far as in Roblox or in Fortnite, hmmm.
I saw that they have some ad banners like in this "Popcorn Palace" , thats actually really strong, but there are more possiblities
- since the dawn of time
- depends on your current background. but generally not bad
- yes
- by using the Creator Economy: https://creators.vrchat.com/economy/welcome-to-the-ce
- no
- N/A
- It's almost as flexible as using standard C# scripts in Unity, but only has some (but a lot) of the functions whitelisted. It also executes slower than standard C#. But despite the limitations you can do a crazy amount with it if you're willing to work some of the jank of it sometimes
ooh there's a link, nice.
Okay, thank you buddy.
Are there groups or channels or something like that, which are looking for udon coders for their worlds or something ?
Or are there like not shy coders, who would join for some calls. Can you get like contracts for what you are coding. Like % of the monthly earnings or do most of the people, who build words only do a one time payment?
-# I wonder if you've misunderstood the name of this channel
What, to commission someone to code for you? You'll probably want to head to the https://discord.com/channels/189511567539306508/1204506421631393913 server
pretty sure it's usually a one-time-fee kind of deal most of the time. but you could technically work out whatever deal you want
okay okay, thank you
Any tips on getting VRC Object Sync to actually function?
And yes, I have already regenerated Scene IDs with the Network ID Utility
do you have any other additional scripts on the pickup?
nope
I do have an udon script on a separate gameobject that references the gameobjects with the vrcpickups on them, idk if that counts
that should be fine if it's on a different object
as long as the pickups aren't being instantiated by the other script at least
so these references wouldn't be an issue?
no
those aren't touching the ObjectSync anyway
ObjectSync should "just work". Try spawning in a default cube, add VRCPickup and VRCObjectSync to it, and see if that syncs
i have another group of vrcpickup objects and the sync does work
it's just the gems for some reason??
what exactly is your other script doing to the pickups?
actually that might be the issue
hold on
basically i wanted to transfer ownership to whoever picks up the gems so that when a late joiner joins, another udon script would sync an animator int to the late joiner
this is probably not the best way to do this, im new to networking
ownership is already automatically transferred to the player who picks up a pickup
well, earlier I had another script that detected when the pickup entered the TriggerEnter thing, and the "get currentPlayer" thing spat out an error if the player wasn't holding the pickup while the pickup entered the trigger.
I did want physics to be on the pickups, I guess that's not a good idea??
idk
physics shouldn't be a problem
your graph there is likely redundant, and maybe even confusing the networking system, since you could be setting ownership basically every frame
@buoyant ginkgo why you have smth like ownership change in update loop, thats calling for problems
it doesn't like when ownership is changed too often
you set currentPlayer as the owner, but that's the player that's holding the pickup. It's guaranteed that they will already be the owner
i guess that’s on me, I’m kinda new to this it’s so confusing
I'd try removing or disabling that entire graph and see if that changes anything
When a networked events is sent when the network is busy, it gets sent at some point right? Just later?
Im worried it might just be discarded instead
it just gets added to the queue
thank you :) I'm hoping I get my current side project done before the end of this month, so I'll be ready to make something for the Space Jam next month
When you're expecting the network to be clogged often with your udon script, is it better to have one udonbehavior controlling all of the heavy networking traffic? or many udonbehaviors fighting for it?
Oh they've announced when the next jam is happening?
not a specific date yet, but Flare mentioned the "next Jam should be in February"
I see
I see that OnOwnershipRequest can break completely if two non-owners request ownership simultaneously
It gets even worse if an Owner and non-owner both try updating a network value simultaneously; the non-owner will never get the Owner set value
is there any way to simulate network latency?
like i have a system that works on my machine ™ , but i tested with someone far away and there was big race condition problem
then i wanted to test some redesigns but couldnt be sure if it addressed the problem without asking someone to test again
rather than blind testing why not post your code here so we can tear apart its architecture to see what is wrong and how to fix it
I agree with the above statement, but to actually answer your question directly here's a lagswitch
https://jagt.github.io/clumsy/
It's what I use to test horrid networking conditions
thank u
also the system is across 2 scripts so im trying to organize it into a message that is.. coherent lmao
"please hjelp i hab problem"
"gib code
"
Alrighty here's some more context on it. To start, the system i want to test is essentially a game of tag with infection. So a player with roleA is a tagger and roleB has other unrelated tasks, and when tagged they become roleA and take on those qualities. When no more roleB remain, roleA wins. The GameManager script is the core game logic and is handled by master of the lobby. PlayerManager script is owned by each player, it's on a player object and it's used to manager player states and have synced variables propagated. The GameManager runs some logic to assign roles to players by calling the method to become the role on each active players PlayerManager. When the player runs this method, it's then networked out and master waits to receive the update. When players and master receive the update, they toggle the appropriate role based objects and master does some special logic related to game state management (tracking role counts and checking win conditions).
(ran outta characters)
// GameManager script methods
// master only start game button calls this method
public void TriggerPreRound()
{
if (!Networking.IsMaster) return;
if (currentRound == RoundState.Waiting && activePlayerCount >= 2)
{
currentRound = RoundState.PreRound;
// 10 sec
roundTimer = preRoundDuration;
AssignRoles();
RequestSerialization();
}
}
private void StartRound()
{
if (!Networking.IsMaster) return;
if (currentRound == RoundState.PreRound)
{
currentRound = RoundState.Playing;
// 2 minutes
roundTimer = roundDuration;
RequestSerialization();
}
}
...
public void AssignRoles()
{
if (!Networking.IsMaster) return;
for (int i = 0; i < activePlayers.Length; i++)
{
if (activePlayers[i] != null)
{
GameObject[] playerObjects = Networking.GetPlayerObjects(activePlayers[i]);
if (i == 0)
{
playerObjects[0].GetComponent<UdonBehaviour>().SendCustomNetworkEvent(NetworkEventTarget.Owner, "BecomeRoleA");
}
else
{
playerObjects[0].GetComponent<UdonBehaviour>().SendCustomNetworkEvent(NetworkEventTarget.Owner, "BecomeRoleB");
}
}
}
RequestSerialization();
}
public void RecountRoleB()
{
int count = 0;
foreach (var player in activePlayers)
{
GameObject[] playerObjects = Networking.GetPlayerObjects(player);
PlayerRole role = (PlayerRole)playerObjects[0].GetComponent<UdonBehaviour>().GetProgramVariable("role");
if (role == PlayerRole.RoleB)
{
count++;
}
}
remainingRoleB = count;
// networked so that nonmaster players keep text views updated
RequestSerialization();
}
{
// 3 second delay was added to account for latency
SendCustomEventDelayedSeconds(nameof(CheckWin_NoRemainingRoleB), 3f);
}
public void CheckWin_NoRemainingRoleB()
{
if (remainingRoleB <= 0)
{
EndRound();
Winner = "RoleA";
}
}```
{
if (remainingRoleB <= 0)
{
EndRound();
Winner = "RoleA";
}
}
--
// PlayerManager role management methods
public void BecomeRoleA()
{
role = PlayerRole.RoleA;
RoleBObj.SetActive(false);
RoleAObj.SetActive(true);
if (Networking.IsMaster)
{
GameManager.SendCustomEvent("RecountRoleB");
GameManager.SendCustomEvent("CheckWin_NoRemainingRoleB_Delayed");
}
RequestSerialization();
}
public void BecomeRoleB()
{
role = PlayerRole.RoleB;
RoleBObj.SetActive(true);
RoleAObj.SetActive(false);
if (Networking.IsMaster)
{
GameManager.SendCustomEvent("RecountRoleB");
}
RequestSerialization();
}
public void ResetRole()
{
role = PlayerRole.None;
RoleBObj.SetActive(false);
RoleAObj.SetActive(false);
if (Networking.IsMaster)
{
GameManager.SendCustomEvent("RecountRoleB");
}
RequestSerialization();
}```
{
if (Networking.IsMaster == false)
{
switch (role)
{
case PlayerRole.None:
RoleBObj.SetActive(false);
RoleAObj.SetActive(false);
break;
case PlayerRole.RoleB:
RoleBObj.SetActive(false);
RoleAObj.SetActive(true);
break;
case PlayerRole.RoleA:
RoleBObj.SetActive(true);
RoleAObj.SetActive(false);
break;
}
}
if (Networking.IsMaster == true)
{
switch (role)
{
case PlayerRole.None:
RoleBObj.SetActive(false);
RoleAObj.SetActive(false);
break;
case PlayerRole.RoleA:
RoleBObj.SetActive(false);
RoleAObj.SetActive(true);
GameManager.SendCustomEvent("CheckWin_NoRemainingRoleB_Delayed");
break;
case PlayerRole.RoleB:
RoleBObj.SetActive(true);
RoleAObj.SetActive(false);
break;
}
GameManager.SendCustomEvent("RecountRoleB");
}
}```
this is the current iteration, in isolation from other stuff
i want to test other systems but i run into the race conditions much later when i move on to testing with real players
is the Master also becoming the Owner of the GameManager script?
yeah master is suppose to always be owner of that
can be fine if you're guaranteeing that, but for networking it's usually best to check for the Owner instead of the Master
I think I get how you've got it set up though. So now, what is currently not happening, that you expect to be happening?
SendCustomNetworkEvent is capped to 5 events a second by default isnt it? so you trying to loop though every player and send network events to assign everyone is going to be not ideal if you have more than 5 people in the instance
i was previously having an issue where the win condition was tripped at the very start of the round but i discovered it was because the master was running the recount method without the updated player roles. it was working fine until a little real network latency was introduced and it broke there. when i figured out just what went wrong i redesigned that part into what it is now. then i wanted to really be sure without needing to like pull my friend back to test, so i was trying to think of where to maybe add a delay to simulate high ping and see the effects of that.
i think so, im going to look at the docs rn
you can increase that cap
also, having every player's playerobject fire a requestserialization at the same time, is going to spike everyone's network
and... probably racing with each other
true, that could be an issue with more people
https://creators.vrchat.com/worlds/udon/networking/events#calling-an-event
i didnt know you could change the rate limit i wonder how long thats been there
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.
since network events with parameters :)
while I'm not going to ask you to rewrite a bunch of your code... the network events with parameters would probably be really useful for this
going to have to do some test projects on that
eheh, i just checked this and i thought it was going to be a udon system
i see i see
I'd say since you are trying to sync a persistent state rather than a momentary event, I would just make the GameManager store and sync every player's role, and then assign everybody's role locally, that way it is consistent because it is centrally managed by a single source, and doesnt need to network more than necessisary (no telling A to tell B and telling B to tell A at the same time)
and late joiners will see the states automatically
this is another way i want to try
the master > player > back to master way i got it now opens up the door to some jank
Per this GDC talk I got recommended by Phasedragon, a good way to look at it is this:
sync is for telling everyone the state of a thing, for example health amounts, if something is live or dead, etc
events are for telling everyone the "why" of things (for example indicating the explosion that caused the thing to die) and for making requests back and forth
go to 15:08 if you want to get to that specific part of the talk (on a side note, the way VRC does events guarantees their delivery; but they do arrive at different times than sync which can create race conditions if you try to make them dependent on each other)
In this 2011 GDC session, Bungie's David Aldridge discusses the programming that drove Halo: Reach's online networking.
Register for GDC: http://ubm.io/2gk5KTU
Join the GDC mailing list: http://www.gdconf.com/subscribe
Follow GDC on Twitter: https://twitter.com/Official_GDC
GDC talks cover a range of developmental topics including game des...
very cool thank you i will give this a look!
does this script show how to teleport all players instead of only the host when the game starts?? I've been having issues for weeks that only the host spawns into the game but doesn't spawn anyone else.
my guess is you're looping through all players and calling TeleportTo on them
which won't work, since you can only teleport the local player
it doesnt show there
awww
public void TeleportPlayerToGame()
{
VRCPlayerApi player = Networking.LocalPlayer;
GameObject[] playerObjects = Networking.GetPlayerObjects(player);
UdonBehaviour playerManager = playerObjects[0].GetComponent<UdonBehaviour>();
PlayerRole role = (PlayerRole)playerManager.GetProgramVariable("role");
if (role == PlayerRole.RoleB)
{
Transform spawn = spawnPointsToGame_RoleB[Random.Range(0, spawnPointsToGame_RoleB.Length)];
player.TeleportTo(spawn.position, spawn.rotation);
}
if (role == PlayerRole.RoleA)
{
Transform spawn = spawnPointsToGame_RoleA[Random.Range(0, spawnPointsToGame_RoleA.Length)];
player.TeleportTo(spawn.position, spawn.rotation);
}
}```
oooooo thank you so much!!!!
based on role,it puts players in certain spots
that's what i've been trying to do. I got alot of differnet roles for humans and scps.
master calls it on itself during normal game logic it manages, then in ondeserialization the players check the game state and teleport if it is the right state
there is also a flag for if they teleported already, which gets reset so it doesnt trigger multiple times
importantly for your issue, teleporting players can only teleport themselves
but you can ask a player to teleport themselves, such as wrapping it in a method
sorry if thats a little all over the place, i am a bit busy atm
your good. i'm still learning all of this.
there isn't alot of information around to teach me how to do things the vrc way
ah nice so this would work right?
// -----------------------------
// FIRST WAVE TELEPORT
// -----------------------------
public void TeleportFirstWave()
{
if (!Networking.IsMaster) return;
// Reset flags so all players can teleport
foreach (PlayerHealth ph in gameManager.playerHealths)
if (ph != null) ph.ResetTeleportFlag();
// Trigger local teleport on all clients
SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.All, nameof(LocalTeleportFirstWave));
}
public void LocalTeleportFirstWave()
{
PlayerHealth ph = gameManager.GetPlayerHealth(Networking.LocalPlayer);
if (ph != null) ph.TeleportToSpawn();
PlayTeleportAudio();
}
pretty much the way i did it was sorta like:
void StartGame()
{
if (!Networking.IsMaster) return;
roundstate = roundstate.play;
TeleportPlayerToGame();
}
void OnDeserialization()
{
switch(roundstate)
case roundstate.play:
TeleportPlayerToGame();
}
// similar to teleport back to lobby
void TeleportPlayerToGame()
{
VRCPlayerApi player = Networking.LocalPlayer;
GameObject[] playerObjects = Networking.GetPlayerObjects(player);
UdonBehaviour playerManager = playerObjects[0].GetComponent<UdonBehaviour>();
PlayerRole role = (PlayerRole)playerManager.GetProgramVariable("role");
if (role == PlayerRole.RoleB)
{
Transform spawn = spawnPointsToGame_RoleB[Random.Range(0, spawnPointsToGame_RoleB.Length)];
player.TeleportTo(spawn.position, spawn.rotation);
}
if (role == PlayerRole.RoleA)
{
Transform spawn = spawnPointsToGame_RoleA[Random.Range(0, spawnPointsToGame_RoleA.Length)];
player.TeleportTo(spawn.position, spawn.rotation);
}
}
in StartGame(), the master only the master is teleported. in OnDeserialization(), only the other (active) players will be teleported
specifically in this case, master is the owner and OnDeserialization() wont be called for them so it masks them out
same with the master check, it masks out the active players
its also like this specificially in my case because its in line with the design of how game states are managed and what information is used by the players and for what purpose, such as updating displays or anything that needs to be ran by the player related to the game state
so its kinda an isolated part of something more complex, but in general a player needs to call teleport on themselves
ah
i've tried playerid and transform vector3 in the past but it only helped the host and not the rest of the players when i test it with clients.
even tried to get the joinzone to teleport the hitboxes that were on the players
but i found out that joinzone just collects the players and tells who and how many is on it
you can't access any component of the player directly, such as their transform or hitbox
ik that so I had to make fakehitboxes
so when players also get shot it can kill the player and make them respawn.
has anyone found a solution to having a whitelist that can be updated without needing to update the world? and its not a whitelist for whos allowed in the world. but rather Staff/Admin etc
String loading
i can use that to pull of a github or something?
oh nvm i found the details on their site.
quick sanity check: if i initialize a synced string variable to an empty string, and a late joiner joins an instance, how many bytes are sent over the network to tell them it's still an empty string? ignoring the whole "all networking data is doubled" thing, would that be 2 bytes? or does it just not send anything? or?
also, does that answer change if it's initialized to null instead?
There will at least be the "overheads"
It will definitely send something, not nothing, because if someone wants to update a string to an empty string, there has to be at least something that says, change this string to this empty string
In networked events a null can be sent, docs says
I'd imagine null can also be synced in a variable
I just tested and an empty string and null are the same amount of data
if the string is changed to an empty string from some other string, yes
the scenario I'm asking about is one where the string is initialized to empty or null, and remains empty or null forever, and a late joiner joins the instance while the string is empty or null
i can't imagine a reason Udon wouldn't be able to just "it hasn't changed from the default value, therefore nothing was sent" but I'd imagine it would send something anyway for whatever reason bc Udon
that amount being 2 bytes ? or ?
probably 2 bytes. The overhead adds a lot so I can't tell
1 string being synced in Build & Test sends 68 bytes
my guess is the empty string and null string are actually just the char for null and empty respectively, so it's just 1 char of data for the string
per variable ?? and does that scale with length, or is it just 68 + 2n ?
68 is so much tho-
it should be the latter
yeah this would make sense, just unfortunate that it has to send data for no reason
also noted
well, data is data, even if it's empty
things would break if it didn't sync if the variable was empty
right but if it's still the initialization value, idk why it would need to serialize. like not because it's empty, but because it's the initialized value
68 bytes might seem like a lot of data. but you've got another 280,428 bytes to work with
all late joiners will get a OnDeserialization() and get the latest value, so they would know the value hasnt changed
well total yeah, but 11k per second 😔
to clarify, i'm not trying to send more than 11k per second constantly, this is just in reference to the fact that OnDeserialization() upon joining a pre-existing instance seems limited to 11k for that first second, since exceeding that value seems to frighten VRCObjectSync. so I'd wanna get as far below that value as possible so no networked behaviours get messed up when new players join any given instance
but yeah, it'd be nice if I could configure it so that it sends an extra byte per 8 variables so that each bit can communicate whether or not a variable has changed, significantly optimizing late joiner network bandwidth when multiple variables are often left at their initialized value for long periods of time
but c'est la vie i suppose
I think new player will only fire OnDeserialization() once the sync is complete... actually that is why you shouldn't do anything until OnDeserialization() in your network codes
that is such a small optimization (only matters in the first serialization when someone joins) I doubt it is implemented
you mean until every behaviour is synced? i'd hope not, that wouldn't make much sense
it would be small in most cases yes, and so it shouldn't be implemented by default, i agree. that's why i said it'd be nice if i could opt into such a behavior per-script in case i have thousands of behaviours that each have a string that might not need to be synced each serialization. that adds up fairly quickly given the 11kb limit, so replacing 2 bytes with 1 would be significant in such cases
i recognize they won't implement that though, it was just an unrealistic ideal hypothetical
well, if the bandwidth is exceeded, your network is clogged and it is not just VRCObjectSync will suffer, your player movements and continuous syncs will do their thing and start skipping info
because the limit is... the limit
correct, hence why i've been going through all the optimization i can
well why would you have so much stuff to sync? before optimizing your networking maybe you can optimize the other things to make it require less networking
And, if your code is correct, it should be immune to race conditions
it shouldn't have to require such optimization in the first place to even not crash
and if it can crash in the first second because of a simple serialization, it wont survive all the future serializations if there is any amount of lag that makes your syncs become slightly unstable
my code works perfectly fine, VRCObjectSync breaks bc of a bug I've reported on canny that is currently tracked
everything being synced is necessary (partially because Udon hates when you disable synced objects), but can be optimized in various ways I've been working on
I'm still having issues with my game not teleporting everyone and I saw this link but idk if it's too old sense alot of youtube and links don't work sense sdk keeps updating.
https://creators.vrchat.com/worlds/udon/players/player-collisions/#:~:text=To use these events%2C add,about Collision in Unity's documentation.&text=There are some edge cases,or is moving VERY fast.
Udon has three ways to detect when a Player and an Object Collide - Triggers, Physics, and Particles.
been struggling for months now and every link and help that I get nothing works and the host always teleports and no one else ever teleports.
that is the up-to-date doc
ok cool
you're issue is probably related to using the TeleportTo function
https://creators.vrchat.com/worlds/udon/players/player-positions/#teleportto
The player's position, rotation, and velocity can be accessed and changed with Udon. All of the following nodes require VRCPlayerAPI as an input.
You need to call the TP on the local player
……………..
your*
lemme find my example from my prototype TTT
ah ok. when I was looking around a link said you can use hitboxs/coilder that you put on a player and you can teleport the player with the hixbox on them.
but the problem i'm having atm for hitboxes is that they destroy themselves when the world loads and when the game button is pressed
In your script, you'd go through all the players (id imagine from a list or zone) and have the Host/Owner of your game call the TP player on their PlayerObject hitbox
Are you using Noodles?
private void TeleportAllPlayersToLobby()
{
if (lobbySpawnPoint == null)
{
logger.LogWarning("[LobbySystem] Cannot teleport players - no spawn point set");
return;
}
// Send network event to all players to teleport themselves
SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.All, nameof(TeleportSelfToLobby));
logger.Log("[LobbySystem] Sent teleport command to all players");
}
public void TeleportSelfToLobby()
{
// Each player runs this on their own client
if (lobbySpawnPoint == null) return;
VRCPlayerApi localPlayer = Networking.LocalPlayer;
if (localPlayer != null && localPlayer.IsValid())
{
localPlayer.TeleportTo(lobbySpawnPoint.position, lobbySpawnPoint.rotation);
logger.Log("[LobbySystem] Teleported to lobby");
}
}
This is an example of how I did it
But this is specifically for the end of the round
I use gamemanger to create and place hitboxes on each player that spawns in.
[Header("PlayerHealth Prefab & Slots")]
public GameObject playerHealthPrefab;
public int maxPlayers = 50;
[HideInInspector] public PlayerHealth[] playerHealths;
// ───────────── START ─────────────
void Start()
{
if (!Networking.IsMaster) return;
playerHealths = new PlayerHealth[maxPlayers];
// Create PlayerHealth objects only
for (int i = 0; i < maxPlayers; i++)
{
GameObject phObj = VRCInstantiate(playerHealthPrefab);
phObj.name = "PlayerHealth_" + i;
phObj.transform.SetParent(transform);
PlayerHealth ph = phObj.GetComponent<PlayerHealth>();
ph.gameManager = this;
playerHealths[i] = ph;
}
I would suggest just making Hitboxes via PlayerObjects
So you know they always exist for each player
(this is how I did it)
This ^
here is like my entire script for the player stuff
Feel free to pick apart whatever
(it is bad, sorry)
But for your use case, the update is where you wanna do it or even the postupdate or whatever
If you wanna learn a bit more on how to setup PlayerObjects
https://www.youtube.com/watch?v=fx9d7jiUwBY
Also combining this concept with this
https://www.youtube.com/watch?v=TlRSzmnDV18
Is how you can get your hitboxes sorted
If you want to duplicate an object for every player, you will want to use the vrc player object component. Here's a video showing just how to do that!
So hopefully this is helpful. As always, if you have any questions, feel free to ask in the comments bellow! ^^
Come join the discord! https://discord.gg/jjdnCWVvKg
Wanna help support me in maki...
Here's a tutorial on how to make an object follow either the local player, or a selected player in the scene. That being said, it's more of a how to make an object lock-onto a player, rather than just follow.
You could however use this same script to create a target for your object to try and reach, and then do a lerp on the object itself betwe...
I thought those videos are too old and are not up to date?
if you run this on Start, it will only instantiate for the local player.
Also, VRCIstantiate is deprecated. use the normal Unity Instantiate (Object.Instantiate) instead
Also even if a video is old, you can often build whatever they are doing, just can't blindly copy. Have to put your thinkin cap on
on top of that, you won't be able to do any networking on scripts that are on instantiated objects
so you definitely want to be using PlayerObjects for stuff like custom hitboxes
does there is a way to attach an object directly to someone ?
like i put an object in the world and i take it and put it on a player and it will stay on him is it possible ?
Yeah, those 2 videos should guide you in the right direction
Or wait
Well for the object attaching, I believe you can do that
There also is a prefab somewhere that accomplishes that... Don't know what it's called
it's AttachToMe, but last I heard it doesn't work anymore apparently
Does this mean Drinking Nights sticky cheese no longer exists?
mayhaps
oh yea i tried it it was not working
I think it's just with the new SDK it might not work right, may require a bit of manual tweaking to get it to work
but it at least shows what you want is possible.
You just get the closest player -> get the closet bone on them -> make the object follow that bone
there's already a BoneFollower script available as an example in the SDK
can someone explain Cannot convert from VRCPlayerApi to int that I sometimes keep having issues with?
I try to do ph.AssignOwner(player); // player is VRCPlayerApi to call
public void AssignOwner(VRCPlayerApi player)
{
if (!Utilities.IsValid(player)) return;
Networking.SetOwner(player, gameObject);
ownerPlayer = player;
}
trying to network the health system to sync with each player so they can own their own health system when the world starts they have their own.
what line specifically is throwing the error?
I have it in my public override void OnPlayerJoined(VRCPlayerApi player)
public override void OnPlayerJoined(VRCPlayerApi player)
{
if (!Utilities.IsValid(player)) return;
for (int i = 0; i < playerHealths.Length; i++)
{
if (playerHealths[i] == null)
{
GameObject phObj = Object.Instantiate(playerHealthPrefab);
phObj.name = "PlayerHealth_" + player.displayName;
phObj.transform.SetParent(transform);
PlayerHealth ph = phObj.GetComponent<PlayerHealth>();
ph.gameManager = this;
// <-- Here is the problematic line
ph.AssignOwner(player);
playerHealths[i] = ph;
break;
}
}
}
it calls to the this part of my health script:
public void AssignOwner(VRCPlayerApi player)
{
if (!Utilities.IsValid(player)) return;
Networking.SetOwner(player, gameObject);
ownerPlayer = player;
You can just attach this to the existing hitbox prefab and have it manage that
So you have a PlayerObject for hitboxes, you can then add an empty game object and have the health prefab in there
what type is ownerPlayer?
VRCPlayerApi
hm. I don't see where it's being used as an int
this would all kind of already be done for you with PlayerObjects
ok so should I just remove anything on the scripts that gives the player owner of the health and do playerObjects?
on the inspector
pretty much
the Owner of the script will be forced to be the player that the object belongs to
I also had my gamemanager create each health to be put on players that spawn in the world and I set it to max 50 players [even tho my world will only support 32 players.] just in case a group owner joins the world and makes it 33 players. but the health would destroy itself because it would spawn all 50 healths before players join so i'm trying to get my script to add to each player when they join but it sounds like playerobjects fixes that issue.
yeah PlayerObjects will also deal with the instantiation stuff for you automatically
and you can still do networked stuff on them. It would be a very good idea to switch to using them for this
ok
playerobjects can also help me with teleporting or is that a completely differnet issue?
they could actually
I been using a teleport manager because of my wave system where deadplayers/latejoiners can join the current game as reinforcements.
and using my gamemanager to call the teleportmanager to tell when players can spawn in.
is there a link that can explain playerobjects so i have more information?
PlayerObjects allow you to automatically give each player who joins your world a copy of a GameObject, such as a flashlight, a health bar, or a sword.
thank you
so a PlayerObject could make teleporting pretty easy, if you first have a function on the PlayerObject that teleports the local player to wherever.
Then, from the GameManager (or wherever else relevant), if you want to teleport a specific player, you'd just have to:
- Get a VRCPlayerApi reference to that player
- use
GetPlayerObjects(orFindComponentInPlayerObjects) to find the PlayerObject that belongs to that player - use
SendCustomNetworkEventto call that teleport function, but target the Owner
since the Owner will always be the player that belongs to that object, if you send the event to the Owner, it will teleport that player that you've targeted
I have a transform[] for my different spawns connected to my gamemanger and 7 different roles. will playerobject know where to spawn the players or does my gamemanager tell playerobjects by using getplayerobjects for teleporting?
you could do either, just have to code it to do that
Network Events could have parameters now. So you could easily add a parameter to the PlayerObject teleport function that indicates where they are spawning
the parameter could be the index of your transform array that you want to teleport them, or you could just directly pass the position and rotation as parameters
you can only pass the syncable types as parameters, so you can't pass in the Transform directly.
If your roles determine spawns, yeah its doable. Personally my project just teleports them to a random spawn
100 ways to skin a cat or something
I like to use "many ways to fry an egg", that one is a little morbid 😅
I agree
I don't wanna learn the history of that saying
Guess people wore cat fur
so i got teleporting working its just I get a null with assignedspawns and whenever i try to give local player it's keeps saying null and not teleporting the player because it doesn't know where to go
public void TeleportToAssignedSpawn()
{
Debug.Log("[TeleportDebug] TeleportToAssignedSpawn called for PlayerHealth of " + gameObject.name);
if (!Utilities.IsValid(Networking.LocalPlayer))
{
Debug.Log("[TeleportDebug] LocalPlayer not valid");
return;
}
// Only teleport if this PlayerHealth belongs to the local player
GameObject[] playerObjects = Networking.GetPlayerObjects(Networking.LocalPlayer);
bool belongsToLocalPlayer = false;
for (int i = 0; i < playerObjects.Length; i++)
{
if (!Utilities.IsValid(playerObjects[i])) continue;
PlayerHealth ph = playerObjects[i].GetComponentInChildren<PlayerHealth>();
if (ph == this)
{
belongsToLocalPlayer = true;
break;
}
}
if (!belongsToLocalPlayer)
{
Debug.Log("[TeleportDebug] PlayerHealth does NOT belong to local player");
return;
}
Debug.Log("[TeleportDebug] Teleporting player now");
if (currentAssignedSpawn != null)
{
Networking.LocalPlayer.TeleportTo(currentAssignedSpawn.position, currentAssignedSpawn.rotation);
}
else
{
Debug.Log("[TeleportDebug] currentAssignedSpawn is null");
}
where does currentAssignedSpawn get assigned?
it's in my gamemanager it calls it right here.
private void AssignInitialRolesAndSpawns()
{
if (!Networking.IsMaster) return;
VRCPlayerApi[] players = new VRCPlayerApi[VRCPlayerApi.GetPlayerCount()];
VRCPlayerApi.GetPlayers(players);
int validCount = 0;
for (int i = 0; i < players.Length; i++)
{
PlayerHealth ph = GetPlayerHealth(players[i]);
if (ph != null && ph.IsValidPlayer())
validCount++;
}
it's long so i could only do the short
the master calls the spawns for all of the players
you should get a tool like ShareX to screenshot your code, you can combine screenshots for it to be easier to read.
Also, to do special coding type in discord, do ` 3 times, followed by C# and another 3 of those symbols
If your spawns are like synced, its probably not syncing. Lowkey the pattern I would use it
Assign roles to users -> wait a bit to ensure everyone playing has been given their roles.
Gamemanger calls a function for everyone and then the local player will determine their spawn relative to their role
Yeah Sharex is like a snipping tool
is the SetSpawn function networked?
You restrict AssignInitialRolesAndSpawns to the Master, so it's only going to run for that player. If the assignments made in SetSpawn aren't networked, only the Master will actually have it assigned, everyone else will see the variable as null
as I suggested before, you could also use network events with parameters to pass the position and rotation as parameters
it will be local to the Master who's running this function
but it will not execute for other players, namely the players you're trying to teleport.
Once you get the hang of how networking works, it gets a bit easier
Intuitively the way you'd think something works in VRC, it probably won't.
vrc is a special needs for coding. it wants only certain special stuff
Where is that 30 min video explaining the way you deal with networking?
By PhaseDragon
this isn't a problem exclusive to VRChat. It's just multiplayer games in general
it's probably why 90% of player i met give up on making their worlds because they couldn't understand vrc logic.
this isn't a problem exclusive to VRChat. It's just coding logic in general
I can show you some modifications you can do that should allow it to work.
sure I need to learn this so i can get better at this.
links would also be grateful so i can save them and read them while i work.
I can give some doc links sure, but I'm writing the code. lol
https://creators.vrchat.com/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.
firstly, reduce the TeleportToAssignedSpawn function to just this:
[NetworkCallable]
public void TeleportToAssignedSpawn(Vector3 targetPosition, Quaternion targetRotation)
{
Debug.Log("[TeleportDebug] TeleportToAssignedSpawn called for PlayerHealth of " + gameObject.name);
Debug.Log("[TeleportDebug] Teleporting player now");
Networking.LocalPlayer.TeleportTo(targetPosition, targetRotation);
}
it's effectively only one line. the TeleportTo function
the trick to this working is how the game host/Master calls this function.
currentAssignedSpawn is only getting assigned locally by the Master, but they can still access the Transform's values themselves, and just pass in the position and rotation as parameters, and most importantly only tell the Owner to run the function
so whenever the game starts or whatever, the Master loops through all players, then finds their PlayerHealth PlayerObject, and calls SendCustomEvent targeting the owner.
that call would be something like:
SendCustomNetworkEvent(NetworkEventTarget.Owner, nameof(TeleportToAssignedSpawn), currentAssignedSpawn.position, currentAssignedSpawn.rotation);
(might be slightly different depending on a few things)
[NetworkCallable] is the attribute required to use network events with parameters
you need to import using VRC.SDK3.UdonNetworkCalling; in order to use it
that's not good!
that is SDK3 but it's a very old version.
you should update immediately
3.10.1?
so when was the sdk updated? I've been using the same sdk sense i created the world.
3.7.6 is like 10 months old....
there's usually an SDK update every month or two lol.
3.10.1 was released early December
ah so now I see TeleportToAssignedSpawn is not supported anymore
huh? that's your function lol. what do you mean by not supported
oh nvm it's saying Duplicate network callable method name 'TeleportToAssignedSpawn'. Overloading of any kind is not supported.
means you've two functions named TeleportToAssignedSpawn. delete the old one
yeh I change it to TeleportToAssignedSpawnNetwork
ok now that i'm updated with a better sdk is there a link that explains what's new with the sdk?
i've been using the 3.7.6 for a long while
yeah, here's all of the release notes
https://creators.vrchat.com/releases/release-3-10-1
Summary
anyway, for the second part
with that modification with the teleport function, all that matters is calling it in the right way later once you actually want to teleport that player
This will likely need modifications depending on what you're doing, but this is a basic example:
//get an array of all players (better to cache this elsewhere, but it's here as an example)
VRCPlayerApi[] players = new VRCPlayerApi[VRCPlayerApi.GetPlayerCount()];
VRCPlayerApi.GetPlayers(players);
//loop through all players and find their PlayerHealth PlayerObject
PlayerHealth ph;
foreach (VRCPlayerApi player in players)
{
GameObject[] objects = Networking.GetPlayerObjects(player);
for (int i = 0; i < objects.Length; i++)
{
if (!Utilities.IsValid(objects[i])) continue;
ph = objects[i].GetComponentInChildren<PlayerHealth>();
if (Utilities.IsValid(ph)) break;
}
//tell the PlayerObject's Owner to teleport themselves
ph.SendCustomNetworkEvent(NetworkEventTarget.Owner, nameof(PlayerHealth.TeleportToAssignedSpawn), ph.currentAssignedSpawn.position, ph.currentAssignedSpawn.rotation);
}
at whatever point you teleport all the players, you just send an event to that PlayerObject's Owner to make them teleport themselves
ah so this will work better then what I had before
that's the goal, at least
Quick question so in the logs. Player 1 and player two have been successfully teleported, and their role has been given and gives the coordinates of where they spawn. The assume spawn role says it’s given the role to player two. So in the lodge, it says everything has worked, but the second player still hasn’t teleported. What else could spa prevent player too from teleporting even though the log say that everything works
does the logs on Player 2's client show they teleported, or on Player 1's?
Player one logs there’s nothing on the player logs
Player 2 needs to be logging that they are being teleported. If Player 1 logs it, then they aren't properly sending the event TO Player2 to run
Ok cause originally before I made a certain change player two did have logs where it said it did successfully teleport, but the assigned spawn wasn’t working and I fixed that and then there was no more logs on player too
Sadly had a rush to work so it’s gonna be a couple of days before I can look at the code again
I've set this up before, and I'm recreating this in my new world, but it doesn't work. Nothing will be toggled.
SetOwner is missing the player input. You likely want to assign the local player there
I remember you. You saved my life before lmao
I practically live here
yeah
whats even the point of that script?
Ah, it works now. The set up is different than I remember
I think you have the graph doing some extra things that aren't necessary
properly setup manual sync stuff just works
Please teach me, thank you
without any care for late joiners
you were also previously assigning DeskChairs instead of LateJoiner
It's like you've got two different variables for tracking the state and confusing yourself with them
when you only need one
I do this for my world where I teach, and I turn on the screen and chairs, so people can join and see the toggled-on stuff.
yeah, I noticed it when I posted it, and fixed it
that was probably what the actual issue was, since your change event is on LateJoiner
Btw this toggle is on UI
@mild robin ondeserialization happens to anyone asap, be it late joiner or person in the world
this includes the Change event(s) as well
so i still dont get it
Well, excuse me if I'm still learning and figuring it out
the whole right section you've grouped as Late Joiner should all be not needed. the LateJoiner Change event is already doing the job of that code, but better
Got it
I cant even read the screenshot it is so low res😭
so that's it?
@mild robin #udon-networking message maybe thatd help
other than the Get activeSelf and UnaryNegation that goes nowhere, yeah should be good now
i mean the whole idea
I saw that. But it's somehow not helpful lmao
Get rid of them?
they're doing nothing, so might as well. Unless you think they look pretty or something there. then maybe keep them
@mild robin 🤷♂️ sad, was very useful for me.
maybe I'll check it out again anyway
ye ig with more context and experience it may make more and more sense
Yeah, I create worlds for a couple of years, but rarely get into udon stuff
i'm looking through some code, and don't you need to use the IsOwner check on a VRCPlayerApi?
if (!Networking.IsOwner(gameObject)) return;
or would that automatically default to use the local player?
yeah it uses local player
if (!Networking.IsOwner(gameObject)) is probably the most commonly recurring line of code in all of my projects
I should probably put an abbreviated version of it in my universal base class at this point >.>
is it equivalent to doing a Networking.LocalPlayer call in that case?
if so, would caching the local player and running IsOwner on that reference be any better?
don't think it matters
You should cache the LocalPlayer variable regardless, but IsOwner is also an extern, so supplying a cached player reference wouldn't make any difference in this case
Hey, I’m having trouble with teleporting multiple players at the right random spawnpoints. Like I got the local player to execute the network to teleport inside OnDeserialization
Each player gets a random role and a unique spawn point, but everyone ends up at the same spawn, even though the logs show different assigned positions.
The big log is from player 1 and the one line is from player 2-32. I tired using these to help but i keep hitting a wall...
SetSpawn() to assign each player a spawn
TeleportToAssignedSpawn() locally for the owner
TeleportToAssignedSpawnNetwork() for syncing remote players
Proper ownership assignment per player
what exactly are you syncing?
a Vector3?
Since it is a momentary effect and not meaningful for late-joiners, consider sending network events with parameters, that way you dont have to worry about ownership for a particular variable
I’m syncing a Transform (position + rotation) per player
yes a vector 3
[UdonSynced] private Vector3 assignedSpawnPosition;
[UdonSynced] private Quaternion assignedSpawnRotation;
I store the position Vector3 and rotation Quaternion in a synced variable on the player object and make the remote players use OnDeserialization() to read the synced position/rotation and teleport themselves.
Player Object is always owned by the player, so you cannot transfer ownership to someone else, in other words you cannot sync a variable to a remote player's Player Object because you cannot become the owner
So you cannot do such one-to-many syncing if I understand what you are trying to do
You can however send a network event with parameters to tell each Player Object to do stuff
The performance benefit you'd likely get from locally caching the output of LocalPlayer is so small that it's most probably completely imperceptible unless you're doing every frame stuff (and even then I'm not sure how much it realistically impacts performance).
Ive always wondered 🙏
Lots of little “cache everything” things I wonder how much are necessary
I was trying to get the master to tell the players what roles and where to teleport and it did work but it caused every player to spawn where the host is.
by telling each playerobject that the player owned where to go
been looking up multiplayer games tutorials and from what I seen the host can just set positions and tell the player where to go and what role they have.
what do you mean by telling each playerobject where to go?
how did you do that exactly, can you post the code
foreach (var player in players)
{
PlayerHealth ph = GetPlayerHealth(player);
if (ph == null) continue;
if (Networking.GetOwner(ph.gameObject) != player)
Networking.SetOwner(player, ph.gameObject); //
// Assign spawn and role
Transform spawn = GetSpawnForPlayer(player);
ph.SetSpawn(spawn);
// Teleport the owner locally
ph.TeleportToAssignedSpawn();
}
if (Utilities.IsValid(Networking.LocalPlayer) && Networking.GetOwner(gameObject) != Networking.LocalPlayer)
{
Networking.SetOwner(Networking.LocalPlayer, gameObject);
Debug.Log($"[DEBUG] Ownership claimed by local player: {Networking.LocalPlayer.displayName} -> {gameObject.name}");
}
triple back-quote ` for nice formatting 🙂
Ive did passes over my code before and made the micro optimizations to reduce externs and other things putting less on Udon and the difference was very noticeable. I had doors managed by Udon and then used Animators but that wouldnt always tick so that couldnt have been a big hit to avg but the combination was big
i dont see any syncing code
or any networking activity
it looks like everything is happening locally on the masters client
for (int i = 0; i < players.Length; i++)
{
PlayerHealth ph = GetSpawnedPlayerHealth(players[i]);
if (!Utilities.IsValid(ph) || ph.currentAssignedSpawn == null) continue;
if (Networking.GetOwner(ph.gameObject) != players[i])
{
Networking.SetOwner(players[i], ph.gameObject);
Debug.Log($"[Ownership] Assigned {players[i].displayName} as owner of {ph.gameObject.name}");
}
ph.TeleportToAssignedSpawnNetwork(ph.currentAssignedSpawn.position, ph.currentAssignedSpawn.rotation);
if (Networking.LocalPlayer == players[i])
ph.TeleportToAssignedSpawn();
}
is ph a VRC PlayerObject?
and where is the syncing happening, there is no request serialization or deserialization
no ph is PlayerHealth ph = GetSpawnedPlayerHealth(players[i]);
syncedTeleportPosition = targetPosition;
syncedTeleportRotation = targetRotation;
teleportRequested = true;
RequestSerialization();
what is PlayerHealth ?
playerhealth is a game core. it helps with teleporting and giving roles and spawnpoints and game logic.
it was originally suppost to be a hitbox but I saw other vrchat scripts of games and decided to turn it into game logic. the playerhealth name just stayed because i was too lazy to change the game across other scripts.
i think you can research on ownership and synced variables and see what is wrong, and check if the variable are synced
that's what i've been doing honestly but it's alot to read and figure out.
i'm still learning to code for vrchat/udon
@placid sedge
If it helps the way I've been doing synced spawns is by having the master sync a string for all of the player IDs in the lobby
The string contains pairs consisting of a playerID followed by an index from an array of GameObjects that represent a spawn location
Ex: [1, 1, 2, 3, 3, 7]
Player ID 1 will spawn at spawn location '1', ID 2 will spawn at 3, ID 3 will spawn at 7 . . . so on
Once the spawn order string is synced from the master the clients parse it to see if their own player ID matches with it
If it does, . . I as the client will teleport to my matching paired spawn:
public void _ParseSpawnOrder(string spawnOrder)
{
string[] spawnOrderDelimited = spawnOrder.Split(',');
Log($"Received spawn order: {spawnOrder}");
for (int index = 0; index < spawnOrderDelimited.Length - 1; index += 2)
{
int playerID = int.Parse(spawnOrderDelimited[index]);
int spawnIndex = int.Parse(spawnOrderDelimited[index + 1]);
Log($"Parsing playerID {playerID} and spawnIndex {spawnIndex}");
if (Networking.LocalPlayer.playerId == playerID)
{
GameObject spawnPointObj = this.GetSpawnObjFromIndex(spawnIndex);
if (Utilities.IsValid(spawnPointObj)) this.TeleportMe(spawnPointObj.transform.position, spawnPointObj.transform.rotation);
break;
}
}
}
I thought player id don’t work cause I’ve tried using it but it never works
I’ve been using playerobject to tell the master to give roles with special spawnpoints for each team.
The local player teleports themselves when the master tell them where and when to teleport with deserialization
It’s the only thing that I got working because other methods failed and the host always teleported and no other player would teleport and It’s taken too long to get working
I’ll try and see if it works.
To be so honest with you, somewhere in the PlayerObject realm sounds like a path I'd take in the future for something like this
Generally anything player-specific related are where PlayerObjects shine so this seems like a solid use-case
The issue with your approach Stealth is only the user can make changes to themselves. (Ie the owner of the player object)
So if the master manager or whatever trys to set a PObjects value to something, it won't work.
Instead you need to call a custom network event to that specific object, and pass the transform or whatever. I have personally never done this yet.
In my initial example for my hitbox pobject, I have functions like Deal20Damage or something.
So either you pass the variable through a networkcallable OR you create a function on the PObject for each spawn and call them specifically from the manager script
The ttt core?
Yeah with that script. An example of making multiple functions is with how I handle damage
And the attacker calls that when their hitray hits the hitbox
but for your case, you could have a spawn script for each spawn location
but also OnDes's solution works too. Sync a list of player IDs, wait a few seconds, then tell all playerhealths to teleport (ie, read from the master script for their data)
OR do what someone said above with network callable
but IDK how they function on PObjects?
My game manager handles the spawns
Each team has spawns for them
6 teams with special spawns
4 teams spawn in the first wave
2nd wave for reinforcements for dead players/late joiners. They spawn as friendly or aggressive reinforcements which are the last 2 teams but both don’t spawn at the same time
That’s why there are alot of spawns
I've seen some convos about Udon best practices and I wanna understand this better...
So Demon Bartending System has the drinks all disabled and activated via object managers that sync activated drinks.
IE Shaker determines drink based on recipe -> Finds the correct Object manager which syncs enabled/disabled pickups -> User gains ownership and then modifies the data, syncing to everyone and the update handles showing visible states.
Someone recently said it isn't good to have disabled pickups, and instead, I should disable the mesh renderer? Why?
networking goofiness
if an UdonBehaviour is on a disabled object, none of its networking events will fire, which can cause things to desync easily
in some cases it's probably fine.... but it's just better to keep it enabled when you can, and just visually hide the object by disabling the mesh renderer
for a pickup it might be okay, especially a synced one, since it's going to be Continuous synced anyway. So even if none of its networking stuff runs when it's disabled, it will resync fairly quickly once it gets enabled again. It's moreso a problem with Manual sync scripts, where that oh so crucial OnDeserialization could be missed because the object was disabled
Gotcha ok
So if the disabled item has any data valuable to the game and is manually sync, you are better off either moving the script to a parent of the object being disabled/enabled
OR
Just disable the meshrenderer
(Goal is just to have the udon behavior always available if it matters in the context)
yeah anything Manual sync I'd always keep enabled
if it's some sort of Game Manager or a driver, I'd just have it on its own empty GameObject
Something to keep in mind, though, is that having as much related network data in a single instance of an object is preferrable whenever reasonably possible. Having related network information across different objects creates race conditions you'll need to design around.
hello! i'm having difficulty getting networked events working
i'm trying to call a function on a different player's playerobject with
script.SendCustomNetworkEvent(NetworkEventTarget.Others, "FunctionNetwork");
where script is found set with pretty much exactly vrc's find playerobject example
var objectArray = Networking.GetPlayerObjects(target); for (int i = 0; i < objectArray.Length; i++) { if (!Utilities.IsValid(objectArray[i])) continue; Script foundScript = objectArray[i].GetComponentInChildren<ScaleScript>(); if (Utilities.IsValid(foundScript)) return foundScript; } return null;
but i can't seem to get the event to fire on other players, though if I do
script.SendCustomNetworkEvent(NetworkEventTarget.All, "FunctionNetwork");
it seems to call on the local player, is there something with ownership i'm missing?
the owner of the playerobject is the other player but the object that is firing the networked event is owned by the local player
As I understand it, are you trying to run a function meant for someone by calling it thru their PlayerObject?
If so, wouldn't NetworkEventTarget.Owner be relevant?
there's two different scripts, the script that is sending the call is owned by a different player than the script it is sending the event to, so I thought owner would be the owner of the sender script
let me give that a shot, thanks!
Tbh, it should have worked with Others anyways
But we'll see
that seems to make the event fire on the sender scripts owner, so at least i know it's actually sending
i must be doing something else wrong then
hold on, does OnPlayerParticleCollision() fire only for the player who gets hit?
i mightve misunderstood how that works and that might be my problem since the sender script wouldnt be able to fire the on particle collision if the remote player is the one getting it
If it works like triggers it's per-client calculation
hmm
as it is it seems it fires the network event if the particle hits the owner of the sender but not if it hits a remote player
either way, got something to chase now with that maybe! thanks for the help! might be back soon lol
OnPlayerParticleCollision() fires for anyone, it outputs a playerAPI player to say which player was hit
But if it uses the same player collider as OnPlayerTriggerEnter(), then you gotta be careful the difference of collider shape attached on local vs remote players
The local player has a capsule collider 0.4 m diameter and 1.65m height attached
But remote players has a 1 m diameter sphere attached at their player position i.e. their location on the ground, so it looks like a half buried oversized ball
So depending on your particle, the one hitting your torso on your local screen, is not going to hit you as a remote player in the other guy's client
It's one of those Udon Jank
TBF that wouldn't be so much Udon jank but VRC jank
true
While we are on it can we apprecieate VRChat at least acknowledged this bug, and then roleplayed to have fixed it in a dev blog? https://ask.vrchat.com/t/developer-update-30-march-2023/17186
Welcome to the Developer Update for 30 March 2023! This is the tenth Developer Update of 2023. Today’s featured world is Exoplanet Journey by Niko∗! Visit planets beyond our solar system while lying in comfort within an ultra-tech spaceship. Polarity inverter not required. If you’d like to catch up, read our previous Developer Update ...
So we can pretend it is fixed in our minds
Well, might as well:
https://feedback.vrchat.com/udon/p/remote-player-collider-is-a-sphere-buried-on-the-ground-rather-than-matching-the
Here is how the remote player collider looks: https://global.discourse-cdn.com/vrchat/original/3X/4/5/45bf8cf5343d645a5e99baab04cb6cf8dd8a1dde.
whats currently the best method to respawn a VRC Pickup?
Do I have to call the "Respawn" Method on VRC_Pickup for each player?
if its synced, VRCObjectSync Respawn()
If not, just set position
any player can call VRCObjectSync Respawn() and it respawns globally?
only the owner can, but yes
just set the owner just before
But check if the object is picked up, or picked up within the last say 1 minute, in case someone else is toying with it and you dont wanna take away their toy
but that is up to you
thank you :3
I came up with this.
```c#
public void respawnItem()
{
if(pickup.IsHeld){
return;
}
if (!Networking.IsOwner(gameObject))
{
Networking.SetOwner(Networking.LocalPlayer, gameObject);
}
sync.Respawn();
}
`respawnItem` is called via a button in the world
looks alright
Unsure what is failing to serialize. But I'm noticing my Playerobject intended to serialize VRCPlayerURLs isn't loading in the world, so I'd assume it's that. Why isn't it saving/syncing/serializing?
Is the script using a lot of synced variables?
try initializing your array to something when you declare it
How so?
do this line at the variable declaration, rather than later
if an array is null, serialization will fail
even if you eventually do give it a value before you sync it, I often still run into issues if the array is null at any point, so as long as it has some sort of value it shouldn't complain
Made this post in the wrong catagory before. Remade it under Bug Reports. If anyone is interested in looking at it and verifying it's a bug that you are also experiencing then that would be great or if you have other questions / feedbadk about the report then let me know. Thanks!
You can trigger network events in ClientSim?
Oh good catch. I found the page though google when trying to understand what order events are fired in and I didn’t notice it was under clientsim category instead of the actual client. I’ve been testing with the actual client for networking events. Now I’m more curious because why would the clientsim have a different order of event firing then the real game?
Also I just found this page that says the clientsim can simulate remote players. I’ll need to poke around and see if that’s really possible I’ve never tried to do it. https://clientsim.docs.vrchat.com/systems/runtime/player/
time to do more investigation… thanks for the second set of eyes!
The ClientSim representation of a player has been split into many components compared to CyanEmu. Each component handles a different aspect of the player. Below you can see the hierarchy of both the Local and Remote player prefabs.
After more testing I can spawn a remote player but none of the functions are called such as OnPlayerTriggerEnter or OnOwnershipTransferred. I'm guessing that the clientsim stuff is half baked based on it being tagged as beta. on the second page I found. Just not tagged as such on the first page I found
triggerenter def works
I don't think the spawnable "player" in the clientsim can do or trigger anything
does a disabled gameobject still sync states via udon?
maybe
Personally I've had a lot of trouble with disabled objects and networking, so it's been forever since I've tried using a disabled network object.
oh, also an object will not have functional networking until a few frames after its start is run for the first time
okay I'll keep this in mind.
I have a global UI toggle on the other side of the map.
And a new spawn might never see this ui.
Now I trigger a global state container (always active) instead
Not for the simulated remote player only the main player. I tested it this morning
@rich salmon dragging a remote player inside a trigger def does work. it running checks for is local=true and the rest of the code - not.
remote players in clientsim 100% activate triggers (because remote players ingame do as well)
do keep in mind though the remote players in ClientSim are purely only remote players. they cannot simulate another player executing code.
So things that can be triggered by remote players (such as OnPlayerTriggerEnter) work, since those only run for the local player when they witness another player entering the trigger. But functions like OnOwnerShipTransferred may not, since there are cases where that other player is executing the function instead of the local player.
Yeah I was calling an ownership change inside OnPlayerEnter and that was not working. So I guess I assumed incorrectly OnPlayerEnter wasn’t being called at all. That’s fair
The only networking related thing I know of that works in clientsim is SendCustomNetworkEvent in instances where it would cause a local event to be fired
Yeah I got off track because I didn’t see it was talking about clientsim. I really care about the order that events fire in the real game.
Does OnPlayerLeft always get called before OwnershipChanged? In my limited testing it is in that order. but I’ve only tried it a handful of times. Or is this a race condition and one could potentially come before the other at random?
Or is it planned to change in the future maybe?
What threw me off was how it seemed to describe a situation where ownership cleanup happens before OnPlayerLeft is called. And now I’m just questioning everything lol
Original link that describes ownership cleanup before OnPlayerLeft for reference as I deleted the bug report. https://clientsim.docs.vrchat.com/systems/runtime/synced-object-manager/
The SyncedObjectManager keeps track of all initialized synced objects (IClientSimSyncable) in the scene. These synced objects are put into two lists: one list for all synced objects, and another for all position-synced objects. The SyncedObjectManager currently has only two main functions. The first is to check all position-synced objects to ver...
Hello, I'm never sure which is the right place to ask questions so please forgive me if this isnt appropriate but i am trying to learn how I can use the persistance functionality to make a univerally global counter for a world im working on, like the counter in the giant coin pusher world, preferably updating live if possible. Where is the documentation for persistance so i can study how it works besides reverse engineering the examples in example central? I am an artist not a coder but i can figure it out if i had even the minimal information on how it works. Thanks in advance!
here's the general doc page on persistence, you'll most likely want to check out the info on PlayerData
https://creators.vrchat.com/worlds/udon/persistence/
What is Persistence?
there are also several example projects that are available in Example Central that showcase some simple examples of persistence in action
https://creators.vrchat.com/worlds/examples/persistence
Learn about the possibilities of Persistence and User Data by exploring these example prefabs.
but there is a little problem, by "universally global counter", do you want this counter to be synced across all instances, or just saved per-player?
VRChat's persistence doesn't quite support cross-instance persistence
It needs to be synced across all instances and keep a total count of how many times a button was pressed, and not be reset in each instance or when all instances are closed. It has to be possible in some way because ive seen something similar done in the giant coin pusher world where the total number of coins earned by everyone has a counter. I am guessing, in my uneducated opinion, that this comes from each players persistant score added up to the total displayed but how is beyond me.
your assumption is incorrect, unfortunately
it's pretty tricky to have that kind of functionality
the ways I can think of on how to do it are: you "virus spread" the saved data between players, so every player "shares" their current accumulated data; if enough players play with enough other players, then the data is close enough to what a "global" score would be. This is often how highscore systems are implemented
the other way: you host your own web server that saves this data instead of using persistrence. That way, anyone in any instance can ping the server to accumulate it, getting around persistence's limitations
an accumulated count would probably be more accurate with option 2
all players would need to have Untrusted URLs enabled. And you'll have to learn how to host a web server and how to set it up to work with this functionality
if it wasn't obvious, you can think of persistence as individual players having their own little save files, and you can save whatever data you want. This save file is not per-world, it's per-player
What if i could aggrigate the "score" from every player into one number that could then be displayed? That way each player has a local count, and that is added into a global count? Can those numbers be accessed with a script?
you can access the persistent data of all players in a single instance
so you could add up all of the values of everyone currently in the same lobby, but not all players globally playing in your world
forgive me for my ignorance but why not? Are all players not having their score stored in the same data storage on the server?
well logically, yeah. But your scripts cannot access it
your scripts will only be able to access everyone in the same instance
Huh. Well i guess my idea will have to wait until the dev team implements a more comprehensive system then. Most MMO games have leaderboards and other metrics available, social media and youtube has viewercounts and like metrics. It is possible just not here and not now. Thanks for your time.
Japan Street's "virus spread" method works very well, I dont think it ever becomes super outdated, because all it takes is one player to update the instance with the most updated data, so it is exceedingly unlikely the leaderboard becomes degraded, statistically speaking. As long as you always have a few visitors the "virus" will persist pretty well globally.
VRC example central does have a leaderboard example for a starting point
Also I believe Idle Fishing World has an outer api that they use as you need to enable untrusted urls
There are boxes that track how many cubes were input to work towards global goals
Acting as a psuedo voting system for future updates
I agree. I don't like enabling untrusted
I doubt it's ever going to happen. That's potentially massive amounts of data anyone can arbitrarily have the VRC servers run through and, perhaps more importantly, there is no guarantee each player will have identically formatted saved data, something necessary for an iterative task like that. The problem is already solvable, and much better solved, by the web server approach.
Cross instance global variables might be possible, but accessing the player data of players outside the instance seems highly unlikely.
it works within limit
maybe it is your code
see if debug log says anything in game
I can almost guarantee the issue is with your code or setup. Could you actually describe what issue you are encountering? Then someone may be able to assist you
Yeah drop the script(s)
hey if you want to know how to sync images between players (in your udon worlds), there's a way. remember that you're going to have to ensure it's low enough res that the image isn't megs, try keep it around 200kb tops. but yeah. you normally can't sync the data because it's not a primitive, you'll end up with a renderTexture output, FAAAAR from a primitive. Udon restricts the active property on renderTextures too, so you cannot get the image data that way either. Only way is to create a script for the camera and add an OnPostRender method. This, set a bool that will say when to snap a photo and if it's not set, return, if it's set, falsy that flag. Now in this method you have access to the render buffer, you can create a Texture2D here and read the pixels and apply them to this Texture2D. Why a Texture2D? You can get the color32[] array (a primitive, but restricted). With that array you can serialise it into a string, batch it and sync it. I suggest using [UdonSynced, FieldChangeCallback(nameof(CHUNKINGMETHODHERE))] private string _syncedImageChunk, split that string into chunks and sync them with a delay so that it doesn't overload the networking and it doesn't slow your map's syncing.
i wasted a week on that, hopefully i'll save someone else that week
i made my world with leaderboard api public on github, use it for whatever idc
coincidentally someone talked about this a month ago #udon-showoff message
#udon-showoff message o here's the OP
oh nice, damn, i could do with that compression script, syncing 300~400 kb takes about a 1~2 minutes through udon sync
TTIL that if serialization fails, you get absolutely zero information on why it failed. First time I've even that happen and I've done a decent amount of networking.
This works:
public override void OnPreSerialization()
{
int gameStateInt = (int)gameState;
gameStateByte = (byte)gameStateInt;
}
This silently breaks networking
public override void OnPreSerialization()
{
gameStateByte = (byte)gameState;
}
learn the difference
what is the difference?
the working version doesn't directly cast an enum to a synced byte
Udon tends to have a panic attack if you try converting an enum to anything but an int...
wait, u# can do enums?
yah; since 1.0 I think
They just have to be declared outside of the class
til
btw would gameStateByte = (byte)(int)gameState; save some memory
or save one udon instruction?
I think Udon needs it to be two separate instructions
yes for a long time. but its only outside Namespace enums aka. we dont get the actual value out.
you need to use Convert.ToByte or or Convert.ToInt both of those work. since as you found yourself casting does not work with enums in udon. also its this class you need to use for it https://learn.microsoft.com/en-us/dotnet/api/system.convert?view=net-10.0
also if you wish to use the enum in a switch case its the same thing. you need to convert to the numeric type you use
I see so many people using ints instead of enums and each time I am like: why??
inexperience, I'd guess
Let me repeat again since you seemed to miss it, casting an enum to an int works just fine. You can see that in the working code I posted. The issue happens when you try directly casting it from an enum to a byte. Convert.ToByte does work, however.
Also I have zero idea what you mean by "also if you wish to use the enum in a switch case its the same thing. you need to convert to the numeric type you use." Enums work just fine in switch cases and I have no idea what "the numeric type you use" means in the context of a switch statement.
I remember trying enum but got errors and never bothered to find out why, never saw any working example either
So no namespace? thats the trick?
But why?
(That is a dumb udon jank...)
you probably got the error "cannot declare nested types" or something like that
You can define them within a namespace
Just tested this and it worked perfectly fine:
namespace EnumTest
{
public enum GameState
{
Unused,
Starting,
Running,
PostGame
}
}
You can't put them inside a class, but you can inside a namespace.
(and probably should)
Why’s this?
I never saw or have been told any positive reasons for using namespaces
makes sure your stuff doesn't get confused with someone else's stuff
if you're not planning to distribute your code, it doesn't really matter that much
I disagree, but you do you.
I also find it helps force you to recognize your dependency spread
Enums don’t work properly in switch statements if the enum’s underlying type is byte—at least from what I’ve found. In those cases, you need to convert the value from int to byte, because Udon doesn’t seem able to handle it otherwise.
i assume it's a bug that it atleast can't handle casting an Enum from int to byte and i dont know if its the same for other non Enum cases
no sorry i actually did mean class lol. Not namespace. You can put them inside a namespace just fine.
I also recommend using assembly definitions for similar reasons.
really? I've got a bunch of them being used in switch statements
oh byte
yeah that's pretty interesting.
yea since Enum defaults to int32 i believe. i have not tested if its only byte that doesnt work. but i think its safe to assume any type of casting when doing any other type results in fail
there's also this https://feedback.vrchat.com/udon/p/converting-enum-to-int-in-datatoken-parameter-errors-at-runtime
which I'm pretty sure has been a bug long before this canny was submitted
wow thats from 2024. and prob has not been fixed.
i did find when dealing with enums in udon you are kinda left with that above or the one i prefer which is. Convert.ToByte / Convert.ToInt32 etc is realiable and has not failed. the downside of using the Convert is it has a higher cost then just casting.
I was hoping someone could assist me with it. I am trying to add networking to my Instantiated objects. Here is what the logs are throwing in error. Attached is what I am trying to network:
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
public class PlayerJoinButton : UdonSharpBehaviour
{
public GameObject MyBoard;
public int PlayerNumber;
public VRCPlayerApi LocalPlayer;
public GameState state;
public void init(GameObject myBoard, int playerNumber)
{
MyBoard = myBoard;
PlayerNumber = playerNumber;
}
public override void Interact()
{
state = MyBoard.GetComponent<GameState>();
LocalPlayer = Networking.LocalPlayer;
SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.All, "NetworkInteract");
}
public void NetworkInteract()
{
if (PlayerNumber == 0)
{
return;
}
if (PlayerNumber == 1)
{
state.setPlayer1(LocalPlayer);
}
else if (PlayerNumber == 2)
{
state.setPlayer2(LocalPlayer);
}
else if (PlayerNumber == 3)
{
state.ResetBoard();
}
}
}
in a separate class for my other GameObject:
{
Player1 = player;
OpenMapSelection();
Player1Button.SetActive(false);
}```
in testing, like on unity's end, everything works fine, but in a published version it no longer works
not that I am aware of?\
oh nvm that's your avatar failing to load in testing, that's normal
those logs don't seem to have an error coming from Udon
if you test in ClientSim, do you get a proper error in the console?
nothing in the console
one sec ill show you
or are you able to hop in a vc that I can stream it
no
you should have your console open in-game to see if it's crashing
those logs at the bottom with the [Behaviour] tag. Are those objects being instantiated intead of existing in the scene beforehand?
correct
that would explain why they couldn't be network configured
what are the buttons calling in order to allow them to work? SendCustomNetworkEvent?
just the basic Interact() method
yup just above
yeah they are calling SendCustomNetworkEvent. that isn't going to work for instantiated objects
instantiated objects cannot perform any networking on their own
I see. essentially what I am trying to do is create one "board" that Instantiates all of its pieces. That's how I have it right now. The idea is I can just drop multiple boards in, then each one references the initial pieces for it and gives them its own instance
so then rather doing the networked event from the buttons side, would it work if I did all the networking from the board then and stores the button there? since the board is the only object that isn't instantiated
the pieces can probably be instantiated fine, but the buttons themselves probably don't need to be instantiated
yeah that's one way to do it. A script that's non-instantiated can theoretically manipulate instantiated objects, so that's one way to sync them
I shall try this! I am doing the best I can to wrap my head around networking lol
would I still want to give the objects I instantiate the VRC Oobject Sync component?
or is that just redundant?
would be redundant if you're already setting the position yourself
it'd be easier to just network the setting of the board to everyone, then everyone locally moves the pieces where they need to be
that many objects moving simultaneously with VRCObjectSync will instantly fry the networking
It wouldn't work, anyways. Any component with any networking has to be present in the scene pre-start.
In you want to "instantiate" networked objects at runtime you'll need to use something like an object pool
The only other sort-of exception are PlayerObjects, which get instantiated once per player and can only be owned by said player
So I think I have managed to solve this on my end, though I am still encountering some crashes with objects being null, or just not functioning properly. Unfortunately the only way I am able to test this is with two clients involved. I think I will need to look into object pooling though. I was able to solve most of the networking issues.
Circling back to the first video for this, whoever ISN'T the same client of who interacts with the "Start Game" button causes it to crash on their end. The very first tile spawns, but then errors out after that. Im sure I can figure it out
There is a pattern where you have a controller script that is in the game at run time and tracks a synced array of what would be instantiated locally. Then the objects could just report locally to the controller and that will send out where the remote player clients should move their instances. But at that point I think it'd be cleaner to have an object pool that it pulls pieces out of for each board and have the manager track those instead of instantiate shenanigans. Just dont have each piece handling its own sync, let a manager with manual sync mode do that 😅 the object pool itself only syncs the enabled/disabled states of objects within it but it's nice to incorporate it, one less thing for you to do
Yeah I have decided to fully refactor my code with this in mind and to avoid instantiating overall. I think there are some points where it'll work with more temporary objects, but I think I'll just settle for keeping x amount of the game pieces off to the side and reuse them
I learned the same lesson the same way back when i started, but I was trying to make a vending machine 🥲
I am working on a trading card tower defense. being able to instantiate and sync what I need to at runtime just seems like it'll be too much of a hassel to try and make work
"this"?
the cubes bouncing up and down
wait wrong video x.x
but basically each of the cubes keep like bouncing up and down before snapping back to their original position, then bounce around again
everything looks good on the hosts end, so I assume its a networking issue, however I have:
[UdonSynced] public Vector3 Position;
and I store the position here to then
public override void OnDeserialization()
{
gameObject.transform.position = Position;
}
they aren't supposed to be moving at all
did you select manual sync or continuous sync?
continuous
they probably need to be Manual
I hate networking
like everything works fine for the host, but the moment someone joins late nothing works 🥲 I accidentally had VRC Object sync on my tiles causing them to bounce, since I already "set" them on deserialization for late joiners
but now for whatever reason whenever I start the game on the late joiner, it just resets without even going through my reset method and im like ??? how
how are you expecting it to run your reset method?
when the reset button is clicked. so essentially I have a set of buttons that they click through before finally clicking the "start game" button, but for whatever reason whenever the late joiner clicks the start game button, it resets everything back to its "default" state, but the ONLY way for this to fire is by clicking the reset button. It works perfectly fine for the original player in the instance
you don't call this reset method anywhere else? In Start, OnPlayerJoined, OnDeserialization, etc.?
im adding debug lines to EVERYTHING right now to see if I can trace what is going on, but the only place I call my reset method is from the button, and when the instance initally starts to load the board
yes
that will run locally for all players as they join the instance
not just when the instance is first opened
would this not work? I think there may be an issue with GameTIles, but im changing that now:
[UdonSynced] public bool Initialized = false;
void Start()
{
GameTiles = new GameObject[144];
if (!Initialized)
{
Debug.Log("Initializing");
InitReset();
}
}```
inside InitReset() it sets Initialized = true
Start is going to run before that variable's value gets synced. So it's going to use the default value of false
where would a safer place to do this be then? Update() ?
no; if you need to update something based on a synced value, you need to wait for OnDeserialization to run
Any player that joins an instance will be sent the synced data, and the moment it's ready to use, OnDeserialization fires
either that or use a FieldChangeCallback
I think this makes sense
so I feel I am SUPER close. Here it is running from the host side
from the late join side
does the Start Game button send an event to the owner, or does it run locally?
{
Debug.Log("start game");
SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.All, "NewStartGame");
SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.All, "NewInitPlayers");
SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.All, "NewStartDeckLoading");
}```
hm targets All...
do any of those three functions write to synced variables? Do you check for Owner before writing to synced variables?
They all write to synced variables yes
let's see one of them, pick your favorite
lets do the first one, since its the most important imo
{
Debug.Log("new strat game");
for (int x = 0; x < MyMapButtons.Length; x++)
{
MyMapButtons[x].SetActive(false);
MapSelectButton buttonVar = MyMapButtons[x].GetComponent<MapSelectButton>();
buttonVar.Active = false;
}
Debug.Log("past map");
GameID = Random.Range(1, 10000000);
Health = 100;
Wave = 1;
Player1Money = 100;
Player2Money = 50;
Player1DeckCycle = 0;
Player2DeckCycle = 0;
Debug.Log("tiles");
if (MapDatabaseObject == null || TileDatabase == null || GameTiles == null)
{
Debug.Log("Missing references in ApplyMapToTiles");
return;
}
MapDatabase mapVars = MapDatabaseObject.GetComponent<MapDatabase>();
TilesDatabase allTiles = TileDatabase.GetComponent<TilesDatabase>();
if (mapVars == null || mapVars.Maps == null || allTiles == null || allTiles.Tiles == null)
{
Debug.Log("MapDatabase or TileDatabase not ready");
return;
}
if (MapID < 0 || MapID >= mapVars.Maps.Length)
{
Debug.Log("Invalid MapID");
return;
}
GameObject myMap = mapVars.Maps[MapID];
if (myMap == null)
{
Debug.Log("Map object is null");
return;
}
Map map = myMap.GetComponent<Map>();
if (map == null || map.MapArray == null)
{
Debug.Log("Map component or MapArray missing");
return;
}
GameTiles = GetDirectChildren(MyTilesForGame);
int count = Mathf.Min(GameTiles.Length, map.MapArray.Length);
for (int x = 0; x < count; x++)
{
Debug.Log("Tile: " + x);
GameObject tempTile = GameTiles[x];
if (tempTile == null) continue;
Tile myTileVar = tempTile.GetComponent<Tile>();
if (myTileVar == null) continue;
int oldTileIndex = map.MapArray[x];
if (oldTileIndex < 0 || oldTileIndex >= allTiles.Tiles.Length) continue;
GameObject oldTileObj = allTiles.Tiles[oldTileIndex];
if (oldTileObj == null) continue;
Tile oldTileVar = oldTileObj.GetComponent<Tile>();
if (oldTileVar == null) continue;
// Copy properties
myTileVar.TileID = oldTileVar.TileID;
myTileVar.Color = oldTileVar.Color;
myTileVar.PathNumber = oldTileVar.PathNumber;
myTileVar.Buildable = oldTileVar.Buildable;
myTileVar.GameID = GameID;
myTileVar.Name = oldTileVar.Name;
}
Debug.Log("past tiles");
Player1JoinButton.SetActive(false);
Player2JoinButton.SetActive(false);
StartButton.SetActive(false);
PlayerJoinButton button1Var = Player1JoinButton.GetComponent<PlayerJoinButton>();
button1Var.Active = false;
PlayerJoinButton button2Var = Player2JoinButton.GetComponent<PlayerJoinButton>();
button2Var.Active = false;
StartGame startVar = StartButton.GetComponent<StartGame>();
startVar.Active = false;
Player1Zone.SetActive(true);
PlayerSpace space1 = Player1Zone.GetComponent<PlayerSpace>();
space1.Active = true;
Player2Zone.SetActive(true);
PlayerSpace space2 = Player2Zone.GetComponent<PlayerSpace>();
space2.Active = true;
InGame = true;
}```
I know it doesn't showcase well in the video for the logs becuase if the mass amount of tiles printing, but none of the error checks every print (which is good)
so, what variables are synced here, and where do you call RequestSerialization?
also probably only the Owner should be running these functions
so for the tile:
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
public class Tile : UdonSharpBehaviour
{
[UdonSynced] public int TileID;
[UdonSynced] public string Name;
[UdonSynced] public int GameID;
[UdonSynced] public bool HasTower;
[UdonSynced] public int Rarity;
[UdonSynced] public Color Color;
[UdonSynced] public int PathNumber;
[UdonSynced] public bool Buildable;
[UdonSynced] public int GameX;
[UdonSynced] public int GameY;
public GameObject MyBoard;
[UdonSynced] public float timeToSleep;
[UdonSynced] public bool move;
[UdonSynced] public bool Spawnable;
public GameObject CurrentTower;
[UdonSynced] public Vector3 Position;
[UdonSynced] bool Loaded = false;
[UdonSynced] float SyncTimer = 0.0f;
public void Init(int tileID, string name, int pathNumber, int rarity, bool buildable, Color color, bool spawnable)
{
TileID = tileID;
Name = name;
GameID = 0;
HasTower = false;
Rarity = rarity;
Color = color;
PathNumber = pathNumber;
Buildable = buildable;
move = false;
Spawnable = spawnable;
Loaded = false;
}
public void Reset()
{
Name = "null";
GameID = 0;
HasTower = false;
Color = Color.black;
}
[UdonSynced] public float moveDelay = 0.1f;
[UdonSynced] public float moveSpeed = 5f;
[UdonSynced] private Vector3 targetPos;
[UdonSynced] private float timer = 0.05f;
public void Update()
{
Renderer renderer = GetComponent<Renderer>();
if (renderer != null)
{
if(renderer.material.color != Color)
{
renderer.material.color = Color;
}
}
if(MyBoard != null)
{
GameState state = MyBoard.GetComponent<GameState>();
if (move)
{
gameObject.transform.position = Vector3.MoveTowards(gameObject.transform.position, getTargetPosition(), 0.1f);
if(gameObject.transform.position == getTargetPosition())
{
move = false;
}
}
}
}
public override void OnDeserialization()
{
gameObject.transform.position = Position;
}
public Vector3 getTargetPosition()
{
GameState state = MyBoard.GetComponent<GameState>();
GameObject TargetObject = state.Board;
Vector3 TargetPosition = new Vector3(TargetObject.transform.position.x - 1.63f + (0.3f * GameX), TargetObject.transform.position.y + 0.35f, TargetObject.transform.position.z - 1.63f + (0.3f * GameY));
return TargetPosition;
}
public override void Interact()
{
VRCPlayerApi player = Networking.LocalPlayer;
if(MyBoard != null)
{
GameState stateVars = MyBoard.GetComponent<GameState>();
stateVars.SelectTile(gameObject, player);
}
}
}
Health = 100;
Wave = 1;
Player1Money = 100;
Player2Money = 50;
Player1DeckCycle = 0;
Player2DeckCycle = 0;```
the tile is the main issue
where do you call RequestSerialization?
no where. Does the continous sync not handle this?
it's continuous synced? I thought you switched it to Manual
Nope, I left it on continuous because I discovered what the issue was all my tiles had VRC Object Sync which was initially breaking it
maybe remove what you've got in OnDeserialization then
It's not easy to follow what your logic is doing I'm afraid... but if you have a script set to Continuous sync, OnDeserialization is going to fire several times a second, as often as the variables are synced
So it's constantly resetting the tile to the Position variable, and I don't see Position being written to anywhere
Position is writtin at the VERY beginning with loading, but it might be redundant now that I have added local loading
you've kind of... written some of your networking logic as if it's Manual sync, and some other bits of it as if it's Continuous.... which is why you're seeing a lot of unexpected behavior
hmm I wonder what could be causing this. I removed the OnDeserialization, but this just caused the tiles to not set their initial positions properly, but despite this it unfortunately did not solve the rest of the issues
I might actually be able to solve this by breaking this to the local client, since a lot of whats being done is mostly cosmetic
I would recommend making some small test scripts just to solidify your understanding of networking. That way you're not fighting to learn networking and figuring out your game at the same time
Mostly because you really need to have a good understanding of networking first, before you start coding the logic for the game, rather than trying to learn it midway through
It's very often people do a ton of work and get it all working perfectly for the local player, but didn't write the code in a way that easily translates to networking it.
A script that syncs a single number with Continous sync. Then one with Manual. Then maybe another syncing, the color of a cube or something. Just one thing at a time
with networking you can make things a LOT easier by taking advantage of FieldChangeCallbacks as well
there's also a video from Phasedragon I want to dig up
I suppose what I am having troubles wrapping around my head is what should be synced compared to what doesn't need to be, if that makes sense at all. I also think I have a pretty similar coding style to when I was working on minecraft mods 😭
I also think I may have just solved it. I am referencing instantiated objects. This is probably 1000% the issue
yeah instantiated cant do networking
yeah syncing instantiated objects is already an advanced thing. Hence why it's not recommended to use them when you need things to be networked
the rule of thumb with Udon is really, whenever possible, have your objects always in the scene in some way, rather than instantiating them
object pools
I am not sure if this is actually the problem, however these are instantiated at the beginning of a method for Start() so everyone should have a copy local to them. I am going to go through and make each database object an initial one
if everyone needs 1 copy locally, then why not just have it already in the scene in the first place?
I had initially made it with the intent of just having it run through a thick list of code, but instantiating them at runtime creating this monstrosity:
{
Tiles = new GameObject[7];
Vector3 StartPosition = Tile.transform.position;
float newPos = Tile.transform.position.x + 0.35f;
GameObject tempTile0x0 = GameObject.Instantiate(Tile);
Tile myTile0x0 = tempTile0x0.GetComponent<Tile>();
myTile0x0.Init(0, "null", 0, 100000, false, Color.white, false);
myTile0x0.transform.SetPositionAndRotation(StartPosition, Tile.transform.rotation);
Tiles[0] = tempTile0x0;
newPos += 0.35f;
StartPosition = new Vector3(newPos, Tile.transform.position.y, Tile.transform.position.z);
GameObject tempTile0x1 = GameObject.Instantiate(Tile);
Tile myTile0x1 = tempTile0x1.GetComponent<Tile>();
myTile0x1.Init(1, "Monster Path", 1, 1, false, Color.black, false);
myTile0x1.transform.SetPositionAndRotation(StartPosition, Tile.transform.rotation);
Tiles[1] = tempTile0x1;
newPos += 0.35f;
StartPosition = new Vector3(newPos, Tile.transform.position.y, Tile.transform.position.z);
GameObject tempTile0x2 = GameObject.Instantiate(Tile);
Tile myTile0x2 = tempTile0x2.GetComponent<Tile>();
myTile0x2.Init(2, "Monster Path Alt", 2, 2, false, Color.gray, false);
myTile0x2.transform.SetPositionAndRotation(StartPosition, Tile.transform.rotation);
Tiles[2] = tempTile0x2;
newPos += 0.35f;
StartPosition = new Vector3(newPos, Tile.transform.position.y, Tile.transform.position.z);
GameObject tempTile0x3 = GameObject.Instantiate(Tile);
Tile myTile0x3 = tempTile0x3.GetComponent<Tile>();
myTile0x3.Init(3, "End Tile", 0, 1, false, Color.red, false);
myTile0x3.transform.SetPositionAndRotation(StartPosition, Tile.transform.rotation);
Tiles[3] = tempTile0x3;
newPos += 0.35f;
StartPosition = new Vector3(newPos, Tile.transform.position.y, Tile.transform.position.z);
GameObject tempTile0x4 = GameObject.Instantiate(Tile);
Tile myTile0x4 = tempTile0x4.GetComponent<Tile>();
myTile0x4.Init(4, "Basic Tower Tile", 0, 1, true, Color.cyan, false);
myTile0x4.transform.SetPositionAndRotation(StartPosition, Tile.transform.rotation);
Tiles[4] = tempTile0x4;
newPos += 0.35f;
StartPosition = new Vector3(newPos, Tile.transform.position.y, Tile.transform.position.z);
GameObject tempTile0x5 = GameObject.Instantiate(Tile);
Tile myTile0x5 = tempTile0x5.GetComponent<Tile>();
myTile0x5.Init(5, "Scope Tile", 1, 1, true, Color.green, false);
myTile0x5.transform.SetPositionAndRotation(StartPosition, Tile.transform.rotation);
Tiles[5] = tempTile0x5;
newPos += 0.35f;
StartPosition = new Vector3(newPos, Tile.transform.position.y, Tile.transform.position.z);
GameObject tempTile0x6 = GameObject.Instantiate(Tile);
Tile myTile0x6 = tempTile0x6.GetComponent<Tile>();
myTile0x6.Init(6, "Monster Spawn Tile", 1, 1, true, Color.yellow, true);
myTile0x6.transform.SetPositionAndRotation(StartPosition, Tile.transform.rotation);
Tiles[6] = tempTile0x6;
newPos += 0.35f;
StartPosition = new Vector3(newPos, Tile.transform.position.y, Tile.transform.position.z);
}```
scary.
I think when I initially learned I couldn't make a constructor I lost it and went off the deep end lol
it happens more than you think
I also didn't fully understand how components worked yet either. So I didn't know about
{
if (parent == null) return null;
int count = parent.transform.childCount;
GameObject[] children = new GameObject[count];
for (int i = 0; i < count; i++)
{
children[i] = parent.transform.GetChild(i).gameObject;
}
return children;
}```
but im learnin
The Unity manual is your best friend for stuff like that
Does ObjectSync require some sort of interaction with a rigidbody to update it? For instance a rigidbody ball being shot in a zero G physics-less setup. I have this and after a time of travel of about 30 seconds the drift between players gets to be insanley large (5~ second difference) despite having vrcobjectsync attached, so I suspect it is only updating to the first impulse of force to the object and then no longer updating the sync. And if this is true, what sort of things can I do to cause objectsync to Do Its (blankety blank) Job?
nope, moving rigindbody should update at like 5hz from the owner to everyone else and interpolate. you prob want to look at network debuggin view in game, tho its being attached to an object and thus flying away as wall may be a porblem
unity engine itself puts rigidbody controlled object to sleeping state when it wasnt moving more than uhh some micrometers for a while and that what puts sync to sleep as well
afaik ofc🤷♂️
VRC Object Sync doesnt need rigidbody to work. But it does sync the kinematic state and gravity state for the rigidbody if it has one.
I was reading up on the doccumentation for netwroking, I attempted to send a parameter just like it showed in its example, but its saying it is unable to find the method?
{
Debug.Log("Setting map id");
MapID = x;
RequestSerialization();
this.SendCustomNetworkEvent(NetworkEventTarget.All, "NetworkedStartGameButton", x);
}```
```public void NetworkedStartGameButton(int x)
{
Debug.Log("Network start game button");
StartButton.SetActive(true);
StartGame startGameButtonVars = StartButton.GetComponent<StartGame>();
startGameButtonVars.Active = true;
}```
if your network event has a parameter, the function needs to have the [NetworkCallable] attribute added to it
But as already pointed out it is a low frequency continuous sync similar to player movement. If your movement is totally deterministic from an initial condition, it makes more sense to do it with a manual sync, to save bandwidth, and to make it more precise. Manual sync is more precise/predictable because continuous sync (like VRC Object Sync) does interpolation between snapshots AND has automatic rate control and can drop snapshots depending on network load.
(re the previous person)
Okay sweet! I think this is the last bit I need to solve the issue from yesterday. I solved them loading, now I just need to link the correct mapID, but for whatever reason has been 0 despite changing it. But this should hopefully solve that!
Unfortunatly that's a bit buggy atm https://feedback.vrchat.com/udon/p/vrcobjectsync-doesnt-sync-gravitykinematic-flags-reliably
Also if you're using continuous sync to sync position, you should be using VRCObjectSync instead unless you really know what you're doing any you need to do something that won't work with either VRCObjectSync or manual sync.
In general you should avoid continuous sync whenever possible due to how taxing it is on the network while giving you so limited control over it.
So it could have just been VRCObjectSync just not doing its job because the network was busy? I was testing with only 5 players, I can't imagine how bad it'd get with 20
you can use debug menu 4 and 5 to see if there are any networking issues going on https://creators.vrchat.com/worlds/udon/world-debug-views/
These are the tools you can use to debug your worlds in-game.
note this page is a little out of date as they added some stuff and changed the visuals, but debug menu 4 and 5 are fundamentally the same
sorry, 4 and 6
this solved me issue! Everything is now syncing properly. Thanks for your help I appreciate you so much!
Feel like I'm going slightly mad, does SendCustomNetworkEvent with NetworkEventTarget.All not actually work in Build and Test?
I know, but no other events should have happened.
Are you using a U# script using [UdonBehaviourSyncMode(BehaviourSyncMode.NoVariableSync)] ?
IIRC, there is a bug where if you have an object in the scene with a script using [UdonBehaviourSyncMode(BehaviourSyncMode.None)] and then change the script to [UdonBehaviourSyncMode(BehaviourSyncMode.NoVariableSync)], the object will still remain set to None.
You need to change the script to manual, save, let scene reload, and THEN change to NoVariableSync
that's a weird bug
it might have something to do with NoVariableSync seeming to not be a base Udon thing
My guess is that something within the editor-side of Udon has no ability to tell the difference between None and NoVariableSync
Is there much of an overhead difference between having a udon synced variable vs a local variable that is kept at the correct state via SendCustomNetworkEvent on state changes?
Like if you had say 400 of a single instance of an synced pickup object, so I've avoided using any synced variables on them but im curious if if anyone has tested the difference in overhead of SendCustomNetworkEvent vs RequestSerialization on networking traffic?
VRC tends to allow you do do more with SendCustomNetworkEvent than with manual sync (manual sync appears to have a much lower scene-wide syncs per second than Events; manual sync values seem to be saved on the vrc for late joiner support so that is likely why)
But you should avoid if possible using SendCustomNetworkEvent for sync states
The overhead difference is not much, but since they are for completely different use case, overhead difference should be the least of your worries
So im trying to get this to network so everyone see it and have it on themselfs as well i been trying to google it but im not getting much luck
what exactly is being synced here? If you set something to the position of the LocalPlayer, then you'll only see it on yourself (won't see other players having the object on them)
if this is one object you're meant to see on every player, you could take advantage of PlayerObjects
instead of following the LocalPlayer, you make the PlayerObject follow its Owner (who will always be the player the object is assigned to)
there's actually one or two example projects in Example Central that sound like what you're trying to do. You could try looking at the Health Bar example
https://creators.vrchat.com/worlds/examples/persistence/health-bar
Save player health amounts in a PlayerObject.
isnt that a really old, deprecated feature too?
yea
whatever you're trying to use those for, there's probably a better way to do it now
hmmm...
https://creators.vrchat.com/worlds/udon/players/getting-players/
I didn't see anything about it being depreciated
These nodes are useful for getting an individual Player, a group of them, or all of them.
For context:
I am making an admin UI panel, with a selection tool to mass-freeze and jail players as needed
while the freeze/jail doesn't necessarily have to be synced, if there is a second admin, they would need the ability to un-freeze / un-jail someone who was set by the first admin
I figured that tags could be good for this, so that I won't have to sync arrays of (max 80) player names
unless...
syncing 80 strings isn't that bad? I could probably use Manual (RequestSerialization) to reduce traffic
it'd be even better to just have a PlayerObject that saves these values
I'd use a https://creators.vrchat.com/worlds/udon/persistence/player-object/ playerobject and sync the values on that
PlayerObjects allow you to automatically give each player who joins your world a copy of a GameObject, such as a flashlight, a health bar, or a sword.
Yee what LEGOS said XD
I've made a simmilar system that does exactly that for an admin panel
Eh, they can overlap.
I'm managing a large stack of objects where they get an implied order by the order in which they're activated from an objectpool or a user picks it up & sends an update for that object to everyone.
Which works great 99 % of the time unless i do something that activates a whole bunch at once where the activation order doesnt always seem to be the same on the receiving end compared to the person doing the activation.
Which isnt the end of the world, its just for a nice visual tidyness thing, but I could resolve it by changing it to a synced int rather than implied via event order.
But that's not worth it to me if there's is anything more than a marginal preformance overhead for it as I'm trying to keep it as lightweight as possible.
Events by design are not a "sync" because it is not about getting a state of things (variables) to sync up to the owner's and for late joiners to also see it. Events are "shoot and forget" uses like emotes, and any syncing with events is just using a wrong tool for the job imo.
If you need a deterministic order maybe you can shoot an event with a parameter of some sort (treat it like a one time effect), and then follow up after the "effect" with a manual sync to formally record the final state (mainly for late joiners)
It's not aimed to be an exacty sync in this case, deterministic is fine, the events are just to keep everyone currently there in the same stage of the determining tree so to spee.
I know it would be more exact propper state sync if i just synced an int, for sure.
Its an asset for full lobbies of people though and I'll happily put up with a little difference between clients here and there if it means its more preformant vs using a synced value for exactness.
Hence questioning the difference in overhead between the two on networking. I'd assume its not much buuuuut gut to know for sure before I change up any system architecture.
okay, I got around to implementing the thingy
Is it me, or is the PlayerObject just another roundabout version of SetPlayerTag / GetPlayerTag ?
I give everyone a playerStateTracker, then that has a dict to set/check jailStates
yeah. The biggest difference being it's stupidly easier to network sync
Teleporting players only works on the local player, btw
just gotta add this, then?
oh wait
nvm
I gotta send a network event to proc the local player to check it's tags/selected, then teleport
I don't think it's technically deprecated, but I think it's pre-Udon
thanks - @twin portal and @tired quail
#1410808437822197770 message
That’s so weird
Is there a way to automatically set the sync mode (continuous/manual/off) of an udon graph, like there is with u#?
graph does not get such a luxury, unfortunately
using graphs is lowkey insanity when logic gets complex anyway
Are the YouTube videos not a thing again. seems like they are requiring cookies from the browser now and is there a work around or another player that corrects this issue on there end?
if you're on PC, there's VRCVideoCacher
Oh crap, nevermind, I think it's the network I'm on. It's very limited.
that would do it too lol
Hello! New to world creation, and I was wondering for networking how much should I put into it? Meaning is there a set list of things that need to be networked? Are there things that are naturally networked out of the box? I have been doing a bit of research but I am not getting a solid answer on if I need to apply networking to every thing that I add into my world.
generally most things are local unless you explicitly network them, you could look at vrc's object sync component for something that adds sync to objects or vrc pickups out of the box
if there was a set list of things to be in the world in the first place, networked or not...
what do you mean by a set list?
Everything is not-networked by default. Beside the player avatars, movements, voices etc of course.
so I am trying to wrap my hand around network and events being clogged, which it doesn't make much sense to me. So if I have something like this:
public void SoftResetNetworked()
{
Debug.Log("soft reset networked");
InGame = false;
GameIsReady = false;
Player1 = null;
Player2 = null;
MyMapButtons = GetDirectChildren(MapButtons);
for (int x = 0; x < MyMapButtons.Length; x++)
{
MyMapButtons[x].SetActive(false);
MapSelectButton button = MyMapButtons[x].GetComponent<MapSelectButton>();
button.Active = false;
}
for (int x = 0; x < GameTiles.Length; x++)
{
TilesDatabase allTiles = TileDatabase.GetComponent<TilesDatabase>();
GameObject tempTile = GetDirectChildren(MyTilesForGame)[x];
Tile myTileVar = tempTile.GetComponent<Tile>();
myTileVar.Reset();
}
CurrentCardsPlayer1 = GetDirectChildren(CardsForPlayer1);
CurrentCardsPlayer2 = GetDirectChildren(CardsForPlayer2);
for (int x = 0; x < CurrentCardsPlayer1.Length; x++)
{
Card cardPlayer1 = CurrentCardsPlayer1[x].GetComponent<Card>();
cardPlayer1.Reset();
Card cardPlayer2 = CurrentCardsPlayer2[x].GetComponent<Card>();
cardPlayer2.Reset();
}
DeadMonsters = GetDirectChildren(AllMonsters);
SpawnedMonsters = GetDirectChildren(AllMonsters);
QueuedMonsters = GetDirectChildren(AllMonsters);
QueuedBossMonsters = GetDirectChildren(AllMonsters);
SpawnedBossMonsters = GetDirectChildren(AllMonsters);
for (int x = 0; x < DeadMonsters.Length; x++)
{
DeadMonsters[x].GetComponent<PhysicalMonster>().Reset();
SpawnedMonsters[x] = null;
QueuedMonsters[x] = null;
QueuedBossMonsters[x] = null;
SpawnedBossMonsters[x] = null;
}
MapID = 0;
GameID = 0;
}```
it seems to clog the network with ~300 pending events in the queue. Except the thing is this only gets called ONCE. Essentially this just gets sent to everyone, but I am unsure why it would cause there to be such a clogged network
is there a way i can manage this better?
not entirely sure, but you could put a bandaid on it by changing your [NetworkCallable] attribute to [NetworkCallable(maxEventsPerSecond: 100)]
I thought about this. But its really bizzare to me since the button is only interacted with once
you could potentially troubleshoot using NetworkCalling.GetQueuedEvents to check if it's actually this function that's getting queued that many times
or if this is causing some sort of chain reaction and other events are getting queued
I have this running in my update to monitor it:
Debug.Log("Queued" + NetworkCalling.GetAllQueuedEvents());
things could be happening in subsequent functions calls that you have, like Reset()
{
Debug.Log("soft reset");
if (Networking.IsInstanceOwner)
{
SendCustomNetworkEvent(NetworkEventTarget.Others, "SoftResetNetworked");
SoftResetNetworked();
}
}```
this is the non netowkred one to fire it
then
{
GameState state = MyBoard.GetComponent<GameState>();
VRCPlayerApi player = Networking.LocalPlayer;
if(player == state.Player1 || player == state.Player2 || player == Networking.InstanceOwner)
{
state.SoftReset();
}
}```
turns out I am just dumb lol
DeadMonsters[x].GetComponent<PhysicalMonster>().Reset();
{
SendCustomNetworkEvent(NetworkEventTarget.All, "NetWorkedReset");
}```
that would do it
I made alternative to VRCVideoCacher instead of caching videos on disk mine is coverts videos on the fly
that's great man
https://github.com/TheArmagan/vrcvidfix/tree/main shitty source code I had to make it in about 8 hours
interesting approach
You know there's a option to just disable the caching completely in vrcvideocacher right?

Im not sure why you are needing to convert at all when you can just serve the hls links directly from yt
it requested from my friend who uses amd gpu it can't encode or decode vp9 videos due to bug on drivers
so direct hls links just gives random errors to the video players
... the hls links are not vp9
Avpro cant even play vp9 lol
they are check out the direct responses from youtube for some videos
also they are become vp9 when its decoded with ejs
checkout the yt-dlp docs etc
You know im one of the cacher devs and literally patched ytdlp to support the hls streams right..?
You can get mp4 hls at 1080
ok but still it was not working correctly for my friend so i made a alternative in a very short time
also supports live streams too thats a big plus for us
So does cacher..

but im saying it was not working for us
you dont get ig
instead of try to patching source code of cacher
i made my own
Nice command injection btw
bcus why not
Was it erroring or anything?, normally its because people dont install the cookie extension
[AVProVideo] Error: Loading failed. File not found, codec not supported, video resolution too high or insufficient system resources.
exact error that my script fixes
but sadly not your's
we did install cookie extension so
this errors happend only on some amd drivers and directly gpus
and can't be fixed directly just redirecting the sources from youtube
sadly i can't just say buy a new gpu to friend lol
Very strange, cant say ive had any reports of that. And we have plenty of amd users, wonder if it was a a/b test of the widevine Drm, ive seen that a few rare times

might be idk
probbly
if its a a/b testing it will eventually come to all us
If they implement that fully we are all screwed either way xD
checkout the yt-dlp's ejs decrpt tthing it bypasses that somehow
Na this is another thing
Did you open said feature request... or find a work around?
I don’t quite understand what the subject is, but if it’s just about moving platforms there are multiple prefabs that exist for that
I also developed my own just to understand it and it’s not impossible to put together yourself once you understand the order of operations and why
If I SetOwner(Networking.LocalPlayer, parentObject), where parentObject has other synced/network applicable objects as children, does that also set the owner to the given player, or do you have to loop through all objects you want and SetOwner on each child as well?
each networked object is unique network id and you have to transfer ownership of each
imagine if you had idk some scoreboard script on scene's root so every object in the world changed its owner every time someone need to update it
Thought I'd check. Yeah, that makes sense in hindsight, but the specifics were never mentioned anywhere and it'd be nice to confirm.
Im not sure if this is networking, but does anyone know from what ip a string load get request will be sent from? It says it is limited to one request every five seconds, is this one request per user per five seconds if i do it locally, or one request per world per five seconds?
I'm assuming that all the Udon is running on a client, so it'll come from their IP and be rate-limited per client
What do I change or add to turn this into a global toggle upon passcode entry?
- Add
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]just before the class - Add
[UdonSynced]beforepublic bool isEnable - In
Interact()set ownership ofgameObjectto local player, and thenisEnable = !isEnable;, and thenRequestSerialization();, and thenexec() - Make a
public override void OnDeserialization()
{
exec();
}
- In
exec()just set loop through your objects, check for null, and.SetActive(isEnable);
And then make another passcode lock that enables or disables the interact trigger collider
How do I test my networking by myself? Do I need to have the game running on multiple devices at once? I haven't really messed around with it since EAC was implemented and now it just gives me this error when trying to open multiple clients through my project.
In the sdk setting, you have to choose vrchat client as VRChat.exe, not launch.exe.
ooo, I must've missed that in the docs. Thank you 👍
In build and test you can launch multiple clients, they will all be you but they will be different player in the instance
And they will network
i have an idea for fixing latency in game worlds that is similar in concept to real competitive game servers, that being server rewinding. the basic concept is if player A hits player B the server will check players's B position at the time player A hit them. it sorta cuts latency in half. to implement it ill use server/client time and a peer to peer ping to get proper latency in order to rewind and thus have some latency fix. not sure if anyone else has had this idea but ima give it a shot should be interesting
I've always been curious how games mitigate latency. However your idea lost me. Can you explain more?
the term for this is "rollback netcode"
if two players have a ping of 40, thats 80 ms to get from one player to another, we can cut that in half by keeping track of players positions in the past and seeing where they are 40 ms ago to sorta alf the latency. im not sure a great way to explain it, im sure theres videos that can explain it betteer
lag compensation doesn't fix latency, it's for verifying hits from the server's perspective; we also don't have a server, so you'd be relying on players to blast their positions faster than vrc's send rate, so the master could record them into a buffer, and then attempt to place their hitbox in a reasonable position to replay the shot; and it only matters if you're trying to pass raw inputs over network. trusting a client to advertise "i hit x player" is the standard way to approach shooter hit reg in vrc and has no more inherent latency than player interpolation and the time it takes to send the hit to the receiving player
VRChat has a built in smoothing window and all remote players are rendered with a delay (in a "simulation time" timeline that lags behind the real time), and it is usually at least half a second, that means there is no way to improve the latency enough for any competitive real time action game... even if you lower it by half, 250 ms is still a lot, and you will have to desync their voice and avatar
You can get a lower latency idea of where a player is via having all players spam out SendCustomEvents of their current position; the issue is that the player avatar will still lag behind; you'd need to use a desync station to hide the avatar and replace it with a different model you have control over
events don't have terrible latency, though, so if you have a practical way of using this more up-to-date player position you should use it
an example of what this looks like in practice (the pickup is on vrc's object sync as a reference object); it's not actually that you hide the avi, you can control where the station is remotely
That technique would work, yes. I haven't tested it so I'm not sure how valid this concern would be, but I would be worried doing it this way could produce a constant, noticeable and distracting amount of lag between avatar movement and avatar animations
the ik/anims could end up looking out of sync yeah; for someone in full body it'd probably look reasonable to what they're doing
i'd be more concerned about the pickups, but it's preferable to handle them relative to the hand bone at that point
apologies for text wall
a few questions about networking particulars. In my case i am most concerned with manual synchronizations, as my important data is going to be managed bit-wise, and it must not be lost or in networking, and it must be received in-order, otherwise I will get some problematic desyncs.
-
What is the actual rate limit for networking? I read in a couple places 11 kilobytes per second, do you have any insight on how accurate that is as far as how discretely vrchat will check that second or those kb? I.e. some example scenarios, will it
-- every unix second, reset the KB to zero
-- check every networking request from a world, and see if the sum of it and the previous requests in the last second is greater than 11 KB
-- something else, i.e. its not a coded-in limit, but rather a natural limit as a result of their infrastructure -
What happens if you exceed the 11 KB/s limit? I read on the synced-variables wiki page that manual syncs will be "cached and tried again", but how is this done? Are the events queued? stacked? randomly tried again? Queuing events is my hope, such that the first one to fail to be sent/received is the first one tried until successful, but i just need to know how this is handled so i can manage the possible results accordingly.
-
Are there any risks which increase as you get closer to the 11KB/s limit, or is it pretty clear-cut 11KB/s before any problems arise? I.e. clogging 5% of the time at 5KB/s, versus 50% at 10KB/s, versus 80% of the time at 11KB/S? Or is it pretty much no issues until you pass 11KB/s?
-
About how much data per second does an auto-synced object which just syncs transform data use? I want to design my world with safety buffers in mind, so this is important for my purposes.
I can’t remember what exactly is covered but hopefully this helps with at least one of your questions!
i appreciate it, i will take a look 👍
you can see network state in debug window, if its Suffering then youre above the limit. afaik theres no actual guarantees as soon as you hit that state, it may or may not be fired again and may or may not desync
from my experience you can have smth around 300 scriptless but active (moving rigindbody) pickups ie vrcsync. if theyre not moving, theyre sleeping and dont send any data
neat. I should be well under that threshold
I'm not sure exactly what you're trying to do, but with manual sync you really, really want to be synching effectively a state machine where any state has the ability to transition into any other state. The only thing really guaranteed with manual sync is that the latest state will eventually get synched.
IIRC in Udon, networked events are both guaranteed to be delivered and to be delivered in order (with the obvious exception of a network timeout/dc) so you could try using that. But misuse of events can create fragile networking prone to issues. From what I understand, these days most professional game networking is primarily state based.
I highly recommend checking out this talk: https://youtu.be/h47zZrqjgLc?si=cceguL3Bkpk6Qsy-
In this 2011 GDC session, Bungie's David Aldridge discusses the programming that drove Halo: Reach's online networking.
Register for GDC: http://ubm.io/2gk5KTU
Join the GDC mailing list: http://www.gdconf.com/subscribe
Follow GDC on Twitter: https://twitter.com/Official_GDC
GDC talks cover a range of developmental topics including game des...
yes this is what i am doing.
Im passing a state to everyone else, and using that to reconstruct the derived data
I just need to make sure the states arrive in-order
Manual sync is not designed to guaranteed in-between states, only the delivery of the most recent state (thus why making a state machine that can transition from any state to any other state is crucial).
RequestSerialization() itself does not immediately sync a value. It instead tells Udon's networking that you want the object to be synced and it will eventually do so (the timing it will do this is outside our control and is based on how much synced data the object holds). When it eventually does perform the sync it will only use the latest values and any local in-between values are lost.
Consider this: if you have a scrip that looks like:
syncInt = 5;
RequestSerialization()
syncInt = 4;
remote people will receive syncInt with a value of 4
Also the answer to "What is the actual rate limit for networking?" is fairly complicated with a bunch of different rules. First consider this line from the Networking Events doc page: "Total outgoing data is hard-capped at approximately 18 KB/s. This includes all network overhead however, so in practice it is unlikely you will see more than 8-10 KB/s." This "background" consumption seems to go up as more players join the world regardless of what your world is doing. Or in short: the more people in the world the less networking capability you'll likely have.
Continuous and VRCObjectSync are pretty straightforward: they'll work until you hit the network cap.
NetworkEvents are a bit more complicated: besides the network cap they are also have a global outgoing limit of ~100 events per second. Another important caveat is that since events have guaranteed delivery, they require everyone receiving them to send data back confirming reception. In practice, everyone in an instance that receives an event will have their own outgoing network utilization increase.
ManualSync is the most complicated. It also has guaranteed delivery and thus has the same network utilization caveat as NetworkingEvents. It also appears to have a global outgoing rate limit that appears to be much lower then NetworkingEvents. Last time I tested it, having ~30 manual syncs per second across my world caused the networking to enter a suffering state well before hitting any hard networking data limits.
This is really useful info, thanks a ton. I am pretty certain what I need to do, since I am encoding and decoding the information manually, is sending a network event with the 16 bytes of data I need to convey the game state and derive the remaining information from it, especially since I need to do a handful of receiver-specific things once I do so. I should fall well under 100 events per second, id estimate an average of like 5 per second, capping out at maybe 30 per second, 50 if things get really crazy
I appreciate the detailed response, I'll be doing a lot more reading around the wiki and these channels to learn more as I get ready to dive into networking more, and I'll have a lot more questions going forward 😅
Good luck. Take a lot of what you hear with a grain of salt and I'm including what I said above as well. A lot of this information has to be garnered from observations of how networking performs under different tests and it is very easy for there to be mistakes. We're not doing peer reviewed studies here.
yeah my programmer's intuiton has told me so far that networking is a bit of a black box and I'll need to do some tweaking no matter what
I've been talking to a handful of creators to get their vibes as well which has been great for getting a feel of the limits ill be working with
This makes me wish there were some more in depth udon networking samples in CC. Having something to reference like a "simple" state controlled game, or gameMaster/server state sync, to say "this is how you can start to do this" would make learning a lot simpler
do you have late join handling doing it this way? (if it's even important to what you're doing)
it sounds like you're having every client process the moves themselves from previous -> current
I will have late joining be handled via network events yeah, since on joiners I'll need to sync the data in a slightly different way
because i encode and decode the data though I can't just sync the variables through the network
you could encode on preserialize and decode on deserialize, but it's not explicitly better or worse
who actually handles passing the late join data?
not quite sure yet haha, I'm not at the point where I've begun handling the networking of my project, so for now it's still all the planning stages.
My current plan is to handle it as a network event because that will allow me to control the entire flow of data without waiting for vrchat to do anything with it, but there's certainly an argument for a serialized variable, since it might be quite a lot of data to synchronize for one prefab, and i will have several identical prefabs doing the same thing
I still dont even have a confident grasp of the use cases and mechanisms for synchronization, so its hard for me to give a well-refined dissertation on what the best implementation method is
Check out the persistence examples in example central
if you have one of the players who is actively playing do it, it seems reasonable (i'd probably prefer something like lowest playerId player, i wouldn't rely on the current turn player as it seems possible for both to pass the data in some cases depending on latency)
personally i think it'd be better to handle the absolute board state as synced state (something that can be interpreted at any single point through the latest synced vars), vrc handles passing that automatically and clients could render it as if they didn't know any of the board logic
where only the player submitting the turn would need to validate their own move and every other client just replicates the visuals from state rather than everyone independently playing the turn
The way you are describing it is indeed how i have it set up. Turn player makes a move, locally evaluating legality etc., then sends the network event with 16B of data, from which everyone receives the data and builds the game state by decoding those 16B and replicating that move from the data decoded.
not sure what you mean by "aboslute board state" as a "synced state". I need the non-playing clients to have access to the game history, as I plan to allow them to tab through the game while it is in progress. Thus, it is inevitable that I end up syncing the array of 200 or so ulongs for each board when a new player joins.
tabbing through the turn history changes things, so synced vars might be the wrong choice yeah
i'm saying that udonsynced state implicitly handles late joins, while network events would require your own bespoke handling, but that may be more necessary if you want to advertise the entire match history to them
i'd be worried about things like
- Player 5 joins the instance
- Player 1 decides to pass them late join info
- Player 2 makes a valid move and advertises it to everyone
- Player 5 receives the move before they receive the late join info, which now lacks Player 2's move
like you can solve things like that, but it introduces complexity. though i'm not sure i'd suggest udonsynced vars for the entire match history either
Your example you are worried about is indeed something I am also worried about 😅
I plan to also have a "sync gamestate" emergency button to try and fix issues like this, which I am hoping to be able to kind of "request" the same initial synchronization they would attempt on join for that specific board
but that introduces its own potential issues
All quite nebulous at this point
you could have both players push their late join state to the client, and just take the one with the highest sequence number, that would solve the case i described, but again it's more complexity (and probably prone to an arbitrary wait time)
yeah, that will be alpha stage testing i think
the cleanest solution would be something like you suggested, have some attempt to push both player's gamestate and have them fight to the death
I'll come up with some "safe enough" failsafe synchronization method that will get all the data through consistently
I'd say use synced variables for latest state, and locally cache the history. When someone looks back at the history, they still receive synced variables of the latest state and add it to their own cache in the background, so that when they come out of that state they can load the latest. And if a late joiner attempts to look at the history, they must first request the history from someone who saw it, which can be done with network events without touching the actual synced variables
separating the current board state from the separately advertised history makes a lot of sense yeah
This is more or less already what I am buildling for, I think. My current plan uses network events instead of the synced variables, just because it lets me minimize the data package size. Since I was planning on reconstructing all the data from the data package, it seemed like the logical choice. I could shift to syncing variables, but it would require me to sync a couple extra variables that would end up making the total data synceed per request jump from 16B per instance to 68B per instance (my current plan networks the move instead of the gamestate, and i have everyone rebuild that gamestate from the move)
The part about "requesting the history from someone who saw it" is kind of the main issue though, I haven't yet read up on how to have this precise of control of network events and/or synced variables to be able to control from who the data is pulled.
for individual moves 16B vs 68B is probably below the event overhead, it's a very small amount either way; unless you want to use that to show the move history to late joins (strongly consider chunking/batching your late sends either way, the per-event overhead will hurt more)
mhm, i was thinking about something like this as well, i think for the late joiners im going to use a different approach than the per-move setup i normally do, chunking/batching as you suggest sounds reasonable, though again I dont have quite enough experience to know how to implement this kind of thing per-player when it comes to the details of networking
If it was all local I could do it fine! 😭
for the history specifically, the easy/implicit way to start would probably be to request the table data, then have the active players emit the history. anyone seeing the data could fill in the history to a dict if they don't already have an entry filled for that turn
could I ask for a bit of elaboration on the request/emission model? My current, possibly naive, understanding is that networking events are delivered to everyone, not just to and from certain players. Is there a way to control that kind of thing, who gets what, or would that be something I would handle in the parameters of the event?
these are the network event target modes, i was suggesting you simply send to all and receiving players can interpret if they've already seen the data
this isn't necessarily any less efficient, the outbound data hits the relay once, paying to send to all vs owner has no meaningful difference for that data
typically if you do need to filter to a specific player, you'd send their player id as header and have other players early return
oh
this actually makes things incredibly "easy" lol
thank you so much
As long as I keep some track of who has the most updated boards I should be able to batch the data to new joiners relatively easily then
so i can have new joiners send networking requests to the owner of the game asking for the game history in batches
as long as im making sure the owner is the one with the most complete version of the game history that sounds relatively straightforward
i would consider an incrementing int per game, you can allow games >= to your current highest cached, where if you begin seeing a higher generation you'd clear the local history
otherwise you'd probably need to advertise something like an explicit game start to purge the history and it could have ordering issues
you can look into generational indexing for what it might look like
I'm pretty confident I can come up with a protocol that transmits the information safely if I can specify the targets of the networking events
The way I'm keeping the game history is in a flattened array with mutiplier indexing (i.e. indicies 0-9 are turn 1, 10-19 are turn 2, 20-29 turn 3, etc), so I can just check the base indices for certain patterns to know what chunks arent filled, as long as I keep the chunk sizing consistent it seems a pretty linear problem. I can just iterate through the array and request the chunks as needed until they all arrive, which I should be able to verify via a network event that matches up the last game state stored in the history with the current game state of the board from the board's owner's perspective.
Not sure I completely understand what you are suggesting 🤔 in my head, asking for the synchronization of the boards to the new player is enough, and in the case they desync due to a race condition between synchronizing the board state and a new move being made on that board, I was planning on having a "sync board" button which checks for the desync and then fixes it if it finds it, using a similar method to new joiners, but targeted only for the requested board.
I do already have methods to "purge" the game history; this is how I start new games, though I think i am misunderstanding your intent with this part.
I apologize if I'm butchering my understanding of what you are suggesting, this is still all really new to me 😭
practically it'd probably be rare, but if you imagine a new joiner that requests the history
- They receive history from a new game (let's say it's from one of the players playing the game)
- They receive the purge signal at a similar time
what happens if the purge arrives after people provide them history?
i.e. can you guarantee the order of the purge signal always being before the history sends
what happens if they request and someone who was previously playing and lagging sees their request before they see another player starting a fresh game and begins telling the new player about a stale game?
if each game/turn has an outer number associated with it, you can tell if data from a game is valid or stale without any explicit ordering or signaling
i don't know if i'd worry about it for testing though, the new game signal should be fine a majority of the time and a button to fix any desync would cover it, i'm probably being a bit overkill on suggesting it; though i think it's complete to consider
I really apprecaite the completeness of your considerations, it makes me happy that your concerns and mine are lining up 😄 makes me confident that im at least close to the right track
Yeah, there's a bunch of cases I was thinking of that could pop up, just because race conditions are an inherent thorn in coding's side, which is why I planned on the "emergency sync" button being my failsafe. Since at the end of the day correctly syncing is a problem of statistics and getting "lucky enough", as long as I give people the ability to retry syncing the board data, they can keep trying until they can confirm between the other players that they're all seeing the same thing, which is hopefully the correct complete board
what are these network ids that keep growing in number
you can see exactly what they are in either VRChat SDK > Utilities > Network ID Import and Export Utility, or in your Scene Descriptor under Network IDs
every object in your scene that needs to be networked in any way, gets assigned a network ID
it appears to be my gameobjects
so every time you add a new script, a new pickup, whatever, it'll get added to the list
oh
is that what the "synchronization method" dropdown for the u# scripts is doing
kinda, I guess
that is gonna be annoying to go and change for every script
if it's anything other than "None" it'll get an ID
sheesh
are you using UdonSharp? you can force the sync type in the script
if you know a particular script should only be one specific sync type and isn't designed to be anything else, you can add the UdonBehaviourSyncMode attribute right before the class declaration, and then it'll be forced to always be this sync mode in the Inspector
oh that will be much faster thank you
this page gives a few more details, and describes the options BehaviourSyncMode can be
https://udonsharp.docs.vrchat.com/udonsharp/#udonbehavioursyncmode
All supported attributes in UdonSharp
doing it this way also lets you use a secret 4th option for syncing. NoVariableSync, if that sounds useful
I will need to be able to send network events for some of these so that might end up being useful yeah
but hiding that dropdown for most of these will be nice
many thanks
I could be wrong, but I presume that even objects marked as "None" still get a network id. This is due to any object potentially being turned into a networking object at any time while in Unity.
It might clean up those excess network ids to the uploaded version of your world when you build it, however