yyl
2026-04-24 97de31e9a015cf139f5293a22e1575a43dfb6733
Main/Component/UI/Effect/EllipseMask.cs
@@ -1,30 +1,33 @@
using UnityEngine;
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
/// <summary>
/// 椭圆形遮罩组件
/// 基于模板测试实现椭圆形遮罩效果
/// 妞渾褰㈤伄缃╃粍浠?
/// 鍩轰簬妯℃澘娴嬭瘯瀹炵幇妞渾褰㈤伄缃╂晥鏋?
/// </summary>
[RequireComponent(typeof(RectTransform))]
[AddComponentMenu("UI/Effects/Ellipse Mask")]
[ExecuteInEditMode]
public class EllipseMask : MonoBehaviour
{
    [Header("椭圆参数")]
    [Header("妞渾鍙傛暟")]
    [SerializeField] private Vector2 m_EllipseCenter = new Vector2(0.5f, 0.5f);
    [SerializeField] private Vector2 m_EllipseRadius = new Vector2(0.5f, 0.5f);
    [SerializeField] [Range(0, 0.5f)] private float m_Softness = 0f;    //暂无效果
    [SerializeField] private int m_StencilID = 1;   //多个遮罩时
    [SerializeField] [Range(0, 0.5f)] private float m_Softness = 0f;    //鏆傛棤鏁堟灉
    [SerializeField] private int m_StencilID = 1;   //澶氫釜閬僵鏃?
    [SerializeField] private bool m_ShowMaskGraphic = false;
    private Material m_MaskMaterial;
    private Image m_MaskImage;
    private RectTransform m_RectTransform;
    private List<Graphic> m_MaskedChildren = new List<Graphic>();
    // 缓存GetComponentsInChildren结果,避免每次OnEnable分配数组
    // 缂撳瓨GetComponentsInChildren缁撴灉锛岄伩鍏嶆瘡娆nEnable鍒嗛厤鏁扮粍
    private static List<Graphic> _graphicCacheList = new List<Graphic>();
    private bool m_MaskShaderLoading = false;
    public Vector2 EllipseCenter
    {
@@ -98,10 +101,10 @@
        StencilID = order;
        m_RectTransform = GetComponent<RectTransform>();
        
        // 创建遮罩图像
        // 鍒涘缓閬僵鍥惧儚
        CreateMaskImage();
        
        // 创建遮罩材质
        // 鍒涘缓閬僵鏉愯川
        CreateMaskMaterial();
        
        UpdateMask();
@@ -112,7 +115,7 @@
        CreateMaskMaterial();
        UpdateMask();
        
        // 为所有子对象添加模板测试
        // 涓烘墍鏈夊瓙瀵硅薄娣诲姞妯℃澘娴嬭瘯
        UpdateChildrenStencil();
    }
@@ -123,8 +126,9 @@
            DestroyImmediate(m_MaskMaterial);
            m_MaskMaterial = null;
        }
        m_MaskShaderLoading = false;
        
        // 移除子对象的模板测试
        // 绉婚櫎瀛愬璞$殑妯℃澘娴嬭瘯
        RemoveChildrenStencil();
    }
