yyl
2026-05-11 51b0f6ed9f4e1d3bb6f8144470b46908c7699a96
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
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("妞渾鍙傛暟")]
    [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>();
    // 缂撳瓨GetComponentsInChildren缁撴灉锛岄伩鍏嶆瘡娆nEnable鍒嗛厤鏁扮粍
    private static List<Graphic> _graphicCacheList = new List<Graphic>();
    private bool m_MaskShaderLoading = false;
 
    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();
            }
        }
    }
    static int order=1;
    protected virtual void Awake()
    {
        order++;
        StencilID = order;
        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;
        }
        m_MaskShaderLoading = false;
        
        // 绉婚櫎瀛愬璞$殑妯℃澘娴嬭瘯
        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;
            }
        }
        #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 && !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);
                    // shader使用ColorMask 0,恢复color不会显示颜色,但保证Canvas提交DrawCall写入stencil
                    if (m_MaskImage != null)
                        m_MaskImage.color = Color.white;
                    UpdateMask();
                    UpdateChildrenStencil();
                }
            }).Forget();
        }
        #else
        if (m_MaskMaterial == null)
        {
            m_MaskMaterial = new Material(Shader.Find("GUI/EllipseMask"));
        }
        #endif
    }
 
    /// <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>
    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);
        }
        _graphicCacheList.Clear();
    }
 
    /// <summary>
    /// 涓哄瓙瀵硅薄鍒涘缓閬僵鏉愯川
    /// </summary>
    private void CreateChildMaskMaterial(Graphic graphic)
    {
        if (graphic.material == null || !graphic.material.shader.name.Contains("EllipseMaskedContent"))
        {
 
#if !UNITY_EDITOR            
            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
            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;
            }
#endif
        }
        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
}