Network Proximity Checker and major FPS drop with large GC collection

I have 450 objects that use Network Proximity Checker and the servers fps drops by 30 every time it updates. I find it strange that it seems to check every object with the proximity checker if there is a player nearby, rather than checking every player if there are any objects with the proximity checker.

The 450 objects is mainly a test, as my game is going to have a lot more than that. Is this meant only for very small scale games or am I doing something wrong?

I tried with 2,500 objects and the GC allocation went up to 130mb and my fps would drop to 1 from 120 every time it updated.

Anybody?
Is there not an option to spread it over multiple frames?

You could stagger creation of the objects, or give them slightly different visUpdateIntervals so that they diverge.

The NetworkProximityCheck is not highly optimized, but is uses the public APIs of the visibility system.

http://docs.unity3d.com/Manual/UNetVisibility.html

You could create your own component to do visibility in a way that is optimized for your game.

#if ENABLE_UNET
using System;
using System.Collections.Generic;
using UnityEngine;

namespace UnityEngine.Networking
{

[AddComponentMenu("Network/NetworkProximityChecker")]
[RequireComponent(typeof(NetworkIdentity))]
public class NetworkProximityChecker : NetworkBehaviour
{
    public enum CheckMethod
    {
        Physics3D,
        Physics2D
    };

    public int visRange = 10;
    public float visUpdateInterval = 1.0f; // in seconds
    public CheckMethod checkMethod = CheckMethod.Physics3D;

    public bool forceHidden = false;

    float m_VisUpdateTime;

    void Update()
    {
        if (!NetworkServer.active)
            return;

        if (Time.time - m_VisUpdateTime > visUpdateInterval)
        {
            GetComponent<NetworkIdentity>().RebuildObservers(false);
            m_VisUpdateTime = Time.time;
        }
    }

    // called when a new player enters
    public override bool OnCheckObserver(NetworkConnection newObserver)
    {
        if (forceHidden)
            return false;

        // this cant use newObserver.playerControllers[0]. must iterate to find a valid player.
        GameObject player = null;
        foreach (var p in newObserver.playerControllers)
        {
            if (p != null && p.gameObject != null)
            {
                player = p.gameObject;
                break;
            }
        }
        if (player == null)
            return false;

        var pos = player.transform.position;
        return (pos - transform.position).magnitude < visRange;
    }

    public override bool OnRebuildObservers(HashSet<NetworkConnection> observers, bool initial)
    {
        if (forceHidden)
        {
            // ensure player can still see themself
            var uv = GetComponent<NetworkIdentity>();
            if (uv.connectionToClient != null)
            {
                observers.Add(uv.connectionToClient);
            }
            return true;
        }

        // find players within range
        switch (checkMethod)
        {
            case CheckMethod.Physics3D:
            {
                var hits = Physics.OverlapSphere(transform.position, visRange);
                foreach (var hit in hits)
                {
                    // (if an object has a connectionToClient, it is a player)
                    var uv = hit.GetComponent<NetworkIdentity>();
                    if (uv != null && uv.connectionToClient != null)
                    {
                        observers.Add(uv.connectionToClient);
                    }
                }
                return true;
            }

            case CheckMethod.Physics2D:
            {
                var hits = Physics2D.OverlapCircleAll(transform.position, visRange);
                foreach (var hit in hits)
                {
                    // (if an object has a connectionToClient, it is a player)
                    var uv = hit.GetComponent<NetworkIdentity>();
                    if (uv != null && uv.connectionToClient != null)
                    {
                        observers.Add(uv.connectionToClient);
                    }
                }
                return true;
            }
        }
        return false;
    }

    // called hiding and showing objects on the host
    public override void OnSetLocalVisibility(bool vis)
    {
        SetVis(gameObject, vis);
    }

    static void SetVis(GameObject go, bool vis)
    {
        foreach (var r in go.GetComponents<Renderer>())
        {
            r.enabled = vis;
        }
        for (int i=0; i < go.transform.childCount; i++)
        {
            var t = go.transform.GetChild(i);
            SetVis(t.gameObject, vis);
        }
    }
}

}
#endif
1 Like

