using System.Collections.Generic; using UnityEngine; namespace Mirror { /// /// Component that controls visibility of networked objects for players. /// Any object with this component on it will not be visible to players more than a (configurable) distance away. /// [AddComponentMenu("Network/NetworkProximityChecker")] [RequireComponent(typeof(NetworkIdentity))] [HelpURL("https://mirror-networking.com/xmldocs/articles/Components/NetworkProximityChecker.html")] public class NetworkProximityChecker : NetworkBehaviour { /// /// Enumeration of methods to use to check proximity. /// public enum CheckMethod { Physics3D, Physics2D } /// /// The maximim range that objects will be visible at. /// [Tooltip("The maximum range that objects will be visible at.")] public int visRange = 10; /// /// How often (in seconds) that this object should update the list of observers that can see it. /// [Tooltip("How often (in seconds) that this object should update the list of observers that can see it.")] public float visUpdateInterval = 1; /// /// Which method to use for checking proximity of players. /// Physics3D uses 3D physics to determine proximity. /// Physics2D uses 2D physics to determine proximity. /// [Tooltip("Which method to use for checking proximity of players.\n\nPhysics3D uses 3D physics to determine proximity.\nPhysics2D uses 2D physics to determine proximity.")] public CheckMethod checkMethod = CheckMethod.Physics3D; /// /// Flag to force this object to be hidden for players. /// If this object is a player object, it will not be hidden for that player. /// [Tooltip("Enable to force this object to be hidden from players.")] public bool forceHidden; // Layers are used anyway, might as well expose them to the user. /// /// Select only the Player's layer to avoid unnecessary SphereCasts against the Terrain, etc. /// ~0 means 'Everything'. /// [Tooltip("Select only the Player's layer to avoid unnecessary SphereCasts against the Terrain, etc.")] public LayerMask castLayers = ~0; float lastUpdateTime; // OverlapSphereNonAlloc array to avoid allocations. // -> static so we don't create one per component // -> this is worth it because proximity checking happens for just about // every entity on the server! // -> should be big enough to work in just about all cases static Collider[] hitsBuffer3D = new Collider[10000]; static Collider2D[] hitsBuffer2D = new Collider2D[10000]; void Update() { if (!NetworkServer.active) return; if (Time.time - lastUpdateTime > visUpdateInterval) { netIdentity.RebuildObservers(false); lastUpdateTime = Time.time; } } /// /// Called when a new player enters /// /// /// public override bool OnCheckObserver(NetworkConnection newObserver) { if (forceHidden) return false; return Vector3.Distance(newObserver.playerController.transform.position, transform.position) < visRange; } /// /// Called when a new player enters, and when scene changes occur /// /// List of players to be updated. Modify this set with all the players that can see this object /// True if this is the first time the method is called for this object /// True if this component calculated the list of observers public override bool OnRebuildObservers(HashSet observers, bool initial) { // if force hidden then return without adding any observers. if (forceHidden) // always return true when overwriting OnRebuildObservers so that // Mirror knows not to use the built in rebuild method. return true; // find players within range switch (checkMethod) { case CheckMethod.Physics3D: { // cast without allocating GC for maximum performance int hitCount = Physics.OverlapSphereNonAlloc(transform.position, visRange, hitsBuffer3D, castLayers); if (hitCount == hitsBuffer3D.Length) Debug.LogWarning("NetworkProximityChecker's OverlapSphere test for " + name + " has filled the whole buffer(" + hitsBuffer3D.Length + "). Some results might have been omitted. Consider increasing buffer size."); for (int i = 0; i < hitCount; i++) { Collider hit = hitsBuffer3D[i]; // collider might be on pelvis, often the NetworkIdentity is in a parent // (looks in the object itself and then parents) NetworkIdentity identity = hit.GetComponentInParent(); // (if an object has a connectionToClient, it is a player) if (identity != null && identity.connectionToClient != null) { observers.Add(identity.connectionToClient); } } break; } case CheckMethod.Physics2D: { // cast without allocating GC for maximum performance int hitCount = Physics2D.OverlapCircleNonAlloc(transform.position, visRange, hitsBuffer2D, castLayers); if (hitCount == hitsBuffer2D.Length) Debug.LogWarning("NetworkProximityChecker's OverlapCircle test for " + name + " has filled the whole buffer(" + hitsBuffer2D.Length + "). Some results might have been omitted. Consider increasing buffer size."); for (int i = 0; i < hitCount; i++) { Collider2D hit = hitsBuffer2D[i]; // collider might be on pelvis, often the NetworkIdentity is in a parent // (looks in the object itself and then parents) NetworkIdentity identity = hit.GetComponentInParent(); // (if an object has a connectionToClient, it is a player) if (identity != null && identity.connectionToClient != null) { observers.Add(identity.connectionToClient); } } break; } } // always return true when overwriting OnRebuildObservers so that // Mirror knows not to use the built in rebuild method. return true; } /// /// Called when hiding and showing objects on the host /// /// public override void OnSetLocalVisibility(bool visible) { foreach (Renderer rend in GetComponentsInChildren()) { rend.enabled = visible; } } } }