天天看點

UGUI 擴充集

孫廣東   2015.8.10     基于Unity5.2版本

1、UIButton 單獨可使用        這個腳本可以 提供外部監聽按下/點選/釋放的3個事件(參數附帶着資料)。  源官方Button隻有 點選事件

using UnityEngine.Events;
using UnityEngine.EventSystems;

namespace UnityEngine.UI.Extensions
{
    /// <summary>
    /// UIButton
    /// </summary>
    [AddComponentMenu("UI/Extensions/UI Button")]
    public class UIButton : Button, IPointerDownHandler, IPointerUpHandler
    {
        #region Sub-Classes
        [System.Serializable]
        public class UIButtonEvent : UnityEvent<PointerEventData.InputButton> { }
        #endregion

        #region Events
        public UIButtonEvent OnButtonClick;
        public UIButtonEvent OnButtonPress;
        public UIButtonEvent OnButtonRelease;
        #endregion

        public override void OnPointerClick(PointerEventData eventData)
        {
            base.OnSubmit(eventData);

            if (OnButtonClick != null)
            {
                OnButtonClick.Invoke(eventData.button);
            }
        }


        void IPointerDownHandler.OnPointerDown(PointerEventData eventData)
        {
            DoStateTransition(SelectionState.Pressed, false);

            if (OnButtonPress != null)
            {
                OnButtonPress.Invoke(eventData.button);
            }
        }


        void IPointerUpHandler.OnPointerUp(PointerEventData eventData)
        {
            DoStateTransition(SelectionState.Normal, false);

            if (OnButtonRelease != null)
            {
                OnButtonRelease.Invoke(eventData.button);
            }
        }
    }
}
           

2、UIFlippable  單獨可使用  我需要一些時間現在翻轉精靈和使用scale解決方案被證明是一個糟糕的主意,當使用布局網格等,是以試着寫一個小腳本來翻轉頂點相反。它目前做這份工作對我來說,我想分享它是以它在這兒......

     這種翻轉還有兩種實作方式: 設定 Rotation x/y =>180,    Scale x/y =>-1;

using System.Collections.Generic;

namespace UnityEngine.UI.Extensions
{
    [RequireComponent(typeof(RectTransform), typeof(Graphic)), DisallowMultipleComponent]
    [AddComponentMenu("UI/Effects/Extensions/Flippable")]
    public class UIFlippable : MonoBehaviour, IMeshModifier
    {     
        [SerializeField] private bool m_Horizontal = false;
        [SerializeField] private bool m_Veritical = false;
     
        /// <summary>
        /// 擷取或設定一個值,該值訓示是否應水準翻轉
        /// </summary>
        /// <value><c>true</c> if horizontal; otherwise, <c>false</c>.</value>
        public bool horizontal
        {
            get { return this.m_Horizontal; }
            set { this.m_Horizontal = value; }
        }
     
        /// <summary>
        /// 擷取或設定一個值,該值訓示是否應垂直翻轉
        /// </summary>
        /// <value><c>true</c> if vertical; otherwise, <c>false</c>.</value>
        public bool vertical
        {
            get { return this.m_Veritical; }
            set { this.m_Veritical = value; }
        }
     
        protected void OnValidate()
        {
            this.GetComponent<Graphic>().SetVerticesDirty();
        }
     
		// 從mesh 得到 頂點集
        public void ModifyMesh(/*List<UIVertex> verts*/ Mesh mesh)
        {
			List<UIVertex> verts = new List<UIVertex> ();
			using (VertexHelper vertexHelper = new VertexHelper (mesh))
			{
				vertexHelper.GetUIVertexStream (verts);
			}

            RectTransform rt = this.transform as RectTransform;
         
            for (int i = 0; i < verts.Count; ++i)
            {
                UIVertex v = verts[i];
             
                // Modify positions
                v.position = new Vector3(
                    (this.m_Horizontal ? (v.position.x + (rt.rect.center.x - v.position.x) * 2) : v.position.x),
                    (this.m_Veritical ?  (v.position.y + (rt.rect.center.y - v.position.y) * 2) : v.position.y),
                    v.position.z
                );
             
                // Apply
                verts[i] = v;
            }

			// 在合成mesh
			using (VertexHelper vertexHelper2 = new VertexHelper ())
			{
				vertexHelper2.AddUIVertexTriangleStream (verts);
				vertexHelper2.FillMesh (mesh);
			}
        }
    }
}
           

3、uGUITools   單獨可使用        // 讓你更了解 錨點、RectTransform等,   Editor腳本

using UnityEditor;
namespace UnityEngine.UI.Extensions
{
    public class uGUITools : MonoBehaviour
    {
        [MenuItem("uGUI/Anchors to Corners %[")]
        static void AnchorsToCorners()
        {
            foreach (Transform transform in Selection.transforms)
            {
                RectTransform t = transform as RectTransform;
                RectTransform pt = Selection.activeTransform.parent as RectTransform;

                if (t == null || pt == null) return;

                Vector2 newAnchorsMin = new Vector2(t.anchorMin.x + t.offsetMin.x / pt.rect.width,
                                                    t.anchorMin.y + t.offsetMin.y / pt.rect.height);
                Vector2 newAnchorsMax = new Vector2(t.anchorMax.x + t.offsetMax.x / pt.rect.width,
                                                    t.anchorMax.y + t.offsetMax.y / pt.rect.height);

                t.anchorMin = newAnchorsMin;
                t.anchorMax = newAnchorsMax;
                t.offsetMin = t.offsetMax = new Vector2(0, 0);
            }
        }

        [MenuItem("uGUI/Corners to Anchors %]")]
        static void CornersToAnchors()
        {
            foreach (Transform transform in Selection.transforms)
            {
                RectTransform t = transform as RectTransform;

                if (t == null) return;

                t.offsetMin = t.offsetMax = new Vector2(0, 0);
            }
        }

        [MenuItem("uGUI/Mirror Horizontally Around Anchors %;")]
        static void MirrorHorizontallyAnchors()
        {
            MirrorHorizontally(false);
        }

