#udon-networking

1 messages · Page 29 of 1

twin portal
#

You just, don't send data out. Don't request serialization

tawdry sequoia
#

Hm. Okay. Thank you.

warm steeple
#

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

fallow mountain
#

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.

north thistle
#

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)

normal sonnet
#

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~

warped latch
#

Welcome to GB/NES graphics coding

tawny basin
#

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?

sick gull
#

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

tawny basin
#

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

twin portal
#

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

sick gull
#

Do Pickups w/ Object sync break when the script on it is set to manual?

foggy jackal
#

I noticed you get a warning about that

sick gull
#

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?

twin portal
#

as long as the scripts are not on the same exact GameObject, it should be fine

foggy jackal
#

oh right - I got the warning 'cause mine were the same object, good point

sick gull
#

Gotcha, thanks 👍

strange token
#

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?

fallow mountain
#

Same time? What do you mean

strange token
# fallow mountain 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

fallow mountain
#

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

strange token
#

oh...

#

I had no idea lol

fallow mountain
#

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

strange token
#

Thanks!

cursive gyro
#

Hey hey would anyone be down for a call to ask some questions ? 🙂

foggy jackal
#

you could just type them here

cursive gyro
#

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.

foggy jackal
#

sounds like you want to interview someone

cursive gyro
#

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

foggy jackal
#

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

cursive gyro
#

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

twin portal
#
  1. since the dawn of time
  2. depends on your current background. but generally not bad
  3. yes
  4. by using the Creator Economy: https://creators.vrchat.com/economy/welcome-to-the-ce
  5. no
  6. N/A
  7. 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
foggy jackal
#

ooh there's a link, nice.

cursive gyro
#

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?

twin portal
twin portal
cursive gyro
#

okay okay, thank you

buoyant ginkgo
#

Any tips on getting VRC Object Sync to actually function?
And yes, I have already regenerated Scene IDs with the Network ID Utility

twin portal
buoyant ginkgo
#

nope

#

I do have an udon script on a separate gameobject that references the gameobjects with the vrcpickups on them, idk if that counts

twin portal
#

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

buoyant ginkgo
#

so these references wouldn't be an issue?

twin portal
#

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

buoyant ginkgo
#

i have another group of vrcpickup objects and the sync does work

#

it's just the gems for some reason??

twin portal
#

what exactly is your other script doing to the pickups?

buoyant ginkgo
#

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

twin portal
#

ownership is already automatically transferred to the player who picks up a pickup

buoyant ginkgo
#

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

twin portal
#

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

tulip sphinx
#

@buoyant ginkgo why you have smth like ownership change in update loop, thats calling for problems

twin portal
#

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

buoyant ginkgo
#

i guess that’s on me, I’m kinda new to this it’s so confusing

twin portal
#

I'd try removing or disabling that entire graph and see if that changes anything

buoyant ginkgo
#

ill give that a try

#

that script was the issue 😭

#

thanks yall for the help

hoary frost
#

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

twin portal
#

it just gets added to the queue

hoary frost
#

sweet

#

hope youre doing well legos :)

#

may you succeed in your projects

twin portal
#

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

normal sonnet
#

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?

north thistle
twin portal
#

not a specific date yet, but Flare mentioned the "next Jam should be in February"

north thistle
#

I see

north thistle
#

I see that OnOwnershipRequest can break completely if two non-owners request ownership simultaneously

north thistle
#

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

wicked vessel
#

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

fallow mountain
#

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

tawny basin
#

It's what I use to test horrid networking conditions

wicked vessel
wicked vessel
# fallow mountain rather than blind testing why not post your code here so we can tear apart its a...

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

twin portal
#

is the Master also becoming the Owner of the GameManager script?

wicked vessel
#

yeah master is suppose to always be owner of that

twin portal
#

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?

fallow mountain
#

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

wicked vessel
#

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.

wicked vessel
twin portal
#

you can increase that cap

fallow mountain
#

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

wicked vessel
#

true, that could be an issue with more people

