Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NetworkObject CheckObjectVisibility doesn't work on client connection #3185

Open
babaq opened this issue Dec 29, 2024 · 6 comments
Open

NetworkObject CheckObjectVisibility doesn't work on client connection #3185

babaq opened this issue Dec 29, 2024 · 6 comments
Labels
stat:awaiting response Status - Awaiting response from author. stat:awaiting triage Status - Awaiting triage from the Netcode team. Tracking Has been added to tracking type:docs Docs feedback or issue type:support Questions or other support

Comments

@babaq
Copy link

babaq commented Dec 29, 2024

Description

I want to spawn a networkprefab without any client visibility, first i tried to set no observers before spawn:

NetworkObject.SpawnWithObservers = false;
NetworkObject.Spawn();

this results a spawned object with zero observers, but when a late-join client connects, the object gets spawned on client. The documentation says this is the intended normal behavior, because it only works at the initial spawning.

Then, the documentation says the CheckObjectVisibility will work at both initial spawning and new client connection, so i tried this:

NetworkObject.CheckObjectVisibility += _ => false;
NetworkObject.Spawn();

it did spawn with no observers as the previous approach, but when late-join client connects, it still spawn the object on client.

Reproduce Steps

  1. start server/host
  2. spawn a network prefab with NetworkObject.CheckObjectVisibility += _ => false;
  3. start a client and connect to server

Actual Outcome

a spawned object with visibility always set to false still spawns on late-join client

Expected Outcome

a spawned object with visibility always set to false should be invisible to all connected client and future late-join clients

Environment

  • OS: [win10]
  • Unity Version: [6000.0.32f1]
  • Netcode Version: [2.1.1]
@babaq babaq added stat:awaiting triage Status - Awaiting triage from the Netcode team. type:bug Bug Report labels Dec 29, 2024
@NoelStephensUnity NoelStephensUnity added stat:Investigating Issue is currently being investigated and removed stat:awaiting triage Status - Awaiting triage from the Netcode team. labels Jan 7, 2025
@NoelStephensUnity
Copy link
Collaborator

NoelStephensUnity commented Jan 7, 2025

Hi @babaq,

I put together an example project to see if I could replicate the first issue you are having using NetworkObject.SpawnWithObservers:

ObjectSpawning.zip
I start out with just a hosted session and hit the (1) key twice to spawn two network prefab instances with the NetworkObject.SpawnWithObservers set to false.
Image

I then connect a client to replicate the late joining client issue described.
Image
The late joined client did not spawn the NetworkObject instances when it joined.

I then spawned one more NetworkObject with the NetworkObject.SpawnWithObservers set to false to verify it doesn't spawn the NetworkObject with a client connected.
Image
The NetworkObject spawned while the client was connected only spawns on the host side.

I then hit the (2) key to show the hidden NetworkObjects to the connected client.
Image
The NetworkObjects become "visible" to the connected client.

I then connected a 2nd client to verify that even though the host and the connected client have visibility to the 3 spawned NetworkObject instances that they would still be invisible (not spawned) to the 2nd client.
Image
The 2nd client did not spawn the NetworkObject instances.

I then was able to show the NetworkObject instances and all connected clients have visibility to spawned objects.
Image

Could you look at the project I have provided you and see if you can determine something that you might be doing differently?
If you cannot spot what you are doing that is different, could you provide me with a project that replicates the issue you are having?

@NoelStephensUnity NoelStephensUnity added stat:awaiting response Status - Awaiting response from author. type:support Questions or other support and removed type:bug Bug Report labels Jan 7, 2025
@babaq
Copy link
Author

babaq commented Jan 8, 2025

Hi @NoelStephensUnity I tested again and the problems disappeared, maybe because i have upgraded to Netcode v2.2.

As the same result you show,

NetworkObject.SpawnWithObservers = false;
NetworkObject.Spawn();

this make object invisible not only at the initial spawning, but also when late-joining client connects. I guess when new client join, the server will spawn the object, hence also initial spawning at client side. So the Doc need to update, current description is kind of confusing. Later when needed, the object can be visible using NetworkObject.NetworkShow.