        [MenuItem("uGUI/Mirror Horizontally Around Parent Center %:")]
        static void MirrorHorizontallyParent()
        {
            MirrorHorizontally(true);
        }

        static void MirrorHorizontally(bool mirrorAnchors)
        {
            foreach (Transform transform in Selection.transforms)
            {
                RectTransform t = transform as RectTransform;
                RectTransform pt = Selection.activeTransform.parent as RectTransform;

                if (t == null || pt == null) return;

                if (mirrorAnchors)
                {
                    Vector2 oldAnchorMin = t.anchorMin;
                    t.anchorMin = new Vector2(1 - t.anchorMax.x, t.anchorMin.y);
                    t.anchorMax = new Vector2(1 - oldAnchorMin.x, t.anchorMax.y);
                }

                Vector2 oldOffsetMin = t.offsetMin;
                t.offsetMin = new Vector2(-t.offsetMax.x, t.offsetMin.y);
                t.offsetMax = new Vector2(-oldOffsetMin.x, t.offsetMax.y);

                t.localScale = new Vector3(-t.localScale.x, t.localScale.y, t.localScale.z);
            }
        }

        [MenuItem("uGUI/Mirror Vertically Around Anchors %'")]
        static void MirrorVerticallyAnchors()
        {
            MirrorVertically(false);
        }

        [MenuItem("uGUI/Mirror Vertically Around Parent Center %\"")]
        static void MirrorVerticallyParent()
        {
            MirrorVertically(true);
        }

        static void MirrorVertically(bool mirrorAnchors)
        {
            foreach (Transform transform in Selection.transforms)
            {
                RectTransform t = transform as RectTransform;
                RectTransform pt = Selection.activeTransform.parent as RectTransform;

                if (t == null || pt == null) return;

                if (mirrorAnchors)
                {
                    Vector2 oldAnchorMin = t.anchorMin;
                    t.anchorMin = new Vector2(t.anchorMin.x, 1 - t.anchorMax.y);
                    t.anchorMax = new Vector2(t.anchorMax.x, 1 - oldAnchorMin.y);
                }

                Vector2 oldOffsetMin = t.offsetMin;
                t.offsetMin = new Vector2(t.offsetMin.x, -t.offsetMax.y);
                t.offsetMax = new Vector2(t.offsetMax.x, -oldOffsetMin.y);

                t.localScale = new Vector3(t.localScale.x, -t.localScale.y, t.localScale.z);
            }
        }
    }
}
           

4、ToolTip 【需要外部調用函數顯示和隐藏内容】請注意--目前情況下僅适用于Screenspace Camera的畫布設定,要包括Screenspace and Worldspace需要更新

namespace UnityEngine.UI.Extensions
{
    [RequireComponent(typeof(RectTransform))]
    [AddComponentMenu("UI/Extensions/Tooltip")]
    public class ToolTip : MonoBehaviour
    {
        //text of the tooltip
        private Text _text;
        private RectTransform _rectTransform;

        //if the tooltip is inside a UI element
        private bool _inside;

        private bool _xShifted, _yShifted = false;

        private float width, height, canvasWidth, canvasHeight;

        private int screenWidth, screenHeight;

        private float YShift,xShift;

        private RenderMode _guiMode;

        private Camera _guiCamera;

        // Use this for initialization
        public void Awake()
        {
            var _canvas = GetComponentInParent<Canvas>();
            _guiCamera = _canvas.worldCamera;
            _guiMode = _canvas.renderMode;
            _rectTransform = GetComponent<RectTransform>();

            _text = GetComponentInChildren<Text>();

            _inside = false;

            //size of the screen
            screenWidth = Screen.width;
            screenHeight = Screen.height;

            xShift = 0f;
            YShift = -30f;

            _xShifted = _yShifted = false;


            this.gameObject.SetActive(false);

        }

        //Call this function externally to set the text of the template and activate the tooltip
        public void SetTooltip(string ttext)
        {

            if (_guiMode == RenderMode.ScreenSpaceCamera)
            {
                //set the text and fit the tooltip panel to the text size
                _text.text = ttext;

                _rectTransform.sizeDelta = new Vector2(_text.preferredWidth + 40f, _text.preferredHeight + 25f);

                OnScreenSpaceCamera();

            }
        }

        //call this function on mouse exit to deactivate the template
        public void HideTooltip()
        {
            if (_guiMode == RenderMode.ScreenSpaceCamera)
            {
                this.gameObject.SetActive(false);
                _inside = false;
            }
        }

        // Update is called once per frame
        void FixedUpdate()
        {
            if (_inside)
            {
                if (_guiMode == RenderMode.ScreenSpaceCamera)
                {
                    OnScreenSpaceCamera();
                }
            }
        }

