using System.Collections.Generic; using UnityEngine; /// /// 全局鼠标悬停呼吸灯效果管理器 /// 只需将此脚本挂载到Camera或任意游戏对象上 /// public class GlobalHoverBreathingManager : MonoBehaviour { [Header("全局呼吸效果设置")] [Tooltip("呼吸灯颜色")] public Color breathColor = Color.cyan; [Tooltip("呼吸速度")] public float breathSpeed = 0.7f; [Tooltip("最小亮度 (0-1)")] [Range(0f, 1f)] public float minIntensity = 0.2f; [Tooltip("最大亮度 (0-1)")] [Range(0f, 1f)] public float maxIntensity = 0.9f; [Header("悬停触发设置")] [Tooltip("悬停触发延时(秒)")] public float hoverDelay = 1.2f; [Tooltip("悬停时是否显示高亮轮廓(可选)")] public bool useOutline = false; [Header("模型处理设置")] [Tooltip("模型根节点(自动递归处理其下的所有子物体)")] public GameObject modelRoot; [Tooltip("需要交互的层")] public LayerMask interactableLayers = -1; [Tooltip("是否自动为子物体添加碰撞体(如无)")] public bool autoAddColliders = true; [Tooltip("碰撞体类型:Box(性能好)或 Mesh(精确但耗性能)")] public ColliderType colliderType = ColliderType.Mesh; [Header("渲染器过滤设置")] [Tooltip("最小渲染器尺寸(过滤过小的碎片)")] public float minRendererSize = 0.01f; [Tooltip("忽略名称包含以下关键词的物体(用逗号分隔)")] public string ignoreKeywords = "ignore,skip,hidden,internal,debug"; [Tooltip("只包含名称包含以下关键词的物体(为空则包含所有)")] public string includeKeywords = ""; [Tooltip("忽略透明材质(Alpha < 0.5)的物体")] public bool ignoreTransparentMaterials = true; [Header("调试设置")] [Tooltip("是否显示调试信息")] public bool showDebugInfo = true; [Tooltip("是否绘制悬停物体的边框")] public bool drawHoverBounds = true; [Tooltip("边框颜色")] public Color boundsColor = Color.yellow; // 枚举:碰撞体类型 public enum ColliderType { Box, Mesh } // 内部数据结构 private class RendererInfo { public Renderer renderer; public Material originalMaterial; public Material breathMaterial; public Collider collider; public float hoverTimer = 0f; public float breathPhase = 0f; public bool isBreathing = false; } private Camera mainCamera; private List allRendererInfos = new List(); private Dictionary rendererToInfo = new Dictionary(); private RaycastHit[] raycastHits = new RaycastHit[20]; private Bounds? hoverBounds = null; // 忽略关键词数组 private string[] ignoreKeywordArray = new string[0]; private string[] includeKeywordArray = new string[0]; void Start() { // 初始化主相机 mainCamera = GetComponent(); if (mainCamera == null) mainCamera = Camera.main; if (mainCamera == null) { Debug.LogError("未找到相机!请将此脚本挂载到相机上。", this); enabled = false; return; } // 解析关键词 ignoreKeywordArray = ignoreKeywords.ToLower().Split(new char[] { ',' }, System.StringSplitOptions.RemoveEmptyEntries); includeKeywordArray = includeKeywords.ToLower().Split(new char[] { ',' }, System.StringSplitOptions.RemoveEmptyEntries); // 如果指定了模型根节点,自动处理 if (modelRoot != null) { ProcessModelRecursively(modelRoot.transform); } else { Debug.LogWarning("未指定模型根节点,将不会自动处理任何物体。"); } if (showDebugInfo) { Debug.Log($"管理器初始化完成,共处理 {allRendererInfos.Count} 个渲染器"); } } void Update() { HandleMouseHover(); UpdateAllBreathingEffects(); } /// /// 递归处理模型的所有子物体 /// private void ProcessModelRecursively(Transform parent) { if (parent == null) return; // 处理当前物体的所有Renderer ProcessRenderersOnObject(parent.gameObject); // 递归处理所有子物体 foreach (Transform child in parent) { // 如果物体名称包含忽略关键词,跳过整个子树 if (ShouldIgnoreObject(child.gameObject)) { if (showDebugInfo) { Debug.Log($"忽略物体及其子物体: {child.name}", child.gameObject); } continue; } ProcessModelRecursively(child); } } /// /// 判断是否应忽略此物体 /// private bool ShouldIgnoreObject(GameObject obj) { string objName = obj.name.ToLower(); // 检查忽略关键词 foreach (string keyword in ignoreKeywordArray) { if (!string.IsNullOrEmpty(keyword) && objName.Contains(keyword)) { return true; } } // 检查包含关键词(如果设置了包含关键词) if (includeKeywordArray.Length > 0 && !string.IsNullOrEmpty(includeKeywords)) { bool shouldInclude = false; foreach (string keyword in includeKeywordArray) { if (!string.IsNullOrEmpty(keyword) && objName.Contains(keyword)) { shouldInclude = true; break; } } if (!shouldInclude) return true; } return false; } /// /// 处理单个物体上的所有Renderer /// private void ProcessRenderersOnObject(GameObject obj) { Renderer[] renderers = obj.GetComponents(); foreach (Renderer renderer in renderers) { // 检查渲染器有效性 if (renderer == null || renderer.material == null) continue; // 检查最小尺寸 if (renderer.bounds.size.magnitude < minRendererSize) { if (showDebugInfo) { Debug.Log($"跳过过小渲染器: {renderer.name} (尺寸: {renderer.bounds.size.magnitude})", renderer.gameObject); } continue; } // 检查透明材质 if (ignoreTransparentMaterials && renderer.material.HasProperty("_Color")) { Color matColor = renderer.material.color; if (matColor.a < 0.5f) continue; } // 检查层 if (((1 << renderer.gameObject.layer) & interactableLayers) == 0) continue; // 创建Renderer信息 RendererInfo info = new RendererInfo { renderer = renderer, originalMaterial = renderer.material }; // 创建呼吸材质 info.breathMaterial = new Material(renderer.material); renderer.material = info.breathMaterial; // 确保有碰撞体 Collider existingCollider = renderer.GetComponent(); if (existingCollider == null && autoAddColliders) { info.collider = AddColliderToRenderer(renderer); } else { info.collider = existingCollider; } // 添加到列表 allRendererInfos.Add(info); rendererToInfo[renderer] = info; if (showDebugInfo) { Debug.Log($"添加渲染器: {renderer.name} (物体: {obj.name}, 材质: {renderer.material.name})", renderer.gameObject); } } } /// /// 为渲染器添加碰撞体 /// private Collider AddColliderToRenderer(Renderer renderer) { GameObject obj = renderer.gameObject; switch (colliderType) { case ColliderType.Box: BoxCollider boxCollider = obj.AddComponent(); boxCollider.isTrigger = true; return boxCollider; case ColliderType.Mesh: if (renderer is MeshRenderer meshRenderer) { MeshFilter meshFilter = obj.GetComponent(); if (meshFilter != null && meshFilter.sharedMesh != null) { MeshCollider meshCollider = obj.AddComponent(); meshCollider.convex = true; meshCollider.isTrigger = true; return meshCollider; } } // 回退到BoxCollider BoxCollider fallbackBox = obj.AddComponent(); fallbackBox.isTrigger = true; return fallbackBox; default: return obj.AddComponent(); } } /// /// 处理鼠标悬停 /// private void HandleMouseHover() { Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition); // 清空之前的悬停状态 ClearPreviousHover(); // 射线检测 int hitCount = Physics.RaycastNonAlloc(ray, raycastHits, Mathf.Infinity, interactableLayers); RendererInfo closestHitInfo = null; float closestDistance = Mathf.Infinity; hoverBounds = null; // 找到最近的、在管理范围内的渲染器 for (int i = 0; i < hitCount; i++) { RaycastHit hit = raycastHits[i]; Renderer hitRenderer = hit.collider.GetComponent(); if (hitRenderer != null && rendererToInfo.TryGetValue(hitRenderer, out RendererInfo hitInfo)) { if (hit.distance < closestDistance) { closestDistance = hit.distance; closestHitInfo = hitInfo; // 记录边界框用于调试绘制 hoverBounds = hitRenderer.bounds; } } } // 更新悬停计时器 if (closestHitInfo != null) { closestHitInfo.hoverTimer += Time.deltaTime; if (closestHitInfo.hoverTimer >= hoverDelay && !closestHitInfo.isBreathing) { closestHitInfo.isBreathing = true; if (showDebugInfo) { Debug.Log($"开始呼吸效果: {closestHitInfo.renderer.name}", closestHitInfo.renderer.gameObject); } } } } /// /// 清空之前的悬停状态 /// private void ClearPreviousHover() { foreach (RendererInfo info in allRendererInfos) { if (info.isBreathing) continue; // 保持呼吸效果 if (info.hoverTimer > 0f) { info.hoverTimer = 0f; } } } /// /// 更新所有呼吸效果 /// private void UpdateAllBreathingEffects() { foreach (RendererInfo info in allRendererInfos) { if (info.isBreathing) { UpdateBreathingEffect(info); } } } /// /// 更新单个渲染器的呼吸效果 /// private void UpdateBreathingEffect(RendererInfo info) { info.breathPhase += breathSpeed * Time.deltaTime; float t = (Mathf.Sin(info.breathPhase) + 1f) * 0.5f; // 0~1 float intensity = Mathf.Lerp(minIntensity, maxIntensity, t); // 混合呼吸颜色和原始颜色 Color targetColor = Color.Lerp(info.originalMaterial.color, breathColor, intensity); info.breathMaterial.color = targetColor; // 可选:轮廓效果 if (useOutline) { // 这里可以添加轮廓Shader效果 // 需要配合自定义Shader实现 } } /// /// 停止单个渲染器的呼吸效果 /// public void StopBreathingForRenderer(Renderer renderer) { if (rendererToInfo.TryGetValue(renderer, out RendererInfo info)) { info.isBreathing = false; info.hoverTimer = 0f; info.breathPhase = 0f; info.breathMaterial.color = info.originalMaterial.color; } } /// /// 停止所有呼吸效果 /// public void StopAllBreathing() { foreach (RendererInfo info in allRendererInfos) { info.isBreathing = false; info.hoverTimer = 0f; info.breathPhase = 0f; info.breathMaterial.color = info.originalMaterial.color; } } /// /// 手动开始渲染器的呼吸效果 /// public void StartBreathingForRenderer(Renderer renderer) { if (rendererToInfo.TryGetValue(renderer, out RendererInfo info)) { info.isBreathing = true; } } /// /// 动态添加单个渲染器 /// public void AddRenderer(Renderer renderer) { if (renderer == null || rendererToInfo.ContainsKey(renderer)) return; RendererInfo info = new RendererInfo { renderer = renderer, originalMaterial = renderer.material, breathMaterial = new Material(renderer.material) }; renderer.material = info.breathMaterial; allRendererInfos.Add(info); rendererToInfo[renderer] = info; } /// /// 动态移除单个渲染器 /// public void RemoveRenderer(Renderer renderer) { if (!rendererToInfo.TryGetValue(renderer, out RendererInfo info)) return; // 恢复原始材质 renderer.material = info.originalMaterial; // 从列表中移除 allRendererInfos.Remove(info); rendererToInfo.Remove(renderer); } /// /// 获取所有管理的渲染器 /// public List GetAllManagedRenderers() { List renderers = new List(); foreach (RendererInfo info in allRendererInfos) { renderers.Add(info.renderer); } return renderers; } /// /// 调试绘制 /// void OnDrawGizmos() { if (!Application.isPlaying || !drawHoverBounds || !hoverBounds.HasValue) return; Gizmos.color = boundsColor; Gizmos.DrawWireCube(hoverBounds.Value.center, hoverBounds.Value.size); } void OnDestroy() { // 恢复所有原始材质 foreach (RendererInfo info in allRendererInfos) { if (info.renderer != null && info.originalMaterial != null) { info.renderer.material = info.originalMaterial; } } } }