Home > Software design >  How to click on instantiated objects and get points, then link points to score text?
How to click on instantiated objects and get points, then link points to score text?

Time:04-01

What I want: I want the player to be able to click on instantiated objects and get points, then have those points show in the score-keeping text.

What I’ve done: I’m currently using the following “FindGameObjectsWithTag” code to retrieve the buttons that are components of the instantiated prefab objects:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
 
public class CPointScore : MonoBehaviour
{
    public TextMeshProUGUI CPointsText;
    private float ScoreNum;
    private GameObject[] CButtonGmeObjsHolder;
 
 
    private void CTagFinder()
    {
        CButtonGmeObjsHolder = GameObject.FindGameObjectsWithTag("Ctag");
     
        foreach (GameObject CButtonGmeObj in CButtonGmeObjsHolder)
        {
            Debug.Log("GmeObj Found");
       
            Button CButton = CButtonGmeObj.GetComponent<Button>();
            CButton.onClick.AddListener(AddScore);
        }
    }
 
    public void AddScore()
    {
        ScoreNum  = 1;
        Debug.Log("Point Added # "   ScoreNum);
    }
 
    void Start()
    {
        InvokeRepeating("CTagFinder", 1f, 15.1f);
    }
 
    void Update()
    {
        CPointsText.text = ScoreNum.ToString();
    }
}

Because FindGameObjectsWithTag only calls once I have the InvokeRepeating code in start. I have game objects spawning throughout the duration of the game so it needs to be constantly checking for tags.

Issue: So the code finds the tags, the buttons are able to be clicked, and the score-keeping text updates which is great. The problem is that if I click one tagged button it will register a point for itself and every tagged button currently in the scene that spawned after it. For example, lets say I have 4 spawned objects currently on scene, when the first object spawned is clicked it will add 4 points instead of 1. If the second object spawned is clicked it will add 3 points instead of 1. I would like to have only the tagged button that is clicked register a point.

Question: What can I change in my code so that only the tagged button that is clicked registers a point?

Thank you

CodePudding user response:

I think there are two things here:

  • You repeatedly add the listener so you will end up with multiple callbacks when the button is finally clicked.
  • The repeated FindGameObjectsWithTag is also quite inefficient

Your main issue is the repeated calling.

For each repeated call of CTagFinder you go through all existing buttons and do

CButton.onClick.AddListener(AddScore);

so these existing buttons end up with multiple listeners attached!

You either want to make sure it is only called once per button, e.g. keeping track of those you already did this for:

private readonly HashSet<Button> alreadyRegisteredButtons = new HashSet<Button>();

and then

if(!alreadyRegisteredbuttons.Contains(CButton))
{
     CButton.onClick.AddListener(AddScore);
     alreadyRegisteredButtons.Add(CButton);
}

or alternatively make sure you remove the callback before you add it like

CButton.onClick.RemoveListener(AddScore);
CButton.onClick.AddListener(AddScore);

In general I would not use FindGameObjectWithTag an poll objects repeatedly. Rather make your code event driven. This would already avoid the issue at all since there would be no repeated attaching of the listener anyway.

I would simply have a dedicated component YourComponent attached to the same GameObject as the buttons and have a global

public static event Action<YourComponent> OnCTSButtonSpawned;

and in this dedicated component do

private void Start()
{
    OnCTSButtonSpawned?.Invoke(this);
}

and in your CPointScore listen to this event like

private void Awake()
{
    YourComponent.OnCTSButtonSpawned  = AttachListener;
}

private void AttachListener(YourComponent component)
{
    if(compoenent.TryGetComponent<Button>(out var button))
    {
        button.onClick.AddListener(AddScore);
    }
}

private void AddScore()
{
    ScoreNum  ;
    CPointsText.text = ScoreNum.ToString();
}
  • Related