        //main tooltip edge of screen guard and movement
        public void OnScreenSpaceCamera()
        {
            Vector3 newPos = _guiCamera.ScreenToViewportPoint(Input.mousePosition - new Vector3(xShift, YShift, 0f));
            Vector3 newPosWVP = _guiCamera.ViewportToWorldPoint(newPos);

            width = _rectTransform.sizeDelta[0];
            height = _rectTransform.sizeDelta[1];

            // check and solve problems for the tooltip that goes out of the screen on the horizontal axis
            float val;

            Vector3 lowerLeft = _guiCamera.ViewportToWorldPoint(new Vector3(0.0f, 0.0f, 0.0f));
            Vector3 upperRight = _guiCamera.ViewportToWorldPoint(new Vector3(1.0f, 1.0f, 0.0f));

            //check for right edge of screen
            val = (newPosWVP.x + width / 2);
            if (val > upperRight.x)
            {
                Vector3 shifter = new Vector3(val - upperRight.x, 0f, 0f);
                Vector3 newWorldPos = new Vector3(newPosWVP.x - shifter.x, newPos.y, 0f);
                newPos.x = _guiCamera.WorldToViewportPoint(newWorldPos).x;
            }
            //check for left edge of screen
            val = (newPosWVP.x - width / 2);
            if (val < lowerLeft.x)
            {
                Vector3 shifter = new Vector3(lowerLeft.x - val, 0f, 0f);
                Vector3 newWorldPos = new Vector3(newPosWVP.x + shifter.x, newPos.y, 0f);
                newPos.x = _guiCamera.WorldToViewportPoint(newWorldPos).x;
            }

            // check and solve problems for the tooltip that goes out of the screen on the vertical axis

            //check for upper edge of the screen
            val = (newPosWVP.y + height / 2);
            if (val > upperRight.y)
            {
                Vector3 shifter = new Vector3(0f, 35f + height / 2, 0f);
                Vector3 newWorldPos = new Vector3(newPos.x, newPosWVP.y - shifter.y, 0f);
                newPos.y = _guiCamera.WorldToViewportPoint(newWorldPos).y;
            }

            //check for lower edge of the screen (if the shifts of the tooltip are kept as in this code, no need for this as the tooltip always appears above the mouse bu default)
            val = (newPosWVP.y - height / 2);
            if (val < lowerLeft.y)
            {
                Vector3 shifter = new Vector3(0f, 35f + height / 2, 0f);
                Vector3 newWorldPos = new Vector3(newPos.x, newPosWVP.y + shifter.y, 0f);
                newPos.y = _guiCamera.WorldToViewportPoint(newWorldPos).y;
            }

            this.transform.position = new Vector3(newPosWVP.x, newPosWVP.y, 0f);
            this.gameObject.SetActive(true);
            _inside = true;
        }
    }
}
           

5、 ReturnKeyTriggersButton    首先肯定是要配合 InputField 元件使用,是處理Enter鍵, 【剛剛試了試 InputField 元件 很強大的哦】

using UnityEngine.EventSystems;
namespace UnityEngine.UI
{
    [RequireComponent(typeof(InputField))]
    [AddComponentMenu("UI/Extensions/Return Key Trigger")]
    public class ReturnKeyTriggersButton : MonoBehaviour, ISubmitHandler
    {
        private EventSystem _system;

        public Button button;
        private bool highlight = true;
        public float highlightDuration = 0.2f;
        
        void Start()
        {
            _system = EventSystem.current;
        }

        void RemoveHighlight()
        {
            button.OnPointerExit(new PointerEventData(_system));
        }

        public void OnSubmit(BaseEventData eventData)
        {
            if (highlight) button.OnPointerEnter(new PointerEventData(_system));
            button.OnPointerClick(new PointerEventData(_system));

            if (highlight) Invoke("RemoveHighlight", highlightDuration);
        }
    }
}
           

6、 NicerOutline  和系統的 Outline效果上沒有差別,但是實作方式不同可以好好研究一下

using System.Collections.Generic;
namespace UnityEngine.UI.Extensions
{
	//An outline that looks a bit nicer than the default one. It has less "holes" in the outline by drawing more copies of the effect
	[AddComponentMenu("UI/Effects/Extensions/Nicer Outline")]
	public class NicerOutline : BaseMeshEffect
	{
		[SerializeField]
		private Color m_EffectColor = new Color (0f, 0f, 0f, 0.5f);
		
		[SerializeField]
		private Vector2 m_EffectDistance = new Vector2 (1f, -1f);
		
		[SerializeField]
		private bool m_UseGraphicAlpha = true;
		//
		// Properties
		//
		public Color effectColor
		{
			get
			{
				return this.m_EffectColor;
			}
			set
			{
				this.m_EffectColor = value;
				if (base.graphic != null)
				{
					base.graphic.SetVerticesDirty ();
				}
			}
		}
		
		public Vector2 effectDistance
		{
			get
			{
				return this.m_EffectDistance;
			}
			set
			{
				if (value.x > 600f)
				{
					value.x = 600f;
				}
				if (value.x < -600f)
				{
					value.x = -600f;
				}
				if (value.y > 600f)
				{
					value.y = 600f;
				}
				if (value.y < -600f)
				{
					value.y = -600f;
				}
				if (this.m_EffectDistance == value)
				{
					return;
				}
				this.m_EffectDistance = value;
				if (base.graphic != null)
				{
					base.graphic.SetVerticesDirty ();
				}
			}
		}
		
		public bool useGraphicAlpha
		{
			get
			{
				return this.m_UseGraphicAlpha;
			}
			set
			{
				this.m_UseGraphicAlpha = value;
				if (base.graphic != null)
				{
					base.graphic.SetVerticesDirty ();
				}
			}
		}
		

		//
		// Methods
		//
		protected void ApplyShadow (List<UIVertex> verts, Color32 color, int start, int end, float x, float y)
		{
			//Debug.Log("verts count: "+verts.Count);
			int num = verts.Count * 2;
			if (verts.Capacity < num)
			{
				verts.Capacity = num;
			}
			for (int i = start; i < end; i++)
			{
				UIVertex uIVertex = verts [i];
				verts.Add (uIVertex);

				Vector3 position = uIVertex.position;
				//Debug.Log("vertex pos: "+position);
				position.x += x;
				position.y += y;
				uIVertex.position = position;
				Color32 color2 = color;
				if (this.m_UseGraphicAlpha)
				{
					color2.a = (byte)(color2.a * verts [i].color.a / 255);
				}
				uIVertex.color = color2;
				//uIVertex.color = (Color32)Color.blue;
				verts [i] = uIVertex;
			}
		}
		
