Home > Enterprise >  Recycling a Button for individual object - Unity3D
Recycling a Button for individual object - Unity3D

Time:09-27

I am a beginner in Unity and I am currently making a simple game. I have a problem where the button should reappear on the object I am getting contact with. It only shows on the first object that I put the script on.

What I wanted to do is to have a recyclable button that will appear whenever I have contact with objects like the "view" button in most of the games. What keeps happening in my project is that the collision is triggered but the position of the object is in the first object where I put the script to set it active and set the position. I put it in another object because I want the button to show in that object but it keeps appearing in the first object.

This is the script I used:

using UnityEngine;
using UnityEngine.UI;

public class InteractNPC : MonoBehaviour
{
    //public Button UI;
    [SerializeField] GameObject uiUse, nameBtn, dialogBox;
    [SerializeField] TextAsset characterData;
    // [SerializeField] UnityEngine.UI.Text dialogMessage;
    private Transform head;
    private Vector3 offset = new Vector3(0, 1.0f, 0);

    // Start is called before the first frame update
    void Start()
    {
        //uiUse = Instantiate(UI, FindObjectOfType<Canvas>().transform).GetComponent<Button>();
        uiUse.gameObject.SetActive(true);
        head = this.transform.GetChild(0);
        uiUse.transform.position = Camera.main.WorldToScreenPoint(head.position   offset);
        nameBtn.transform.position = Camera.main.WorldToScreenPoint(head.position   offset);

        nameBtn.GetComponent<Button>().onClick.AddListener(onNameClick);
    }

    // Update is called once per frame
    void Update()
    {
        uiUse.transform.position = Camera.main.WorldToScreenPoint(head.position   offset);
        nameBtn.transform.position = Camera.main.WorldToScreenPoint(head.position   offset);
    }

    private void OnTriggerEnter(Collider collisionInfo)
    {
        if (collisionInfo.CompareTag("Player"))
        {
            //Character characterJson = JsonUtility.FromJson<Character>(characterData.text);

            nameBtn.gameObject.SetActive(true);
            // Text lbl = nameBtn.gameObject.GetComponentInChildren(typeof(Text), true) as Text;
            // lbl.text = "BOBO";
            // nameBtn.GetComponent<Button>().GetComponentInChildren<Text>().text = characterJson.name;
        }
    }

    private void OnTriggerExit(Collider collisionInfo)
    {
        if (collisionInfo.CompareTag("Player"))
        {
            nameBtn.gameObject.SetActive(false);
        }
    }

    // DIALOGUE SYSTEM
    public void onNameClick()
    {
        Text dialogMessage, dialogName;
        if (dialogBox.gameObject.activeInHierarchy)
        {  
            dialogName = GameObject.Find("Canvas/DialogBox/DialogueName").GetComponent<Text>();
            dialogMessage = GameObject.Find("Canvas/DialogBox/Dialogue").GetComponent<Text>();
            if (dialogMessage != null && dialogName != null)
            {
                loadCharacterData(dialogMessage, dialogName);
                Debug.Log("not null dialog message");
            }
            else
            {
                Debug.Log("null dialog message");
            }
        }
    }

    public void loadCharacterData(Text dialogMessage, Text dialogName)
    {
        Character characterJson = JsonUtility.FromJson<Character>(characterData.text);
        dialogName.text = characterJson.name;

        dialogMessage.text = characterJson.dialogs[0].choices[1];
    }
}

CodePudding user response:

As far as I see you never create a new Button, I guess that Unity may think, you want the same Button on all NPCs.

Maybe try to call the constructor for a new Button in the Start Method and see if it then generates a new Button per NPC.

CodePudding user response:

Well your issue is that each and every instnce of your scrip will all the time overwrite

void Update()
{
    uiUse.transform.position = Camera.main.WorldToScreenPoint(head.position   offset);
    nameBtn.transform.position = Camera.main.WorldToScreenPoint(head.position   offset);
}

every frame.

What you want is set it only once in

[SerializeField] private Button nameBtn;

pivate Character character;

private void Start()
{
    ...

    // do this only once!
    character = JsonUtility.FromJson<Character>(characterData.text);
}

private void OnTriggerEnter(Collider collisionInfo)
{
    if (collisionInfo.CompareTag("Player"))
    {
        nameBtn.gameObject.SetActive(true);
        var lbl = nameBtn.gameObject.GetComponentInChildren<Text>(true);
        lbl.text = characterJson.name;

        var position = Camera.main.WorldToScreenPoint(head.position   offset);

        uiUse.transform.position = position;
        nameBtn.transform.position = position;
    }
}

Further you will have another issue: Each and every instance of your script attaches a callback

nameBtn.GetComponent<Button>().onClick.AddListener(onNameClick);

that is not good! Now whenever you click the button once, the allback is fired for each and every instance of your script.

You would rather do this only if the click was actually invoked for the current object.

For this you could attach the listener only add-hoc when needed like e.g.

private void OnTriggerEnter(Collider collisionInfo)
{
    if (collisionInfo.CompareTag("Player"))
    {
        ...

        nameBtn.onClick.RemoveListener(onNameClick);
        nameBtn.onClick.AddListener(onNameClick);
    }
}

private void OnTriggerExit(Collider other)
{
    if (collisionInfo.CompareTag("Player"))
    {
        nameBtn.onClick.RemoveListener(onNameClick);

        nameBtn.gameObject.SetActive(false);
    }
}
  • Related