From 27b64ec49dad0e5d7304355e4c1094ce6e651d30 Mon Sep 17 00:00:00 2001 From: yangbear Date: Sun, 12 Jul 2026 05:55:44 +0800 Subject: [PATCH] Complete rewrite: use screen-space distance detection instead of colliders for ping clicks --- .../Scripts/Interaction/PingClickHandler.cs | 159 ++++++++++-------- 1 file changed, 93 insertions(+), 66 deletions(-) diff --git a/Assets/Scripts/Interaction/PingClickHandler.cs b/Assets/Scripts/Interaction/PingClickHandler.cs index bf3c1ad..a06159d 100644 --- a/Assets/Scripts/Interaction/PingClickHandler.cs +++ b/Assets/Scripts/Interaction/PingClickHandler.cs @@ -8,41 +8,13 @@ public class PingClickHandler : MonoBehaviour private bool _modelShown; private bool _isBlinking; - // 静态变量,记录上一帧被点击的帧号,防止同一帧内多个 Ping 发生连锁反应 - private static int _lastClickedFrame = -1; - - // 新生成的 ping 在激活后的前2帧不允许被点击,防止被射线立刻打中 - private int _enabledFrame = -1; - public bool CanBeClicked() { return _isBlinking; } void OnEnable() { - _enabledFrame = Time.frameCount; // 记录被激活的帧号 - _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; // 缩小到 0.15 米,防止多个 ping 碰撞体重叠 - sphere.center = Vector3.zero; - sphere.isTrigger = false; - sphere.enabled = true; CreateOutlineMesh(); _modelShown = false; @@ -78,37 +50,78 @@ public class PingClickHandler : MonoBehaviour while (true) { if (_outlineMesh != null) _outlineMesh.enabled = !_outlineMesh.enabled; yield return new WaitForSeconds(0.4f); } } + /// + /// 全新的点击检测方式:由一个静态方法统一管理,不再依赖碰撞体。 + /// 在 Update 里,遍历所有活跃的 PingClickHandler,找到离鼠标最近的那个,只触发它。 + /// void Update() { - if (Input.GetMouseButtonDown(0)) + // 只让 pingIndex 最小的那个实例来做全局检测,避免多个实例重复执行 + if (!_isBlinking) return; + + // 只有一个实例负责全局判断 + var allHandlers = FindObjectsOfType(); + bool iAmFirst = true; + foreach (var h in allHandlers) { - if (!_isBlinking) return; - - // 同一帧只能有一个 ping 被触发 - if (Time.frameCount == _lastClickedFrame) return; - - // 刚被激活的前2帧不允许被点击(防止 nextPing 被立即触发) - if (Time.frameCount - _enabledFrame < 2) return; - - foreach (var c in Camera.allCameras) + if (!h._isBlinking) continue; + if (h.GetInstanceID() < this.GetInstanceID()) { - if (!c.enabled) continue; - - Ray ray = c.ScreenPointToRay(Input.mousePosition); - RaycastHit[] hits = Physics.RaycastAll(ray, 1000f); - foreach (var hit in hits) - { - if (hit.collider != null && hit.collider.gameObject == gameObject) - { - Debug.Log($"[PingClick] Update手动射线贯穿点中 (使用相机: {c.name}): {name}"); - _lastClickedFrame = Time.frameCount; - _isBlinking = false; - PerformClick(); - return; - } - } + 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() @@ -157,11 +170,34 @@ public class PingClickHandler : MonoBehaviour int activePingCount = 0; foreach (var p in remainingPings) { - var col = p.GetComponent(); - if (col != null && col.enabled) activePingCount++; + if (p._isBlinking || p._modelShown) activePingCount++; } - if (activePingCount == 0) + // 自己刚刚隐藏了,所以要减 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(); @@ -186,16 +222,12 @@ public class PingClickHandler : MonoBehaviour { if (!_modelShown) return; - var col = GetComponent(); if (chanraoObject != null && chanraoObject.activeSelf) { - if (col != null) col.enabled = false; return; } _isBlinking = true; - _enabledFrame = Time.frameCount; // 重新激活时也要保护2帧 - if (col != null) col.enabled = true; if (_outlineMesh != null) { @@ -229,32 +261,27 @@ public class PingClickHandler : MonoBehaviour StopAllCoroutines(); if (chanraoObject != null) chanraoObject.SetActive(false); foreach (var r in GetComponentsInChildren(true)) r.enabled = false; - var col = GetComponent(); if (col != null) col.enabled = false; gameObject.SetActive(false); } private void HideSelf() { foreach (var r in GetComponentsInChildren(true)) r.enabled = false; - var col = GetComponent(); if (col != null) col.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; - var col = GetComponent(); if (col != null) col.enabled = true; } public void ResetState() { _modelShown = false; - var col = GetComponent(); - if (col != null) col.enabled = true; - _isBlinking = true; if (_modelRenderer != null) _modelRenderer.enabled = false; foreach (var r in GetComponentsInChildren(true)) { if (_outlineMesh != null && r == _outlineMesh) continue; r.enabled = false; }