		public override void ModifyMesh (/*List<UIVertex> verts*/ Mesh mesh)
		{
			if (!this.IsActive ())
			{
				return;
			}
			// 從mesh 得到 頂點集
			List<UIVertex> verts = new List<UIVertex> ();
			using (VertexHelper vertexHelper = new VertexHelper (mesh))
			{
				vertexHelper.GetUIVertexStream (verts);
			}

			Text foundtext = GetComponent<Text>();
			
			float best_fit_adjustment = 1f;
			
			if (foundtext && foundtext.resizeTextForBestFit)  
			{
				best_fit_adjustment = (float)foundtext.cachedTextGenerator.fontSizeUsedForBestFit / (foundtext.resizeTextMaxSize-1); //max size seems to be exclusive 

			}

			float distanceX = this.effectDistance.x * best_fit_adjustment;
			float distanceY = this.effectDistance.y * best_fit_adjustment;

			int start = 0;
			int count = verts.Count;
			this.ApplyShadow (verts, this.effectColor, start, verts.Count, distanceX, distanceY);
			start = count;
			count = verts.Count;
			this.ApplyShadow (verts, this.effectColor, start, verts.Count, distanceX, -distanceY);
			start = count;
			count = verts.Count;
			this.ApplyShadow (verts, this.effectColor, start, verts.Count, -distanceX, distanceY);
			start = count;
			count = verts.Count;
			this.ApplyShadow (verts, this.effectColor, start, verts.Count, -distanceX, -distanceY);

			start = count;
			count = verts.Count;
			this.ApplyShadow (verts, this.effectColor, start, verts.Count, distanceX, 0);
			start = count;
			count = verts.Count;
			this.ApplyShadow (verts, this.effectColor, start, verts.Count, -distanceX, 0);

			start = count;
			count = verts.Count;
			this.ApplyShadow (verts, this.effectColor, start, verts.Count, 0, distanceY);
			start = count;
			count = verts.Count;
			this.ApplyShadow (verts, this.effectColor, start, verts.Count, 0, -distanceY);

			// 在合成mesh
			using (VertexHelper vertexHelper2 = new VertexHelper ())
			{
				vertexHelper2.AddUIVertexTriangleStream (verts);
				vertexHelper2.FillMesh (mesh);
			}
		}
		
		protected override void OnValidate ()
		{
			this.effectDistance = this.m_EffectDistance;
			base.OnValidate ();
		}
	}
}
           

7、 LetterSpacing 單獨使用 每個字元間可以設定間距了!【uGUI Text 元件支援行間距,但不是字元間距。 問題是-text layout 引擎不是開源的的一部分,你無法拿到合适的 API,一種解決辦法是,隻需修改現有的字型,每個字元添加額外的空間。這是那樣難以管理,它的聲音,和我到其中有很多的不同間隔标題工作不切實際的最近的設計。我需要為每個不同的間距不同的字型。    我最好的解決辦法是,隻需修改現有的字型fonts ,每個字元添加額外的空間。這樣難以管理,我到其中有很多的不同間隔标題工作不切實際的設計。我需要為每個不同的間距不同的字型。     然後偶然看了看Shadow 效果,使用 BaseVertexEffect 來修改繪制的源對象的幾何形狀的頂點清單。和這個技術相同,文本 間距(雖然它不會産生任何額外的verts)。将元件添加到 UI 文本字段,調整間距值,完成工作。但它不能很好與rich text或自動換行的文本工作】

using System.Collections.Generic;

namespace UnityEngine.UI.Extensions
{
	[AddComponentMenu("UI/Effects/Extensions/Letter Spacing")]
	public class LetterSpacing : BaseMeshEffect
	{
		[SerializeField]
		private float m_spacing = 0f;
		
		protected LetterSpacing() { }
		
		#if UNITY_EDITOR
		protected override void OnValidate()
		{
			spacing = m_spacing;
			base.OnValidate();
		}
		#endif
		
		public float spacing
		{
			get { return m_spacing; }
			set
			{
				if (m_spacing == value) return;
				m_spacing = value;
				if (graphic != null) graphic.SetVerticesDirty();
			}
		}
		
		public override void ModifyMesh(/*List<UIVertex> verts*/Mesh mesh)
		{
			if (! IsActive()) return;

			// 從mesh 得到 頂點集
			List<UIVertex> verts = new List<UIVertex> ();
			using (VertexHelper vertexHelper = new VertexHelper (mesh))
			{
				vertexHelper.GetUIVertexStream (verts);
			}
			
			Text text = GetComponent<Text>();
			if (text == null)
			{
				Debug.LogWarning("LetterSpacing: Missing Text component");
				return;
			}
			
			string[] lines = text.text.Split('\n');
			Vector3  pos;
			float    letterOffset    = spacing * (float)text.fontSize / 100f;
			float    alignmentFactor = 0;
			int      glyphIdx        = 0;
			
			switch (text.alignment)
			{
			case TextAnchor.LowerLeft:
			case TextAnchor.MiddleLeft:
			case TextAnchor.UpperLeft:
				alignmentFactor = 0f;
				break;
				
			case TextAnchor.LowerCenter:
			case TextAnchor.MiddleCenter:
			case TextAnchor.UpperCenter:
				alignmentFactor = 0.5f;
				break;
				
			case TextAnchor.LowerRight:
			case TextAnchor.MiddleRight:
			case TextAnchor.UpperRight:
				alignmentFactor = 1f;
				break;
			}
			
			for (int lineIdx=0; lineIdx < lines.Length; lineIdx++)
			{
				string line = lines[lineIdx];
				float lineOffset = (line.Length -1) * letterOffset * alignmentFactor;
				
				for (int charIdx = 0; charIdx < line.Length; charIdx++)
				{
					int idx1 = glyphIdx * 4 + 0;
					int idx2 = glyphIdx * 4 + 1;
					int idx3 = glyphIdx * 4 + 2;
					int idx4 = glyphIdx * 4 + 3;
					
					// Check for truncated text (doesn't generate verts for all characters)
					if (idx4 > verts.Count - 1) return;
					
					UIVertex vert1 = verts[idx1];
					UIVertex vert2 = verts[idx2];
					UIVertex vert3 = verts[idx3];
					UIVertex vert4 = verts[idx4];
					
					pos = Vector3.right * (letterOffset * charIdx - lineOffset);
					
					vert1.position += pos;
					vert2.position += pos;
					vert3.position += pos;
					vert4.position += pos;
					
					verts[idx1] = vert1;
					verts[idx2] = vert2;
					verts[idx3] = vert3;
					verts[idx4] = vert4;
					
					glyphIdx++;
				}
				
				// Offset for carriage return character that still generates verts
				glyphIdx++;
			}

			// 在合成mesh
			using (VertexHelper vertexHelper2 = new VertexHelper ())
			{
				vertexHelper2.AddUIVertexTriangleStream (verts);
				vertexHelper2.FillMesh (mesh);
			}
		}
	}
}
           