Then, i also test the second approach,

NetworkObject.CheckObjectVisibility += _ => false;
NetworkObject.Spawn();

this have achieved the same result, as the Doc description. However, later NetworkObject.NetworkShow can not respawn the object, so it seems the CheckObjectVisibility take precedence over SpawnWithObservers ?

@NoelStephensUnity
Copy link
Collaborator

NoelStephensUnity commented Jan 8, 2025

However, later NetworkObject.NetworkShow can not respawn the object, so it seems the CheckObjectVisibility take precedence over SpawnWithObservers ?

This is expected behavior.
Here is a rough draft of an update for our documentation. Let me know if providing updates to this in both the API and public NGO documentation site would help further clarify how SpawnWithObservers and CheckObjectVisibility work as well as how they relate to each other.

NetworkObject.SpawnWithObservers:
This controls the spawn default value for network visibility for all clients. When this is set to true (the default), the NetworkObject will always be visible to currently connected and late joining clients. When set to false, the NetworkObject will not be visible to currently connected clients and late joining clients. If set to false, user script controls the network visibility on a per client basis using NetworkObject.NetworkShow.

NetworkObject.CheckObjectVisibility:
This allows you to dynamically control a NetworkObject's visibility on a per client identifier basis and is why the delegate includes a clientId parameter when invoked. When set to null (default), it will assume all client identifiers have visibility. If NetworkObject.SpawnWithObservers is set to true (the default) and NetworkObject.CheckObjectVisibility has a delegate handler assigned, then the delegate handler dictates (on a per clientId basis) which clients has network visibility of the NetworkObject (for spawning purposes only). If NetworkObject.SpawnWithObservers is set to false, then NetworkObject.CheckObjectVisibility is not checked when determining if the NetworkObject should be spawned for a specific client identifier.
NetworkObject.NetworkShow checks if NetworkObject.CheckObjectVisibility has an assigned a delegate handler. If so, then the delegate handler acts as a means to control which clients can have network visibility to a NetworkObject.

@NoelStephensUnity NoelStephensUnity added type:docs Docs feedback or issue Tracking Has been added to tracking and removed stat:Investigating Issue is currently being investigated labels Jan 8, 2025
@babaq
Copy link
Author

babaq commented Jan 8, 2025

Here is my understanding and small rephrasing:

NetworkObject.SpawnWithObservers:
This controls the initial spawning network visibility for all clients. When this is set to true (the default), the NetworkObject will be visible to currently connected and late joining clients. When set to false, the NetworkObject will not be visible to currently connected clients and late joining clients. After initial spawning, network visibility can be changed on a per client basis using NetworkObject.NetworkShow/NetworkObject.NetworkHide.

NetworkObject.CheckObjectVisibility:
This gives you finer control of NetworkObject's visibility on a per client identifier basis and is why the delegate includes a clientId parameter when invoked. When set to null (default), it has no effect, so the initial spawning network visibility solely relay on NetworkObject.SpawnWithObservers. When NetworkObject.CheckObjectVisibility has a delegate handler assigned, and NetworkObject.SpawnWithObservers = true , then the delegate handler dictates (on a per clientId basis) which clients has network visibility of the NetworkObject. If NetworkObject.SpawnWithObservers = false, then NetworkObject.CheckObjectVisibility is not checked when determining if the NetworkObject should be spawned for a specific client identifier.

Only NetworkObject.NetworkShow, but not NetworkObject.NetworkHide checks if NetworkObject.CheckObjectVisibility has an assigned a delegate handler. If so, then the delegate handler controls which clients can have network visibility to a NetworkObject.

@babaq
Copy link
Author

babaq commented Jan 8, 2025

