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;
}
}
}
}