8、 Gradient    和之前發表的blog差不多吧,這個是可以對Text文本内容的而且有global/local模式,選項更多了【可以研究源碼】

using System.Collections.Generic;

namespace UnityEngine.UI.Extensions
{
	// [RequireComponent(typeof(Text), typeof(RectTransform))]
    [AddComponentMenu("UI/Effects/Extensions/Gradient")]
    public class Gradient : BaseMeshEffect
    {
        public GradientMode gradientMode = GradientMode.Global;
        public GradientDir gradientDir = GradientDir.Vertical;
        public bool overwriteAllColor = false;
        public Color vertex1 = Color.white;
        public Color vertex2 = Color.black;
        private Graphic targetGraphic;

        protected override void Start()
        {
            targetGraphic = GetComponent<Graphic>();
        }

		public override void ModifyMesh(/*List<UIVertex> vertexList*/ Mesh mesh)
        {
			// 從mesh 得到 頂點集
			List<UIVertex> vertexList = new List<UIVertex> ();
			using (VertexHelper vertexHelper = new VertexHelper (mesh))
			{
				vertexHelper.GetUIVertexStream (vertexList);
			}

            if (!IsActive() || vertexList.Count == 0)
            {
                return;
            }
            int count = vertexList.Count;
            UIVertex uiVertex = vertexList[0];
            if (gradientMode == GradientMode.Global)
            {
                if (gradientDir == GradientDir.DiagonalLeftToRight || gradientDir == GradientDir.DiagonalRightToLeft)
                {
#if UNITY_EDITOR
                    Debug.LogWarning("Diagonal dir is not supported in Global mode");
#endif
                    gradientDir = GradientDir.Vertical;
                }
                float bottomY = gradientDir == GradientDir.Vertical ? vertexList[vertexList.Count - 1].position.y : vertexList[vertexList.Count - 1].position.x;
                float topY = gradientDir == GradientDir.Vertical ? vertexList[0].position.y : vertexList[0].position.x;

                float uiElementHeight = topY - bottomY;

                for (int i = 0; i < count; i++)
                {
                    uiVertex = vertexList[i];
                    if (!overwriteAllColor && uiVertex.color != targetGraphic.color)
                        continue;
                    uiVertex.color *= Color.Lerp(vertex2, vertex1, ((gradientDir == GradientDir.Vertical ? uiVertex.position.y : uiVertex.position.x) - bottomY) / uiElementHeight);
                    vertexList[i] = uiVertex;
                }
            }
            else
            {
                for (int i = 0; i < count; i++)
                {
                    uiVertex = vertexList[i];
                    if (!overwriteAllColor && !CompareCarefully(uiVertex.color, targetGraphic.color))
                        continue;
                    switch (gradientDir)
                    {
                        case GradientDir.Vertical:
                            uiVertex.color *= (i % 4 == 0 || (i - 1) % 4 == 0) ? vertex1 : vertex2;
                            break;
                        case GradientDir.Horizontal:
                            uiVertex.color *= (i % 4 == 0 || (i - 3) % 4 == 0) ? vertex1 : vertex2;
                            break;
                        case GradientDir.DiagonalLeftToRight:
                            uiVertex.color *= (i % 4 == 0) ? vertex1 : ((i - 2) % 4 == 0 ? vertex2 : Color.Lerp(vertex2, vertex1, 0.5f));
                            break;
                        case GradientDir.DiagonalRightToLeft:
                            uiVertex.color *= ((i - 1) % 4 == 0) ? vertex1 : ((i - 3) % 4 == 0 ? vertex2 : Color.Lerp(vertex2, vertex1, 0.5f));
                            break;

                    }
                    vertexList[i] = uiVertex;
                }
            }

			// 在合成mesh
			using (VertexHelper vertexHelper2 = new VertexHelper ())
			{
				vertexHelper2.AddUIVertexTriangleStream (vertexList);
				vertexHelper2.FillMesh (mesh);
			}
        }
        private bool CompareCarefully(Color col1, Color col2)
        {
            if (Mathf.Abs(col1.r - col2.r) < 0.003f && Mathf.Abs(col1.g - col2.g) < 0.003f && Mathf.Abs(col1.b - col2.b) < 0.003f && Mathf.Abs(col1.a - col2.a) < 0.003f)
                return true;
            return false;
        }
    }

    public enum GradientMode
    {
        Global,
        Local
    }

    public enum GradientDir
    {
        Vertical,
        Horizontal,
        DiagonalLeftToRight,
        DiagonalRightToLeft
        //Free
    }
    //enum color mode Additive, Multiply, Overwrite
}
           

9、UIWindowBase      挂上之後, 可以拖動UI元素

using System;
using UnityEngine.EventSystems;

namespace UnityEngine.UI.Extensions
{
    /// <summary>
    /// Includes a few fixes of my own, mainly to tidy up duplicates, remove unneeded stuff and testing. (nothing major, all the crew above did the hard work!)
    /// </summary>
    [RequireComponent(typeof(RectTransform))]
    [AddComponentMenu("UI/Extensions/UI Window Base")]
    public class UIWindowBase : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
    {
        RectTransform m_transform = null;
        private bool _isDragging = false;
        public static bool ResetCoords = false;
        private Vector3 m_originalCoods = Vector3.zero;
        private Canvas m_canvas;
        private RectTransform m_canvasRectTransform;
        public int KeepWindowInCanvas = 5;            // # of pixels of the window that must stay inside the canvas view.

        // Use this for initialization
        void Start()
        {
            m_transform = GetComponent<RectTransform>();
            m_originalCoods = m_transform.position;
            m_canvas = GetComponentInParent<Canvas>();
            m_canvasRectTransform = m_canvas.GetComponent<RectTransform>();
        }

