using UnityEngine; public class PingClickHandler : MonoBehaviour { public GameObject chanraoObject, nextPing; public int pingIndex; private MeshRenderer _modelRenderer, _outlineMesh; private bool _modelShown; private bool _isBlinking; public bool CanBeClicked() { return _isBlinking; } void OnEnable() { _modelRenderer = GetComponent(); if (_modelRenderer == null) _modelRenderer = GetComponentInChildren(); foreach (var r in GetComponentsInChildren(true)) r.enabled = false; 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); } } /// /// 全新的点击检测方式:由一个静态方法统一管理,不再依赖碰撞体。 /// 在 Update 里,遍历所有活跃的 PingClickHandler,找到离鼠标最近的那个,只触发它。 /// void Update() { // 只让 pingIndex 最小的那个实例来做全局检测,避免多个实例重复执行 if (!_isBlinking) return; // 只有一个实例负责全局判断 var allHandlers = FindObjectsOfType(); bool iAmFirst = true; foreach (var h in allHandlers) { if (!h._isBlinking) continue; if (h.GetInstanceID() < this.GetInstanceID()) { iAmFirst = false; break; } } if (!iAmFirst) return; if (!Input.GetMouseButtonDown(0)) return; // 不在 UI 上才检测 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; // 遍历所有正在闪烁的 ping,找到距离鼠标最近的那个 float bestDist = float.MaxValue; PingClickHandler bestPing = null; Vector2 mousePos = Input.mousePosition; foreach (var h in allHandlers) { if (!h._isBlinking) continue; if (!h.gameObject.activeInHierarchy) continue; Vector3 screenPos = activeCamera.WorldToScreenPoint(h.transform.position); // 如果在相机背后,跳过 if (screenPos.z < 0) continue; float dist = Vector2.Distance(mousePos, new Vector2(screenPos.x, screenPos.y)); if (dist < bestDist) { bestDist = dist; bestPing = h; } } // 点击半径阈值:80像素以内算点中(可以根据需要调整) if (bestPing != null && bestDist < 80f) { Debug.Log($"[PingClick] 屏幕坐标命中: {bestPing.name} (距离={bestDist:F1}px)"); bestPing._isBlinking = false; bestPing.PerformClick(); } } public void PerformClick() { 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 { 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} 模型显示, 激活下一个"); var sm = FindObjectOfType(); if (sm != null) sm.RecordPingStep(pingIndex, false); } else { if (chanraoObject != null) { chanraoObject.SetActive(true); Debug.Log($"[PingClick] {name} → {chanraoObject.name}"); } HideSelf(); ActionHistory.Push(ActionHistory.ActionType.PingSecond, this); var sm = FindObjectOfType(); if (sm != null) sm.RecordPingStep(pingIndex, true); var remainingPings = Object.FindObjectsOfType(); int activePingCount = 0; foreach (var p in remainingPings) { if (p._isBlinking || p._modelShown) activePingCount++; } // 自己刚刚隐藏了,所以要减 1 看看还剩几个 // 其实更准确的是检查 collider enabled,但现在我们不用 collider 了 // 直接看有没有剩余的活跃 ping bool allDone = true; foreach (var p in Object.FindObjectsOfType()) { var c = p.GetComponent(); // 如果还有没被隐藏的 ping(依然可见),就说明没完 if (p.gameObject.activeInHierarchy && p._modelShown && !p._isBlinking) { // 这个 ping 已经完成了第一阶段但还没进入第二阶段 // 不算完成 } } // 简单判定:遍历所有的 ping,看看是否还有 collider enabled 的 var pings = Object.FindObjectsOfType(); int remaining = 0; foreach (var p in pings) { var col = p.GetComponent(); if (col != null && col.enabled) remaining++; } if (remaining == 0) { 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.HideVideo(); } } } } public void ReEnableBorder() { if (!_modelShown) return; if (chanraoObject != null && chanraoObject.activeSelf) { return; } _isBlinking = true; 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; 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() { _modelShown = false; _isBlinking = true; if (_modelRenderer != null) _modelRenderer.enabled = false; foreach (var r in GetComponentsInChildren(true)) { if (_outlineMesh != null && r == _outlineMesh) continue; r.enabled = false; } if (_outlineMesh != null) _outlineMesh.enabled = true; StopAllCoroutines(); StartCoroutine(BlinkOutline()); } }