Additional TMP UGUI is not appearing on device

I have followed a tutorial for flappy bird and I got it working just fine.
Then I proceeded with implementing a high score logic, and also got it working just fine (at least when tested within Unity).

When I build the project and run the game on any android device the high score text is not appearing at all. I tried to find the solution online but had no luck.

this score text is fine

this high score text is not appearing on the device

Also this is a more generic screenshot of the project showing which text is not appearing

In general, I would avoid using scaling for text, I see the scale of the element is (2, 2, 1), I would either make it (2,2,2) or just keep it (1,1,1).

Do you have a fallback font like Arial? Or anything that you are sure is available on android.

If the “bg” GameObject is also a canvas, are you sure that it is placed/rendered behind the UI canvas?

Are you sure that the text isn’t just set to an empty string instead of 0 when the game start? For example, if there is an exception thrown before it gets to setting the text to some value, then it might appear empty because it very much is.

I can see in the scene view that the 0 character exceeds the size of its bounding box (the grey rectangle) have you tried making the font size a bit smaller and see if it changes anything?

You can also try disabling every other element on the screen except for the text just to make sure it is not rendered behind something else for some reason.

You can also try to set the text element to be position relative to top right bottom and left, then set everything to 0px so that the bounding box covers the entire screen, and then change the text alignment to upper left and upper right, because I see that the position of the text elements are “hard coded” as in “256px from the right”, if the size of the screen happens to not be the one you expected then the elements might end up outside the canvas.

UGUI is not a 2D feature so I removed the 2D tag and added the correct UGUI tag for you.

Currently, I noticed that I am loading the saved highScore in the start function. Which is working fine this way when I am testing it in the Editor but not on the actual device.

If I comment out the loading of the highScore, then the highScore object appears fine on the device but its always 0 since I am not loading the saved highScore.

So something is wrong here I believe. I will share the script with you if you could have a look please.
Probably my logic in the actions I am doing is overcomplicated but please not that I am just learning.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using UnityEngine.SceneManagement;
using System;


public class LogicScript : MonoBehaviour
{
    public int playerScore;
    public TextMeshProUGUI scoreText;
    public GameObject gameOverScreen;
    public BirdScript bird;
    public TextMeshProUGUI highScore;

    public int scoreToInt;
    public int highScoreToInt;

    public int scoreTextStringToInt;
    public int highScoreStringToInt;



    void Start()
    {
        bird = GameObject.FindGameObjectWithTag("Bird").GetComponent<BirdScript>(); //drag and drop the LogicScript to logic game object but with code
        LoadHighScore();      
    }


    public void addScore(int scoreToAdd)
    {
        playerScore = playerScore + scoreToAdd;
        scoreText.text = playerScore.ToString();
    }

    public void restartGame()
    {
        SceneManager.LoadScene(SceneManager.GetActiveScene().name);
    }

    public void gameOver()
    {
        gameOverScreen.SetActive(true);
        bird.killBird();
        ConvertValuesToInt();
    }

    public void SaveHighScore()
    {
       PlayerPrefs.SetString("HighScore", scoreText.text);
       

    }

    public void LoadHighScore()
    {
        highScore.text = PlayerPrefs.GetString("HighScore");
    }

    public void CompareScores()
    {
        if (scoreTextStringToInt > highScoreStringToInt)
        {
            scoreText.SetText(scoreTextStringToInt.ToString());
            highScore.SetText(highScoreStringToInt.ToString());
            SaveHighScore();
        }
    }

    public void ConvertValuesToInt()
    {
        string scoreTextString = scoreText.text;
        string highScoreString = highScore.text;

        scoreTextStringToInt = int.Parse(scoreTextString);
        highScoreStringToInt = int.Parse(highScoreString);

        CompareScores();
    }

}

Thank you for the correction

I don’t see anything “wrong” in your code in the sense that I don’t see any “error”.

However, there are a couple of things that are important to know from the start in Unity, in my opinion.

In general it’s best to avoid trying to retrieve gameobjects and/or components at runtime (while the game is running) because it has a heavy cost in performance. For example, this line here: bird = GameObject.FindGameObjectWithTag("Bird").GetComponent<BirdScript>();.

What you should do instead, if your bird GameObject is already in the scene from the start, is to add to your LogicScript a variable public BirdScript Bird;, this variable is public so it is serialized by Unity, meaning that if a GameObject in your scene has a LogicScript attached to it, Unity will “serialize” meaning “save” its serializable variables and remember them.

So if you select your LogicScript GameObject in the hierarchy, you should see your Bird variable in the inspector window. You can then drag and drop your “Bird” GameObject from the hierarchy onto the variable in the inspector, and then save your scene.

Now you don’t even need the line bird = GameObject.FindGameObjectWithTag("Bird").GetComponent<BirdScript>(); because Bird will already have a reference to the BirdScript you are looking for.

You can also do this for your high/low score TMP_Texts if they are already in the scene.

You can also do this within prefabs.

What you can also do to really know what is going wrong is to look into the logs of your program, although I don’t know how to access them on android, there is probably a way to get them out, because if you have for example a string that is null (which is not the same as an empty string), you might get errors when you are doing int.Parse(scoreTextString) for example. For that reason it’s often better to use TryParse instead of Parse unless you are absolutely sure that the text you’re trying to parse is, for sure, a valid number.

If you look at the logs, I would expect you to see something like “NullReferenceExeption” followed by the line and type in which it happened, so you can find the line of code where things went wrong. Here for example, if GameObject.FindGameObjectWithTag("Bird") does not find any GameObject, it would return null, and then the following .GetComponent<BirdScript>() would throw an exception because it would try to access … something that doesn’t exist, and the code that comes after an exception is skipped, so LoadHighScore(); would never be executed in my example.

A side note but I would have liked to know that when I started unity, remember that GetComponent is heavy because it has to access the C++ side of unity and that has a huge cost, so try to store your components in variables instead of getting components over and over each frame.

One last thing, when it comes to UI, it should only be used to show data, it should not hold any data. So it would be better to store your scores as int variables, and work with that, instead of trying to get your score data out of the string displayed to the user. If you want to go much deeper into how to write code I suggest checking out the S.O.L.I.D. principles and just try to get a very basic understanding of what they stand for and why, it’s far beyond your concern here, but I also would have liked to hear about it much sooner when I started Unity.

Also, in a few months when you wonder how to really organize your code well, take a look at VContainer, I can’t imagine starting a project without something like it nowadays.

I just saw that you do have all those variables already there and already public.

So it’s possible that your problem simply is that when you load your “HighScore” on the android device, you’re actually loading an empty string because no “HighScore” has ever been saved. So you should add this at the beginning of your Start method:

if (!PlayerPrefs.HasKey("HighScore") || string.IsNullOrEmpty(PlayerPrefs.GetString("HighScore"))
    PlayerPrefs.SetString("HighScore", "0");

Note that you can also use SetInt and GetInt instead of SetString Unity - Scripting API: PlayerPrefs that would help you remove the parsing from string to int, and you would be certain to be dealing with a number (that cannot be null because it is a value type, so you would get 0 if the pref didn’t exist).

This resolved my issue.
Thank you for your time and the tips!