        void Update()
        {
            if (ResetCoords)
                resetCoordinatePosition();
        }

        public void OnDrag(PointerEventData eventData)
        {
            if (_isDragging)
            {
                var delta = ScreenToCanvas(eventData.position) - ScreenToCanvas(eventData.position - eventData.delta);
                m_transform.localPosition += delta;
            }
        }

        //Note, the begin drag and end drag aren't actually needed to control the drag.  However, I'd recommend keeping it in case you want to do somethind else when draggging starts and stops
        public void OnBeginDrag(PointerEventData eventData)
        {

            if (eventData.pointerCurrentRaycast.gameObject == null)
                return;

            if (eventData.pointerCurrentRaycast.gameObject.name == name)
            {
                _isDragging = true;
            }
        }

        public void OnEndDrag(PointerEventData eventData)
        {
            _isDragging = false;
        }

        void resetCoordinatePosition()
        {
            m_transform.position = m_originalCoods;
            ResetCoords = false;
        }

        private Vector3 ScreenToCanvas(Vector3 screenPosition)
        {
            Vector3 localPosition;
            Vector2 min;
            Vector2 max;
            var canvasSize = m_canvasRectTransform.sizeDelta;

            if (m_canvas.renderMode == RenderMode.ScreenSpaceOverlay || (m_canvas.renderMode == RenderMode.ScreenSpaceCamera && m_canvas.worldCamera == null))
            {
                localPosition = screenPosition;

                min = Vector2.zero;
                max = canvasSize;
            }
            else
            {
                var ray = m_canvas.worldCamera.ScreenPointToRay(screenPosition);
                var plane = new Plane(m_canvasRectTransform.forward, m_canvasRectTransform.position);

                float distance;
                if (plane.Raycast(ray, out distance) == false)
                {
                    throw new Exception("Is it practically possible?");
                };
                var worldPosition = ray.origin + ray.direction * distance;
                localPosition = m_canvasRectTransform.InverseTransformPoint(worldPosition);

                min = -Vector2.Scale(canvasSize, m_canvasRectTransform.pivot);
                max = Vector2.Scale(canvasSize, Vector2.one - m_canvasRectTransform.pivot);
            }

            // keep window inside canvas
            localPosition.x = Mathf.Clamp(localPosition.x, min.x + KeepWindowInCanvas, max.x - KeepWindowInCanvas);
            localPosition.y = Mathf.Clamp(localPosition.y, min.y + KeepWindowInCanvas, max.y - KeepWindowInCanvas);

            return localPosition;
        }
    }
}
           

10、FlowLayoutGroup      一種布局元件

using System.Collections.Generic;

namespace UnityEngine.UI.Extensions
{
	/// <summary>
	/// Layout Group controller that arranges children in rows, fitting as many on a line until total width exceeds parent bounds
	/// </summary>
	[AddComponentMenu("Layout/Extensions/Flow Layout Group")]
	public class FlowLayoutGroup : LayoutGroup
	{
		public float Spacing = 0f;

		public bool ChildForceExpandWidth = false;
		public bool ChildForceExpandHeight = false;

		private float _layoutHeight;

		public override void CalculateLayoutInputHorizontal()
		{

			base.CalculateLayoutInputHorizontal();

			var minWidth = GetGreatestMinimumChildWidth() + padding.left + padding.right;

			SetLayoutInputForAxis(minWidth, -1, -1, 0);

		}

		public override void SetLayoutHorizontal()
		{
			SetLayout(rectTransform.rect.width, 0, false);
		}

		public override void SetLayoutVertical()
		{
			SetLayout(rectTransform.rect.width, 1, false);
		}

		public override void CalculateLayoutInputVertical()
		{
			_layoutHeight = SetLayout(rectTransform.rect.width, 1, true);
		}

		protected bool IsCenterAlign
		{
			get
			{
				return childAlignment == TextAnchor.LowerCenter || childAlignment == TextAnchor.MiddleCenter ||
					   childAlignment == TextAnchor.UpperCenter;
			}
		}

		protected bool IsRightAlign
		{
			get
			{
				return childAlignment == TextAnchor.LowerRight || childAlignment == TextAnchor.MiddleRight ||
					   childAlignment == TextAnchor.UpperRight;
			}
		}

		protected bool IsMiddleAlign
		{
			get
			{
				return childAlignment == TextAnchor.MiddleLeft || childAlignment == TextAnchor.MiddleRight ||
					   childAlignment == TextAnchor.MiddleCenter;
			}
		}

		protected bool IsLowerAlign
		{
			get
			{
				return childAlignment == TextAnchor.LowerLeft || childAlignment == TextAnchor.LowerRight ||
					   childAlignment == TextAnchor.LowerCenter;
			}
		}

		/// <summary>
		/// Holds the rects that will make up the current row being processed
		/// </summary>
		private readonly IList<RectTransform> _rowList = new List<RectTransform>(); 