twin portal
#

since network events with parameters :)

wicked vessel
#

ooo so sorta new (to me xD)

#

i havent even tried those yet

twin portal
#

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

wicked vessel
#

going to have to do some test projects on that

wicked vessel
#

i see i see

fallow mountain
#

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

wicked vessel
#

the master > player > back to master way i got it now opens up the door to some jank

north thistle
#

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)

https://youtu.be/h47zZrqjgLc?si=1V8SzzLqOOMKWFrq

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...

▶ Play video
wicked vessel
placid sedge
twin portal
#

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

placid sedge
#

awww

wicked vessel
#
    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);
        }
    }```
placid sedge
wicked vessel
#

based on role,it puts players in certain spots

placid sedge
#

that's what i've been trying to do. I got alot of differnet roles for humans and scps.

wicked vessel
#

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

placid sedge
#

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

wicked vessel
#

heres some examples that are in the docs

placid sedge
#

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();
}
wicked vessel
#

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

placid sedge
#

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

twin portal
#

you can't access any component of the player directly, such as their transform or hitbox

placid sedge
#

ik that so I had to make fakehitboxes

#

so when players also get shot it can kill the player and make them respawn.

finite sierra
#

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

finite sierra
#

oh nvm i found the details on their site.

violet mist
#

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?

fallow mountain
#

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

twin portal
#

I just tested and an empty string and null are the same amount of data

violet mist
# fallow mountain It will definitely send something, not nothing, because if someone wants to upda...

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

violet mist
twin portal
#

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

violet mist
#

68 is so much tho-

twin portal
#

it should be the latter

violet mist
violet mist
twin portal
#

well, data is data, even if it's empty

#

things would break if it didn't sync if the variable was empty

violet mist
twin portal
#

68 bytes might seem like a lot of data. but you've got another 280,428 bytes to work with

fallow mountain
#

all late joiners will get a OnDeserialization() and get the latest value, so they would know the value hasnt changed

violet mist
violet mist
# violet mist 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

violet mist
fallow mountain
#

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

violet mist
violet mist
# fallow mountain that is such a small optimization (only matters in the first serialization when ...

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

fallow mountain
#

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

violet mist
fallow mountain
#

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

violet mist
placid sedge
#

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.

twin portal
#

that is the up-to-date doc

placid sedge
#

ok cool

twin portal
sick gull
#

You need to call the TP on the local player

sick gull
#

lemme find my example from my prototype TTT

placid sedge
#

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.

sick gull
#

Yeah exactly

#

so for example

placid sedge
#

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

sick gull
#

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

placid sedge
#

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;
}
sick gull
#

I would suggest just making Hitboxes via PlayerObjects

#

So you know they always exist for each player

#

(this is how I did it)

strange token
#

This ^

sick gull
#

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...

▶ Play video

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...

▶ Play video
placid sedge
#

I thought those videos are too old and are not up to date?

sick gull
#

Nah

#

Some things can be out of date but specifically PlayerObjects are pretty new

twin portal
sick gull
#

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

twin portal
#

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

placid sedge
iron burrow
#

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 ?

sick gull
#

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

twin portal
#

it's AttachToMe, but last I heard it doesn't work anymore apparently

sick gull
#

Does this mean Drinking Nights sticky cheese no longer exists?

twin portal
#

mayhaps

iron burrow
twin portal
#

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

placid sedge
# sick gull

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.

twin portal
#

what line specifically is throwing the error?

placid sedge
#

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;

sick gull
#

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

twin portal
#

what type is ownerPlayer?

placid sedge
#

VRCPlayerApi

twin portal
#

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

placid sedge
#

ok so should I just remove anything on the scripts that gives the player owner of the health and do playerObjects?

#

on the inspector

twin portal
#

pretty much

#

the Owner of the script will be forced to be the player that the object belongs to

placid sedge
#

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.

twin portal
#

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

placid sedge
#

ok

#

playerobjects can also help me with teleporting or is that a completely differnet issue?

twin portal
#

they could actually

placid sedge
#

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.

twin portal
#

if it works it works

#

but if it doesn't...

placid sedge
#

is there a link that can explain playerobjects so i have more information?

twin portal
placid sedge
#

thank you

twin portal
#

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:

  1. Get a VRCPlayerApi reference to that player
  2. use GetPlayerObjects (or FindComponentInPlayerObjects) to find the PlayerObject that belongs to that player
  3. use SendCustomNetworkEvent to 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

placid sedge
#

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?

twin portal
#

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.

sick gull
#

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

twin portal
#

I like to use "many ways to fry an egg", that one is a little morbid 😅

sick gull
#

I agree

#

I don't wanna learn the history of that saying

#

Guess people wore cat fur

placid sedge
#

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");
}
twin portal
#

where does currentAssignedSpawn get assigned?

placid sedge
#

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

sick gull
#

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

placid sedge
#

sharex?

sick gull
#

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

twin portal
#

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

placid sedge
#

the setspawn is local

#

for the local player

twin portal
#

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.

sick gull
#

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.

placid sedge
#

vrc is a special needs for coding. it wants only certain special stuff

sick gull
#

Where is that 30 min video explaining the way you deal with networking?

#

By PhaseDragon

twin portal
placid sedge
#

it's probably why 90% of player i met give up on making their worlds because they couldn't understand vrc logic.

twin portal
#

I can show you some modifications you can do that should allow it to work.

placid sedge
#

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.

twin portal
#

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)

placid sedge
#

what is [NetworkCallable]?

#

unity doesn't know what that is

twin portal
#

[NetworkCallable] is the attribute required to use network events with parameters
you need to import using VRC.SDK3.UdonNetworkCalling; in order to use it

placid sedge
#

ok i'll add that then

#

i don't think I have sdk3

#

I have the 3.7.6 sdk version

twin portal
#

that's not good!

#

that is SDK3 but it's a very old version.

#

you should update immediately

placid sedge
#

3.10.1?

twin portal
#

yes

#

you won't be able to use network events with parameters without updating

placid sedge
#

so when was the sdk updated? I've been using the same sdk sense i created the world.

twin portal
#

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

placid sedge
#

ah so now I see TeleportToAssignedSpawn is not supported anymore

twin portal
#

huh? that's your function lol. what do you mean by not supported

placid sedge
#

oh nvm it's saying Duplicate network callable method name 'TeleportToAssignedSpawn'. Overloading of any kind is not supported.

twin portal
#

means you've two functions named TeleportToAssignedSpawn. delete the old one

placid sedge
#

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

twin portal
placid sedge
#

thanks

#

oh

twin portal
#

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

placid sedge
#

ah so this will work better then what I had before

twin portal
#

that's the goal, at least

placid sedge
#

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

twin portal
#

does the logs on Player 2's client show they teleported, or on Player 1's?

placid sedge
#

Player one logs there’s nothing on the player logs

twin portal
#

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

placid sedge
#

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

mild robin
#

I've set this up before, and I'm recreating this in my new world, but it doesn't work. Nothing will be toggled.

twin portal
#

SetOwner is missing the player input. You likely want to assign the local player there

mild robin
#

I remember you. You saved my life before lmao

twin portal
#

I practically live here

mild robin
#

nice nice

#

like this?

twin portal
#

yeah

tulip sphinx
#

whats even the point of that script?

mild robin
#

Ah, it works now. The set up is different than I remember

twin portal
#

I think you have the graph doing some extra things that aren't necessary

tulip sphinx
#

properly setup manual sync stuff just works

mild robin
#

Please teach me, thank you

tulip sphinx
#

without any care for late joiners

twin portal
#

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

mild robin
#

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.

mild robin
twin portal
#

that was probably what the actual issue was, since your change event is on LateJoiner

mild robin
#

Btw this toggle is on UI

tulip sphinx
#

@mild robin ondeserialization happens to anyone asap, be it late joiner or person in the world

twin portal
#

this includes the Change event(s) as well

tulip sphinx
#

so i still dont get it

mild robin
#

Well, excuse me if I'm still learning and figuring it out

twin portal
#

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

mild robin
#

Got it

fallow mountain
#

I cant even read the screenshot it is so low res😭

mild robin
#

so that's it?

tulip sphinx
twin portal
#

other than the Get activeSelf and UnaryNegation that goes nowhere, yeah should be good now

tulip sphinx
#

i mean the whole idea

mild robin
twin portal
#

they're doing nothing, so might as well. Unless you think they look pretty or something there. then maybe keep them

mild robin
#

got it, thank you very much

#

really appreicate your help

tulip sphinx
#

@mild robin 🤷‍♂️ sad, was very useful for me.

mild robin
tulip sphinx
#

ye ig with more context and experience it may make more and more sense

mild robin
#

Yeah, I create worlds for a couple of years, but rarely get into udon stuff

timber ferry
#

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?

warm steeple
#

yeah it uses local player

north thistle
#

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 >.>

timber ferry
#

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?

warm steeple
#

don't think it matters

heavy spindle
#

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

placid sedge
#

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

fallow mountain
#

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

placid sedge
#

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.

fallow mountain
#

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

north thistle
strange token
#

Lots of little “cache everything” things I wonder how much are necessary

placid sedge
#

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.

fallow mountain
#

what do you mean by telling each playerobject where to go?

#

how did you do that exactly, can you post the code

placid sedge
#

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}");
}

foggy jackal
#

triple back-quote ` for nice formatting 🙂

