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;
}
}
}