using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; #if UNITY_EDITOR using UnityEditor; #endif [RequireComponent(typeof(PolygonCollider2D))] public class UIPolygon : Image { private PolygonCollider2D _polygon = null; private PolygonCollider2D polygon { get { if (_polygon == null) _polygon = GetComponent<PolygonCollider2D>(); return _polygon; } } //设置只响应点击,不进行渲染 protected UIPolygon() { useLegacyMeshGeneration = true; } protected override void OnPopulateMesh(VertexHelper toFill) { toFill.Clear(); } public override bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera) { return polygon.OverlapPoint(eventCamera.ScreenToWorldPoint(screenPoint)); } #if UNITY_EDITOR protected override void Reset() { base.Reset(); transform.position = Vector3.zero; float w = (rectTransform.sizeDelta.x * 0.5f) + 0.1f; float h = (rectTransform.sizeDelta.y * 0.5f) + 0.1f; polygon.points = new Vector2[] { new Vector2(-w,-h), new Vector2(w,-h), new Vector2(w,h), new Vector2(-w,h) }; } #endif #if UNITY_EDITOR [CustomEditor(typeof(UIPolygon), true)] public class UIPolygonInspectorGUI : Editor { public override void OnInspectorGUI() { } } #endif }
把按钮的Image组件和Text组件的Raycast Target取消勾选,在Button下新建一个空物体添加UIPolygon脚本,标记好点击区域就好了。注意,这一检测点击需要使用到相机,所以Canvas的Render Mode要选择相机模式,所用的相机使用正交模式,不然多边形点击区域就会不准了。
using UnityEngine; [AddComponentMenu("UI/UIOrder")] public class UIOrder : MonoBehaviour { [SerializeField] private int _sortingOrder = 0; public int sortingOrder { get { return _sortingOrder; } set { if (_sortingOrder != value) { _sortingOrder = value; Refresh(); } } } private Canvas _canvas = null; public Canvas canvas { get { if (_canvas == null) { _canvas = gameObject.GetComponent<Canvas>(); if (_canvas == null) _canvas = gameObject.AddComponent<Canvas>(); _canvas.hideFlags = HideFlags.NotEditable; } return _canvas; } } public void Refresh() { canvas.overrideSorting = true; canvas.sortingOrder = _sortingOrder; foreach(ParticleSystemRenderer particale in transform.GetComponentsInChildren<ParticleSystemRenderer>()) { particale.sortingOrder = _sortingOrder; } } #if UNITY_EDITOR private void OnValidate() { Refresh(); } private void Reset() { Refresh(); } #endif }
Canvas下的3个物体,分别挂上脚本,设置sortingOrder分别为0,1,2,这样粒子就被两张图片夹在中间了。
using UnityEngine; using UnityEngine.UI; #if UNITY_EDITOR using UnityEditor; #endif public class UIGray : MonoBehaviour { private bool _isGray = false; public bool isGray { get { return _isGray; } set { if (_isGray != value) { _isGray = value; SetGray(_isGray); } } } static private Material _defaultGrayMaterial; static private Material grayMaterial { get { if (_defaultGrayMaterial == null) { _defaultGrayMaterial = new Material(Shader.Find("UI/MyGray")); } return _defaultGrayMaterial; } } public void SetGray(bool isGray) { Image[] images = transform.GetComponentsInChildren<Image>(); for (int i = 0; i < images.Length; i++) { Image g = images[i]; if (isGray) { g.material = grayMaterial; } else { g.material = null; } } } #if UNITY_EDITOR //提供编辑器预览 [CustomEditor(typeof(UIGray))] public class UIGrayInspector : Editor { public override void OnInspectorGUI() { base.OnInspectorGUI(); UIGray gray = target as UIGray; gray.isGray = GUILayout.Toggle(gray.isGray, "Is Gray"); if (GUI.changed) { EditorUtility.SetDirty(target); } } } #endif }
Shader "UI/MyGray" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); float gray = dot(col.xyz, float3(0.299, 0.587, 0.114)); col.xyz = float3(gray, gray, gray); return col; } ENDCG } } }
相对于以前的自动打包图集,2017提供了可以自行打包和管理图集,只需要在Project视图中Create创建Sprite Atlas,选择图片或则整个文件夹打包到这个图集中。
还可以创建图集的变种,就是新建一个图集,复制事先打包好的图集,设置缩放,使图集变小,优化内存。
二、图集的加载
using UnityEditor; using UnityEngine; using UnityEngine.U2D; using UnityEngine.UI; public class Script_8 : MonoBehaviour { Sprite sprite = null; Sprite sprite1 = null; Image image = null; void Start () { image = this.transform.parent.GetChild(0).GetComponent<Image>(); SpriteAtlas atlas = AssetDatabase.LoadAssetAtPath<SpriteAtlas>("Assets/8/New Sprite Atlas.spriteatlas"); SpriteAtlas atlasMin = AssetDatabase.LoadAssetAtPath<SpriteAtlas>("Assets/8/New Sprite Atlas 1.spriteatlas"); sprite = atlas.GetSprite("TIM图片20190401133117"); sprite1 = atlasMin.GetSprite("TIM图片20190401133117"); } private void Update() { if (Input.GetMouseButtonDown(0)) { if (image.sprite != sprite) image.sprite = sprite; else image.sprite = sprite1; } } }
在Layout根结点添加Horizontal Layout Group和Content Size Fiter组件,设置好参数。添加需要布局的物体到根节点下面。
为Text物体也添加一个Content Size Fitter组件,设置你要适应的方向,因为这个物体的大小也是需要重新计算的,动态设置文字长度。
using UnityEngine; using UnityEngine.UI; public class Script_8 : MonoBehaviour { void Start () { GetComponent<Text>().text = "吃ua谁会丢爱u恢复到大街上的风格i偶会若干个的疯狂购ID防静电服"; //Layout的根节点,立刻重新计算布局 LayoutRebuilder.ForceRebuildLayoutImmediate(this.transform.parent.GetComponent<RectTransform>()); } }
在Image中添加AspectRatioFitter,就会将图片全屏显示在父物体中。
WidthControlsHeight,让Height随着Width自动调节
HeightControlsWidth,让Width随着Height自动调节
FitInParent,宽度、高度、位置和锚点都会被自动调整,以使得该矩形拟合父物体的矩形内,同时保持宽高比例
EnvelopeParent,宽度、高度、位置和锚点都会被自动调整,以使得该矩形覆盖父物体的整个区域,同时保持宽高比
AspectRatio,为宽高比例
using UnityEngine; using UnityEngine.UI; public class Script_aspect : MonoBehaviour { void Start () { Rect rect = this.GetComponent<Image>().sprite.rect; AspectRatioFitter aspect = this.GetComponent<AspectRatioFitter>(); aspect.aspectMode = AspectRatioFitter.AspectMode.EnvelopeParent; aspect.aspectRatio = rect.width / rect.height; } }
using UnityEngine; using UnityEngine.UI; public class Script_05_11 : MonoBehaviour { //需要聚合的对象(例子中的Unity图标) public Image target; //Canvas对象 public Canvas canvas; private Vector4 m_Center; private Material m_Material; private float m_Diameter; // 直径 private float m_Current =0f; Vector3[] corners = new Vector3[4]; void Awake () { target.rectTransform.GetWorldCorners (corners); //计算四个角的坐标 左下 左上 右上 右下 m_Diameter = Vector2.Distance (WordToCanvasPos(canvas,corners [0]), WordToCanvasPos(canvas,corners [2])) / 2f; float x =corners [0].x + ((corners [3].x - corners [0].x) / 2f); float y =corners [0].y + ((corners [1].y - corners [0].y) / 2f); Vector3 center = new Vector3 (x, y, 0f); Vector2 position = Vector2.zero; //坐标转换到相对canvas下的局部坐标 RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, center, canvas.GetComponent<Camera>(), out position); center = new Vector4 (position.x,position.y,0f,0f); m_Material = GetComponent<Image>().material; m_Material.SetVector ("_Center", center); (canvas.transform as RectTransform).GetWorldCorners (corners); for (int i = 0; i < corners.Length; i++) { m_Current = Mathf.Max(Vector3.Distance (WordToCanvasPos(canvas,corners [i]), center),m_Current); } m_Material.SetFloat ("_Silder", m_Current); } float yVelocity = 0f; void Update () { float value = Mathf.SmoothDamp(m_Current, m_Diameter, ref yVelocity, 0.3f); if (!Mathf.Approximately (value, m_Current)) {//两个数是否相近 m_Current = value; m_Material.SetFloat ("_Silder", m_Current); } } void OnGUI(){ if(GUILayout.Button("Test")){ Awake (); } } Vector2 WordToCanvasPos(Canvas canvas,Vector3 world){ Vector2 position = Vector2.zero; RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, world, canvas.GetComponent<Camera>(), out position); return position; } }
Shader "UI/Default_Mask" { Properties { [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} _Color ("Tint", Color) = (1,1,1,1) _StencilComp ("Stencil Comparison", Float) = 8 _Stencil ("Stencil ID", Float) = 0 _StencilOp ("Stencil Operation", Float) = 0 _StencilWriteMask ("Stencil Write Mask", Float) = 255 _StencilReadMask ("Stencil Read Mask", Float) = 255 _ColorMask ("Color Mask", Float) = 15 [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0 //-------------------add---------------------- _Center("Center", vector) = (0, 0, 0, 0) _Silder ("_Silder", Range (0,1000)) = 1000 // sliders //-------------------add---------------------- } SubShader { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" } Stencil { Ref [_Stencil] Comp [_StencilComp] Pass [_StencilOp] ReadMask [_StencilReadMask] WriteMask [_StencilWriteMask] } Cull Off Lighting Off ZWrite Off ZTest [unity_GUIZTestMode] Blend SrcAlpha OneMinusSrcAlpha ColorMask [_ColorMask] Pass { Name "Default" CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 2.0 #include "UnityCG.cginc" #include "UnityUI.cginc" #pragma multi_compile __ UNITY_UI_ALPHACLIP struct appdata_t { float4 vertex : POSITION; float4 color : COLOR; float2 texcoord : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; float2 texcoord : TEXCOORD0; float4 worldPosition : TEXCOORD1; UNITY_VERTEX_OUTPUT_STEREO }; fixed4 _Color; fixed4 _TextureSampleAdd; float4 _ClipRect; //-------------------add---------------------- float _Silder; float2 _Center; //-------------------add---------------------- v2f vert(appdata_t IN) { v2f OUT; UNITY_SETUP_INSTANCE_ID(IN); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT); OUT.worldPosition = IN.vertex; OUT.vertex = UnityObjectToClipPos(OUT.worldPosition); OUT.texcoord = IN.texcoord; OUT.color = IN.color * _Color; return OUT; } sampler2D _MainTex; fixed4 frag(v2f IN) : SV_Target { half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color; color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect); #ifdef UNITY_UI_ALPHACLIP clip (color.a - 0.001); #endif //-------------------add---------------------- color.a*=(distance(IN.worldPosition.xy,_Center.xy) > _Silder); color.rgb*= color.a; //-------------------add---------------------- return color; } ENDCG } } }
将所有的坐标都转换到相对于Canvas父物体下的局部坐标,统一在同一个坐标系中计算距离。计算目标焦点的直径和中心点等,根据距离修改遮罩的透明度。
using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; public class Script_5 : MonoBehaviour,IPointerClickHandler { public void OnPointerClick(PointerEventData eventData) { Debug.LogError("图片点击"); PassEvent(eventData, ExecuteEvents.pointerClickHandler); } public void PassEvent<T>(PointerEventData data, ExecuteEvents.EventFunction<T> function) where T : IEventSystemHandler { List<RaycastResult> results = new List<RaycastResult>(); EventSystem.current.RaycastAll(data, results); GameObject current = data.pointerCurrentRaycast.gameObject; for (int i = 0; i < results.Count; i++) { if (current != results[i].gameObject) { ExecuteEvents.Execute(results[i].gameObject, data, function); //break; } } } } //按钮的脚本 using UnityEngine; public class Script_5_1 : MonoBehaviour { public void OnButtonClick()//方法在Inspector中绑定给了按钮 { Debug.LogError("按钮点击"); } }
UGUI的点击事件是基于射线的。如果不需要响应事件,千万不要在Image和Text组件上勾选RaycastTarget。UI事件会在EventSystem的Update()方法中调用Process时触发。UGUI会遍历屏幕中所有Raycasttarget是true的UI,接着就会发射线,并且排序找到玩家最先触发的UI,再抛出事件给逻辑层去响应,这样无形中就会带来很多开销。我可以通过辅助方法,标记那些勾选了RaycastTarget的ui,这样就可以去发现那些不必要勾选了。
using UnityEngine; using UnityEngine.UI; public class ShowRayCatchUI : MonoBehaviour { #if UNITY_EDITOR static Vector3[] fourCorners = new Vector3[4]; private void OnDrawGizmos() { foreach (MaskableGraphic g in GameObject.FindObjectsOfType<MaskableGraphic>()) { if (g.raycastTarget) { RectTransform rectTransform = g.rectTransform; rectTransform.GetWorldCorners(fourCorners); Gizmos.color = Color.blue; for (int i = 0; i < 4; i++) Gizmos.DrawLine(fourCorners[i], fourCorners[(i + 1) % 4]); } } } #endif }
UGUI的动态字体会动态生成材质,开始是256*256,然后根据字体的使用情况慢慢扩大。直到4096*4096。当文字太多不够放的时候,会触发UGUI内部重建字体贴图命令,接着就可能造成文字花屏了。可以监听重建的事件,然后刷新一下当前场景中的所有字体即可。
using UnityEngine; using UnityEngine.UI; public class FontRebuild : MonoBehaviour { private Font m_NeedRebuildFont = null; void Start () { Font.textureRebuilt += delegate (Font font) { m_NeedRebuildFont = font; Debug.LogError(font.name); }; } void Update () { if (m_NeedRebuildFont) { Text[] texts = GameObject.FindObjectsOfType<Text>(); if (texts != null) { foreach (Text text in texts) { text.FontTextureChanged(); } } m_NeedRebuildFont = null; } } }