heavy spindle
fallow mountain
#

or any networking activity

#

it looks like everything is happening locally on the masters client

placid sedge
#

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();

}

fallow mountain
#

is ph a VRC PlayerObject?

#

and where is the syncing happening, there is no request serialization or deserialization

placid sedge
#

no ph is PlayerHealth ph = GetSpawnedPlayerHealth(players[i]);

syncedTeleportPosition = targetPosition;
syncedTeleportRotation = targetRotation;
teleportRequested = true;
RequestSerialization();

fallow mountain
#

what is PlayerHealth ?

placid sedge
#

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.

fallow mountain
#

i think you can research on ownership and synced variables and see what is wrong, and check if the variable are synced

placid sedge
#

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

tawny basin
#

@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;
        }
    }
}
placid sedge
#

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

placid sedge
tawny basin
sick gull
#

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

sick gull
#

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?

placid sedge
#

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

sick gull
#

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?

twin portal
#

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

sick gull
#

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)

twin portal
#

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

north thistle
odd birch
#

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

tawny basin
odd birch
#

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!

tawny basin
#

Tbh, it should have worked with Others anyways
But we'll see

odd birch
#

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

tawny basin
#

If it works like triggers it's per-client calculation

odd birch
#

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

fallow mountain
#

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

north thistle
#

