IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    [原]Unity 关于Spine不规则响应区域解决方法

    u010019717发表于 2016-11-08 09:30:45
    love 0
    孙广东   2016.10.15

    http://blog.csdn.net/u010019717



                 起这个标题完全是为了区分于 《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 >)

    Unity3D游戏开发之在uGUI中使用不规则精灵制作按钮

     

    来自 <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
    
    
        }
    }
    



               其中要说明的    1、  是否参与绘制 _isShowUI  (只是接受检测,涉及到填充率, 在编辑器下编辑完多边形测试OK,就设置为false) 。   2、算法不涉及到  镂空精灵的算法,所以可以设置Sprite属性。







    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("点击到精灵");
            }
        }
    }
    

                 Canvas 下的空对象上  添加这个组件  测试





    提醒,  检测的开关还有  (这两种方式  都会受到  下面的开关影响!)





沪ICP备19023445号-2号
友情链接