using UnityEngine; public class PingClickHandler : MonoBehaviour { private const float ScreenHitRadius = 160f; public GameObject chanraoObject, nextPing; public int pingIndex; private MeshRenderer _modelRenderer, _outlineMesh; private bool _modelShown; private bool _isBlinking; // 全局静态变量,防止同一帧内有多个 PingClickHandler 被同时触发 private static int _lastClickedFrame = -1; // 实例变量,记录自己被启用(或重新闪烁)的那一帧 private int _enableFrame = -1; // 添加一个全局静态标志位,记录“语音是否已经被播放过了”,防止重复播放 private static bool _hasPlayedFinishVoice = false; public bool CanBeClicked() { return _isBlinking; } void OnEnable() { _enableFrame = Time.frameCount; // 每次重新启用时(如果是第一轮的第一步),重置语音播放标志位 // 通过判断是否是第一轮的第一个 ping,或者直接在第一轮就重置 if (name == "ping.004" && !_modelShown) { _hasPlayedFinishVoice = false; } _modelRenderer = GetComponent(); if (_modelRenderer == null) _modelRenderer = GetComponentInChildren(); foreach (var r in GetComponentsInChildren(true)) r.enabled = false; var colliders = GetComponents(); foreach (var c in colliders) { if (c is BoxCollider || c is CapsuleCollider) { Destroy(c); } } var sphere = GetComponent(); if (sphere == null) sphere = gameObject.AddComponent(); float maxScale = Mathf.Max(Mathf.Abs(transform.lossyScale.x), Mathf.Abs(transform.lossyScale.y), Mathf.Abs(transform.lossyScale.z)); if (maxScale < 0.001f) maxScale = 1f; sphere.radius = 0.15f / maxScale; sphere.center = Vector3.zero; sphere.isTrigger = false; sphere.enabled = true; CreateOutlineMesh(); _modelShown = false; _isBlinking = true; StopAllCoroutines(); StartCoroutine(BlinkOutline()); } void CreateOutlineMesh() { if (_outlineMesh != null) return; var mf = GetComponent(); if (mf == null) mf = GetComponentInChildren(); if (mf == null || mf.sharedMesh == null) return; var shader = Shader.Find("Custom/OutlineUnlit"); if (shader == null) return; var mat = new Material(shader); mat.SetColor("_OutlineColor", new Color(0.2f, 0.5f, 1f, 1f)); mat.SetColor("_MainColor", new Color(0, 0, 0, 0)); mat.SetFloat("_OutlineWidth", OutlineConfig.Width); var go = new GameObject("Outline"); go.transform.SetParent(transform, false); go.transform.position += Vector3.up * 0.05f; go.AddComponent().sharedMesh = mf.sharedMesh; _outlineMesh = go.AddComponent(); _outlineMesh.material = mat; } System.Collections.IEnumerator BlinkOutline() { if (_outlineMesh != null) _outlineMesh.enabled = true; yield return new WaitForSeconds(0.4f); while (true) { if (_outlineMesh != null) _outlineMesh.enabled = !_outlineMesh.enabled; yield return new WaitForSeconds(0.4f); } } void Update() { if (!_isBlinking) return; if (!Input.GetMouseButtonDown(0)) return; if (Time.frameCount == _enableFrame) return; if (Time.frameCount == _lastClickedFrame) return; if (UnityEngine.EventSystems.EventSystem.current != null && UnityEngine.EventSystems.EventSystem.current.IsPointerOverGameObject()) return; Camera activeCamera = null; foreach (var c in Camera.allCameras) { if (c.enabled && c.gameObject.activeInHierarchy) { activeCamera = c; } } if (activeCamera == null) return; Vector3 screenPos = activeCamera.WorldToScreenPoint(transform.position); if (screenPos.z < 0) return; Vector2 mousePos = Input.mousePosition; float myDist = Vector2.Distance(mousePos, new Vector2(screenPos.x, screenPos.y)); var allHandlers = FindObjectsOfType(); foreach (var h in allHandlers) { if (h == this) continue; if (!h._isBlinking) continue; if (!h.gameObject.activeInHierarchy) continue; Vector3 otherScreen = activeCamera.WorldToScreenPoint(h.transform.position); if (otherScreen.z < 0) continue; float otherDist = Vector2.Distance(mousePos, new Vector2(otherScreen.x, otherScreen.y)); if (otherDist < myDist) return; } if (myDist < ScreenHitRadius) { Debug.Log($"[PingClick] 屏幕坐标命中: {name} (距离={myDist:F1}px)"); _lastClickedFrame = Time.frameCount; _isBlinking = false; PerformClick(); } } public void PerformClick() { InteractionClickGuard.MarkHandled(); if (!_modelShown) { _modelShown = true; _isBlinking = false; ActionHistory.Push(ActionHistory.ActionType.PingFirst, this); StopAllCoroutines(); if (_modelRenderer != null) _modelRenderer.enabled = true; foreach (var r in GetComponentsInChildren(true)) r.enabled = true; if (_outlineMesh != null) _outlineMesh.enabled = false; if (nextPing != null) nextPing.SetActive(true); else { bool handledBySplintPractice = false; var splintFlow = FindObjectOfType(); if (splintFlow != null) { handledBySplintPractice = splintFlow.OnPingFirstClick(this); } bool isSplintFixation = GameManager.Instance != null && GameManager.Instance.CurrentFixationMethod == FixationMethod.SplintFixation; if (!handledBySplintPractice && !isSplintFixation) { var chanraotuibu = GameObject.Find("chanraotuibu"); if (chanraotuibu == null) { var mi = GameObject.Find("manInjury"); if (mi != null) { var t = mi.transform.Find("chanraotuibu"); if (t != null) chanraotuibu = t.gameObject; } } if (chanraotuibu != null) chanraotuibu.SetActive(true); Debug.Log($"[PingClick] {name} 是最后一个 ping 模型,现已激活 chanraotuibu"); } } if (nextPing != null) Debug.Log($"[PingClick] {name} 模型显示, 激活 {nextPing.name}"); else Debug.Log($"[PingClick] {name} 模型显示, 激活下一个"); ShowLimbFirstStageTip(nextPing != null); var sm = FindObjectOfType(); if (sm != null) sm.RecordPingStep(pingIndex, false); var examMgr = FindObjectOfType(); if (examMgr != null) examMgr.OnObjectClicked(this); if (nextPing != null) { var splintFlow = FindObjectOfType(); if (splintFlow != null) splintFlow.OnPingFirstClick(this); } } else { if (chanraoObject != null) { chanraoObject.SetActive(true); Debug.Log($"[PingClick] {name} → {chanraoObject.name}"); } HideSelf(); ActionHistory.Push(ActionHistory.ActionType.PingSecond, this); var examMgr = FindObjectOfType(); if (examMgr != null) examMgr.OnObjectClicked(this); var sm = FindObjectOfType(); if (sm != null) sm.RecordPingStep(pingIndex, true); var splintFlow = FindObjectOfType(); bool handledBySplintPractice = splintFlow != null && splintFlow.OnPingSecondClick(this); if (handledBySplintPractice) return; var pings = Object.FindObjectsOfType(); int remaining = 0; foreach (var p in pings) { if (p.gameObject.activeInHierarchy && (p._isBlinking || p._modelShown)) remaining++; } bool isExam = GameManager.Instance != null && GameManager.Instance.CurrentMode == ExperimentMode.Exam; if (remaining == 0) { ShowLimbSecondStageTip(true); if (!_hasPlayedFinishVoice) { _hasPlayedFinishVoice = true; if (isExam) return; Debug.Log("[PingClick] 所有的 ping 都已经绑成 chanrao 了!播放完成语音。"); var am = FindObjectOfType(); if (am != null) { #if UNITY_EDITOR var finishClip = UnityEditor.AssetDatabase.LoadAssetAtPath("Assets/Audio/people/伤员一切正常,包扎完毕.mp3"); if (finishClip != null) am.PlayVoice(finishClip); #endif } var uiMgr = FindObjectOfType(); if (uiMgr != null) { uiMgr.StartCoroutine(ShowEndPanelDelayed(uiMgr)); } } } else { ShowLimbSecondStageTip(false); } } } private void ShowLimbFirstStageTip(bool hasNextPing) { if (GameManager.Instance == null || GameManager.Instance.CurrentFixationMethod != FixationMethod.LimbFixation) return; UIManager uiManager = FindObjectOfType(); if (uiManager == null) return; if (hasNextPing) { uiManager.ShowTip("继续由远端向近端点击下一个蓝色绑带点,完成绑带摆放。"); } else { uiManager.ShowTip("四条绑带已摆放。下一步:点击大腿环绕区域,显示环绕固定带。"); } } private void ShowLimbSecondStageTip(bool complete) { if (GameManager.Instance == null || GameManager.Instance.CurrentFixationMethod != FixationMethod.LimbFixation) return; UIManager uiManager = FindObjectOfType(); if (uiManager == null) return; uiManager.ShowTip(complete ? "肢体固定已完成,请观察固定效果并点击结束确认。" : "继续点击下一个蓝色绑带点,由远端向近端完成系带固定。"); } private System.Collections.IEnumerator ShowEndPanelDelayed(UIManager uiMgr) { yield return new WaitForSeconds(1f); if (uiMgr == null) yield break; uiMgr.HideVideo(); uiMgr.ShowEndPanel(); } public void ReEnableBorder() { if (!_modelShown) return; if (chanraoObject != null && chanraoObject.activeSelf) { return; } _isBlinking = true; _enableFrame = Time.frameCount; if (_outlineMesh != null) { _outlineMesh.enabled = true; StopAllCoroutines(); StartCoroutine(BlinkOutline()); } } public void DisableBorder() { _isBlinking = false; if (_outlineMesh != null) _outlineMesh.enabled = false; StopAllCoroutines(); } public void UndoSecondClick() { if (chanraoObject != null) chanraoObject.SetActive(false); ShowSelf(); _modelShown = true; _isBlinking = true; _enableFrame = Time.frameCount; _hasPlayedFinishVoice = false; if (_outlineMesh != null) _outlineMesh.enabled = true; StopAllCoroutines(); StartCoroutine(BlinkOutline()); } public void UndoShow() { _modelShown = false; _isBlinking = false; StopAllCoroutines(); if (chanraoObject != null) chanraoObject.SetActive(false); foreach (var r in GetComponentsInChildren(true)) r.enabled = false; gameObject.SetActive(false); } private void HideSelf() { foreach (var r in GetComponentsInChildren(true)) r.enabled = false; _isBlinking = false; StopAllCoroutines(); gameObject.SetActive(false); } private void ShowSelf() { foreach (var r in GetComponentsInChildren(true)) r.enabled = true; if (_outlineMesh != null) _outlineMesh.enabled = false; } public void ResetState() { ResetState(true); } public void ResetState(bool startBlink) { _modelShown = false; _isBlinking = startBlink; _enableFrame = Time.frameCount; _hasPlayedFinishVoice = false; if (_modelRenderer != null) _modelRenderer.enabled = false; if (chanraoObject != null) chanraoObject.SetActive(false); foreach (var r in GetComponentsInChildren(true)) { if (_outlineMesh != null && r == _outlineMesh) continue; r.enabled = false; } if (_outlineMesh != null) _outlineMesh.enabled = startBlink; StopAllCoroutines(); if (startBlink && isActiveAndEnabled) { StartCoroutine(BlinkOutline()); } } public void ForceHideOutline() { _isBlinking = false; StopAllCoroutines(); if (_outlineMesh != null) _outlineMesh.enabled = false; } }