TBF that wouldn't be so much Udon jank but VRC jank

fallow mountain
#

true

tawny basin
#

typa statement

fallow mountain
#

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

#

So we can pretend it is fixed in our minds

hot shale
#

whats currently the best method to respawn a VRC Pickup?
Do I have to call the "Respawn" Method on VRC_Pickup for each player?

fallow mountain
#

if its synced, VRCObjectSync Respawn()
If not, just set position

hot shale
fallow mountain
#

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

hot shale
#

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
fallow mountain
#

looks alright

next halo
#

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?

strange token
next halo
twin portal
#

try initializing your array to something when you declare it

next halo
#

How so?

twin portal
#

do this line at the variable declaration, rather than later

next halo
#

Oh, I see.

#

I shall let it compile and see what happens.

twin portal
#

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

rich salmon
#

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!

https://feedback.vrchat.com/bug-reports/p/syncedobjectmanager-ownership-transfer-not-working-as-documented

I believe I have found a bug in udon sharp. Maybe udon as well but I didn’t test the gui version. I have only tested on PC but I expect that all clients are

north thistle
#

You can trigger network events in ClientSim?

rich salmon
# north thistle 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.

rich salmon
#

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

tulip sphinx
#

triggerenter def works

north thistle
#

I don't think the spawnable "player" in the clientsim can do or trigger anything

