Unity中点击其他可交互对象时重置语音与描述显示状态的实现问题
Unity中点击其他可交互对象时重置语音与描述显示状态的实现问题
嘿,我明白你遇到的麻烦了——连续点击不同的可交互对象时,之前的描述文本和语音播放没被完全重置,要么新描述显示不出来,要么时长沿用之前剩余的时间,对吧?我帮你分析下问题根源,再给你两个可行的解决办法。
问题出在哪?
你现在的代码里,每个ClickableObject实例都自己维护着淡出协程和Invoke计时。当你点击第二个对象时,它只能停止自己的协程和Invoke,但第一个对象的协程可能还在后台运行,继续修改描述面板的透明度;而且第一个对象的Invoke计时到点后,还是会触发淡出,把新显示的文本给隐藏掉。如果多个对象共享同一个描述面板,这种冲突就会更明显。
解决方案一:快速修改现有代码(最小改动)
我们可以用静态变量来全局跟踪当前的淡出协程和活跃对象,确保点击新对象时能完全终止前一个对象的所有相关操作。修改后的代码如下:
using System; using System.Collections.Generic; using NUnit.Framework; using TMPro; using UnityEditor; using UnityEngine; using UnityEngine.UI; public class ClickableObject : MonoBehaviour { public string objectName; public string description; public bool isCollectible; public GameObject inventorySlot; public TextMeshProUGUI descriptionText; public GameObject descriptionPanel; public CanvasGroup panelCanvasGroup; public float displayDuration = 3f; public float fadeDuration = 0.5f; public Texture2D eyeCursor; // Assign in Inspector public Texture2D handCursor; // Assign in Inspector // 静态变量:全局跟踪正在运行的淡出协程和当前活跃对象 private static Coroutine globalFadeOutCoroutine; private static ClickableObject currentActiveObject; void Start() { ResetToDefault(); } void OnMouseDown() { if (isCollectible) { CollectItem(); } else { ShowDescription(); } } void OnMouseEnter() { if (isCollectible) { Cursor.SetCursor(handCursor, Vector2.zero, CursorMode.Auto); } else { Cursor.SetCursor(eyeCursor, Vector2.zero, CursorMode.Auto); } } void OnMouseExit() { ResetToDefault(); } void CollectItem() { Debug.Log("Collected: " + objectName); inventorySlot.GetComponent<Image>().sprite = GetComponent<SpriteRenderer>().sprite; gameObject.SetActive(false); ResetToDefault(); } void ShowDescription() { // 1. 停止全局的淡出协程,不管是哪个对象启动的 if (globalFadeOutCoroutine != null) { var coroutineOwner = globalFadeOutCoroutine.GetOwner() as MonoBehaviour; if (coroutineOwner != null) { coroutineOwner.StopCoroutine(globalFadeOutCoroutine); } globalFadeOutCoroutine = null; } // 2. 取消前一个活跃对象的淡出计时 if (currentActiveObject != null && currentActiveObject != this) { currentActiveObject.CancelInvoke("StartFadeOut"); } // 3. 强制重置面板状态 panelCanvasGroup.alpha = 1f; descriptionPanel.SetActive(true); // 4. 更新文本并重启语音 descriptionText.text = description; WindowsVoice.stop(); WindowsVoice.speak(description); // 5. 记录当前活跃对象,设置新的淡出计时 currentActiveObject = this; CancelInvoke("StartFadeOut"); Invoke("StartFadeOut", displayDuration); } void StartFadeOut() { globalFadeOutCoroutine = StartCoroutine(FadeOut()); } System.Collections.IEnumerator FadeOut() { float startAlpha = panelCanvasGroup.alpha; float rate = 1.0f / fadeDuration; float progress = 0.0f; while (progress < 1.0f) { panelCanvasGroup.alpha = Mathf.Lerp(startAlpha, 0, progress); progress += rate * Time.deltaTime; yield return null; } panelCanvasGroup.alpha = 0; descriptionPanel.SetActive(false); globalFadeOutCoroutine = null; currentActiveObject = null; } private void ResetToDefault() { Cursor.SetCursor(null, Vector2.zero, CursorMode.Auto); } }
这个修改的核心是用静态变量把所有对象的状态统一起来,点击新对象时彻底终止前一个对象的所有相关操作,确保新的描述能正常显示并从头计时。
解决方案二:重构为单例管理器(更健壮的长期方案)
如果你的项目后续会有更多UI交互需求,我更推荐把描述面板和语音的管理逻辑抽成一个单例UIManager,所有交互对象都通过这个管理器来操作,从根源上避免状态冲突。
首先创建UIManager:
using UnityEngine; using TMPro; using UnityEngine.UI; public class UIManager : MonoBehaviour { public static UIManager Instance { get; private set; } public TextMeshProUGUI descriptionText; public GameObject descriptionPanel; public CanvasGroup panelCanvasGroup; public float displayDuration = 3f; public float fadeDuration = 0.5f; private Coroutine fadeOutCoroutine; private void Awake() { // 单例模式确保全局唯一 if (Instance != null && Instance != this) { Destroy(gameObject); } else { Instance = this; } } public void ShowDescription(string desc) { // 停止所有正在进行的操作 if (fadeOutCoroutine != null) { StopCoroutine(fadeOutCoroutine); fadeOutCoroutine = null; } CancelInvoke(nameof(StartFadeOut)); // 重置面板状态 panelCanvasGroup.alpha = 1f; descriptionPanel.SetActive(true); // 更新文本和语音 descriptionText.text = desc; WindowsVoice.stop(); WindowsVoice.speak(desc); // 设置新的淡出计时 Invoke(nameof(StartFadeOut), displayDuration); } private void StartFadeOut() { fadeOutCoroutine = StartCoroutine(FadeOutCoroutine()); } private IEnumerator FadeOutCoroutine() { float startAlpha = panelCanvasGroup.alpha; float rate = 1f / fadeDuration; float progress = 0f; while (progress < 1f) { panelCanvasGroup.alpha = Mathf.Lerp(startAlpha, 0, progress); progress += rate * Time.deltaTime; yield return null; } panelCanvasGroup.alpha = 0; descriptionPanel.SetActive(false); } }
然后修改ClickableObject,去掉UI相关的引用,改为调用管理器:
using UnityEngine; using UnityEngine.UI; public class ClickableObject : MonoBehaviour { public string objectName; public string description; public bool isCollectible; public GameObject inventorySlot; public Texture2D eyeCursor; // Assign in Inspector public Texture2D handCursor; // Assign in Inspector void Start() { ResetToDefault(); } void OnMouseDown() { if (isCollectible) { CollectItem(); } else { UIManager.Instance.ShowDescription(description); } } void OnMouseEnter() { if (isCollectible) { Cursor.SetCursor(handCursor, Vector2.zero, CursorMode.Auto); } else { Cursor.SetCursor(eyeCursor, Vector2.zero, CursorMode.Auto); } } void OnMouseExit() { ResetToDefault(); } void CollectItem() { Debug.Log("Collected: " + objectName); inventorySlot.GetComponent<Image>().sprite = GetComponent<SpriteRenderer>().sprite; gameObject.SetActive(false); ResetToDefault(); } private void ResetToDefault() { Cursor.SetCursor(null, Vector2.zero, CursorMode.Auto); } }
这种方式把UI逻辑和交互对象解耦了,后续不管加多少可交互对象,都不用担心状态冲突,代码也更易于维护。
备注:内容来源于stack exchange,提问作者Sharper
相关产品推荐
相关产品推荐

