Unity3D中为单个对象复用按钮的问题求助
问题:可复用交互按钮始终固定在第一个挂载对象位置
我是Unity新手,正在制作一款简单游戏,想要实现一个可复用的"查看"按钮——玩家接触对象时,按钮显示在对应对象的上方。但实际测试时发现,碰撞触发后按钮只会显示在第一个挂载该脚本的对象位置,哪怕把脚本挂载到其他对象上,按钮仍会固定在第一个对象处。
原脚本代码:
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]; } }
问题根源
你当前的uiUse和nameBtn是直接拖拽了场景中同一个UI实例到所有脚本的字段中,所有NPC脚本都在修改同一个UI对象的位置。由于Update方法的执行顺序,最后生效的是第一个挂载脚本的对象位置,导致按钮看起来固定在那里。
解决方案
方案1:为每个NPC实例化独立的专属按钮(推荐)
将按钮做成预制体,每个NPC启动时创建属于自己的按钮实例,保证每个按钮只对应一个NPC:
using UnityEngine; using UnityEngine.UI; public class InteractNPC : MonoBehaviour { // 改为拖拽按钮预制体,而非场景中的现有实例 [SerializeField] GameObject uiUsePrefab, nameBtnPrefab, dialogBox; [SerializeField] TextAsset characterData; private Transform head; private Vector3 offset = new Vector3(0, 1.0f, 0); // 存储当前NPC的专属按钮实例 private GameObject _uiUse, _nameBtn; private Camera _mainCam; void Start() { // 缓存主相机,避免Update中频繁查找 _mainCam = Camera.main; head = transform.GetChild(0); // 获取场景中的Canvas Canvas canvas = FindObjectOfType<Canvas>(); // 实例化专属按钮,父对象设为Canvas _uiUse = Instantiate(uiUsePrefab, canvas.transform); _nameBtn = Instantiate(nameBtnPrefab, canvas.transform); // 默认隐藏按钮 _uiUse.SetActive(false); _nameBtn.SetActive(false); // 绑定点击事件 _nameBtn.GetComponent<Button>().onClick.AddListener(onNameClick); } void Update() { // 仅当按钮激活时更新位置 if (_uiUse.activeSelf) _uiUse.transform.position = _mainCam.WorldToScreenPoint(head.position + offset); if (_nameBtn.activeSelf) _nameBtn.transform.position = _mainCam.WorldToScreenPoint(head.position + offset); } private void OnTriggerEnter(Collider collisionInfo) { if (collisionInfo.CompareTag("Player")) { _nameBtn.SetActive(true); _uiUse.SetActive(true); // 可选:设置NPC名字到按钮文本 // _nameBtn.GetComponentInChildren<Text>().text = "你的NPC名字"; } } private void OnTriggerExit(Collider collisionInfo) { if (collisionInfo.CompareTag("Player")) { _nameBtn.SetActive(false); _uiUse.SetActive(false); } } // 对话系统代码保持不变 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]; } }
方案2:按钮对象池(适合大量NPC场景)
如果你的游戏中有大量NPC,频繁实例化/销毁UI会有性能开销,可以提前创建一批按钮存入对象池,玩家触发NPC时取出闲置按钮绑定到当前NPC,离开时将按钮归还池并隐藏。
额外注意事项
- 确保每个NPC的Collider勾选了
IsTrigger,玩家对象带有Rigidbody组件(否则触发事件无法触发) - 场景中只能有一个Camera标记为
MainCamera,否则Camera.main会返回null - 避免在Update中频繁调用
GameObject.Find,可以提前缓存对话UI的引用
内容的提问来源于stack exchange,提问作者Richard Johnson
相关产品推荐
相关产品推荐