hot shale
#

does a disabled gameobject still sync states via udon?

north thistle
#

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

hot shale
#

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

rich salmon
tulip sphinx
#

@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.

twin portal
#

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.

rich salmon
#

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

north thistle
#

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

rich salmon
#

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?

rich salmon
#

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...

slate garden
#

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!

twin portal
#

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

slate garden
# twin portal but there is a little problem, by "universally global counter", do you want this...

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.

twin portal
#

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

slate garden
twin portal
#

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

slate garden
twin portal
#

well logically, yeah. But your scripts cannot access it

#

your scripts will only be able to access everyone in the same instance

slate garden
#

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.

fallow mountain
#

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.

sick gull
#

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

fallow mountain
#

i dont trust urls

#

they do bad things

sick gull
#

I agree. I don't like enabling untrusted

north thistle
# slate garden Huh. Well i guess my idea will have to wait until the dev team implements a more...

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.

mint fractal
#

uhh how broken is vrc's networking???

#

it seems to not work at all

fallow mountain
#

it works within limit

#

maybe it is your code

#

see if debug log says anything in game

twin portal
sick gull
#

Yeah drop the script(s)

proud barn
#

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

rough storm
strange token
proud barn
north thistle
#

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.

north thistle
#

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

north thistle
#

Udon tends to have a panic attack if you try converting an enum to anything but an int...

fallow mountain
#

wait, u# can do enums?

north thistle
#

yah; since 1.0 I think

twin portal
#

They just have to be declared outside of the class

fallow mountain
#

til

#

btw would gameStateByte = (byte)(int)gameState; save some memory

#

or save one udon instruction?

warm steeple
#

just use ints, saving with bytes isn't worth it

#

unless you have a shitton of vars

north thistle
finite sierra
finite sierra
#

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

warm steeple
foggy jackal
#

inexperience, I'd guess

north thistle
# finite sierra you need to use Convert.ToByte or or Convert.ToInt both of those work. since as ...

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.

fallow mountain
#

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...)

twin portal
#

you probably got the error "cannot declare nested types" or something like that

fallow mountain
#

I remember trying inside and outside a class

#

both didnt work

north thistle
#

Just tested this and it worked perfectly fine:

namespace EnumTest
{
    public enum GameState
    {
        Unused,
        Starting,
        Running,
        PostGame
    }
}
foggy jackal
#

You can't put them inside a class, but you can inside a namespace.

#

(and probably should)

strange token
#

I never saw or have been told any positive reasons for using namespaces

foggy jackal
#

makes sure your stuff doesn't get confused with someone else's stuff

twin portal
#

if you're not planning to distribute your code, it doesn't really matter that much

foggy jackal
#

I disagree, but you do you.

north thistle
#

I also find it helps force you to recognize your dependency spread

finite sierra
#

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

finite sierra
cold laurel
#

I also recommend using assembly definitions for similar reasons.

foggy jackal
#

oh byte

#

yeah that's pretty interesting.

finite sierra
#

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

north thistle
#

which I'm pretty sure has been a bug long before this canny was submitted

finite sierra
#

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.

clever river
#

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

twin portal
#

uh, does that say LoadAvatar?

#

are you using the Avatar SDK...?

clever river
#

not that I am aware of?\

twin portal
#

oh nvm that's your avatar failing to load in testing, that's normal

clever river
#

like it works fine on unities end

#

let me make some vids to showcase it

twin portal
#

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?

clever river
#

nothing in the console

#

one sec ill show you

#

or are you able to hop in a vc that I can stream it

twin portal
#

no

clever river
twin portal
#

you should have your console open in-game to see if it's crashing

clever river
#

I see. I shall explore this

twin portal
#

those logs at the bottom with the [Behaviour] tag. Are those objects being instantiated intead of existing in the scene beforehand?

