unity sprite 图集 子图集 读取
在项目开发中,UI常常需要动态加载图片。较为简单的做法是使用UITexture
控件,只需在使用WWW
类下载好图片后,直接为UITexture
控件赋值即可。不过,这种方式存在DrawCall
过高的问题,一个UITexture
就会产生一个DrawCall
,所以通常只有背景图片会使用UITexture
控件。而对于下拉列表项这类情况,我们一般会使用UISprite
控件,这就需要在程序运行时,由程序创建图集供UISprite
控件使用。
在深入探讨之前,大家可以先查看这篇帖子:http://www.unitymanual.com/thread-16004-1-1.html。该帖子的第一种方法已经说明了如何在程序中创建图集,不过它是使用一张图片制作成图集,再供UISprite
控件使用。
单张图片创建图集示例代码
以下是一个使用单张图片创建图集的示例代码:
public class ImageLoader : MonoBehaviour
{
// 需要加载动态图片的对象
public UISprite m_img;
// 自用的Atlas
private UIAtlas m_uiAtlas;
/// <summary>
/// 加载的贴图
/// </summary>
/// <param name="tex">Tex.</param>
public void ImageLoad(Texture2D tex)
{
if (tex == null)
{
return;
}
if (tex.name == m_img.spriteName)
{
return;
}
// 准备对象和材质球
if (m_uiAtlas == null)
{
Material mat;
Shader shader = Shader.Find("Unlit/Transparent Colored");
mat = new Material(shader);
m_uiAtlas = this.gameObject.AddComponent<UIAtlas>();
m_uiAtlas.spriteMaterial = mat;
}
// 设定贴图
m_uiAtlas.spriteMaterial.mainTexture = tex;
m_uiAtlas.coordinates = UIAtlas.Coordinates.Pixels;
// 为对应UISprite接口,给Atlas加对象
UIAtlas.Sprite sprite = new UIAtlas.Sprite();
sprite.name = tex.name;
sprite.outer = sprite.inner = new Rect(0f, 0f, tex.width, tex.height);
m_uiAtlas.spriteList.Clear();
m_uiAtlas.spriteList.Add(sprite);
// 设置完成
m_img.atlas = m_uiAtlas;
m_img.spriteName = tex.name;
}
}
多张图片制作图集
在实际工作中,我们经常需要将多张图片制作成一个图集,供多个UISprite
控件使用。其实,只要理解了上述代码中运行时生成图集的原理,再结合平常在编辑器中制作图集的方法,就能找到解决方案。大家可以仔细研究NGUI的编辑器脚本UIAtlasMaker
,这就是我们平常在编辑器中制作图集时使用的脚本。我们所欠缺的只是将多张图片使用PackTextures
(打包)成一张大图(即图集)。实际上,UIAtlasMaker
脚本虽然很长,但核心操作就是调用unity3d
的API里的Texture2D.PackTextures
函数。大家可以参考我以前转载的一篇关于该函数用法的文章:http://blog.sina.com.cn/s/blog_930ffa0b0102uyl8.html。
下面是我从UIAtlasMaker
脚本中提取出打包操作函数后的工具类,只有CreatAtlasFromTex
这一个对外使用的函数。使用时,只需传入一个UIAtlas
组件和图片列表,图集生成后,UISprite
控件就可以使用该动态图集,这样由多个UISprite
控件组成的下拉列表项就只占用一个DrawCall
。
CreatAtlas.cs代码
using UnityEngine;
using System.Collections.Generic;
/// <summary>
/// 运行时创建NGUI图集,NGUI2.6.3
/// </summary>
public static class CreatAtlas
{
class SpriteEntry
{
public Texture2D tex; // Sprite texture -- original texture or a temporary texture
public Rect rect; // Sprite's outer rectangle within the generated texture atlas
public int minX = 0; // Padding, if any (set if the sprite is trimmed)
public int maxX = 0;
public int minY = 0;
public int maxY = 0;
public bool temporaryTexture = false; // Whether the texture is temporary and should be deleted
}
static Shader s_shader = Shader.Find("Unlit/Transparent Colored");
static Material s_mat = new Material(s_shader);
static int s_maximumAtlasSize = 2048;
static TextureFormat s_TextureFormat = TextureFormat.RGBA32;
static void Init(UIAtlas uIAtlas)
{
if (uIAtlas.spriteMaterial == null)
{
// 准备材质球
uIAtlas.spriteMaterial = s_mat;
}
}
/// <summary>
/// 运行时创建NGUI图集,NGUI2.6.3
/// </summary>
/// <param name="uIAtlas">使用该UIAtlas在运行时创建NGUI图集</param>
/// <param name="textures">用于创建图集的多张小图</param>
public static void CreatAtlasFromTex(UIAtlas uIAtlas, List<Texture> textures)
{
if (null == textures || textures.Count <= 0)
{
Debug.LogWarning("textures is null or count <= 0 !!");
return;
}
if (null == uIAtlas)
{
Debug.LogWarning("uIAtlas is null");
return;
}
else
{
Init(uIAtlas);
// 设定贴图,将小图映射为SpriteEntry
List<SpriteEntry> sprites = CreateSprites(textures);
// 将多个小图PackTexture为一张大图,给图集用
uIAtlas.spriteMaterial.mainTexture = UpdateTexture(uIAtlas, sprites);
ReplaceSprites(uIAtlas, sprites);
}
}
#region copy UIAtlasMaker编辑器脚本
static List<SpriteEntry> CreateSprites(List<Texture> textures)
{
List<SpriteEntry> list = new List<SpriteEntry>();
foreach (Texture tex in textures)
{
Texture2D oldTex = tex as Texture2D;
if (oldTex == null) continue;
// If we want to trim transparent pixels, there is more work to be done
Color32[] pixels = oldTex.GetPixels32();
int xmin = oldTex.width;
int xmax = 0;
int ymin = oldTex.height;
int ymax = 0;
int oldWidth = oldTex.width;
int oldHeight = oldTex.height;
// Find solid pixels
for (int y = 0, yw = oldHeight; y < yw; ++y)
{
for (int x = 0, xw = oldWidth; x < xw; ++x)
{
Color32 c = pixels[y * xw + x];
if (c.a != 0)
{
if (y < ymin) ymin = y;
if (y > ymax) ymax = y;
if (x < xmin) xmin = x;
if (x > xmax) xmax = x;
}
}
}
int newWidth = (xmax - xmin) + 1;
int newHeight = (ymax - ymin) + 1;
// If the sprite is empty, don't do anything with it
if (newWidth > 0 && newHeight > 0)
{
SpriteEntry sprite = new SpriteEntry();
sprite.rect = new Rect(0f, 0f, oldTex.width, oldTex.height);
// If the dimensions match, then nothing was actually trimmed
if (newWidth == oldWidth && newHeight == oldHeight)
{
sprite.tex = oldTex;
sprite.temporaryTexture = false;
}
else
{
// Copy the non - trimmed texture data into a temporary buffer
Color32[] newPixels = new Color32[newWidth * newHeight];
for (int y = 0; y < newHeight; ++y)
{
for (int x = 0; x < newWidth; ++x)
{
int newIndex = y * newWidth + x;
int oldIndex = (ymin + y) * oldWidth + (xmin + x);
newPixels[newIndex] = pixels[oldIndex];
}
}
// Create a new texture
sprite.temporaryTexture = true;
sprite.tex = new Texture2D(newWidth, newHeight);
sprite.tex.name = oldTex.name;
sprite.tex.SetPixels32(newPixels);
sprite.tex.Apply();
// Remember the padding offset
sprite.minX = xmin;
sprite.maxX = oldWidth - newWidth - xmin;
sprite.minY = ymin;
sprite.maxY = oldHeight - newHeight - ymin;
}
list.Add(sprite);
}
}
return list;
}
static Texture2D UpdateTexture(UIAtlas atlas, List<SpriteEntry> sprites)
{
Texture2D tex = new Texture2D(1, 1, s_TextureFormat, false);
PackTextures(tex, sprites);
atlas.spriteMaterial.mainTexture = tex;
return tex;
}
static void PackTextures(Texture2D tex, List<SpriteEntry> sprites)
{
Texture2D[] textures = new Texture2D[sprites.Count];
for (int i = 0; i < sprites.Count; ++i) textures[i] = sprites[i].tex;
Rect[] rects = tex.PackTextures(textures, 1, s_maximumAtlasSize);
for (int i = 0; i < sprites.Count; ++i)
{
sprites[i].rect = NGUIMath.ConvertToPixels(rects[i], tex.width, tex.height, true);
}
}
static void ReplaceSprites(UIAtlas atlas, List<SpriteEntry> sprites)
{
// Get the list of sprites we'll be updating
List<UIAtlas.Sprite> spriteList = atlas.spriteList;
List<UIAtlas.Sprite> kept = new List<UIAtlas.Sprite>();
// The atlas must be in pixels
atlas.coordinates = UIAtlas.Coordinates.Pixels;
// Run through all the textures we added and add them as sprites to the atlas
for (int i = 0; i < sprites.Count; ++i)
{
SpriteEntry se = sprites[i];
UIAtlas.Sprite sprite = AddSprite(spriteList, se);
kept.Add(sprite);
}
// Remove unused sprites
for (int i = spriteList.Count; i > 0; )
{
UIAtlas.Sprite sp = spriteList[--i];
if (!kept.Contains(sp)) spriteList.RemoveAt(i);
}
atlas.MarkAsDirty();
}
static UIAtlas.Sprite AddSprite(List<UIAtlas.Sprite> sprites, SpriteEntry se)
{
UIAtlas.Sprite sprite = null;
// See if this sprite already exists
foreach (UIAtlas.Sprite sp in sprites)
{
if (sp.name == se.tex.name)
{
sprite = sp;
break;
}
}
if (sprite != null)
{
float x0 = sprite.inner.xMin - sprite.outer.xMin;
float y0 = sprite.inner.yMin - sprite.outer.yMin;
float x1 = sprite.outer.xMax - sprite.inner.xMax;
float y1 = sprite.outer.yMax - sprite.inner.yMax;
sprite.outer = se.rect;
sprite.inner = se.rect;
sprite.inner.xMin = Mathf.Max(sprite.inner.xMin + x0, sprite.outer.xMin);
sprite.inner.yMin = Mathf.Max(sprite.inner.yMin + y0, sprite.outer.yMin);
sprite.inner.xMax = Mathf.Min(sprite.inner.xMax - x1, sprite.outer.xMax);
sprite.inner.yMax = Mathf.Min(sprite.inner.yMax - y1, sprite.outer.yMax);
}
else
{
sprite = new UIAtlas.Sprite();
sprite.name = se.tex.name;
sprite.outer = se.rect;
sprite.inner = se.rect;
sprites.Add(sprite);
}
float width = Mathf.Max(1f, sprite.outer.width);
float height = Mathf.Max(1f, sprite.outer.height);
// Sprite's padding values are relative to width and height
sprite.paddingLeft = se.minX / width;
sprite.paddingRight = se.maxX / width;
sprite.paddingTop = se.maxY / height;
sprite.paddingBottom = se.minY / height;
return sprite;
}
#endregion copy UIAtlasMaker编辑器脚本
}
今天关于Unity Sprite图集、子图集读取的内容就介绍到这里,大家可以参考学习。