起这个标题完全是为了区分于 《Unity 关于UGUI不规则图片响应区域解决方法》
最近看到 《Unity 关于NGUI不规则图片响应区域解决方法》 之所以要记录这个也是多自己之前项目的一个总结, 看看那里不好, 该怎么解决!。 我们卡牌游戏就是 UGUI + spine
推荐 雨凇的(看一下评论)
UGUI研究院之不规则按钮的响应区域(十四)
来自 <http://www.xuanyusong.com/archives/3492>
非常巧妙 使用 Polygon Collider2D 作为区域编辑和判断, 但是不能跟随图片的分辨率变化! 这是硬伤!
而且代码可以简化使用 Collider2D.OverlapPoint 判断点在没在多边形碰撞体中
推荐 秦元培 的总结(多边形碰撞器<还是 雨凇 的> 和 精灵像素检测 <出处 http://m.manew.com/forum.php?mod=viewthread&tid=45046&highlight=uGUI%2B%E4%B8%8D%E8%A7%84%E5%88%99&mobile=2 >)
来自 <http://blog.csdn.net/qinyuanpei/article/details/51868638>
首先指出雨凇的代码实现问题(判断一个点在没在多边形内的算法 http://geomalgorithms.com/a03-_inclusion.html )。 同时也说明了 Image.eventAlphaThreshold 的 意义用处!
开始正题吧
扩展 UGUI组件呗!
1、自己设置多边形组件(判断一个点是否在一个多边形内)。 2、就是镂空精灵(透明度)。 首先为什么要有第一种需求, 我们游戏当时使用的是Spine动画, 不是精灵, 所以当时用的 2d碰撞体。 如果用镂空精灵作为检测区域的话,就会增加游戏无用的资源, 因为不参与显示(显示的是spine动画)。所以就有了需求1. 对于2、镂空精灵, 就是 秦元培 他们网上所说的方式!也是需要Sprite资源的!
主要是根据IsRaycastLocationValid这个方法的返回值来进行判断的,而这个方法用到的基本原理则是判断指定点对应像素的RGBA数值中的Alpha是否大于某个指定临界值。
而且
public override boolIsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera)
{
//当透明度>=1.0时,表示点击在可响应区域返回true
if (m_EventAlphaThreshold >= 1)
return true;
//当没有指定精灵时为什么要返回true?
Debug.Log("射线检测");
Sprite sprite = overrideSprite;
if (sprite == null) // 注意这个,如果要想像素检测这个不能为空!, 编辑器一定要赋值一个内容
return true;
1、自己设置多边形组件
参考 我之前的博客: 《Unity游戏选/创建角色界面中职业能力图六角形》 来自 <http://blog.csdn.net/u010019717/article/details/52279010>
中的 脚本 UIPolygon.cs
我的想法是错的, 我没有办法得到 最终显示的渲染状态(颜色表)。 也就没办法脱离 贴图 做判断!, 唉~
还好想到了 Mesh能得到顶点数, 能得到三角形,判断在没在多边形内, 所有三角形内就可以了!!!!!!!! 哈哈~
using UnityEngine; using System.Collections.Generic; using UnityEngine.UI; using UnityEngine.Events; using UnityEngine.EventSystems; using UnityEngine.Assertions.Must; using UnityEngine.UI.Extensions; namespace SGD { /// <summary> /// 描述: /// author: sunguangdong /// </summary> [AddComponentMenu("SGD/PolygonButtonWithPixel")] public class PolygonButtonWithPixel : UIPrimitiveBase, IPointerClickHandler { public bool fill = true; public float thickness = 5; [Range(3, 360)] public int sides = 3; [Range(0, 360)] public float rotation = 0; [Range(0, 1)] public float[] VerticesDistances = new float[3]; private float size = 0; /// ///////////////// 针对 多边形响应区域检测 start public bool _isShowUI; public UnityEvent _ClickEvent = new UnityEvent(); public void Start() { useLegacyMeshGeneration = false; } public void OnPointerClick(PointerEventData eventData) { Debug.LogError("点击到精灵"); _ClickEvent.Invoke(); } ///// <summary> ///// 只参与 点击响应, 不参与绘制 todo 但是在编辑器下也看不到了????? ///// </summary> ///// <param name="toFill"></param> //protected override void OnPopulateMesh(VertexHelper toFill) //{ // toFill.Clear(); //} /// <summary> /// 自定义 多边形响应区域(根据Mesh内的顶点 和 三角形弄的) /// </summary> /// <param name="screenPoint"></param> /// <param name="eventCamera"></param> /// <returns></returns> public override bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera) { Vector2 local; RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, eventCamera, out local); try { return InPolygon(new Vector3(local.x, local.y, 0)); } catch (UnityException e) { Debug.LogError("Using clickAlphaThreshold lower than 1 on Image whose sprite texture cannot be read. " + e.Message + " Also make sure to disable sprite packing for this sprite.", this); return true; } } /// <summary> /// 判断一个点 在没在多边形内! /// </summary> /// <param name="_target"></param> /// <returns></returns> protected bool InPolygon(Vector3 _target) { Vector2 prevX = Vector2.zero; Vector2 prevY = Vector2.zero; Vector2 pos0; Vector2 pos1; Vector2 pos2; float degrees = 360f / sides; int vertices = sides + 1; if (VerticesDistances.Length != vertices) { VerticesDistances = new float[vertices]; for (int i = 0; i < vertices - 1; i++) VerticesDistances[i] = 1; } // last vertex is also the first! VerticesDistances[vertices - 1] = VerticesDistances[0]; for (int i = 0; i < vertices; i++) { float outer = -rectTransform.pivot.x * size * VerticesDistances[i]; float inner = -rectTransform.pivot.x * size * VerticesDistances[i] + thickness; float rad = Mathf.Deg2Rad * (i * degrees + rotation); float c = Mathf.Cos(rad); float s = Mathf.Sin(rad); pos0 = prevX; pos1 = new Vector2(outer * c, outer * s); if (fill) { pos2 = Vector2.zero; } else { pos2 = new Vector2(inner * c, inner * s); } prevX = pos1; prevY = pos2; if (InTrigon(_target, pos0, pos1, pos2)) { return true; } } return false; } /// <summary> /// 判断一个点 在没在三角形内! /// </summary> /// <param name="_target"></param> /// <param name="_center"></param> /// <param name="_left"></param> /// <param name="_right"></param> /// <returns></returns> public static bool InTrigon(Vector3 _target, Vector3 _center, Vector3 _left, Vector3 _right) { Debug.Log(_target.ToString() + _center.ToString() + _left.ToString() + _right.ToString()); Vector3 Ctl = _left - _center; Vector3 Ctr = _right - _center; Vector3 Ctt = _target - _center; Vector3 Ltr = _right - _left; Vector3 Ltc = _right - _center; Vector3 Ltt = _left - _target; Vector3 Rtl = _left - _right; Vector3 Rtc = _center - _right; Vector3 Rtt = _target - _right; if ( Vector3.Dot(Vector3.Cross(Ctl, Ctr).normalized, Vector3.Cross(Ctl, Ctt).normalized) == 1 && Vector3.Dot(Vector3.Cross(Ltr, Ltc).normalized, Vector3.Cross(Ltr, Ltt).normalized) == 1 && Vector3.Dot(Vector3.Cross(Rtc, Rtl).normalized, Vector3.Cross(Rtc, Rtt).normalized) == 1 ) return true; else return false; } /// ///////////////////// 针对 多边形响应区域检测 end public void DrawPolygon(int _sides) { sides = _sides; VerticesDistances = new float[_sides + 1]; for (int i = 0; i < _sides; i++) VerticesDistances[i] = 1; ; rotation = 0; } public void DrawPolygon(int _sides, float[] _VerticesDistances) { sides = _sides; VerticesDistances = _VerticesDistances; rotation = 0; } public void DrawPolygon(int _sides, float[] _VerticesDistances, float _rotation) { sides = _sides; VerticesDistances = _VerticesDistances; rotation = _rotation; } void Update() { size = rectTransform.rect.width; if (rectTransform.rect.width > rectTransform.rect.height) size = rectTransform.rect.height; else size = rectTransform.rect.width; thickness = (float)Mathf.Clamp(thickness, 0, size / 2); } protected override void OnPopulateMesh(VertexHelper vh) { vh.Clear(); if (_isShowUI) { Vector2 prevX = Vector2.zero; Vector2 prevY = Vector2.zero; Vector2 uv0 = new Vector2(0, 0); Vector2 uv1 = new Vector2(0, 1); Vector2 uv2 = new Vector2(1, 1); Vector2 uv3 = new Vector2(1, 0); Vector2 pos0; Vector2 pos1; Vector2 pos2; Vector2 pos3; float degrees = 360f / sides; int vertices = sides + 1; if (VerticesDistances.Length != vertices) { VerticesDistances = new float[vertices]; for (int i = 0; i < vertices - 1; i++) VerticesDistances[i] = 1; } // last vertex is also the first! VerticesDistances[vertices - 1] = VerticesDistances[0]; for (int i = 0; i < vertices; i++) { float outer = -rectTransform.pivot.x * size * VerticesDistances[i]; float inner = -rectTransform.pivot.x * size * VerticesDistances[i] + thickness; float rad = Mathf.Deg2Rad * (i * degrees + rotation); float c = Mathf.Cos(rad); float s = Mathf.Sin(rad); uv0 = new Vector2(0, 1); uv1 = new Vector2(1, 1); uv2 = new Vector2(1, 0); uv3 = new Vector2(0, 0); pos0 = prevX; pos1 = new Vector2(outer * c, outer * s); if (fill) { pos2 = Vector2.zero; pos3 = Vector2.zero; } else { pos2 = new Vector2(inner * c, inner * s); pos3 = prevY; } prevX = pos1; prevY = pos2; vh.AddUIVertexQuad(SetVbo(new[] { pos0, pos1, pos2, pos3 }, new[] { uv0, uv1, uv2, uv3 })); } } } } }
using System; namespace UnityEngine.UI.Extensions { public class UIPrimitiveBase : MaskableGraphic, ILayoutElement, ICanvasRaycastFilter { [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(); } } // Not serialized until we support read-enabled sprites better. internal float m_EventAlphaThreshold = 1; public float eventAlphaThreshold { get { return m_EventAlphaThreshold; } set { m_EventAlphaThreshold = value; } } /// <summary> /// Image's texture comes from the UnityEngine.Image. /// </summary> public override Texture mainTexture { get { if (overrideSprite == null) { if (material != null && material.mainTexture != null) { return material.mainTexture; } return s_WhiteTexture; } return 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 UIVertex[] SetVbo(Vector2[] vertices, Vector2[] uvs) { UIVertex[] vbo = new UIVertex[4]; for (int i = 0; i < vertices.Length; i++) { var vert = UIVertex.simpleVert; vert.color = color; vert.position = vertices[i]; vert.uv0 = uvs[i]; vbo[i] = vert; } return vbo; } #region ILayoutElement Interface 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 Interface public virtual bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera) { if (m_EventAlphaThreshold >= 1) return true; Sprite sprite = overrideSprite; if (sprite == null) return true; Vector2 local; RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, eventCamera, out local); Rect rect = GetPixelAdjustedRect(); // Convert to have lower left corner as reference point. local.x += rectTransform.pivot.x * rect.width; local.y += rectTransform.pivot.y * rect.height; local = MapCoordinate(local, rect); // Normalize local coordinates. Rect spriteRect = sprite.textureRect; Vector2 normalized = new Vector2(local.x / spriteRect.width, local.y / spriteRect.height); // Convert to texture space. float x = Mathf.Lerp(spriteRect.x, spriteRect.xMax, normalized.x) / sprite.texture.width; float y = Mathf.Lerp(spriteRect.y, spriteRect.yMax, normalized.y) / sprite.texture.height; try { return sprite.texture.GetPixelBilinear(x, y).a >= m_EventAlphaThreshold; } catch (UnityException e) { Debug.LogError("Using clickAlphaThreshold lower than 1 on Image whose sprite texture cannot be read. " + e.Message + " Also make sure to disable sprite packing for this sprite.", this); return true; } } /// <summary> /// Return image adjusted position /// **Copied from Unity's Image component for now and simplified for UI Extensions primatives /// </summary> /// <param name="local"></param> /// <param name="rect"></param> /// <returns></returns> private Vector2 MapCoordinate(Vector2 local, Rect rect) { Rect spriteRect = sprite.rect; return new Vector2(local.x * spriteRect.width / rect.width, local.y * spriteRect.height / rect.height); } Vector4 GetAdjustedBorders(Vector4 border, Rect rect) { for (int axis = 0; axis <= 1; axis++) { float combinedBorders = border[axis] + border[axis + 2]; if (rect.size[axis] < combinedBorders && combinedBorders != 0) { float borderScaleRatio = rect.size[axis] / combinedBorders; border[axis] *= borderScaleRatio; border[axis + 2] *= borderScaleRatio; } } return border; } #endregion } }
2、镂空精灵
镂空就是 透明的区域不接受检测,不透明区域接受检测。 透明不透明的指标自己定吧!
using UnityEngine; using System.Collections.Generic; using UnityEngine.UI; using UnityEngine.EventSystems; using UnityEngine.Assertions.Must; namespace SGD { /// <summary> /// 描述: /// author: sunguangdong /// </summary> [AddComponentMenu("SGD/UnregularButtonWithPixel ")] [RequireComponent(typeof(Image))] public class UnregularButtonWithPixel : MonoBehaviour, IPointerClickHandler { /// <summary> /// Image组件 /// </summary> private Image _image; /// <summary> /// 透明度临界值 /// </summary> [Range(0.0f, 0.5f)] public float _Alpha; // 编辑器脚本 start void Reset() { _image = transform.GetComponent<Image>(); } public void OnValidate() { MustExtensions.MustBeFalse(!_image , "UnregularButtonWithPixel 脚本的 Inspector 面板的赋值 不全!"); } // 编辑器脚本 end public void Start() { //获取Image组件 _image = transform.GetComponent<Image>(); //设定透明度临界值 _image.eventAlphaThreshold = _Alpha; } public void OnPointerClick(PointerEventData eventData) { Debug.Log("点击到精灵"); } } }