NGUI 精灵:UISprite
目录:NGUI源码学习
一、UIAtlas图集和UISpriteData,负责数据的存储。
1.UIAtlas:图集是一个ScriptableObject对象。
- 里面保存了图集的相关信息,主要是材质、Pixel Size、图集包含的UISpriteData列表。
GUIAtlas:INGUIAtlas 图集,维护图集内的UISpriteData。只是一个容器概念 property: spriteMaterial/spriteList/texture:图集材质、精灵列表、贴图 pixelSize:每个像素的大小 replacement:替换图集,如果不为空,优先再此图集查找 mSprites:图集内的UISpriteData集合 mSpriteIndices:mSpriteIndices[spriteName] = index,这个index对应mSprites下标 Padding:空白不显示区域 function: GetSprite:按名字查找精灵 MakePixelPerfect:像素修改,奇数修正成偶数值 GetListOfSprites:获取图集内所有精灵的名字集合 MarkAsChanged:刷新用到这个图集的所有对象
unity ScriptableObject对象是什么:https://blog.csdn.net/candycat1992/article/details/52181814
如下图:打出来的大图坐标原点在左上角,即上面的Dimensions的x,y坐标是相对于图集大图的左上角的坐标,(x+width, y-height)是右下角的坐标,这边的坐标最终会转换成基于左下角的(0-1)的uv坐标,并赋值给Mesh四个顶点(mesh.uv = newUV;),当然这是相对于Sample类型的精灵,他只有四个顶点。
2.UISpriteData:记录图片在UIAtlas生成的那张大图的坐标,宽度和高度,以及图片拉伸的一些信息(Border,Padding)。
SpriteData就只是一个很简单的数据存储类。
public string name = "Sprite"; public int x = 0; public int y = 0; public int width = 0; public int height = 0; public int borderLeft = 0; public int borderRight = 0; public int borderTop = 0; public int borderBottom = 0; public int paddingLeft = 0; public int paddingRight = 0; public int paddingTop = 0; public int paddingBottom = 0;
Border:九宫格拉伸需要的参数。九宫格拉伸可以参考文章https://www.cnblogs.com/wang-jin-fu/p/8277774.html。
Padding的作用:四边留空大小,如下Padding.Left就是在左边留空的大小。
|
二、UIAtlasMaker:图集生成、修改工具。
UIAtlasMaker:NGUIAtlas图集生成、属性设置。核心类 property: spriteList:当前图集内图片列表 atlasTexture/spriteMaterial:渲染属性 function: GetSelectedTextures:将当前选中的图片转成Texture并保存在一个list。 PackTextures:大图打包并生成对应的SpriteEntry列表,Texture2D.PackTextures GetSpriteList:获取当前要显示在界面的精灵列表。0-已在图集且未选中,1-已在图集且选中(update),2未在图集且选中(add) CreateSprites:把List<Texture>转换成List<SpriteEntry>() ReplaceSprites:替换图集内的所有图片 ExtractSprites:扩展图集,往图集添加精灵 UpdateTexture:把List<SpriteEntry>打进图集 UpdateAtlas:更新图集,核心方法
临时精灵数据类:SpriteEntry,方便UISpriteData跟Texture关联。
|
-
|
void OnSelectAtlas (Object obj) { // Legacy atlas support if (obj != null && obj is GameObject) obj = (obj as GameObject).GetComponent<UIAtlas>(); if (NGUISettings.atlas != obj as INGUIAtlas) { NGUISettings.atlas = obj as INGUIAtlas; Repaint(); } }
- GetSelectedTextures: 收集当前选中的Texture对象,用于后续添加/更新到图集。
- GetSpriteList:把上一步的选中列表和图集原有的精灵列表集合在一起,设置上面界面的按钮状态。
- 状态:0 = No change ,1 = Update,2 = Add
- textures:上一步收集的当前选中的图片。
Dictionary<string, int> GetSpriteList (List<Texture> textures) { var spriteList = new Dictionary<string, int>(); // If we have textures to work with, include them as well if (textures.Count > 0) { List<string> texNames = new List<string>(); foreach (Texture tex in textures) texNames.Add(tex.name); texNames.Sort(); foreach (string tex in texNames) spriteList.Add(tex, 2); } var atlas = NGUISettings.atlas; if (atlas != null) { var spriteNames = atlas.GetListOfSprites(); if (spriteNames != null) { foreach (string sp in spriteNames) { if (spriteList.ContainsKey(sp)) spriteList[sp] = 1; else spriteList.Add(sp, 0); } } } return spriteList; }
- 打包图集PackTextures:实际调用的是unity 的Texture2D.PackTextures,这边对最终显示的坐标做了处理,把原点为左下角的(0-1)的uv坐标,转换成了原点在左上角的坐标,就是上面UIAtlas在Editor下显示的坐标。
static bool PackTextures (Texture2D tex, List<SpriteEntry> sprites) { Texture2D[] textures = new Texture2D[sprites.Count]; Rect[] rects; #if UNITY_3_5 || UNITY_4_0 int maxSize = 4096; #else int maxSize = SystemInfo.maxTextureSize; #endif #if UNITY_ANDROID || UNITY_IPHONE maxSize = Mathf.Min(maxSize, NGUISettings.allow4096 ? 4096 : 2048); #endif if (NGUISettings.unityPacking) { for (int i = 0; i < sprites.Count; ++i) textures[i] = sprites[i].tex; rects = tex.PackTextures(textures, NGUISettings.atlasPadding, maxSize); } for (int i = 0; i < sprites.Count; ++i) { Rect rect = NGUIMath.ConvertToPixels(rects[i], tex.width, tex.height, true); // Apparently Unity can take the liberty of destroying temporary textures without any warning if (textures[i] == null) return false; // Make sure that we don't shrink the textures确保我们没有缩小纹理 if (Mathf.RoundToInt(rect.width) != textures[i].width) return false; SpriteEntry se = sprites[i]; se.x = Mathf.RoundToInt(rect.x); se.y = Mathf.RoundToInt(rect.y); se.width = Mathf.RoundToInt(rect.width); se.height = Mathf.RoundToInt(rect.height); } return true; }
- uv点坐标和UISpriteData内的x,y坐标的转换。
/// <summary> /// Convert from top-left based pixel coordinates to bottom-left based UV coordinates. /// </summary> static public Rect ConvertToTexCoords (Rect rect, int width, int height) { Rect final = rect; if (width != 0f && height != 0f) { final.xMin = rect.xMin / width; final.xMax = rect.xMax / width; final.yMin = 1f - rect.yMax / height; final.yMax = 1f - rect.yMin / height; } return final; } /// <summary> /// Convert from bottom-left based UV coordinates to top-left based pixel coordinates. /// </summary> static public Rect ConvertToPixels (Rect rect, int width, int height, bool round) { Rect final = rect; if (round) { final.xMin = Mathf.RoundToInt(rect.xMin * width); final.xMax = Mathf.RoundToInt(rect.xMax * width); final.yMin = Mathf.RoundToInt((1f - rect.yMax) * height); final.yMax = Mathf.RoundToInt((1f - rect.yMin) * height); } else { final.xMin = rect.xMin * width; final.xMax = rect.xMax * width; final.yMin = (1f - rect.yMax) * height; final.yMax = (1f - rect.yMin) * height; } return final; }
- UpdateTexture:更新图集,最终还是会调用到PackTextures。
三、UISprite:精灵,核心方法就是OnFill。
UISprite:UIBasicSprite property: mainTexture/material:获取到图集对应的材质spriteMaterial和纹理 atlas:当前使用图集 spriteName:当前使用的的精灵名字。用于在atlas中查找UISpriteData applyGradient:渐变标记 padding:Tiled模式下,重复的间隔 border:Sliced模式下的九宫格数据 function: GetSprite:查找并返回当前atlas中spriteName对应的sprite信息 drawingDimensions:渲染范围计算,Sprite矩形坐标,需要注意的是drawingDimensions是只当前Sprite的局部坐标 GetAtlasSprite:获取当前精灵使用的UISpriteData SetAtlasSprite:设置当前精灵使用的UISpriteData OnFill:核心填充方法
OnFill:和UITexture一样,这个方法调用Fill方法,并把geometry.verts, geometry.uvs, geometry.cols传给Fill,在UIBaseSprite.Fill完成对geometry的顶点、uv、颜色等进行设置。
这边的outer、inter是根据精灵在图集中设置的Border计算来的。
|
public override void OnFill (List<Vector3> verts, List<Vector2> uvs, List<Color> cols) { var tex = mainTexture; if (tex == null) return; if ((!mSpriteSet || mSprite == null) && GetAtlasSprite() == null) return; var outer = new Rect(mSprite.x, mSprite.y, mSprite.width, mSprite.height); var inner = new Rect(mSprite.x + mSprite.borderLeft, mSprite.y + mSprite.borderTop, mSprite.width - mSprite.borderLeft - mSprite.borderRight, mSprite.height - mSprite.borderBottom - mSprite.borderTop); //uv转换,mSprite的坐标轴原点在左上角,转换成左下角,并转换成(0,1)的坐标 //outer.xMin = mSprite.x/tex.width, outer.xMax = (mSprite.x + mSprite.width)/tex.width //outer.yMin = (tex.height - mSprite.y - mSprite.width)/tex.height, outer.yMax = (tex.height - mSprite.y)/tex.height outer = NGUIMath.ConvertToTexCoords(outer, tex.width, tex.height); inner = NGUIMath.ConvertToTexCoords(inner, tex.width, tex.height); //verts, uvs, cols geometry内的属性,这时候还是空的 var offset = verts.Count; Fill(verts, uvs, cols, outer, inner); if (onPostFill != null) #if OPTIMISE_NGUI_GC_ALLOC onPostFill(this, offset, geometry.verts, geometry.uvs, geometry.cols); #else onPostFill(this, offset, verts, uvs, cols); #endif }
一直想把之前工作、学习时记录的文档整理到博客上,一方面温故而知新,一方面和大家一起学习 -程序小白