clever river
#

correct

twin portal
#

that would explain why they couldn't be network configured

#

what are the buttons calling in order to allow them to work? SendCustomNetworkEvent?

clever river
#

just the basic Interact() method

twin portal
#

Interact, and then what?

#

ah you posted it didn't you

clever river
#

yup just above

twin portal
#

yeah they are calling SendCustomNetworkEvent. that isn't going to work for instantiated objects

#

instantiated objects cannot perform any networking on their own

clever river
#

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

twin portal
#

the pieces can probably be instantiated fine, but the buttons themselves probably don't need to be instantiated

twin portal
clever river
#

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?

twin portal
#

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

north thistle
#

The only other sort-of exception are PlayerObjects, which get instantiated once per player and can only be owned by said player

clever river
# clever river

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

harsh pebble
# clever river So I think I have managed to solve this on my end, though I am still encounterin...

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

clever river
harsh pebble
#

I learned the same lesson the same way back when i started, but I was trying to make a vending machine 🥲

clever river
#

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

clever river
#

anyone know why this might be happening?

foggy jackal
#

"this"?

clever river
#

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

fallow mountain
#

did you select manual sync or continuous sync?

clever river
twin portal
#

they probably need to be Manual

clever river
#

I hate networking

twin portal
#

🙂

#

it grows on you eventually

clever river
#

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

twin portal
#

how are you expecting it to run your reset method?

clever river
#

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

twin portal
#

you don't call this reset method anywhere else? In Start, OnPlayerJoined, OnDeserialization, etc.?

clever river
#

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

twin portal
#

and when the instance initially starts? 🤔

#

is that in Start?

clever river
#

yes

twin portal
#

that will run locally for all players as they join the instance

#

not just when the instance is first opened

clever river
#

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
twin portal
#

Start is going to run before that variable's value gets synced. So it's going to use the default value of false

clever river
#

where would a safer place to do this be then? Update() ?

twin portal
#

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

clever river
#

I think this makes sense

clever river
#

from the late join side

twin portal
#

does the Start Game button send an event to the owner, or does it run locally?

clever river
#
    {
        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");
    }```
twin portal
#

hm targets All...

#

do any of those three functions write to synced variables? Do you check for Owner before writing to synced variables?

clever river
#

They all write to synced variables yes

twin portal
#

let's see one of them, pick your favorite

clever river
#

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)

twin portal
#

so, what variables are synced here, and where do you call RequestSerialization?

#

also probably only the Owner should be running these functions

clever river
#

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

twin portal
#

where do you call RequestSerialization?

clever river
#

no where. Does the continous sync not handle this?

twin portal
#

it's continuous synced? I thought you switched it to Manual

clever river
#

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

twin portal
#

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

clever river
#

Position is writtin at the VERY beginning with loading, but it might be redundant now that I have added local loading

twin portal
#

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

clever river
#

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

twin portal
#

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

clever river
#

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 😭

twin portal
#

this video is a good place to start

clever river
#

I also think I may have just solved it. I am referencing instantiated objects. This is probably 1000% the issue

sick gull
#

yeah instantiated cant do networking

twin portal
#

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

sick gull
#

object pools

clever river
#

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

twin portal
#

if everyone needs 1 copy locally, then why not just have it already in the scene in the first place?

clever river
#

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);
    }```
twin portal
#

scary.

clever river
#

I think when I initially learned I couldn't make a constructor I lost it and went off the deep end lol

twin portal
#

it happens more than you think

clever river
#

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
twin portal
#

The Unity manual is your best friend for stuff like that

stiff dune
#

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?

tulip sphinx
#

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🤷‍♂️

fallow mountain
#

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.