Thank you very much!
I have created a couple classes which seem to work a lot faster, I tested with thousands of objects and it was fine :slight_smile:
Here is the code if anyone else would like it.

using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
using System.Collections.Generic;

// Attach this to objects that need their visibility updated as the player moves around
[RequireComponent(typeof(NetworkIdentity))]
public class NetworkVisbility : NetworkBehaviour {

    public List<NetworkConnection> playersObserving = new List<NetworkConnection>();
    public NetworkIdentity networkIdentity;

    void Awake(){
        networkIdentity = GetComponent<NetworkIdentity>();
    }

    public override bool OnRebuildObservers(HashSet<NetworkConnection> observers, bool initial){
         foreach (NetworkConnection net in playersObserving){
            observers.Add(net);
        }
        return true;
    }

    public override bool OnCheckObserver(NetworkConnection newObserver){
        return false;
    }
}
using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
using System.Collections.Generic;

// Attach this to player prefab
[RequireComponent(typeof(SphereCollider))]
public class NetworkPlayerVisibility : NetworkBehaviour {

    [SerializeField]
    private int visRadius = 50; // Radius of sphere collider
    [SerializeField]
    private float visUpdateInterval = 2000; // Update time in ms
    private SphereCollider collider;
    private List<NetworkVisbility> changedObjects = new List<NetworkVisbility>(); // Objects that have changed visibility
    private float visUpdateTime;

    void Awake(){
        collider = GetComponent<SphereCollider>();
        collider.isTrigger = true;
        collider.radius = visRadius;
    }

    void Update(){
        if (!NetworkServer.active)
            return;
       
        if (Time.time - visUpdateTime > visUpdateInterval){
            RebuildChangedObjects();
            visUpdateTime = Time.time;
        }
    }

    void RebuildChangedObjects(){
        foreach (NetworkVisbility net in changedObjects){
            net.networkIdentity.RebuildObservers(false);
        }
        changedObjects.Clear();
    }

    void OnTriggerEnter(Collider col){
        NetworkVisbility net = col.GetComponent<NetworkVisbility>();
        if (net != null && connectionToClient != null){
            net.playersObserving.Add(connectionToClient);
            changedObjects.Add(net);
        }
    }

    void OnTriggerExit(Collider col){
        NetworkVisbility net = col.GetComponent<NetworkVisbility>();
        if (net != null && connectionToClient != null){
            net.playersObserving.Remove(connectionToClient);
            changedObjects.Add(net);
        }
    }

    // Use these to update radius and interval in game
    public void SetVisualRadius(int radius){
        visRadius = radius;
        collider.radius = radius;
    }

    public void SetUpdateInterval(float interval){
        visUpdateInterval = interval;
    }

}
5 Likes

So @Teedo145 the first script you posted would be used for NPC objects?

@Teedo145 Is it possible to let your script work without trigger enter/exit? Cause it interferes with my combat system wich uses a trigger box… Would like to use Physics.OverlapSphere like the original, but it lacks the option to remove a client from the playersObserving list like you do in trigger exit. Any thoughts?

You could keep a record of the entities that are observed each time you use OverlapSphere, and drop visibility from those that are no longer in range.

@Teedo145 I am using your altered proximity checker and I love it! Have a weird problem now tho… It all works fine the first time a client connects and plays. But when I quit and reconnect the server trows a lot of warnings for all objects affected by the checker. Weird part is that it all still works but it just trows a lot of warnings:

Also when I check the obeservers of a BlackPine I see this:

Do you have any ideas??

Anyone?

It looks like the connection is reused or something and bugs the observer list after a reconnect…

Any Unity staff? Do I need to bug report? Cause it looks like a bug to me… (Bottom screenshot observers)

bump

I hate to necro but this is because your connection isn’t set to ready (see in connectionId: 1 isReady: false). If anyone else has this issue, make sure you are checking that the connection is ready