From 994cda07491be0e460c78bd9c8400780530ad07c Mon Sep 17 00:00:00 2001
From: hch <305670599@qq.com>
Date: 星期五, 07 十一月 2025 16:42:32 +0800
Subject: [PATCH] 0312 增加椭圆形遮罩

---
 Main/Component/UI/Effect/EllipseMask.cs.meta |   11 +
 Main/Component/UI/Effect/EllipseMask.cs      |  307 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 318 insertions(+), 0 deletions(-)

diff --git a/Main/Component/UI/Effect/EllipseMask.cs b/Main/Component/UI/Effect/EllipseMask.cs
new file mode 100644
index 0000000..b12ec51
--- /dev/null
+++ b/Main/Component/UI/Effect/EllipseMask.cs
@@ -0,0 +1,307 @@
+using UnityEngine;
+using UnityEngine.UI;
+using System.Collections.Generic;
+
+/// <summary>
+/// 妞渾褰㈤伄缃╃粍浠�
+/// 鍩轰簬妯℃澘娴嬭瘯瀹炵幇妞渾褰㈤伄缃╂晥鏋�
+/// </summary>
+[RequireComponent(typeof(RectTransform))]
+[AddComponentMenu("UI/Effects/Ellipse Mask")]
+[ExecuteInEditMode]
+public class EllipseMask : MonoBehaviour
+{
+    [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] 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>();
+
+    public Vector2 EllipseCenter
+    {
+        get => m_EllipseCenter;
+        set
+        {
+            if (m_EllipseCenter != value)
+            {
+                m_EllipseCenter = value;
+                UpdateMask();
+            }
+        }
+    }
+
+    public Vector2 EllipseRadius
+    {
+        get => m_EllipseRadius;
+        set
+        {
+            if (m_EllipseRadius != value)
+            {
+                m_EllipseRadius = value;
+                UpdateMask();
+            }
+        }
+    }
+
+    public float Softness
+    {
+        get => m_Softness;
+        set
+        {
+            if (m_Softness != value)
+            {
+                m_Softness = Mathf.Clamp(value, 0, 0.5f);
+                UpdateMask();
+            }
+        }
+    }
+
+    public int StencilID
+    {
+        get => m_StencilID;
+        set
+        {
+            if (m_StencilID != value)
+            {
+                m_StencilID = value;
+                UpdateMask();
+                UpdateChildrenStencil();
+            }
+        }
+    }
+
+    public bool ShowMaskGraphic
+    {
+        get => m_ShowMaskGraphic;
+        set
+        {
+            if (m_ShowMaskGraphic != value)
+            {
+                m_ShowMaskGraphic = value;
+                UpdateMaskVisibility();
+            }
+        }
+    }
+
+    protected virtual void Awake()
+    {
+        m_RectTransform = GetComponent<RectTransform>();
+        
+        // 鍒涘缓閬僵鍥惧儚
+        CreateMaskImage();
+        
+        // 鍒涘缓閬僵鏉愯川
+        CreateMaskMaterial();
+        
+        UpdateMask();
+    }
+
+    protected virtual void OnEnable()
+    {
+        CreateMaskMaterial();
+        UpdateMask();
+        
+        // 涓烘墍鏈夊瓙瀵硅薄娣诲姞妯℃澘娴嬭瘯
+        UpdateChildrenStencil();
+    }
+
+    protected virtual void OnDisable()
+    {
+        if (m_MaskMaterial != null)
+        {
+            DestroyImmediate(m_MaskMaterial);
+            m_MaskMaterial = null;
+        }
+        
+        // 绉婚櫎瀛愬璞$殑妯℃澘娴嬭瘯
+        RemoveChildrenStencil();
+    }
+
+    protected virtual void OnDestroy()
+    {
+        if (m_MaskMaterial != null)
+        {
+            DestroyImmediate(m_MaskMaterial);
+        }
+    }
+
+    /// <summary>
+    /// 鍒涘缓閬僵鍥惧儚
+    /// </summary>
+    private void CreateMaskImage()
+    {
+        if (m_MaskImage == null)
+        {
+            m_MaskImage = GetComponent<Image>();
+            if (m_MaskImage == null)
+            {
+                m_MaskImage = gameObject.AddComponent<Image>();
+                m_MaskImage.color = Color.white;
+            }
+        }
+    }
+
+    /// <summary>
+    /// 鍒涘缓閬僵鏉愯川
+    /// </summary>
+    private void CreateMaskMaterial()
+    {
+        if (m_MaskMaterial == null)
+        {
+            Shader ellipseShader = Shader.Find("GUI/EllipseMask");
+            if (ellipseShader != null)
+            {
+                m_MaskMaterial = new Material(ellipseShader);
+            }
+            else
+            {
+                Debug.LogError("EllipseMask shader not found!");
+            }
+        }
+    }
+
+    /// <summary>
+    /// 鏇存柊閬僵
+    /// </summary>
+    private void UpdateMask()
+    {
+        if (m_MaskMaterial != null && m_MaskImage != null)
+        {
+            UpdateMaterialProperties();
+            
+            // 璁剧疆鏉愯川鍒癐mage
+            m_MaskImage.material = m_MaskMaterial;
+            
+            // 寮哄埗閲嶇粯
+            m_MaskImage.SetMaterialDirty();
+        }
+    }
+
+    /// <summary>
+    /// 鏇存柊鏉愯川灞炴��
+    /// </summary>
+    private void UpdateMaterialProperties()
+    {
+        if (m_MaskMaterial != null)
+        {
+            m_MaskMaterial.SetVector("_EllipseCenter", new Vector4(m_EllipseCenter.x, m_EllipseCenter.y, 0, 0));
+            m_MaskMaterial.SetVector("_EllipseRadius", new Vector4(m_EllipseRadius.x, m_EllipseRadius.y, 0, 0));
+            m_MaskMaterial.SetFloat("_Softness", m_Softness);
+            m_MaskMaterial.SetInt("_Stencil", m_StencilID);
+        }
+    }
+
+    /// <summary>
+    /// 鏇存柊閬僵鍙鎬�
+    /// </summary>
+    private void UpdateMaskVisibility()
+    {
+        if (m_MaskImage != null)
+        {
+            m_MaskImage.enabled = m_ShowMaskGraphic;
+        }
+    }
+
+    /// <summary>
+    /// 涓哄瓙瀵硅薄娣诲姞妯℃澘娴嬭瘯
+    /// </summary>
+    private void UpdateChildrenStencil()
+    {
+        // 娓呴櫎涔嬪墠鐨勫垪琛�
+        m_MaskedChildren.Clear();
+        
+        // 鑾峰彇鎵�鏈夊瓙瀵硅薄鐨凣raphic缁勪欢
+        Graphic[] childrenGraphics = GetComponentsInChildren<Graphic>();
+        foreach (var graphic in childrenGraphics)
+        {
+            // 璺宠繃閬僵鏈韩
+            if (graphic.gameObject == this.gameObject)
+                continue;
+                
+            // 涓哄瓙瀵硅薄鍒涘缓閬僵鏉愯川
+            CreateChildMaskMaterial(graphic);
+            m_MaskedChildren.Add(graphic);
+        }
+    }
+
+    /// <summary>
+    /// 涓哄瓙瀵硅薄鍒涘缓閬僵鏉愯川
+    /// </summary>
+    private void CreateChildMaskMaterial(Graphic graphic)
+    {
+        if (graphic.material == null || !graphic.material.shader.name.Contains("EllipseMaskedContent"))
+        {
+            Shader maskedShader = Shader.Find("GUI/EllipseMaskedContent");
+            if (maskedShader != null)
+            {
+                Material maskedMaterial = new Material(maskedShader);
+                maskedMaterial.SetInt("_Stencil", m_StencilID);
+                maskedMaterial.SetInt("_StencilComp", 3); // Equal
+                graphic.material = maskedMaterial;
+            }
+        }
+        else
+        {
+            // 鏇存柊鐜版湁鏉愯川鐨勬ā鏉縄D
+            graphic.material.SetInt("_Stencil", m_StencilID);
+        }
+    }
+
+    /// <summary>
+    /// 绉婚櫎瀛愬璞$殑妯℃澘娴嬭瘯
+    /// </summary>
+    private void RemoveChildrenStencil()
+    {
+        foreach (var graphic in m_MaskedChildren)
+        {
+            if (graphic != null && graphic.material != null)
+            {
+                // 閲嶇疆鏉愯川
+                graphic.material = null;
+            }
+        }
+        m_MaskedChildren.Clear();
+    }
+
+    /// <summary>
+    /// 璁剧疆妞渾鍙傛暟
+    /// </summary>
+    /// <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;
+        m_EllipseRadius = radius;
+        m_Softness = Mathf.Clamp(softness, 0, 0.5f);
+        UpdateMask();
+    }
+
+    /// <summary>
+    /// 璁剧疆鍦嗗舰鍙傛暟锛堢壒娈婃儏鍐电殑妞渾锛�
+    /// </summary>
+    /// <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);
+    }
+
+#if UNITY_EDITOR
+    protected virtual void OnValidate()
+    {
+        if (m_MaskMaterial != null)
+        {
+            UpdateMaterialProperties();
+            UpdateChildrenStencil();
+        }
+    }
+#endif
+}
\ No newline at end of file
diff --git a/Main/Component/UI/Effect/EllipseMask.cs.meta b/Main/Component/UI/Effect/EllipseMask.cs.meta
new file mode 100644
index 0000000..3252759
--- /dev/null
+++ b/Main/Component/UI/Effect/EllipseMask.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d7b138cff435daf4e97efe03e10a756b
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

--
Gitblit v1.8.0