using System.Linq; // 用于可能的LINQ操作,但在此版本中可能不直接使用 using TMPro; // 用于 TextMeshPro 组件 using UnityEngine; using UnityEngine.Events; // 用于 UnityEvent namespace UI { public class Button3D : MonoBehaviour { [Tooltip("当按钮被点击时触发的UnityEvent。可以在Inspector中配置.")] public UnityEvent OnClick; public MeshRenderer renderer; // 请确保在Inspector中拖拽赋值 public Material outlineMaterial; // 请确保在Inspector中拖拽赋值 public TMP_Text text; // 文本组件 private Material[] _originalMaterials; // 用于存储按钮的原始材质,以便正确添加和移除描边 private void Awake() { if (renderer == null) { Debug.LogError("Button3D: MeshRenderer is not assigned. Please assign it in the Inspector.", this); return; } if (outlineMaterial == null) { Debug.LogWarning( "Button3D: Outline Material is not assigned. Outline effect will not work. Please assign it in the Inspector.", this); } if (text == null) { Debug.LogWarning("Button3D: TMP_Text is not assigned. Text features will not work. Please assign it in the Inspector.", this); // 这里不return,允许按钮无文本运行 } // 存储原始材质数组,以便在添加和移除描边时正确操作 // .ToArray() 是为了确保我们拿到的是拷贝,而不是引用,防止外部意外修改 _originalMaterials = renderer.materials.ToArray(); // 确保初始化时没有边框材质 // Important: 调用RemoveOutlineMaterialInternal()可能会清除_originalMaterials,所以要确保先保存 RemoveOutlineMaterialInternal(); // 这一步会根据_originalMaterials重新设置renderer.materials // 初始时隐藏文本 if (text != null) { text.gameObject.SetActive(false); } } private void Update() { // 文本始终朝向摄像机 if (text != null && text.IsActive() && Camera.main != null) { text.transform.LookAt(text.transform.position + Camera.main.transform.rotation * Vector3.forward, Camera.main.transform.rotation * Vector3.up); } } private void OnMouseEnter() { if (renderer == null) return; // 鼠标进入时显示文本 if (text != null) { text.gameObject.SetActive(true); } // 如果没有描边材质,或者渲染器已经有描边材质,就没必要再添加了 // 检查当前材质数组长度是否已经大于等于_originalMaterials的长度+1 // 这样可以避免重复添加 if (outlineMaterial == null || renderer.materials.Length > _originalMaterials.Length) { return; } // 如果当前材质数组和_originalMaterials不一致,说明可能被其他逻辑修改了, // 稳妥起见,最好先恢复到_originalMaterials if (!renderer.materials.SequenceEqual(_originalMaterials)) { //Debug.LogWarning("Button3D: Renderer materials were unexpectedly modified before OnMouseEnter. Restoring original materials."); // 通常不应该发生,除非有其他脚本在修改 renderer.materials = _originalMaterials; } // 创建一个新数组,包含原始材质和描边材质 Material[] newMaterials = new Material[_originalMaterials.Length + 1]; System.Array.Copy(_originalMaterials, newMaterials, _originalMaterials.Length); newMaterials[_originalMaterials.Length] = outlineMaterial; renderer.materials = newMaterials; } private void OnMouseExit() { if (renderer == null) return; // 鼠标离开时隐藏文本 if (text != null) { text.gameObject.SetActive(false); } // 调用内部方法移除边框材质 RemoveOutlineMaterialInternal(); } private void OnMouseDown() { // 触发UnityEvent OnClick.Invoke(); } /// /// 移除渲染器上的边框材质(如果存在),恢复到按钮的原始材质。 /// private void RemoveOutlineMaterialInternal() { if (renderer == null) return; // 即使_originalMaterials == null(因为Awake中的LogError),也可以安全赋值空数组 // 但如果_originalMaterials确实是null,说明Awake有问题,后续操作也可能继续出错 if (_originalMaterials == null) { Debug.LogError("Button3D: _originalMaterials is null. Cannot remove outline. Check Awake method.", this); return; } // 直接将渲染器材质恢复为_originalMaterials的副本 renderer.materials = _originalMaterials.ToArray(); // 使用ToArray()确保赋值一个副本,避免_originalMaterials被意外修改 } } }