Improve exam flow and WebGL build
This commit is contained in:
@@ -99,6 +99,7 @@ public class ChanraoTuibuHandler : MonoBehaviour
|
||||
if (Time.time - _lastClickTime < 0.3f) return; // 防止重复点击
|
||||
_lastClickTime = Time.time;
|
||||
if (_isFinished) return;
|
||||
InteractionClickGuard.MarkHandled();
|
||||
|
||||
Debug.Log($"[ChanraoTuibu] {name} 被点击");
|
||||
ActionHistory.Push(ActionHistory.ActionType.ChanraoTuibu, this);
|
||||
@@ -119,6 +120,12 @@ public class ChanraoTuibuHandler : MonoBehaviour
|
||||
}
|
||||
}
|
||||
Debug.Log("[ChanraoTuibu] chanraotuibu 已显示,所有 ping 边框已重新激活!");
|
||||
|
||||
var uiManager = FindObjectOfType<UIManager>();
|
||||
if (uiManager != null)
|
||||
{
|
||||
uiManager.ShowTip("环绕固定带已显示。下一步:依次点击 4 个蓝色绑带点,完成由远端到近端的系带固定。");
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetState()
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using UnityEngine;
|
||||
|
||||
public static class InteractionClickGuard
|
||||
{
|
||||
private static int _lastHandledFrame = -1;
|
||||
private static float _lastHandledTime = -1f;
|
||||
|
||||
public static void MarkHandled()
|
||||
{
|
||||
_lastHandledFrame = Time.frameCount;
|
||||
_lastHandledTime = Time.unscaledTime;
|
||||
}
|
||||
|
||||
public static bool WasHandledThisFrame()
|
||||
{
|
||||
return _lastHandledFrame == Time.frameCount;
|
||||
}
|
||||
|
||||
public static bool WasHandledRecently()
|
||||
{
|
||||
return Time.frameCount - _lastHandledFrame <= 1 ||
|
||||
(Time.unscaledTime - _lastHandledTime >= 0f &&
|
||||
Time.unscaledTime - _lastHandledTime <= 0.1f);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2db9eba437b804541b3a0143b4c81c7d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,5 +1,6 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using System.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// 核心交互系统:射线检测 + 点击/拖拽处理。
|
||||
@@ -24,6 +25,7 @@ public class InteractionSystem : MonoBehaviour
|
||||
private Vector3 _dragOffset;
|
||||
private float _dragZ;
|
||||
private bool _isDragging;
|
||||
private Coroutine _pendingAnywhereClick;
|
||||
|
||||
// 考核模式序列点击
|
||||
private int _sequenceClickCount;
|
||||
@@ -49,10 +51,19 @@ public class InteractionSystem : MonoBehaviour
|
||||
if (UnityEngine.EventSystems.EventSystem.current != null &&
|
||||
UnityEngine.EventSystems.EventSystem.current.IsPointerOverGameObject())
|
||||
return;
|
||||
if (IsCustomFractureExamActive()) return;
|
||||
if (InteractionClickGuard.WasHandledThisFrame()) return;
|
||||
if (IsSplintPracticeActive()) return;
|
||||
|
||||
Ray ray = _mainCamera.ScreenPointToRay(Input.mousePosition);
|
||||
Camera rayCamera = GetActiveCamera();
|
||||
if (rayCamera == null) return;
|
||||
|
||||
Ray ray = rayCamera.ScreenPointToRay(Input.mousePosition);
|
||||
if (Physics.Raycast(ray, out RaycastHit hit, _maxRayDistance, _interactableMask))
|
||||
{
|
||||
if (HasDirectClickHandler(hit.collider))
|
||||
return;
|
||||
|
||||
string tag = hit.collider.tag;
|
||||
if (!string.IsNullOrEmpty(tag))
|
||||
{
|
||||
@@ -62,9 +73,67 @@ public class InteractionSystem : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
QueueAnywhereClick();
|
||||
}
|
||||
|
||||
private void QueueAnywhereClick()
|
||||
{
|
||||
if (_pendingAnywhereClick != null) StopCoroutine(_pendingAnywhereClick);
|
||||
_pendingAnywhereClick = StartCoroutine(InvokeAnywhereClickNextFrame());
|
||||
}
|
||||
|
||||
private IEnumerator InvokeAnywhereClickNextFrame()
|
||||
{
|
||||
yield return null;
|
||||
_pendingAnywhereClick = null;
|
||||
|
||||
if (InteractionClickGuard.WasHandledRecently()) yield break;
|
||||
if (UnityEngine.EventSystems.EventSystem.current != null &&
|
||||
UnityEngine.EventSystems.EventSystem.current.IsPointerOverGameObject())
|
||||
yield break;
|
||||
|
||||
OnAnywhereClicked?.Invoke();
|
||||
}
|
||||
|
||||
private Camera GetActiveCamera()
|
||||
{
|
||||
if (_mainCamera != null && _mainCamera.enabled && _mainCamera.gameObject.activeInHierarchy)
|
||||
return _mainCamera;
|
||||
|
||||
foreach (var camera in Camera.allCameras)
|
||||
{
|
||||
if (camera != null && camera.enabled && camera.gameObject.activeInHierarchy)
|
||||
return camera;
|
||||
}
|
||||
|
||||
return Camera.main;
|
||||
}
|
||||
|
||||
private bool HasDirectClickHandler(Collider collider)
|
||||
{
|
||||
if (collider == null) return false;
|
||||
|
||||
return collider.GetComponentInParent<Point2ClickHandler>() != null ||
|
||||
collider.GetComponentInParent<TouchPositionHandler>() != null ||
|
||||
collider.GetComponentInParent<ShoseClickHandler>() != null ||
|
||||
collider.GetComponentInParent<PingClickHandler>() != null ||
|
||||
collider.GetComponentInParent<ChanraoTuibuHandler>() != null ||
|
||||
collider.GetComponentInParent<SplintObjectClickHandler>() != null;
|
||||
}
|
||||
|
||||
private bool IsSplintPracticeActive()
|
||||
{
|
||||
var flow = FindObjectOfType<SplintPracticeFlowManager>();
|
||||
return flow != null && flow.IsActive;
|
||||
}
|
||||
|
||||
private bool IsCustomFractureExamActive()
|
||||
{
|
||||
return GameManager.Instance != null &&
|
||||
GameManager.Instance.CurrentExperimentType == ExperimentType.FractureFixation &&
|
||||
GameManager.Instance.CurrentMode == ExperimentMode.Exam;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 拖拽处理
|
||||
@@ -77,7 +146,10 @@ public class InteractionSystem : MonoBehaviour
|
||||
UnityEngine.EventSystems.EventSystem.current.IsPointerOverGameObject())
|
||||
return;
|
||||
|
||||
Ray ray = _mainCamera.ScreenPointToRay(Input.mousePosition);
|
||||
Camera rayCamera = GetActiveCamera();
|
||||
if (rayCamera == null) return;
|
||||
|
||||
Ray ray = rayCamera.ScreenPointToRay(Input.mousePosition);
|
||||
if (Physics.Raycast(ray, out RaycastHit hit, _maxRayDistance, _interactableMask))
|
||||
{
|
||||
var draggable = hit.collider.GetComponent<DraggableObject>();
|
||||
@@ -94,7 +166,10 @@ public class InteractionSystem : MonoBehaviour
|
||||
if (Input.GetMouseButton(0) && _isDragging && _draggedObject != null)
|
||||
{
|
||||
Vector3 screenPos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, _dragZ);
|
||||
Vector3 worldPos = _mainCamera.ScreenToWorldPoint(screenPos);
|
||||
Camera rayCamera = GetActiveCamera();
|
||||
if (rayCamera == null) return;
|
||||
|
||||
Vector3 worldPos = rayCamera.ScreenToWorldPoint(screenPos);
|
||||
_draggedObject.transform.position = worldPos;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ using UnityEngine;
|
||||
|
||||
public class PingClickHandler : MonoBehaviour
|
||||
{
|
||||
private const float ScreenHitRadius = 160f;
|
||||
|
||||
public GameObject chanraoObject, nextPing;
|
||||
public int pingIndex;
|
||||
private MeshRenderer _modelRenderer, _outlineMesh;
|
||||
@@ -129,7 +131,7 @@ public class PingClickHandler : MonoBehaviour
|
||||
if (otherDist < myDist) return;
|
||||
}
|
||||
|
||||
if (myDist < 120f)
|
||||
if (myDist < ScreenHitRadius)
|
||||
{
|
||||
Debug.Log($"[PingClick] 屏幕坐标命中: {name} (距离={myDist:F1}px)");
|
||||
_lastClickedFrame = Time.frameCount;
|
||||
@@ -140,6 +142,8 @@ public class PingClickHandler : MonoBehaviour
|
||||
|
||||
public void PerformClick()
|
||||
{
|
||||
InteractionClickGuard.MarkHandled();
|
||||
|
||||
if (!_modelShown)
|
||||
{
|
||||
_modelShown = true;
|
||||
@@ -152,27 +156,47 @@ public class PingClickHandler : MonoBehaviour
|
||||
if (nextPing != null) nextPing.SetActive(true);
|
||||
else
|
||||
{
|
||||
var chanraotuibu = GameObject.Find("chanraotuibu");
|
||||
if (chanraotuibu == null)
|
||||
bool handledBySplintPractice = false;
|
||||
var splintFlow = FindObjectOfType<SplintPracticeFlowManager>();
|
||||
if (splintFlow != null)
|
||||
{
|
||||
var mi = GameObject.Find("manInjury");
|
||||
if (mi != null)
|
||||
{
|
||||
var t = mi.transform.Find("chanraotuibu");
|
||||
if (t != null) chanraotuibu = t.gameObject;
|
||||
}
|
||||
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 (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<StepManager>();
|
||||
if (sm != null) sm.RecordPingStep(pingIndex, false);
|
||||
var examMgr = FindObjectOfType<ExamFlowManager>();
|
||||
if (examMgr != null) examMgr.OnObjectClicked(this);
|
||||
if (nextPing != null)
|
||||
{
|
||||
var splintFlow = FindObjectOfType<SplintPracticeFlowManager>();
|
||||
if (splintFlow != null) splintFlow.OnPingFirstClick(this);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -183,6 +207,9 @@ public class PingClickHandler : MonoBehaviour
|
||||
if (examMgr != null) examMgr.OnObjectClicked(this);
|
||||
var sm = FindObjectOfType<StepManager>();
|
||||
if (sm != null) sm.RecordPingStep(pingIndex, true);
|
||||
var splintFlow = FindObjectOfType<SplintPracticeFlowManager>();
|
||||
bool handledBySplintPractice = splintFlow != null && splintFlow.OnPingSecondClick(this);
|
||||
if (handledBySplintPractice) return;
|
||||
|
||||
var pings = Object.FindObjectsOfType<PingClickHandler>();
|
||||
int remaining = 0;
|
||||
@@ -195,6 +222,7 @@ public class PingClickHandler : MonoBehaviour
|
||||
bool isExam = GameManager.Instance != null && GameManager.Instance.CurrentMode == ExperimentMode.Exam;
|
||||
if (remaining == 0)
|
||||
{
|
||||
ShowLimbSecondStageTip(true);
|
||||
if (!_hasPlayedFinishVoice)
|
||||
{
|
||||
_hasPlayedFinishVoice = true;
|
||||
@@ -213,14 +241,58 @@ public class PingClickHandler : MonoBehaviour
|
||||
var uiMgr = FindObjectOfType<UIManager>();
|
||||
if (uiMgr != null)
|
||||
{
|
||||
uiMgr.HideVideo();
|
||||
uiMgr.ShowEndPanel();
|
||||
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<UIManager>();
|
||||
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<UIManager>();
|
||||
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;
|
||||
|
||||
@@ -61,6 +61,7 @@ public class Point2ClickHandler : MonoBehaviour
|
||||
public void PerformClick()
|
||||
{
|
||||
if (_clicked) return;
|
||||
InteractionClickGuard.MarkHandled();
|
||||
_clicked = true;
|
||||
|
||||
Debug.Log("[Point2Click] ★ 被点击 ★");
|
||||
@@ -96,6 +97,13 @@ public class Point2ClickHandler : MonoBehaviour
|
||||
if (shoseHandler != null) shoseHandler.enabled = true;
|
||||
else nextObject.SetActive(true);
|
||||
}
|
||||
|
||||
var uiManager = FindObjectOfType<UIManager>();
|
||||
if (uiManager != null)
|
||||
{
|
||||
uiManager.ShowTip("伤情评估完成。下一步:点击伤侧鞋子,脱除鞋子并暴露观察伤肢。");
|
||||
}
|
||||
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,7 @@ public class ShoseClickHandler : MonoBehaviour
|
||||
if (Time.time - _lastClickTime < 0.3f) return;
|
||||
_lastClickTime = Time.time;
|
||||
if (_clicked) return;
|
||||
InteractionClickGuard.MarkHandled();
|
||||
_clicked = true;
|
||||
Debug.Log("[ShoseClick] ★ 被点击 ★");
|
||||
ActionHistory.Push(ActionHistory.ActionType.Shose, this);
|
||||
@@ -139,8 +140,26 @@ public class ShoseClickHandler : MonoBehaviour
|
||||
if (uiMgr != null)
|
||||
{
|
||||
uiMgr.ShowVideo("bandage_demo.mp4");
|
||||
uiMgr.ShowTip(GetNextTipText());
|
||||
}
|
||||
}
|
||||
|
||||
private string GetNextTipText()
|
||||
{
|
||||
bool isExam = GameManager.Instance != null && GameManager.Instance.CurrentMode == ExperimentMode.Exam;
|
||||
bool isSplint = GameManager.Instance != null && GameManager.Instance.CurrentFixationMethod == FixationMethod.SplintFixation;
|
||||
|
||||
if (isExam)
|
||||
{
|
||||
return isSplint
|
||||
? "请阅读考核弹窗并选择固定带宽度,随后按提示预置四条固定带。"
|
||||
: "请阅读考核弹窗并选择带子宽度,随后按提示由远端到近端摆放绑带。";
|
||||
}
|
||||
|
||||
return isSplint
|
||||
? "下一步:按由脚踝到大腿的顺序,依次点击 4 个蓝色绑带点预置固定带。"
|
||||
: "下一步:按由脚踝到大腿的顺序,依次点击 4 个蓝色绑带点摆放绑带。";
|
||||
}
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,332 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class SplintObjectClickHandler : MonoBehaviour
|
||||
{
|
||||
private const float MinColliderSize = 0.35f;
|
||||
private const float ScreenHitPadding = 120f;
|
||||
private const float ScreenCenterFallbackRadius = 160f;
|
||||
|
||||
private MeshRenderer _outlineMesh;
|
||||
private bool _clicked;
|
||||
private float _lastClickTime;
|
||||
private static int _lastHandledFrame = -1;
|
||||
private static readonly List<SplintObjectClickHandler> ActiveHandlers = new List<SplintObjectClickHandler>();
|
||||
|
||||
public bool CanBeClicked()
|
||||
{
|
||||
return !_clicked && gameObject.activeInHierarchy;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (!ActiveHandlers.Contains(this)) ActiveHandlers.Add(this);
|
||||
EnsureCollider();
|
||||
CreateOutlineMesh();
|
||||
ResetState(true);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
ActiveHandlers.Remove(this);
|
||||
StopAllCoroutines();
|
||||
if (_outlineMesh != null) _outlineMesh.enabled = false;
|
||||
}
|
||||
|
||||
private void OnMouseDown()
|
||||
{
|
||||
PerformClick();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!CanBeClicked()) return;
|
||||
if (!Input.GetMouseButtonDown(0)) return;
|
||||
if (_lastHandledFrame == Time.frameCount) return;
|
||||
|
||||
if (UnityEngine.EventSystems.EventSystem.current != null &&
|
||||
UnityEngine.EventSystems.EventSystem.current.IsPointerOverGameObject())
|
||||
return;
|
||||
|
||||
Camera activeCamera = GetActiveCamera();
|
||||
if (activeCamera == null) return;
|
||||
|
||||
Vector2 mousePos = Input.mousePosition;
|
||||
float myDistance;
|
||||
if (!TryGetPointerDistance(activeCamera, mousePos, out myDistance)) return;
|
||||
if (myDistance > ScreenCenterFallbackRadius && !IsPointerInsideExpandedBounds(activeCamera, mousePos)) return;
|
||||
if (!IsNearestClickable(activeCamera, mousePos, myDistance)) return;
|
||||
|
||||
PerformClick();
|
||||
}
|
||||
|
||||
public void PerformClick()
|
||||
{
|
||||
if (_clicked) return;
|
||||
if (_lastHandledFrame == Time.frameCount) return;
|
||||
if (Time.time - _lastClickTime < 0.3f) return;
|
||||
_lastClickTime = Time.time;
|
||||
_lastHandledFrame = Time.frameCount;
|
||||
InteractionClickGuard.MarkHandled();
|
||||
|
||||
_clicked = true;
|
||||
StopAllCoroutines();
|
||||
if (_outlineMesh != null) _outlineMesh.enabled = false;
|
||||
|
||||
SetRenderersVisible(true);
|
||||
ActionHistory.Push(ActionHistory.ActionType.SplintPlank, this);
|
||||
|
||||
SplintPracticeFlowManager flow = FindObjectOfType<SplintPracticeFlowManager>();
|
||||
if (flow != null) flow.OnPlankClicked(this);
|
||||
|
||||
ExamFlowManager examFlow = FindObjectOfType<ExamFlowManager>();
|
||||
if (examFlow != null) examFlow.OnObjectClicked(this);
|
||||
}
|
||||
|
||||
public void ResetState(bool startBlink)
|
||||
{
|
||||
EnsureCollider();
|
||||
_clicked = false;
|
||||
SetRenderersVisible(false);
|
||||
|
||||
StopAllCoroutines();
|
||||
if (_outlineMesh != null) _outlineMesh.enabled = startBlink;
|
||||
if (startBlink && isActiveAndEnabled) StartCoroutine(BlinkOutline());
|
||||
}
|
||||
|
||||
public void ForceHideOutline()
|
||||
{
|
||||
StopAllCoroutines();
|
||||
if (_outlineMesh != null) _outlineMesh.enabled = false;
|
||||
}
|
||||
|
||||
private void EnsureCollider()
|
||||
{
|
||||
BoxCollider box = GetComponent<BoxCollider>();
|
||||
if (box == null) box = gameObject.AddComponent<BoxCollider>();
|
||||
|
||||
Bounds bounds;
|
||||
if (TryGetRendererBounds(out bounds))
|
||||
{
|
||||
Vector3 localCenter = transform.InverseTransformPoint(bounds.center);
|
||||
Vector3 localSize = transform.InverseTransformVector(bounds.size);
|
||||
localSize = new Vector3(Mathf.Abs(localSize.x), Mathf.Abs(localSize.y), Mathf.Abs(localSize.z));
|
||||
localSize = new Vector3(
|
||||
Mathf.Max(localSize.x * 1.8f, MinColliderSize),
|
||||
Mathf.Max(localSize.y * 1.8f, MinColliderSize),
|
||||
Mathf.Max(localSize.z * 1.8f, MinColliderSize));
|
||||
|
||||
box.center = localCenter;
|
||||
box.size = localSize;
|
||||
}
|
||||
|
||||
box.isTrigger = false;
|
||||
box.enabled = true;
|
||||
}
|
||||
|
||||
private void CreateOutlineMesh()
|
||||
{
|
||||
if (_outlineMesh != null) return;
|
||||
|
||||
Mesh mesh = GetMesh();
|
||||
if (mesh == null) return;
|
||||
|
||||
Shader shader = Shader.Find("Custom/OutlineUnlit");
|
||||
if (shader == null) return;
|
||||
|
||||
Material mat = new Material(shader);
|
||||
mat.SetColor("_OutlineColor", new Color(0.2f, 0.5f, 1f, 1f));
|
||||
mat.SetColor("_MainColor", new Color(0f, 0f, 0f, 0f));
|
||||
mat.SetFloat("_OutlineWidth", OutlineConfig.Width);
|
||||
|
||||
GameObject outline = new GameObject("Outline");
|
||||
outline.transform.SetParent(transform, false);
|
||||
outline.AddComponent<MeshFilter>().sharedMesh = mesh;
|
||||
_outlineMesh = outline.AddComponent<MeshRenderer>();
|
||||
_outlineMesh.material = mat;
|
||||
}
|
||||
|
||||
private Mesh GetMesh()
|
||||
{
|
||||
MeshFilter mf = GetComponent<MeshFilter>();
|
||||
if (mf == null) mf = GetComponentInChildren<MeshFilter>(true);
|
||||
if (mf != null && mf.sharedMesh != null) return mf.sharedMesh;
|
||||
|
||||
SkinnedMeshRenderer smr = GetComponent<SkinnedMeshRenderer>();
|
||||
if (smr == null) smr = GetComponentInChildren<SkinnedMeshRenderer>(true);
|
||||
return smr != null ? smr.sharedMesh : null;
|
||||
}
|
||||
|
||||
private bool TryGetRendererBounds(out Bounds bounds)
|
||||
{
|
||||
Renderer[] renderers = GetComponentsInChildren<Renderer>(true);
|
||||
bool found = false;
|
||||
bounds = new Bounds(transform.position, Vector3.zero);
|
||||
|
||||
for (int i = 0; i < renderers.Length; i++)
|
||||
{
|
||||
Renderer renderer = renderers[i];
|
||||
if (renderer == null) continue;
|
||||
if (_outlineMesh != null && renderer == _outlineMesh) continue;
|
||||
|
||||
if (!found)
|
||||
{
|
||||
bounds = renderer.bounds;
|
||||
found = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
bounds.Encapsulate(renderer.bounds);
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
private Camera GetActiveCamera()
|
||||
{
|
||||
Camera activeCamera = Camera.main;
|
||||
foreach (Camera camera in Camera.allCameras)
|
||||
{
|
||||
if (camera != null && camera.enabled && camera.gameObject.activeInHierarchy)
|
||||
{
|
||||
activeCamera = camera;
|
||||
}
|
||||
}
|
||||
|
||||
return activeCamera;
|
||||
}
|
||||
|
||||
private bool TryGetPointerDistance(Camera activeCamera, Vector2 pointer, out float distance)
|
||||
{
|
||||
Rect screenRect;
|
||||
Vector2 center;
|
||||
if (TryGetScreenRect(activeCamera, out screenRect, out center))
|
||||
{
|
||||
float rectDistance = DistanceToRect(pointer, screenRect);
|
||||
float centerDistance = Vector2.Distance(pointer, center);
|
||||
distance = rectDistance + centerDistance * 0.001f;
|
||||
return true;
|
||||
}
|
||||
|
||||
Vector3 screenPosition = activeCamera.WorldToScreenPoint(transform.position);
|
||||
if (screenPosition.z < 0f)
|
||||
{
|
||||
distance = float.MaxValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
center = new Vector2(screenPosition.x, screenPosition.y);
|
||||
distance = Vector2.Distance(pointer, center);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsPointerInsideExpandedBounds(Camera activeCamera, Vector2 pointer)
|
||||
{
|
||||
Rect screenRect;
|
||||
Vector2 center;
|
||||
if (!TryGetScreenRect(activeCamera, out screenRect, out center)) return false;
|
||||
return screenRect.Contains(pointer);
|
||||
}
|
||||
|
||||
private bool TryGetScreenRect(Camera activeCamera, out Rect screenRect, out Vector2 center)
|
||||
{
|
||||
screenRect = new Rect();
|
||||
center = Vector2.zero;
|
||||
|
||||
Bounds bounds;
|
||||
if (!TryGetRendererBounds(out bounds)) return false;
|
||||
|
||||
Vector3 extents = bounds.extents;
|
||||
Vector3 boundsCenter = bounds.center;
|
||||
Vector3[] corners =
|
||||
{
|
||||
boundsCenter + new Vector3(-extents.x, -extents.y, -extents.z),
|
||||
boundsCenter + new Vector3(-extents.x, -extents.y, extents.z),
|
||||
boundsCenter + new Vector3(-extents.x, extents.y, -extents.z),
|
||||
boundsCenter + new Vector3(-extents.x, extents.y, extents.z),
|
||||
boundsCenter + new Vector3(extents.x, -extents.y, -extents.z),
|
||||
boundsCenter + new Vector3(extents.x, -extents.y, extents.z),
|
||||
boundsCenter + new Vector3(extents.x, extents.y, -extents.z),
|
||||
boundsCenter + new Vector3(extents.x, extents.y, extents.z)
|
||||
};
|
||||
|
||||
float minX = float.MaxValue;
|
||||
float minY = float.MaxValue;
|
||||
float maxX = float.MinValue;
|
||||
float maxY = float.MinValue;
|
||||
bool found = false;
|
||||
|
||||
for (int i = 0; i < corners.Length; i++)
|
||||
{
|
||||
Vector3 screenPoint = activeCamera.WorldToScreenPoint(corners[i]);
|
||||
if (screenPoint.z < 0f) continue;
|
||||
|
||||
minX = Mathf.Min(minX, screenPoint.x);
|
||||
minY = Mathf.Min(minY, screenPoint.y);
|
||||
maxX = Mathf.Max(maxX, screenPoint.x);
|
||||
maxY = Mathf.Max(maxY, screenPoint.y);
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (!found) return false;
|
||||
|
||||
minX -= ScreenHitPadding;
|
||||
minY -= ScreenHitPadding;
|
||||
maxX += ScreenHitPadding;
|
||||
maxY += ScreenHitPadding;
|
||||
|
||||
screenRect = Rect.MinMaxRect(minX, minY, maxX, maxY);
|
||||
center = screenRect.center;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsNearestClickable(Camera activeCamera, Vector2 pointer, float myDistance)
|
||||
{
|
||||
int myId = GetInstanceID();
|
||||
|
||||
for (int i = 0; i < ActiveHandlers.Count; i++)
|
||||
{
|
||||
SplintObjectClickHandler other = ActiveHandlers[i];
|
||||
if (other == null || other == this) continue;
|
||||
if (!other.CanBeClicked()) continue;
|
||||
|
||||
float otherDistance;
|
||||
if (!other.TryGetPointerDistance(activeCamera, pointer, out otherDistance)) continue;
|
||||
|
||||
if (otherDistance < myDistance) return false;
|
||||
if (Mathf.Approximately(otherDistance, myDistance) && other.GetInstanceID() < myId) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private float DistanceToRect(Vector2 point, Rect rect)
|
||||
{
|
||||
if (rect.Contains(point)) return 0f;
|
||||
|
||||
float closestX = Mathf.Clamp(point.x, rect.xMin, rect.xMax);
|
||||
float closestY = Mathf.Clamp(point.y, rect.yMin, rect.yMax);
|
||||
return Vector2.Distance(point, new Vector2(closestX, closestY));
|
||||
}
|
||||
|
||||
private void SetRenderersVisible(bool visible)
|
||||
{
|
||||
Renderer[] renderers = GetComponentsInChildren<Renderer>(true);
|
||||
for (int i = 0; i < renderers.Length; i++)
|
||||
{
|
||||
if (_outlineMesh != null && renderers[i] == _outlineMesh) continue;
|
||||
renderers[i].enabled = visible;
|
||||
}
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator BlinkOutline()
|
||||
{
|
||||
yield return new WaitForSeconds(0.4f);
|
||||
while (true)
|
||||
{
|
||||
if (_outlineMesh != null) _outlineMesh.enabled = !_outlineMesh.enabled;
|
||||
yield return new WaitForSeconds(0.4f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1177c878d83314eef959e99933cab1b2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -62,6 +62,7 @@ public class TouchPositionHandler : MonoBehaviour
|
||||
if (_meshRenderer == null || !_meshRenderer.enabled) return; // 防止重复点击
|
||||
if (Time.time - _lastClickTime < 0.3f) return;
|
||||
_lastClickTime = Time.time;
|
||||
InteractionClickGuard.MarkHandled();
|
||||
|
||||
Debug.Log($"[TouchPosition] {name}: 被点击");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user