起这个标题完全是为了区分于 《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("点击到精灵");
- }
- }
- }