		/// <summary>
		/// Main layout method
		/// </summary>
		/// <param name="width">Width to calculate the layout with</param>
		/// <param name="axis">0 for horizontal axis, 1 for vertical</param>
		/// <param name="layoutInput">If true, sets the layout input for the axis. If false, sets child position for axis</param>
		public float SetLayout(float width, int axis, bool layoutInput)
		{
			var groupHeight = rectTransform.rect.height;

			// Width that is available after padding is subtracted
			var workingWidth = rectTransform.rect.width - padding.left - padding.right;

			// Accumulates the total height of the rows, including spacing and padding.
			var yOffset = IsLowerAlign ? (float)padding.bottom : (float)padding.top;

			var currentRowWidth = 0f;
			var currentRowHeight = 0f;

			for (var i = 0; i < rectChildren.Count; i++) {

				// LowerAlign works from back to front
				var index = IsLowerAlign ? rectChildren.Count - 1 - i : i;

				var child = rectChildren[index];

				var childWidth = LayoutUtility.GetPreferredSize(child, 0);
				var childHeight = LayoutUtility.GetPreferredSize(child, 1);

				// Max child width is layout group with - padding
				childWidth = Mathf.Min(childWidth, workingWidth);

				// If adding this element would exceed the bounds of the row,
				// go to a new line after processing the current row
				if (currentRowWidth + childWidth > workingWidth) {

					currentRowWidth -= Spacing;

					// Process current row elements positioning
					if (!layoutInput) {

						var h = CalculateRowVerticalOffset(groupHeight, yOffset, currentRowHeight);
						LayoutRow(_rowList, currentRowWidth, currentRowHeight, workingWidth, padding.left, h, axis);

					}

					// Clear existing row
					_rowList.Clear();

					// Add the current row height to total height accumulator, and reset to 0 for the next row
					yOffset += currentRowHeight;
					yOffset += Spacing;

					currentRowHeight = 0;
					currentRowWidth = 0;

				}

				currentRowWidth += childWidth;
				_rowList.Add(child);

				// We need the largest element height to determine the starting position of the next line
				if (childHeight > currentRowHeight) {
					currentRowHeight = childHeight;
				}

				currentRowWidth += Spacing;
			}

			if (!layoutInput) {
				var h = CalculateRowVerticalOffset(groupHeight, yOffset, currentRowHeight);

				// Layout the final row
				LayoutRow(_rowList, currentRowWidth, currentRowHeight, workingWidth, padding.left, h, axis);
			}

			_rowList.Clear();

			// Add the last rows height to the height accumulator
			yOffset += currentRowHeight;
			yOffset += IsLowerAlign ? padding.top : padding.bottom;

			if (layoutInput) {

				if(axis == 1)
					SetLayoutInputForAxis(yOffset, yOffset, -1, axis);

			}

			return yOffset;
		}

		private float CalculateRowVerticalOffset(float groupHeight, float yOffset, float currentRowHeight)
		{
			float h;

			if (IsLowerAlign) {
				h = groupHeight - yOffset - currentRowHeight;
			} else if (IsMiddleAlign) {
				h = groupHeight*0.5f - _layoutHeight * 0.5f + yOffset;
			} else {
				h = yOffset;
			}
			return h;
		}

		protected void LayoutRow(IList<RectTransform> contents, float rowWidth, float rowHeight, float maxWidth, float xOffset, float yOffset, int axis)
		{
			var xPos = xOffset;

			if (!ChildForceExpandWidth && IsCenterAlign)
				xPos += (maxWidth - rowWidth) * 0.5f;
			else if (!ChildForceExpandWidth && IsRightAlign)
				xPos += (maxWidth - rowWidth);

			var extraWidth = 0f;

			if (ChildForceExpandWidth) {
				extraWidth = (maxWidth - rowWidth)/_rowList.Count;
			}

			for (var j = 0; j < _rowList.Count; j++) {

				var index = IsLowerAlign ? _rowList.Count - 1 - j : j;

				var rowChild = _rowList[index];

				var rowChildWidth = LayoutUtility.GetPreferredSize(rowChild, 0) + extraWidth;
				var rowChildHeight = LayoutUtility.GetPreferredSize(rowChild, 1);

				if (ChildForceExpandHeight)
					rowChildHeight = rowHeight;

				rowChildWidth = Mathf.Min(rowChildWidth, maxWidth);

				var yPos = yOffset;

				if (IsMiddleAlign)
					yPos += (rowHeight - rowChildHeight) * 0.5f;
				else if (IsLowerAlign)
					yPos += (rowHeight - rowChildHeight);

				if (axis == 0)
					SetChildAlongAxis(rowChild, 0, xPos, rowChildWidth);
				else
					SetChildAlongAxis(rowChild, 1, yPos, rowChildHeight);

				xPos += rowChildWidth + Spacing;
			}
		}

		public float GetGreatestMinimumChildWidth()
		{
			var max = 0f;

			for (var i = 0; i < rectChildren.Count; i++) {
				var w = LayoutUtility.GetMinWidth(rectChildren[i]);

				max = Mathf.Max(w, max);
			}

			return max;
		}
	}
}
           

11、CurvedText             讓文本按照曲線進行顯示   

using System.Collections.Generic;

namespace UnityEngine.UI.Extensions
{
	/// <summary>
	/// Curved text.讓文本按照曲線進行顯示 【注意對Image的變形 也是可以的】
	/// 說明: 對Text的操作就和 shadow 和 outline 元件類似。
	/// </summary>
    // [RequireComponent(typeof(Text), typeof(RectTransform))]
    [AddComponentMenu("UI/Effects/Extensions/Curved Text")]
    public class CurvedText : BaseMeshEffect
    {
		// 曲線類型
        public AnimationCurve curveForText = AnimationCurve.Linear(0, 0, 1, 10); 
		// 曲線程度
        public float curveMultiplier = 1;         
        private RectTransform rectTrans;


#if UNITY_EDITOR
        protected override void OnValidate()
        {
            base.OnValidate();
            if (curveForText[0].time != 0)
            {
                var tmpRect = curveForText[0];
                tmpRect.time = 0;
                curveForText.MoveKey(0, tmpRect);
            }
            if (rectTrans == null)
                rectTrans = GetComponent<RectTransform>();
            if (curveForText[curveForText.length - 1].time != rectTrans.rect.width)
                OnRectTransformDimensionsChange();
        }
#endif
        protected override void Awake()
        {
            base.Awake();
            rectTrans = GetComponent<RectTransform>();
            OnRectTransformDimensionsChange();
        }
        protected override void OnEnable()
        {
            base.OnEnable();
            rectTrans = GetComponent<RectTransform>();
            OnRectTransformDimensionsChange();
        }
		/// <summary>
		/// Modifies the mesh. 最重要的重載函數
		/// </summary>
		/// <param name="mesh">Mesh.</param>
		public override void ModifyMesh(/*List<UIVertex> verts*/Mesh mesh)
        {
            if (!IsActive())
                return;

			// 從mesh 得到 頂點集
			List<UIVertex> verts = new List<UIVertex> ();
			using (VertexHelper vertexHelper = new VertexHelper (mesh))
			{
				vertexHelper.GetUIVertexStream (verts);
			}

			// 頂點的 y值按曲線變換
            for (int index = 0; index < verts.Count; index++)
            {
                var uiVertex = verts[index];
                //Debug.Log ();
                uiVertex.position.y += curveForText.Evaluate(rectTrans.rect.width * rectTrans.pivot.x + uiVertex.position.x) * curveMultiplier;
                verts[index] = uiVertex;
            }

			// 在合成mesh
			using (VertexHelper vertexHelper2 = new VertexHelper ())
			{
				vertexHelper2.AddUIVertexTriangleStream (verts);
				vertexHelper2.FillMesh (mesh);
			}
        }
        protected override void OnRectTransformDimensionsChange()
        {
            var tmpRect = curveForText[curveForText.length - 1];
            tmpRect.time = rectTrans.rect.width;
            curveForText.MoveKey(curveForText.length - 1, tmpRect);
        }
    }
}
           

