// UICustomBoundsFade.cs // 挂在使用 UI/Default-Gama shader 的 Graphic 对象上。 // 指定一个目标 RectTransform,每帧将其世界坐标四角范围同步到 _CustomFadeRect。 // 当前 Graphic 会在该范围内渐变,边缘外完全透明。 using UnityEngine; using UnityEngine.UI; #if UNITY_EDITOR using UnityEditor; #endif namespace NLD.UI { [RequireComponent(typeof(Graphic))] [DisallowMultipleComponent] public class UICustomBoundsFade : MonoBehaviour { [Tooltip("使用 UI-Default-Gama shader 的基础材质(必须指定,否则脚本不生效)")] [SerializeField] private Material _baseMaterial; [Tooltip("目标 GameObject 的 RectTransform,渐变范围以其世界坐标四角为准")] public RectTransform targetRect; [Tooltip("渐变过渡宽度(UV 分数):0 = 硬切边界,0.5 = 渐变延伸到 bounds 中心。与 bounds 物理尺寸无关")] [Range(0f, 0.5f)] public float softness = 0.1f; [Tooltip("渐变曲线指数,1 = 线性,>1 慢起快收,<1 快起慢收")] [Range(0.1f, 5f)] public float curve = 1f; // ── 内部状态 ────────────────────────────────────────────────── private Graphic _graphic; private Material _materialInstance; private static readonly int _propRect = Shader.PropertyToID("_CustomFadeRect"); private static readonly int _propSoftness = Shader.PropertyToID("_CustomFadeSoftness"); private static readonly int _propCurve = Shader.PropertyToID("_CustomFadeCurve"); private const string _keyword = "USE_CUSTOM_BOUNDS_FADE"; // ── 生命周期 ────────────────────────────────────────────────── private void OnEnable() { _graphic = GetComponent(); if (_graphic == null) return; // 优先使用指定的 Gama 基础材质,否则回退到 Graphic 当前材质 Material original = _baseMaterial != null ? _baseMaterial : _graphic.material; _materialInstance = new Material(original) { name = original.name + "_CustomBoundsFade" }; _materialInstance.EnableKeyword(_keyword); _graphic.material = _materialInstance; SyncToMaterial(); } private void OnDisable() { if (_graphic != null) _graphic.material = null; // 置 null 即回退到 defaultMaterial if (_materialInstance != null) { DestroyImmediate(_materialInstance); _materialInstance = null; } } private void LateUpdate() => SyncToMaterial(); // ── 核心同步 ────────────────────────────────────────────────── private void SyncToMaterial() { if (_materialInstance == null || targetRect == null) return; // GetWorldCorners: [0]=BL [1]=TL [2]=TR [3]=BR var corners = new Vector3[4]; targetRect.GetWorldCorners(corners); float xMin = corners[0].x; float yMin = corners[0].y; float xMax = corners[2].x; float yMax = corners[2].y; _materialInstance.SetVector(_propRect, new Vector4(xMin, yMin, xMax, yMax)); _materialInstance.SetFloat (_propSoftness, softness); _materialInstance.SetFloat (_propCurve, curve); } #if UNITY_EDITOR // Editor 下实时预览(OnValidate 在 inspector 改值时调用) private void OnValidate() { if (!Application.isPlaying && _materialInstance != null) SyncToMaterial(); } #endif } }