动态图集存在的意义
举例:
选择技能或者头像的时候,如果有100个头像,分成两个图集,加载一个头像图的时候可能需要把两个图集都加载出来;
动态打图集的消耗放在loading条加载过程;动态打图集能很大的优化Drallcall;
动态图集算法
using DaVikingCode.RectanglePacking; using System; using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine; using UnityEngine.Events; namespace DaVikingCode.AssetPacker { public class AssetPacker : MonoBehaviour { public UnityEvent OnProcessCompleted; public float pixelsPerUnit = 100.0f; public bool useCache = false; public string cacheName = ""; public int cacheVersion = 1; public bool deletePreviousCacheVersion = true; protected Dictionary<string, Sprite> mSprites = new Dictionary<string, Sprite>(); protected List<TextureToPack> itemsToRaster = new List<TextureToPack>(); protected bool allow4096Textures = false; /// <summary> /// 设定图集参数 /// </summary> /// <param name="file">图片的路径</param> /// <param name="customID"></param> public void AddTextureToPack(string file, string customID = null) { itemsToRaster.Add(new TextureToPack(file, customID != null ? customID : Path.GetFileNameWithoutExtension(file))); } public void AddTexturesToPack(string[] files) { foreach (string file in files) AddTextureToPack(file); } /// <summary> /// 调用执行方法 /// </summary> /// <param name="allow4096Textures">是否允许创建一个4096的图集</param> public void Process(bool allow4096Textures = false) { this.allow4096Textures = allow4096Textures; //是否用缓存到本地 if (useCache) { if (cacheName == "") throw new Exception("No cache name specified"); string path = Application.persistentDataPath + "/AssetPacker/" + cacheName + "/" + cacheVersion + "/"; bool cacheExist = Directory.Exists(path); //缓存不存在,创建一个新的图集,如果存在,直接去加载 if (!cacheExist) StartCoroutine(createPack(path)); else StartCoroutine(loadPack(path)); } else StartCoroutine(createPack()); } /// <summary> /// 创建一个新的图集 /// </summary> /// <param name="savePath"></param> /// <returns></returns> protected IEnumerator createPack(string savePath = "") { //如果路径不为空,创建文件夹 if (savePath != "") { //把之前的清理掉 if (deletePreviousCacheVersion && Directory.Exists(Application.persistentDataPath + "/AssetPacker/" + cacheName + "/")) foreach (string dirPath in Directory.GetDirectories(Application.persistentDataPath + "/AssetPacker/" + cacheName + "/", "*", SearchOption.AllDirectories)) Directory.Delete(dirPath, true); Directory.CreateDirectory(savePath); } List<Texture2D> textures = new List<Texture2D>(); List<string> images = new List<string>(); //通过数据加载到图片 foreach (TextureToPack itemToRaster in itemsToRaster) { WWW loader = new WWW("file:///" + itemToRaster.file); yield return loader; //有时候会发现加载出来的图片是粉红色的图片带?,itemToRaster.file这个文件的后缀没有加上,例如.png,没加.png textures.Add(loader.texture); images.Add(itemToRaster.id); } //默认创建的是2048的图集 int textureSize = allow4096Textures ? 4096 : 2048; List<Rect> rectangles = new List<Rect>(); for (int i = 0; i < textures.Count; i++) if (textures[i].width > textureSize || textures[i].height > textureSize) throw new Exception("A texture size is bigger than the sprite sheet size!"); else rectangles.Add(new Rect(0, 0, textures[i].width, textures[i].height)); const int padding = 1; int numSpriteSheet = 0; while (rectangles.Count > 0) { //创建一个texture2D图片,图集对象 Texture2D texture = new Texture2D(textureSize, textureSize, TextureFormat.ARGB32, false); Color32[] fillColor = texture.GetPixels32(); //把像素全部清理了一下 for (int i = 0; i < fillColor.Length; ++i) fillColor[i] = Color.clear; RectanglePacker packer = new RectanglePacker(texture.width, texture.height, padding); for (int i = 0; i < rectangles.Count; i++) packer.insertRectangle((int) rectangles[i].width, (int) rectangles[i].height, i); packer.packRectangles(); if (packer.rectangleCount > 0) { texture.SetPixels32(fillColor); IntegerRectangle rect = new IntegerRectangle(); List<TextureAsset> textureAssets = new List<TextureAsset>(); List<Rect> garbageRect = new List<Rect>(); List<Texture2D> garabeTextures = new List<Texture2D>(); List<string> garbageImages = new List<string>(); //计算当前图片需要显示的位置 for (int j = 0; j < packer.rectangleCount; j++) { rect = packer.getRectangle(j, rect); int index = packer.getRectangleId(j); //把当前图片的像素渲染到当前图集上,调用当前方法是需要texture是可读可写的,不然会报错 texture.SetPixels32(rect.x, rect.y, rect.width, rect.height, textures[index].GetPixels32()); TextureAsset textureAsset = new TextureAsset(); textureAsset.x = rect.x; textureAsset.y = rect.y; textureAsset.width = rect.width; textureAsset.height = rect.height; textureAsset.name = images[index]; textureAssets.Add(textureAsset); garbageRect.Add(rectangles[index]); garabeTextures.Add(textures[index]); garbageImages.Add(images[index]); } foreach (Rect garbage in garbageRect) rectangles.Remove(garbage); foreach (Texture2D garbage in garabeTextures) textures.Remove(garbage); foreach (string garbage in garbageImages) images.Remove(garbage); texture.Apply(); if (savePath != "") { File.WriteAllBytes(savePath + "/data" + numSpriteSheet + ".png", texture.EncodeToPNG()); File.WriteAllText(savePath + "/data" + numSpriteSheet + ".json", JsonUtility.ToJson(new TextureAssets(textureAssets.ToArray()))); ++numSpriteSheet; } foreach (TextureAsset textureAsset in textureAssets) mSprites.Add(textureAsset.name, Sprite.Create(texture, new Rect(textureAsset.x, textureAsset.y, textureAsset.width, textureAsset.height), Vector2.zero, pixelsPerUnit, 0, SpriteMeshType.FullRect)); } } OnProcessCompleted.Invoke(); } protected IEnumerator loadPack(string savePath) { int numFiles = Directory.GetFiles(savePath).Length; for (int i = 0; i < numFiles / 2; ++i) { WWW loaderTexture = new WWW("file:///" + savePath + "/data" + i + ".png"); yield return loaderTexture; WWW loaderJSON = new WWW("file:///" + savePath + "/data" + i + ".json"); yield return loaderJSON; TextureAssets textureAssets = JsonUtility.FromJson<TextureAssets> (loaderJSON.text); Texture2D t = loaderTexture.texture; // prevent creating a new Texture2D each time. foreach (TextureAsset textureAsset in textureAssets.assets) mSprites.Add(textureAsset.name, Sprite.Create(t, new Rect(textureAsset.x, textureAsset.y, textureAsset.width, textureAsset.height), Vector2.zero, pixelsPerUnit, 0, SpriteMeshType.FullRect)); } yield return null; OnProcessCompleted.Invoke(); } public void Dispose() { foreach (var asset in mSprites) Destroy(asset.Value.texture); mSprites.Clear(); } void Destroy() { Dispose(); } /// <summary> /// 获取当前图片的方法 /// </summary> /// <param name="id">图片id</param> /// <returns></returns> public Sprite GetSprite(string id) { Sprite sprite = null; mSprites.TryGetValue (id, out sprite); return sprite; } public Sprite[] GetSprites(string prefix) { List<string> spriteNames = new List<string>(); foreach (var asset in mSprites) if (asset.Key.StartsWith(prefix)) spriteNames.Add(asset.Key); spriteNames.Sort(StringComparer.Ordinal); List<Sprite> sprites = new List<Sprite>(); Sprite sprite; for (int i = 0; i < spriteNames.Count; ++i) { mSprites.TryGetValue(spriteNames[i], out sprite); sprites.Add(sprite); } return sprites.ToArray(); } } }创建动态图集
每个图片类
using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class ShowItem : MonoBehaviour { public ShowName ID { get; private set; } private Image _image; private RuntimeAltasItem _altasItem; public void Init(ShowName id) { ID = id; _image = GetComponent<Image>(); _altasItem = GetComponent<RuntimeAltasItem>(); } public void SetSprite(Sprite sprite) { _image.sprite = sprite; } public void AddListener(Action selected) { gameObject.GetComponent<Button>().onClick.AddListener(()=>selected()); } public KeyValuePair<string, string> GetData() { return new KeyValuePair<string, string>(ID.ToString(),_altasItem.Path); } } public enum ShowName { ICON_1, ICON_2, ICON_3, }using UnityEngine; public class RuntimeAltasItem : MonoBehaviour { public string Path; }
using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class SelectedComplete : MonoBehaviour { // Start is called before the first frame update public void Init(Func<Dictionary<string,string>> getPaths,LoadingView loadingView) { GetComponent<Button>().onClick.AddListener(() => { loadingView.SetActiveState(true); AssetPackerMgr.Instance.GentatorNewAltas("Test",getPaths(),()=>loadingView.SwitchScene(SceneName.Game.ToString())); }); } } public enum SceneName { SelectView, Game }
下面是选中图片的类,缓存图片路径的类
using System.Collections.Generic; using UnityEngine; /// <summary> /// 选择的三个图标的管理类,负责管理选中的三个图标 /// </summary> public class SelectShowView : MonoBehaviour { private ShowItem _selectedItem; private RuntimeAltasItem _altasItem; // Start is called before the first frame update void Start() { ShowName id = ShowName.ICON_1; foreach (Transform trans in transform) { RuntimeAltasItem altasItem = trans.gameObject.AddComponent<RuntimeAltasItem>(); //选中的三个小图类 ShowItem item =trans.gameObject.AddComponent<ShowItem>(); item.Init(id); id++; item.AddListener(() => { _selectedItem = item; _altasItem = altasItem; }); } } public void SetShowItem(Sprite sprite,string path) { _selectedItem.SetSprite(sprite); _altasItem.Path = path; } public Dictionary<string,string> GetPaths() { Dictionary<string,string> temp = new Dictionary<string, string>(); KeyValuePair<string, string> tempPair; foreach (ShowItem item in GetComponentsInChildren<ShowItem>()) { tempPair = item.GetData(); temp.Add(tempPair.Key,tempPair.Value); } return temp; } }下面是生成图集的方法调用
打图集的类
using System; using System.Collections.Generic; using DaVikingCode.AssetPacker; using UnityEngine; /// <summary> /// 打图集的时机是在加载资源,读条的过程中 /// </summary> public class AssetPackerMgr : MonoBehaviour { public static AssetPackerMgr Instance { get; private set; } private Dictionary<string,AssetPacker> _packers = new Dictionary<string, AssetPacker>(); private void Awake() { //切换场景有可能重复加载 if (Instance == null) { Instance = this; GameObject.DontDestroyOnLoad(gameObject); } else { Destroy(gameObject); } } /// <summary> /// 创建新图集 /// </summary> /// <param name="altasName">图集名称</param> /// <param name="paths">图片id,图片路径</param> /// <param name="complete">完成的回调</param> public void GentatorNewAltas(string altasName,Dictionary<string,string> paths,Action complete = null) { if(paths == null) return; //打包类 AssetPacker packer = new GameObject(altasName).AddComponent<AssetPacker>(); packer.transform.SetParent(transform); packer.cacheName = altasName; foreach (KeyValuePair<string,string> path in paths) { //图片的路径和id packer.AddTextureToPack(path.Value,path.Key); } //执行打图集的方法 packer.Process(); packer.OnProcessCompleted.AddListener(() => { if (complete != null) complete(); }); _packers.Add(altasName,packer); } /// <summary> /// 获取图集 /// </summary> /// <param name="altasName"></param> /// <returns></returns> public AssetPacker GetAltas(string altasName) { if (_packers.ContainsKey(altasName)) { return _packers[altasName]; } else { Debug.LogError("can not find altas name is "+altasName); return null; } } /// <summary> /// 清空图集 /// </summary> /// <param name="altasName"></param> public void ClearAltas(string altasName) { if (_packers.ContainsKey(altasName)) { AssetPacker packer = _packers[altasName]; _packers.Remove(altasName); Destroy(packer.gameObject); } else { Debug.LogError("can not remove altas,because it can not find,name is"+altasName); return; } } }图集的使用
图片的模式 Single单个,Multiple多个,Polygon多边形模式
精灵的多边形设置
带透明通道的,GPU渲染的时候,透明部分的像素点也需要处理,会增加overdrall,但是会造成顶点面数高,
开启面片查看
需要在图片使用的时候设置