| New file |
| | |
| | | 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(); |
| | | |
| | | // 设置材质到Image |
| | | 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(); |
| | | |
| | | // 获取所有子对象的Graphic组件 |
| | | 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 |
| | | { |
| | | // 更新现有材质的模板ID |
| | | 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 |
| | | } |