Complete rewrite: use screen-space distance detection instead of colliders for ping clicks
This commit is contained in:
@@ -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<MeshRenderer>();
|
||||
if (_modelRenderer == null) _modelRenderer = GetComponentInChildren<MeshRenderer>();
|
||||
foreach (var r in GetComponentsInChildren<Renderer>(true)) r.enabled = false;
|
||||
|
||||
var colliders = GetComponents<Collider>();
|
||||
foreach (var c in colliders)
|
||||
{
|
||||
if (c is BoxCollider || c is CapsuleCollider)
|
||||
{
|
||||
Destroy(c);
|
||||
}
|
||||
}
|
||||
|
||||
var sphere = GetComponent<SphereCollider>();
|
||||
if (sphere == null) sphere = gameObject.AddComponent<SphereCollider>();
|
||||
|
||||
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); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全新的点击检测方式:由一个静态方法统一管理,不再依赖碰撞体。
|
||||
/// 在 Update 里,遍历所有活跃的 PingClickHandler,找到离鼠标最近的那个,只触发它。
|
||||
/// </summary>
|
||||
void Update()
|
||||
{
|
||||
if (Input.GetMouseButtonDown(0))
|
||||
// 只让 pingIndex 最小的那个实例来做全局检测,避免多个实例重复执行
|
||||
if (!_isBlinking) return;
|
||||
|
||||
// 只有一个实例负责全局判断
|
||||
var allHandlers = FindObjectsOfType<PingClickHandler>();
|
||||
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<Collider>();
|
||||
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<PingClickHandler>())
|
||||
{
|
||||
var c = p.GetComponent<Collider>();
|
||||
// 如果还有没被隐藏的 ping(依然可见),就说明没完
|
||||
if (p.gameObject.activeInHierarchy && p._modelShown && !p._isBlinking)
|
||||
{
|
||||
// 这个 ping 已经完成了第一阶段但还没进入第二阶段
|
||||
// 不算完成
|
||||
}
|
||||
}
|
||||
|
||||
// 简单判定:遍历所有的 ping,看看是否还有 collider enabled 的
|
||||
var pings = Object.FindObjectsOfType<PingClickHandler>();
|
||||
int remaining = 0;
|
||||
foreach (var p in pings)
|
||||
{
|
||||
var col = p.GetComponent<Collider>();
|
||||
if (col != null && col.enabled) remaining++;
|
||||
}
|
||||
|
||||
if (remaining == 0)
|
||||
{
|
||||
Debug.Log("[PingClick] 所有的 ping 都已经绑成 chanrao 了!播放完成语音。");
|
||||
var am = FindObjectOfType<AudioManager>();
|
||||
@@ -186,16 +222,12 @@ public class PingClickHandler : MonoBehaviour
|
||||
{
|
||||
if (!_modelShown) return;
|
||||
|
||||
var col = GetComponent<Collider>();
|
||||
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<Renderer>(true)) r.enabled = false;
|
||||
var col = GetComponent<Collider>(); if (col != null) col.enabled = false;
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
private void HideSelf()
|
||||
{
|
||||
foreach (var r in GetComponentsInChildren<Renderer>(true)) r.enabled = false;
|
||||
var col = GetComponent<Collider>(); if (col != null) col.enabled = false;
|
||||
_isBlinking = false;
|
||||
StopAllCoroutines();
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
private void ShowSelf()
|
||||
{
|
||||
foreach (var r in GetComponentsInChildren<Renderer>(true)) r.enabled = true;
|
||||
if (_outlineMesh != null) _outlineMesh.enabled = false;
|
||||
var col = GetComponent<Collider>(); if (col != null) col.enabled = true;
|
||||
}
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
_modelShown = false;
|
||||
|
||||
var col = GetComponent<Collider>();
|
||||
if (col != null) col.enabled = true;
|
||||
|
||||
_isBlinking = true;
|
||||
if (_modelRenderer != null) _modelRenderer.enabled = false;
|
||||
foreach (var r in GetComponentsInChildren<Renderer>(true)) { if (_outlineMesh != null && r == _outlineMesh) continue; r.enabled = false; }
|
||||
|
||||
Reference in New Issue
Block a user