动态图集存在的意义
举例:
选择技能或者头像的时候,如果有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,但是会造成顶点面数高,
开启面片查看
需要在图片使用的时候设置