making a dynamic lives counter in optimized way

Hello, I want to make a lives counter (max lives =3), that decrease when the character hits to obstacles and increase when hits the heartpotion.
I already write an switch case in update method and it worked, but I am concerned about the performance. It feels wrong as the update method work on every frame but change in lives counter is not so often.
How can I do it in a better and optimized way?

Note: Sorry if this is a super-simple and noob question but I am new in coding and trying to figure out things by myself. I would be grateful for any help. Thank you!
this script attached to UI:

    private void Update()
    {
        //gameObject.transform.GetChild();
        //reach to the child
        switch (TotalScores.lives)
        {
            case 3:
                gameObject.transform.GetChild(2).gameObject.SetActive(true);
                gameObject.transform.GetChild(1).gameObject.SetActive(true);
                Debug.Log("can sayısı" + TotalScores.lives);
                break;
            case 2:
                Debug.Log("can sayısı" + TotalScores.lives);
                gameObject.transform.GetChild(2).gameObject.SetActive(false);
                gameObject.transform.GetChild(1).gameObject.SetActive(true);
                break;
            case 1:
                Debug.Log("can sayısı" + TotalScores.lives);
                gameObject.transform.GetChild(2).gameObject.SetActive(false);
                gameObject.transform.GetChild(1).gameObject.SetActive(false);
                break;
            case 0:
                Debug.Log("can sayısı" + TotalScores.lives);
                //gameover
                break;
            default:
                Debug.Log("can sayısı" + TotalScores.lives);
                break;
        }

    }

and this script attached to the character:

 private void OnTriggerEnter2D(Collider2D other)
    {
        if ((other.gameObject.CompareTag("Obstacle")) && (!coroutineRunning)) //corutinede değilse ve engele çarparsa
        {
            if (TotalScores.lives < 3 || TotalScores.lives > 0)
            {
                TotalScores.lives--;
                StartCoroutine(WaitDamage());
            }

        }
        else if (other.gameObject.CompareTag("Scoring"))
        {
            //FindObjectOfType<Scoring>().IncreaseScore();
            TotalScores.totalScore++;
            _textscore.text = "Score " + TotalScores.totalScore.ToString();
        }
        else if (other.gameObject.CompareTag("Artican"))
        {
            if (TotalScores.lives < 3 || TotalScores.lives >= 0)
            {
                TotalScores.lives++;
            }
            Destroy(other.gameObject);
            //FindObjectOfType<Scoring>().Healthgain();
        }
        else if (other.gameObject.CompareTag("Zemin"))
        {
            transform.position = Vector3.zero;
            //FindObjectOfType<Scoring>().Zemintouch();
            StartCoroutine(WaitDamage());
        }
        else if (other.gameObject.CompareTag("Sise"))
        {
            Destroy(other.gameObject);
            //TotalScores.totalScore+= 100
        }

    }

Hey there,

you are absolutely asking the correct questions. The principle that you want to look into are called “DRY”-Code (Don’t Repeat Yourself) . The idea is that you want to write code where no knowledge/code is duplicated and where if you ever were to change something you only have to change it in one place.

How is that applicable for you? Well for once there are your tags (e.g. "Zemin") which you probably use somewhere else as well. If you have a constant containing this, and use that to compare to then you only have to change the string in one place if it should ever change and you are less prone to make spelling mistakes.

The other thing where you can apply this is by using Lists.

So you could have the following:

    private List<GameObject> LifeObjects = new List<GameObject>();

which you can fill up in your Start:

foreach(Transform t in transform) //iterate over all childs
{ 
       LifeObjects.Add(t.gameObject); //add them all
}

for(int i=0;i<TotalScores.lives;i++) {
        LifeObjects*.SetActive(true); //set all active so that current lives are represented*

}
for(int i=TotalScores.lives;i<LiveObjects.Count;i++) {
LiveObjects*.SetActive(false); //if there are more objects → deactivate*
}
and here we get to your other question: Is checking this in Update bad? - Yes - extremely.
Always try to avoid logic which does checks every frame. Try to write code event-based. Do things when they occur, when they have to be done.
So you can just give your Player a function substractLive:
public bool substractLife()
{
TotalScores.lives–;
LifeObjects[TotalScores.lives].SetActive(false);
return TotalScores.lives > 0; //directly return if we are dead or not.
}
//example usage:
if(!player.substractLife()) {
//gameOver
}
public void addLife()
{
TotalScores.lives++;
LifeObjects[TotalScores.lives].SetActive(true);
}
whenever you damage your player or heal it → use the functions above. Then it is clean. This is particulary the case because you are now not tied to having 3 lifes only. You can add/substract lives as you feel it fit (well if there are enough objects). Your current code requires to add at least 5 lines of code for each new life that you want to add. This is what is meant my DRY-Code. If you write something and you’d later have to change it - how simple is it to do it and do you repeat yourself somewhere?
Hope this helps, good luck and let me know if there are any questions.