clever river
#

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;
    }```
twin portal
#

if your network event has a parameter, the function needs to have the [NetworkCallable] attribute added to it

fallow mountain
#

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)

clever river
north thistle
#

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.

stiff dune
#

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

north thistle
#

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

clever river
faint rock
#

Feel like I'm going slightly mad, does SendCustomNetworkEvent with NetworkEventTarget.All not actually work in Build and Test?

fallow mountain
#

Rate limited?

#

Default is 5 events per second

faint rock
#

I know, but no other events should have happened.

north thistle
#

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

foggy jackal
#

that's a weird bug

north thistle
#

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

tired quail
#

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?

north thistle
#

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

fallow mountain
regal halo
#

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

twin portal
#

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)

wise snow
#

simple question:
Are SetPlayerTag / GetPlayerTag networked?

#

nvm, I found the answer

timber ferry
#

isnt that a really old, deprecated feature too?

twin portal
#

yea

#

whatever you're trying to use those for, there's probably a better way to do it now

wise snow
#

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

twin portal
#

it'd be even better to just have a PlayerObject that saves these values

tired quail
#

Yee what LEGOS said XD

#

I've made a simmilar system that does exactly that for an admin panel

tired quail
# fallow mountain The overhead difference is not much, but since they are for completely different...

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.

fallow mountain
#

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)

tired quail
#

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.

wise snow
#

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

twin portal
#

yeah. The biggest difference being it's stupidly easier to network sync

#

Teleporting players only works on the local player, btw

wise snow
#

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

north thistle
wise snow
strange token
#

That’s so weird

normal sonnet
#

Is there a way to automatically set the sync mode (continuous/manual/off) of an udon graph, like there is with u#?

twin portal
#

graph does not get such a luxury, unfortunately

warm steeple
#

using graphs is lowkey insanity when logic gets complex anyway

meager mauve
#

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?

twin portal
meager mauve
twin portal
#

that would do it too lol

coarse void
#

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.

minor gale
tulip sphinx
#

if there was a set list of things to be in the world in the first place, networked or not...

north thistle
#

what do you mean by a set list?

fallow mountain
clever river
#

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?

twin portal
#

not entirely sure, but you could put a bandaid on it by changing your [NetworkCallable] attribute to [NetworkCallable(maxEventsPerSecond: 100)]

clever river
#

I thought about this. But its really bizzare to me since the button is only interacted with once

twin portal
#

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

clever river
#

I have this running in my update to monitor it:
Debug.Log("Queued" + NetworkCalling.GetAllQueuedEvents());

twin portal
#

things could be happening in subsequent functions calls that you have, like Reset()

clever river
#
    {
        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();
        }
    }```
clever river
#

turns out I am just dumb lol