@@ -137,7 +141,7 @@
    }
    /// <summary>
    /// 创建遮罩图像
    /// 鍒涘缓閬僵鍥惧儚
    /// </summary>
    private void CreateMaskImage()
    {
@@ -150,25 +154,36 @@
                m_MaskImage.color = Color.white;
            }
        }
        #if !UNITY_EDITOR
        // 鍦╯hader寮傛鍔犺浇瀹屾垚鍓嶈涓哄叏閫忔槑锛岄槻姝㈤粯璁hader娓叉煋鍑虹櫧鑹茬煩褰?
        if (m_MaskMaterial == null)
        {
            m_MaskImage.color = Color.clear;
        }
        #endif
    }
    /// <summary>
    /// 创建遮罩材质
    /// 鍒涘缓閬僵鏉愯川
    /// </summary>
    private void CreateMaskMaterial()
    {
        #if !UNITY_EDITOR
        if (m_MaskMaterial == null)
        if (m_MaskMaterial == null && !m_MaskShaderLoading)
        {
            m_MaskShaderLoading = true;
            ResManager.Instance.LoadAssetAsync<Shader>("Shader", "GUI_EllipseMask").ContinueWith(ellipseShader =>
            {
                m_MaskShaderLoading = false;
                if (this == null) { return; }
                if (ellipseShader != null)
                {
                    m_MaskMaterial = new Material(ellipseShader);
                }
                else
                {
                    Debug.LogError("EllipseMask shader not found!");
                    // shader使用ColorMask 0,恢复color不会显示颜色,但保证Canvas提交DrawCall写入stencil
                    if (m_MaskImage != null)
                        m_MaskImage.color = Color.white;
                    UpdateMask();
                    UpdateChildrenStencil();
                }
            }).Forget();
        }
@@ -181,7 +196,7 @@
    }
    /// <summary>
    /// 更新遮罩
    /// 鏇存柊閬僵
    /// </summary>
    private void UpdateMask()
    {
@@ -189,16 +204,16 @@
        {
            UpdateMaterialProperties();
            
            // 设置材质到Image
            // 璁剧疆鏉愯川鍒癐mage
            m_MaskImage.material = m_MaskMaterial;
            
            // 强制重绘
            // 寮哄埗閲嶇粯
            m_MaskImage.SetMaterialDirty();
        }
    }
    /// <summary>
    /// 更新材质属性
    /// 鏇存柊鏉愯川灞炴€?
    /// </summary>
    private void UpdateMaterialProperties()
    {
@@ -212,7 +227,7 @@
    }
    /// <summary>
    /// 更新遮罩可见性
    /// 鏇存柊閬僵鍙鎬?
    /// </summary>
    private void UpdateMaskVisibility()
    {
@@ -223,24 +238,24 @@
    }
    /// <summary>
    /// 为子对象添加模板测试
    /// 涓哄瓙瀵硅薄娣诲姞妯℃澘娴嬭瘯
    /// </summary>
    public void UpdateChildrenStencil()
    {
        // 清除之前的列表
        // 娓呴櫎涔嬪墠鐨勫垪琛?
        m_MaskedChildren.Clear();
        
        // 使用静态缓存列表避免每次分配数组
        // 浣跨敤闈欐€佺紦瀛樺垪琛ㄩ伩鍏嶆瘡娆″垎閰嶆暟缁?
        _graphicCacheList.Clear();
        GetComponentsInChildren(false, _graphicCacheList);
        for (int i = 0; i < _graphicCacheList.Count; i++)
        {
            var graphic = _graphicCacheList[i];
            // 跳过遮罩本身
            // 璺宠繃閬僵鏈韩
            if (graphic.gameObject == this.gameObject)
                continue;
            // 为子对象创建遮罩材质
            // 涓哄瓙瀵硅薄鍒涘缓閬僵鏉愯川
            CreateChildMaskMaterial(graphic);
            m_MaskedChildren.Add(graphic);
        }
@@ -248,7 +263,7 @@
    }
    /// <summary>
    /// 为子对象创建遮罩材质
    /// 涓哄瓙瀵硅薄鍒涘缓閬僵鏉愯川
    /// </summary>
    private void CreateChildMaskMaterial(Graphic graphic)
    {
@@ -256,15 +271,16 @@
        {
#if !UNITY_EDITOR            
            // Shader maskedShader = Shader.Find("GUI/EllipseMaskedContent");
            ResManager.Instance.LoadAssetAsync<Shader>("Shader", "GUI_EllipseMaskedContent").ContinueWith(maskedShader =>
            {
                if (this == null || graphic == null) { return; }
                if (maskedShader != null)
                {
                    Material maskedMaterial = new Material(maskedShader);
                    maskedMaterial.SetInt("_Stencil", m_StencilID);
                    maskedMaterial.SetInt("_StencilComp", 3); // Equal
                    graphic.material = maskedMaterial;
                    graphic.SetMaterialDirty();
                }
            }).Forget();
#else
@@ -280,13 +296,13 @@
        }
        else
        {
            // 更新现有材质的模板ID
            // 鏇存柊鐜版湁鏉愯川鐨勬ā鏉縄D
            graphic.material.SetInt("_Stencil", m_StencilID);
        }
    }
    /// <summary>
    /// 移除子对象的模板测试
    /// 绉婚櫎瀛愬璞$殑妯℃澘娴嬭瘯
    /// </summary>
    private void RemoveChildrenStencil()
    {
@@ -294,7 +310,7 @@
        {
            if (graphic != null && graphic.material != null)
            {
                // 重置材质
                // 閲嶇疆鏉愯川
                graphic.material = null;
            }
        }
@@ -302,11 +318,11 @@
    }
    /// <summary>
    /// 设置椭圆参数
    /// 璁剧疆妞渾鍙傛暟
    /// </summary>
    /// <param name="center">椭圆中心(归一化坐标)</param>
    /// <param name="radius">椭圆半径(归一化坐标)</param>
    /// <param name="softness">边缘柔化程度</param>
    /// <param name="center">妞渾涓績锛堝綊涓€鍖栧潗鏍囷級</param>
    /// <param name="radius">妞渾鍗婂緞锛堝綊涓€鍖栧潗鏍囷級</param>
    /// <param name="softness">杈圭紭鏌斿寲绋嬪害</param>
    public void SetEllipseParameters(Vector2 center, Vector2 radius, float softness = 0.1f)
    {
        m_EllipseCenter = center;
@@ -316,11 +332,11 @@
    }
    /// <summary>
    /// 设置圆形参数(特殊情况的椭圆)
    /// 璁剧疆鍦嗗舰鍙傛暟锛堢壒娈婃儏鍐电殑妞渾锛?
    /// </summary>
    /// <param name="center">圆心(归一化坐标)</param>
    /// <param name="radius">半径(归一化坐标)</param>
    /// <param name="softness">边缘柔化程度</param>
    /// <param name="center">鍦嗗績锛堝綊涓€鍖栧潗鏍囷級</param>
    /// <param name="radius">鍗婂緞锛堝綊涓€鍖栧潗鏍囷級</param>
    /// <param name="softness">杈圭紭鏌斿寲绋嬪害</param>
    public void SetCircleParameters(Vector2 center, float radius, float softness = 0.1f)
    {
        SetEllipseParameters(center, new Vector2(radius, radius), softness);
@@ -336,4 +352,4 @@
        }
    }
#endif
}
}