
Unity3D UGUI系列之自定义圆形按钮
1 效果展示
只在圆形区域内点击有效。
2 分析
- 参照UGUI源码中的Image,创建一个子类继承自MaskableGraphic
- 重写OnPopulateMesh方法,创建圆形图片的网格(UGUI控件的本质是网格,这点可参考之前的博客《Unity3D UGUI系列之合批》)
- 实现ICanvasRaycastFilter接口,用来过滤掉圆形区域外的点击
3 项目
项目链接:https://pan.baidu.com/s/1VBHjsIrlTInb8QxdE2phcA
提取码:dn3p
博主个人博客本文链接。
BaseImage.cs
using System;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;
namespace CustomUI
{
public class BaseImage : MaskableGraphic, ISerializationCallbackReceiver, ILayoutElement, ICanvasRaycastFilter
{
[FormerlySerializedAs("m_Frame")]
[SerializeField]
private Sprite m_Sprite;
public Sprite sprite { get { return m_Sprite; } set { if (SetPropertyUtility.SetClass(ref m_Sprite, value)) SetAllDirty(); } }
[NonSerialized]
private Sprite m_OverrideSprite;
public Sprite overrideSprite { get { return m_OverrideSprite == null ? sprite : m_OverrideSprite; } set { if (SetPropertyUtility.SetClass(ref m_OverrideSprite, value)) SetAllDirty(); } }
public override Texture mainTexture
{
get
{
return overrideSprite == null ? s_WhiteTexture : overrideSprite.texture;
}
}
public float pixelsPerUnit
{
get
{
float spritePixelsPerUnit = 100;
if (sprite)
spritePixelsPerUnit = sprite.pixelsPerUnit;
float referencePixelsPerUnit = 100;
if (canvas)
referencePixelsPerUnit = canvas.referencePixelsPerUnit;
return spritePixelsPerUnit / referencePixelsPerUnit;
}
}
protected override void OnPopulateMesh(VertexHelper vh)
{
base.OnPopulateMesh(vh);
}
#region ISerializationCallbackReceiver
public void OnAfterDeserialize()
{
}
// Implement this method to receive a callback after unity serialized your object.
public void OnBeforeSerialize()
{
}
#endregion
#region ILayoutElement
public virtual void CalculateLayoutInputHorizontal() { }
public virtual void CalculateLayoutInputVertical() { }
public virtual float minWidth { get { return 0; } }
public virtual float preferredWidth
{
get
{
if (overrideSprite == null)
return 0;
return overrideSprite.rect.size.x / pixelsPerUnit;
}
}
public virtual float flexibleWidth { get { return -1; } }
public virtual float minHeight { get { return 0; } }
public virtual float preferredHeight
{
get
{
if (overrideSprite == null)
return 0;
return overrideSprite.rect.size.y / pixelsPerUnit;
}
}
public virtual float flexibleHeight { get { return -1; } }
public virtual int layoutPriority { get { return 0; } }
#endregion
#region ICanvasRaycastFilter
public virtual bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera)
{
return true;
}
#endregion
}
}
SetPropertyUtility.cs
using UnityEngine;
namespace CustomUI
{
public static class SetPropertyUtility
{
public static bool SetColor(ref Color currentValue, Color newValue)
{
if (currentValue.r == newValue.r && currentValue.g == newValue.g && currentValue.b == newValue.b && currentValue.a == newValue.a)
return false;
currentValue = newValue;
return true;
}
public static bool SetStruct<T>(ref T currentValue, T newValue) where T : struct
{
if (currentValue.Equals(newValue))
return false;
currentValue = newValue;
return true;
}
public static bool SetClass<T>(ref T currentValue, T newValue) where T : class
{
if ((currentValue == null && newValue == null) || (currentValue != null && currentValue.Equals(newValue)))
return false;
currentValue = newValue;
return true;
}
}
}
CircularImage.cs
using UnityEngine;
using UnityEngine.Sprites;
using UnityEngine.UI;
namespace CustomUI
{
// 圆形按钮
public class CircularImage : BaseImage
{
[SerializeField] public float radius = 50; // 半径
[Range(3, 100)]
[SerializeField] public int segment = 20; // 分段数
public override bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
{
RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, sp, eventCamera,
out Vector2 localPos);
float dis = Vector3.Distance(localPos, Vector3.zero);
return dis <= radius;
}
protected override void OnPopulateMesh(VertexHelper vh)
{
Color32 color32 = color;
vh.Clear();
float deltaRad = 2 * Mathf.PI / segment;
Vector3 center = Vector3.zero;
float tw = rectTransform.rect.width;
float th = rectTransform.rect.height;
Vector4 uv = overrideSprite != null ? DataUtility.GetOuterUV(overrideSprite) : Vector4.zero;
float uvCenterX = (uv.x + uv.z) * 0.5f;
float uvCenterY = (uv.y + uv.w) * 0.5f;
float uvScaleX = (uv.z - uv.x) / tw;
float uvScaleY = (uv.w - uv.y) / th;
// 顶点是以pivot为参考的
for (int i = 0; i < segment; i++)
{
float rad = deltaRad * i;
float sin = Mathf.Sin(rad);
float cos = Mathf.Cos(rad);
float x = radius * sin;
float y = radius * cos;
vh.AddVert(new Vector3(x, y), color32, new Vector2(x * uvScaleX + uvCenterX, y * uvScaleY + uvCenterY));
}
vh.AddVert(center, color32, new Vector2(uvCenterX, uvCenterY));
for (int i = 0; i < segment; i++)
{
if (i == segment - 1)
{
vh.AddTriangle(i, 0, segment);
}
else
{
vh.AddTriangle(i, i+1, segment);
}
}
}
}
}