DeadMonsters[x].GetComponent<PhysicalMonster>().Reset();

    {
        SendCustomNetworkEvent(NetworkEventTarget.All, "NetWorkedReset");
    }```

that would do it
covert nacelle
twin portal
#

that's great man

covert nacelle
foggy jackal
#

interesting approach

fleet arch
#

Im not sure why you are needing to convert at all when you can just serve the hls links directly from yt

covert nacelle
#

so direct hls links just gives random errors to the video players

fleet arch
#

Avpro cant even play vp9 lol

covert nacelle
#

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

fleet arch
#

You can get mp4 hls at 1080

covert nacelle
#

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

fleet arch
covert nacelle
#

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

fleet arch
#

Nice command injection btw

covert nacelle
#

bcus why not

fleet arch
covert nacelle
#

exact error that my script fixes

#

but sadly not your's

covert nacelle
covert nacelle
#

and can't be fixed directly just redirecting the sources from youtube

covert nacelle
fleet arch
#

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

covert nacelle
#

probbly

#

if its a a/b testing it will eventually come to all us

fleet arch
#

If they implement that fully we are all screwed either way xD

covert nacelle
#

checkout the yt-dlp's ejs decrpt tthing it bypasses that somehow

fleet arch
#

Na this is another thing

covert nacelle
#

oh

#

but if is that probly it should not work on my implementation

#

so probly not

fresh geode
#

Did you open said feature request... or find a work around?

strange token
#

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

warped latch
#

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?

tulip sphinx
#

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

warped latch
#

Thought I'd check. Yeah, that makes sense in hindsight, but the specifics were never mentioned anywhere and it'd be nice to confirm.

normal sundial
#

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?

foggy jackal
#

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

peak shard
#

What do I change or add to turn this into a global toggle upon passcode entry?

fallow mountain
#
  1. Add [UdonBehaviourSyncMode(BehaviourSyncMode.Manual)] just before the class
  2. Add [UdonSynced] before public bool isEnable
  3. In Interact() set ownership of gameObject to local player, and then isEnable = !isEnable;, and then RequestSerialization();, and then exec()
  4. Make a
public override void OnDeserialization()
{
exec();
}
  1. 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

bold glade
#

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.

humble girder
bold glade
fallow mountain
#

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

kindred spire
#

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

wise pawn
#

I've always been curious how games mitigate latency. However your idea lost me. Can you explain more?

twin portal
#

the term for this is "rollback netcode"

kindred spire
#

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

minor gale
#

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

fallow mountain
#

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

north thistle
#

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

minor gale
north thistle
minor gale
#

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

normal sundial
#

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.

  1. 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

  2. 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.

  3. 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?

  4. 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.

strange token
#

Someone link to that phase video

strange token
normal sundial
#

i appreciate it, i will take a look 👍

tulip sphinx
normal sundial
north thistle
# normal sundial apologies for text wall a few questions about networking particulars. In my case...

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...

▶ Play video
normal sundial
#

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

north thistle
#

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

north thistle
# normal sundial apologies for text wall a few questions about networking particulars. In my case...

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.

normal sundial
# north thistle Also the answer to "What is the actual rate limit for networking?" is fairly com...

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 😅

north thistle
#

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.

normal sundial
#

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

warped latch
#

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

minor gale
normal sundial
#

because i encode and decode the data though I can't just sync the variables through the network

minor gale
normal sundial
# minor gale you could encode on preserialize and decode on deserialize, but it's not explici...

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

frozen igloo
minor gale
#

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

normal sundial
# minor gale if you have one of the players who is actively playing do it, it seems reasonabl...

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.

minor gale
#

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

normal sundial
#

but that introduces its own potential issues

#

All quite nebulous at this point

minor gale
#

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)

normal sundial
#

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

frozen igloo
#

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

minor gale
#

separating the current board state from the separately advertised history makes a lot of sense yeah

normal sundial
# frozen igloo I'd say use synced variables for latest state, and locally cache the history. Wh...

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.

minor gale
#

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)

normal sundial
#

If it was all local I could do it fine! 😭

minor gale
#

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

normal sundial
minor gale
# normal sundial could I ask for a bit of elaboration on the request/emission model? My current, ...

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

normal sundial
#

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

minor gale
#

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

normal sundial
# minor gale i would consider an incrementing int per game, you can allow games >= to your cu...

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 😭

minor gale
#

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

normal sundial
# minor gale practically it'd probably be rare, but if you imagine a new joiner that requests...

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

normal sundial
#

what are these network ids that keep growing in number

twin portal
#

every object in your scene that needs to be networked in any way, gets assigned a network ID

normal sundial
#

it appears to be my gameobjects

twin portal
#

so every time you add a new script, a new pickup, whatever, it'll get added to the list

normal sundial
#

oh

#

is that what the "synchronization method" dropdown for the u# scripts is doing

twin portal
#

kinda, I guess

normal sundial
#

that is gonna be annoying to go and change for every script

twin portal
#

if it's anything other than "None" it'll get an ID

normal sundial
#

sheesh

twin portal
normal sundial
#

i am

#

though for most of my stuff i plan to use network events

#

maybe

twin portal
#

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

normal sundial
#

oh that will be much faster thank you

twin portal
#

doing it this way also lets you use a secret 4th option for syncing. NoVariableSync, if that sounds useful

normal sundial
#

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

north thistle
#

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

strange token
#

I’m pretty sure they don’t but Idk

#

If a script says not to be able to sync I expect that to block the object’s ability to ever sync so why give it an id