12、CanvasGroupActivator

Canvas Groups Activator 激活編輯器視窗,顯示和隐藏CanvasGroup。

using UnityEditor;

namespace UnityEngine.UI.Extensions
{
    public class CanvasGroupActivator : EditorWindow
    {
        [MenuItem("Window/UI/Extensions/Canvas Groups Activator")]
        public static void InitWindow()
        {
            EditorWindow.GetWindow<CanvasGroupActivator>();
        }

        CanvasGroup[] canvasGroups;

        void OnEnable()
        {
            ObtainCanvasGroups();
        }

        void OnFocus()
        {
            ObtainCanvasGroups();
        }

        void ObtainCanvasGroups()
        {
            canvasGroups = GameObject.FindObjectsOfType<CanvasGroup>();
        }

        void OnGUI()
        {
            if (canvasGroups == null)
            {
                return;
            }

            GUILayout.Space(10f);
            GUILayout.Label("Canvas Groups");

            for (int i = 0; i < canvasGroups.Length; i++)
            {
                if (canvasGroups[i] == null) { continue; }

                bool initialActive = false;
                if (canvasGroups[i].alpha == 1.0f)
                    initialActive = true;

                bool active = EditorGUILayout.Toggle(canvasGroups[i].name, initialActive);
                if (active != initialActive)
                {
                    //If deactivated and initially active
                    if (!active && initialActive)
                    {
                        //Deactivate this
                        canvasGroups[i].alpha = 0f;
                        canvasGroups[i].interactable = false;
                        canvasGroups[i].blocksRaycasts = false;
                    }
                    //If activated and initially deactive
                    else if (active && !initialActive)
                    {
                        //Deactivate all others and activate this
                        HideAllGroups();

                        canvasGroups[i].alpha = 1.0f;
                        canvasGroups[i].interactable = true;
                        canvasGroups[i].blocksRaycasts = true;
                    }
                }
            }

            GUILayout.Space(5f);

            if (GUILayout.Button("Show All"))
            {
                ShowAllGroups();
            }

            if (GUILayout.Button("Hide All"))
            {
                HideAllGroups();
            }
        }

        void ShowAllGroups()
        {
            foreach (var group in canvasGroups)
            {
                if (group != null)
                {
                    group.alpha = 1.0f;
                    group.interactable = true;
                    group.blocksRaycasts = true;
                }
            }
        }

        void HideAllGroups()
        {
            foreach (var group in canvasGroups)
            {
                if (group != null)
                {
                    group.alpha = 0;
                    group.interactable = false;
                    group.blocksRaycasts = false;
                }
            }
        }
    }
}
           

13、BestFitOutline   跟 Outline功能相似

using System.Collections.Generic;
namespace UnityEngine.UI.Extensions
{
    [AddComponentMenu("UI/Effects/Extensions/BestFit Outline")]
	public class BestFitOutline : Shadow
	{
		protected BestFitOutline ()
		{
		}
		public override void ModifyMesh (/*List<UIVertex> verts*/Mesh mesh)
		{
			if (!this.IsActive ())
			{
				return;
			}
			// 從mesh 得到 頂點集
			List<UIVertex> verts = new List<UIVertex> ();
			using (VertexHelper vertexHelper = new VertexHelper (mesh))
			{
				vertexHelper.GetUIVertexStream (verts);
			}

			Text foundtext = GetComponent<Text>();

			float best_fit_adjustment = 1f;

			if (foundtext && foundtext.resizeTextForBestFit)  
			{
				best_fit_adjustment = (float)foundtext.cachedTextGenerator.fontSizeUsedForBestFit / (foundtext.resizeTextMaxSize-1); //max size seems to be exclusive 
			}
			
			int start = 0;
			int count = verts.Count;
			base.ApplyShadow (verts, base.effectColor, start, verts.Count, base.effectDistance.x*best_fit_adjustment, base.effectDistance.y*best_fit_adjustment);
			start = count;
			count = verts.Count;
			base.ApplyShadow (verts, base.effectColor, start, verts.Count, base.effectDistance.x*best_fit_adjustment, -base.effectDistance.y*best_fit_adjustment);
			start = count;
			count = verts.Count;
			base.ApplyShadow (verts, base.effectColor, start, verts.Count, -base.effectDistance.x*best_fit_adjustment, base.effectDistance.y*best_fit_adjustment);
			start = count;
			count = verts.Count;
			base.ApplyShadow (verts, base.effectColor, start, verts.Count, -base.effectDistance.x*best_fit_adjustment, -base.effectDistance.y*best_fit_adjustment);

			// 在合成mesh
			using (VertexHelper vertexHelper2 = new VertexHelper ())
			{
				vertexHelper2.AddUIVertexTriangleStream (verts);
				vertexHelper2.FillMesh (mesh);
			}
		}
	}
}
           

在下一篇文章中介紹到:

14、AimerInputModule

15、HSVPicker

16、SelectionBox

17、Accordion

18、BoundTooltip

19、ComboBox