0
\$\begingroup\$

I'm building a 4x game. I've rendered the galaxy, and I want the text below each star to disappear when I'm zoomed out, so it looks nicer.

My stars are Prefabs with a star sprite and a 'UI' gameobject with TextMeshPro underneath the 'UI' gameobject.

I've put this as the script on the TextMeshPro object, but it says there is no renderer for TextMeshPro:

    Vector3 viewPos = Camera.main.WorldToViewportPoint(transform.position);
    if (viewPos.z > 100F)
    {
        this.gameObject.GetComponent<Renderer>().enabled = false;
    }
\$\endgroup\$

4 Answers 4

1
\$\begingroup\$

One way of achieving this is to use CanvasGroup on the TMP gameObject or its parent and play with its alpha value. Once your given distance between camera and text object is surpassed, set the alpha value to 0, and the text becomes invisible.

To further improve that, you can use your own script which fades in and out the alpha value smoothly. Meaning you just tell that script to FadeIn or FadeOut the CanvasGroup holding the TMP text.

Optionally you can disable the gameObject (not the script) if it is in FadeOut mode and reached 0 alpha.

In order to improve performance, you may want to use something that only checks the distance in higher intervals and/or preferably only when the camera has been moved (or the text gameObject). It's not necessary to run an Update method ~60 times a second for each single text gameObject. It can be reduced to ~2 times per second with 1 single method call, using a list of references to which the text objects automatically subscribe on creation and unsubscribe upon destruction.

\$\endgroup\$
0
1
\$\begingroup\$

If you are going to have lots and lots of these, I recommend you use a single manager class which controls all of them at once, and to help with this, use the CullingGroup API.

Something like this (untested):

This script would go on each text object:

class RegisterInCullingGroup : MonoBehaviour {
    void Start() {
        CullingGroupManager.Instance.AddObject(this);
    }

    void OnDestroy() {
        CullingGroupManager.Instance.RemoveObject(this);
    }
}

And another GameObject in the scene will have this component:

class CullingGroupManager : MonoBehaviour {
    public static CullingGroupManager Instance;

    public float radius = 1;

    private CullingGroup group;
    private BoundingSphere[] spheres;
    private RegisterInCullingGroup[] objects;
    private int count;

    void Awake() {
        Instance = this;
        group = new CullingGroup();
        group.targetCamera = Camera.main;

        objects = FindObjectsOfType<RegisterInCullingGroup>();
        spheres = new BoundingSphere[objects.length];
        count = objects.length;
        for (int i = 0; i < count; i++) {
            spheres[i] = new BoundingSphere(objects[i].transform.position, radius);
        }
        group.SetBoundingSpheres(spheres);
        group.SetBoundingSphereCount(count);
        group.SetBoundingDistances(new float[]{100});
        group.onStateChanged = OnStateChanged;
    }

    //to change the behavior when they disappear or appear, change this method
    private void OnStateChanged(CullingGroupEvent sphere) {
        if (sphere.hasBecomeVisible)
            objects[index].gameObject.SetActive(true);
        else if (sphere.hasBecomeInvisible)
            objects[index].gameObject.SetActive(false);
    }

    void OnDestroy() {
        group.Dispose();
        group = null;
    }

    public void RemoveObject(RegisterInCullingGroup obj) {
        int index = Array.IndexOf(objects, obj);
        if (index == -1)
            return;
        group.EraseSwapBack(index);
        CullingGroup.EraseSwapBack(index, objects, ref count);
    }

    public void AddObject(RegisterInCullingGroup obj) {
        int index = Array.IndexIf(objects, obj);
        if (index != -1)
            return;
        if (count == objects.length) {
            Array.Resize(ref objects, count * 3 / 2 + 1);
            Array.Resize(ref spheres, objects.length);
        }
        objects[count] = obj;
        spheres[count] = BoundingSphere(obj.transform.position, radius);
        ++count;
        group.SetBoundingSphereCount(count);
    }
}

Yes, it does look significantly more complicated, but it is also going to be significantly more efficient. You can also use this sort of system for supporting multiple levels of detail for your stars etc by passing in more than one value when calling SetBoundingDistances, and checking which band the object is in inside of OnStateChanged (with the sphere.currentDistance property).

\$\endgroup\$
3
  • \$\begingroup\$ Wow! Thank you for writing all that for me! I understand what most of it saying, and I corrected a few things, (IndexOf instead of IndexIf, fixed the OnStateChanges method name, etc). The code is throwing one object reference error though. The first script throws the error on "CullingGroupManager.Instance.AddObject(this);" \$\endgroup\$ Mar 24, 2019 at 2:04
  • \$\begingroup\$ And i'm just not quite sure how to fix that. I'm not sure what it's looking for. \$\endgroup\$ Mar 24, 2019 at 2:04
  • \$\begingroup\$ Have you made sure the CullingGroupManager component has been added to a new GameObject in the scene, and made sure that it’s not destroyed? \$\endgroup\$
    – Ed Marty
    Mar 24, 2019 at 12:20
0
\$\begingroup\$

You could get the gameObject of the text mesh pro and use the SetActive(bool b) method to disable it. Alternatively, you could get the text of the TextMeshPro and set it to String.empty.

A little side note: You should not use Camera.main often, because it is performance heavy. Get a refernce to the Camera.main object (e.g. in the Awake-method) and use the reference to that instead.

\$\endgroup\$
2
  • \$\begingroup\$ So, I did try that. but when I zoom back in it won't reenable the gameObject (presumably because the script is now off since the GO is off?) (also, thank you for the point about the Camera.main, I didn't know that!) \$\endgroup\$ Mar 24, 2019 at 2:10
  • \$\begingroup\$ Well, when you zoom back in, you have to do the opposite. So, just use the SetActive again and use true as a parameter. Alternatively, change the string of the TextMeshPro from String.empty back to its original value. If you want smooth transitions, I would also go the CanvasGroup way and animate over the alpha value with iTween animation. Quick and simple. \$\endgroup\$
    – iQew
    Mar 25, 2019 at 9:14
0
\$\begingroup\$

Alright, Ed Marty's answer below is a MUCH better solution to my issue. However, I did figure out the specific issue I was asking about.

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

public class DistanceFromCamera : MonoBehaviour
{

Camera cam;

void Awake()
{
    cam = Camera.main;
}

// Update is called once per frame
void Update()
{
    Vector3 viewPos = cam.WorldToViewportPoint(transform.position); // Grab the distance from the camera
    if (viewPos.z > 200F) // How far away is it?
    {
        float fadeOut = (((viewPos.z - 300F)*-1) / 100F); // Get percentage away it is between 200 and 300 units
        if (fadeOut < 0) // if it's beyond 300 then make it disappear altogether.
        {
            this.gameObject.GetComponent<CanvasRenderer>().SetAlpha(0.0F);
        } else
        {
            this.gameObject.GetComponent<CanvasRenderer>().SetAlpha(fadeOut); // if it's between 200 and 300, fade it out
        }
    } else
    {
        this.gameObject.GetComponent<CanvasRenderer>().SetAlpha(1.0F); // if it's close enough, just set to full alpha
    }
}
}

Apparently, I was looking for the CanvasRenderer component for TMP objects, not just Renderer. But, Seriously, it's not a good solution when considering performance.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .