EnergyEfficiencyManagement/Assets/Zion/Scripts/Utils/GlobalHoverBreathingManager.cs

509 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 全局鼠标悬停呼吸灯效果管理器
/// 只需将此脚本挂载到Camera或任意游戏对象上
/// </summary>
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<RendererInfo> allRendererInfos = new List<RendererInfo>();
private Dictionary<Renderer, RendererInfo> rendererToInfo = new Dictionary<Renderer, RendererInfo>();
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<Camera>();
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();
}
/// <summary>
/// 递归处理模型的所有子物体
/// </summary>
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);
}
}
/// <summary>
/// 判断是否应忽略此物体
/// </summary>
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;
}
/// <summary>
/// 处理单个物体上的所有Renderer
/// </summary>
private void ProcessRenderersOnObject(GameObject obj)
{
Renderer[] renderers = obj.GetComponents<Renderer>();
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<Collider>();
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);
}
}
}
/// <summary>
/// 为渲染器添加碰撞体
/// </summary>
private Collider AddColliderToRenderer(Renderer renderer)
{
GameObject obj = renderer.gameObject;
switch (colliderType)
{
case ColliderType.Box:
BoxCollider boxCollider = obj.AddComponent<BoxCollider>();
boxCollider.isTrigger = true;
return boxCollider;
case ColliderType.Mesh:
if (renderer is MeshRenderer meshRenderer)
{
MeshFilter meshFilter = obj.GetComponent<MeshFilter>();
if (meshFilter != null && meshFilter.sharedMesh != null)
{
MeshCollider meshCollider = obj.AddComponent<MeshCollider>();
meshCollider.convex = true;
meshCollider.isTrigger = true;
return meshCollider;
}
}
// 回退到BoxCollider
BoxCollider fallbackBox = obj.AddComponent<BoxCollider>();
fallbackBox.isTrigger = true;
return fallbackBox;
default:
return obj.AddComponent<BoxCollider>();
}
}
/// <summary>
/// 处理鼠标悬停
/// </summary>
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<Renderer>();
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);
}
}
}
}
/// <summary>
/// 清空之前的悬停状态
/// </summary>
private void ClearPreviousHover()
{
foreach (RendererInfo info in allRendererInfos)
{
if (info.isBreathing) continue; // 保持呼吸效果
if (info.hoverTimer > 0f)
{
info.hoverTimer = 0f;
}
}
}
/// <summary>
/// 更新所有呼吸效果
/// </summary>
private void UpdateAllBreathingEffects()
{
foreach (RendererInfo info in allRendererInfos)
{
if (info.isBreathing)
{
UpdateBreathingEffect(info);
}
}
}
/// <summary>
/// 更新单个渲染器的呼吸效果
/// </summary>
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实现
}
}
/// <summary>
/// 停止单个渲染器的呼吸效果
/// </summary>
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;
}
}
/// <summary>
/// 停止所有呼吸效果
/// </summary>
public void StopAllBreathing()
{
foreach (RendererInfo info in allRendererInfos)
{
info.isBreathing = false;
info.hoverTimer = 0f;
info.breathPhase = 0f;
info.breathMaterial.color = info.originalMaterial.color;
}
}
/// <summary>
/// 手动开始渲染器的呼吸效果
/// </summary>
public void StartBreathingForRenderer(Renderer renderer)
{
if (rendererToInfo.TryGetValue(renderer, out RendererInfo info))
{
info.isBreathing = true;
}
}
/// <summary>
/// 动态添加单个渲染器
/// </summary>
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;
}
/// <summary>
/// 动态移除单个渲染器
/// </summary>
public void RemoveRenderer(Renderer renderer)
{
if (!rendererToInfo.TryGetValue(renderer, out RendererInfo info)) return;
// 恢复原始材质
renderer.material = info.originalMaterial;
// 从列表中移除
allRendererInfos.Remove(info);
rendererToInfo.Remove(renderer);
}
/// <summary>
/// 获取所有管理的渲染器
/// </summary>
public List<Renderer> GetAllManagedRenderers()
{
List<Renderer> renderers = new List<Renderer>();
foreach (RendererInfo info in allRendererInfos)
{
renderers.Add(info.renderer);
}
return renderers;
}
/// <summary>
/// 调试绘制
/// </summary>
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;
}
}
}
}