As a side note, I wrote some helper functions for manipulating network visibility which i wish Netcode could have provide them at the beginning, so i list them here just for your consideration.

 public static class NetVisExt
 {
     public static List<ulong> Observers(this NetworkObject no)
     {
         List<ulong> list = new();
         var obs = no.GetObservers();
         while (obs.MoveNext()) { list.Add(obs.Current); }
         return list;
     }

     public static bool IsNetworkHideFromAll(this NetworkObject no)
     {
         int count = 0;
         var obs = no.GetObservers();
         while (obs.MoveNext()) { count++; }
         return count == 0;
     }

     public static bool IsNetworkShowToAll(this NetworkObject no)
     {
         var nm = NetworkManager.Singleton;
         if (nm != null && nm.IsServer)
         {
             int count = 0;
             var obs = no.GetObservers();
             while (obs.MoveNext()) { count++; }
             return count == nm.ConnectedClientsIds.Count;
         }
         return false;
     }

     public static void NetworkShowHideOnly(this NetworkObject no, ulong clientid, bool isshow)
     {
         if (isshow) { NetworkShowOnlyTo(no, clientid); } else { NetworkHideOnlyFrom(no, clientid); }
     }

     public static void NetworkShowHideAll(this NetworkObject no, bool isshow)
     {
         if (isshow) { NetworkShowToAll(no); } else { NetworkHideFromAll(no); }
     }

     public static void NetworkShowOnlyTo(this NetworkObject no, ulong clientid)
     {
         var nm = NetworkManager.Singleton;
         if (nm != null && nm.IsServer)
         {
             var ss = no.Observers();
             var hs = nm.ConnectedClientsIds.Except(ss).ToArray();
             if (hs.Contains(clientid)) { no.NetworkShow(clientid); }
             foreach (var c in ss) { if (c != clientid) { no.NetworkHide(c); } }
         }
     }

     public static void NetworkShowToAll(this NetworkObject no)
     {
         var nm = NetworkManager.Singleton;
         if (nm != null && nm.IsServer)
         {
             foreach (var c in nm.ConnectedClientsIds.Except(no.Observers()))
             {
                 no.NetworkShow(c);
             }
         }
     }

     public static void NetworkHideOnlyFrom(this NetworkObject no, ulong clientid)
     {
         var nm = NetworkManager.Singleton;
         if (nm != null && nm.IsServer)
         {
             var ss = no.Observers();
             var hs = nm.ConnectedClientsIds.Except(ss).ToArray();
             if (ss.Contains(clientid)) { no.NetworkHide(clientid); }
             foreach (var c in hs) { if (c != clientid) { no.NetworkShow(c); } }
         }
     }

     public static void NetworkHideFromAll(this NetworkObject no)
     {
         var nm = NetworkManager.Singleton;
         if (nm != null && nm.IsServer)
         {
             foreach (var c in no.Observers())
             {
                 no.NetworkHide(c);
             }
         }
     }
 }

@NoelStephensUnity
Copy link
Collaborator

@babaq
Those are some good points you are making in the extensions you wrote.
We could add this to NetworkObject:

        /// <summary>
        /// Returns observers as a list
        /// </summary>
        /// <returns>Observers enumerator</returns>
        public List<ulong> GetObserversList()
        {
            if (!IsSpawned)
            {
                return m_EmptyULongHashSet.ToList();
            }

            return Observers.ToList();
        }

        public void NetworkShowAll()
        {
            foreach(var observer in Observers)
            {
                if (!IsNetworkVisibleTo(observer))
                {
                    NetworkShow(observer);
                }
            }    
        }

        public void NetworkHideAll()
        {
            foreach (var observer in Observers)
            {
                if (IsNetworkVisibleTo(observer) && observer != OwnerClientId)
                {
                    NetworkHide(observer);
                }
            }
        }

Then the rest where you are doing status checks I would need to think about as the context changes between which network topology is being used...but they are all worth contemplating.

@michalChrobot michalChrobot added stat:awaiting triage Status - Awaiting triage from the Netcode team. stat:awaiting response Status - Awaiting response from author. and removed stat:awaiting response Status - Awaiting response from author. labels Jan 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stat:awaiting response Status - Awaiting response from author. stat:awaiting triage Status - Awaiting triage from the Netcode team. Tracking Has been added to tracking type:docs Docs feedback or issue type:support Questions or other support
Projects
None yet
Development

No branches or pull requests

3 participants