Static instance is only works for one gameObject

Hello,

I have run into an issue in getting a Canvas gameobject to be setActive(true) whenever the player is within a specific range. Right now, the Canvas will only be active when the player gets within range of one gameObject, but not for other gameObjects, even though all of them contain the same script. For example

Here the player is within the sphere range of the cube gameobject, which results in the words “Hello” appearing on screen.

However, when the player is in range of another gameobject with the same script, the words “Hello” do NOT appear.

Both of these gameObjects (cube and sphere) have the same script attached to them: Interactable.cs

public class Interactable : MonoBehaviour
{
    // sets the range the player must be in order to interact with an object
    public float interactRange;

  

    // Update is called once per frame
    protected virtual void Update()
    {
        // if the distance between the object and the player are less than the interaction range
        //let the player interact with it
      
        if (Vector3.Distance(gameObject.transform.position, GameManager.instance.player.position) < interactRange)
        {
            //activate the text popup
            TextPopup.instance.inRangeforPopup = true;
            //log the name of the gameobject that the player is currently in range of.
            Debug.Log(gameObject.ToString());
          
        }
        else
        {
            //de-activate the text popup
            TextPopup.instance.inRangeforPopup = false;
        }
   }


   // draw a sphere around the gameobject that this script is attached to
    private void OnDrawGizmosSelected()
    {
        Gizmos.color = Color.yellow;
        Gizmos.DrawWireSphere(transform.position, interactRange);
    }
}

Interactable.cs calls an instance of TextPopup .cs which helps to check a bool if the player is in range

public class TextPopup : MonoBehaviour
{
    //Canvas that holds the popup text
    public GameObject canvasPopup;
   
    //is the player in range?
    public bool inRangeforPopup;

    public static TextPopup instance;

    private void Awake()
    {
        if (instance == null)

        {
            instance = this;
        }

        else

        {
            Destroy(instance);
        }
    }

  
   // Update is called once per frame
    void Update()
    {
        CheckRange();

    }

    public void CheckRange()
    {
        //if player is in range
        if (inRangeforPopup == true)
        {
            messagePopup.SetActive(true);

        }
        //if player is NOT in range
        else if (inRangeforPopup == false)
        {
            messagePopup.SetActive(false);
        }
    }
}

How do I get the static instance of TextPopup to behave consistently for all the other gameObjects in the scene?

I have also attached an exported package if you would like a more hands-on view of the problem. Thank you for your time.

5152859–510515–TextPopupScene.unitypackage (236 KB)

Did you remember to set interactRange to a non-zero value in the other GameObjects?

You probably would have noticed if you didn’t, thanks to OnDrawGizmosSelected, but just making sure to eliminate the simplest possible explanation.

Now I see where the problem lies.

In the Update method of Interactable you’re setting TextPopup.instance.inRangeforPopup to false every frame that said Interactable is not withing range of the player object. So if any interactable in your scene is not within range, that shared variable will be constantly be set to false.

You could switch to using sphere colliders and use OnTriggerEnter to set inRangeforPopup true and OnTriggerExit to set it back to false. This would be more reliable, have better performance and you’d get the range visualization automatically too :slight_smile:

Thanks SisusCo! I added in OnTriggerEnter and OnTriggerExit to my Interactable.cs like you suggested and it worked! I guess I got carried away with using Gizmos that I forgot about the reliability of using simple triggers to set boolean values. For now, the new Interactable script looks like this.

public class Interactable : MonoBehaviour
{
    // sets the range the player must be in order to interact with an object
    public float interactRange;

    // Update is called once per frame
    protected virtual void Update()
    {
        // if the distance between the object and the player are less than the interaction range
        //let the player interact with it
      
        if (Vector3.Distance(gameObject.transform.position, GameManager.instance.player.position) < interactRange)
        {
          
            //log the name of the gameobject that the player is currently in range of.
            Debug.Log(gameObject.ToString());
          
        }
       
   }

    public void OnTriggerEnter(Collider other)
    {
        if (other.tag == "Player")
        {
            TextPopup.instance.inRangeforPopup = true;
        }
    }

    public void OnTriggerExit(Collider other)
    {
        if (other.tag == "Player")
        {
            TextPopup.instance.inRangeforPopup = false;
        }
    }



    // draw a sphere around the gameobject that this script is attached to
    private void OnDrawGizmosSelected()
    {
        Gizmos.color = Color.yellow;
        Gizmos.DrawWireSphere(transform.position, interactRange);
    }
}

Glad to hear you were able to get everything in working order!

You should now be able to remove the Update and OnDrawGizmosSelected methods completely, as they’re no longer needed.

Bonus tip: you can also make Interactable easier to use and less prone to user errors by ensuring that it is always paired with a trigger collider automatically.

using UnityEngine;

public class Interactable : MonoBehaviour
{
    // reset is called when the user selects Reset in the inspector context menu
    // or when adding the component the first time
    private void Reset()
    {
        // make sure that the GameObject has a collider
        var collider = GetComponent<Collider>();
        if(collider == null)
        {
            // add a sphere collider by default if no collider existed on GameObject
            var sphereCollider = gameObject.AddComponent<SphereCollider>();
            collider = sphereCollider;

            // give the sphere collider a good default radius
            const float defaultRadius = 3f;
            sphereCollider.radius = defaultRadius;
        }

        // make sure that collider is set up as trigger so that OnTriggerEnter and OnTriggerExit work
        collider.isTrigger = true;
    }

    public void OnTriggerEnter(Collider other)
    {
        if(other.CompareTag("Player"))
        {
            TextPopup.instance.inRangeforPopup = true;
        }
    }

    public void OnTriggerExit(Collider other)
    {
        if(other.CompareTag("Player"))
        {
            TextPopup.instance.inRangeforPopup = false;
        }
    }
}