工具設(shè)計(jì)目的
NGUI中圖集默認(rèn)使用"Unlit/Transparent Colored" Shader來創(chuàng)建材質(zhì)。這樣的話需要一張RGBA32的帶透明通道的貼圖,一張515x512的圖占用空間1M,加載到內(nèi)存后變成2M。在手機(jī)內(nèi)存還是比較寶貴的時(shí)代這個(gè)是不大能接受的。一般項(xiàng)目的做法是分隔成兩張圖一張color圖包含RGB通道,一張alpha圖包含A通道。通過shader來組合長RGBA的圖,來實(shí)現(xiàn)接近于RGBA的效果。通常Android使用ETC格式,IOS使用PVRTC格式。一張ETC格式的512x512的圖占用128kb,兩張一起256kb。這樣相對(duì)于RGBA32格式的只占用其四分之一的空間。
NGUI自帶的圖集工具并不支持打ETC通道分離。通常一般做法采用第三方工具TexturePacker來做這個(gè),但是使用第三方工具來做這個(gè)也會(huì)比較麻煩。所以通過修改NGUI打圖集工具來實(shí)現(xiàn)這個(gè)通道分離。
圖集增加通道分離
通道分離的做法是生成一張完整RGBA32的圖片,然后讀取RGBA分別生成兩張和RGBA32一樣大小的RGB圖片和Alpha圖片。最后替換shader,生成兩張圖片合成的材質(zhì) 。代碼如下:
using UnityEngine;
using System.Collections;
using UnityEditor;
using System.Collections.Generic;
using System.IO;
using com.geargames.common.utils;
public class UIAtlasChangeShaderTool : EditorWindow
{
private bool _mSearched;
private Object[] mObjects;
private Vector2 mScroll = Vector2.zero;
public static bool useAlpha = true;
const string defShader = "Unlit/Transparent Colored";
const string newShader = "這里填合成兩張圖的shader名字";
private readonly string[] atlasPaths = { "這里填圖集所在路徑"};
[MenuItem("Tool/Open UIAtlas Shader Change Window")]
public static void OpenWindow()
{
UIAtlasChangeShaderTool window = (UIAtlasChangeShaderTool)EditorWindow.GetWindow(typeof(UIAtlasChangeShaderTool));
window.Show();
}
void OnGUI()
{
if (mObjects != null && mObjects.Length != 0)
{
mScroll = GUILayout.BeginScrollView(mScroll);
foreach (Object o in mObjects)
{
DrawUIAtalsObject(o);
}
GUILayout.EndScrollView();
}
GUILayout.Space(6f);
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
bool search = GUILayout.Button("Show All", "LargeButton", GUILayout.Width(120f));
bool Normal = GUILayout.Button("Normal All", "LargeButton", GUILayout.Width(120f));
bool Etc = GUILayout.Button("Etc All", "LargeButton", GUILayout.Width(120f));
bool CreateTexture = GUILayout.Button("CreateTexture All", "LargeButton", GUILayout.Width(200f));
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();
if (search) Search(typeof(UIAtlas));
if (mObjects == null || mObjects.Length == 0) return;
if (Normal) foreach (Object o in mObjects) _ChangeShaderToNormal(o as UIAtlas);
if (Etc) foreach (Object o in mObjects) _ChangeShaderToEtc(o as UIAtlas);
if (CreateTexture)foreach (Object o in mObjects) SeperateRGBAandlphaChannel((o as UIAtlas).spriteMaterial.mainTexture as Texture2D);
}
private void DrawUIAtalsObject(Object obj)
{
if (obj == null) return;
Component comp = obj as Component;
UIAtlas at = obj as UIAtlas;
GUILayout.BeginHorizontal();
{
string path = AssetDatabase.GetAssetPath(obj);
if (string.IsNullOrEmpty(path))
{
path = "[Embedded]";
GUI.contentColor = new Color(0.7f, 0.7f, 0.7f);
}
else if (comp != null && EditorUtility.IsPersistent(comp.gameObject))
GUI.contentColor = new Color(0.6f, 0.8f, 1f);
GUILayout.Label(obj.name, "TextArea", GUILayout.Width(160f), GUILayout.Height(20f));
GUILayout.Label(path.Replace("Assets/", ""), "TextArea", GUILayout.Width(300f), GUILayout.Height(20f));
GUILayout.Label(at.spriteMaterial.shader.name, "TextArea", GUILayout.Width(200f), GUILayout.Height(20f));
GUI.contentColor = Color.white;
if (GUILayout.Button("Normal", "ButtonLeft", GUILayout.Width(60f), GUILayout.Height(16f)))
{
_ChangeShaderToNormal(at);
AssetDatabase.Refresh();
}
if (GUILayout.Button("Etc", "ButtonLeft", GUILayout.Width(60f), GUILayout.Height(16f)))
{
_ChangeShaderToEtc(at);
AssetDatabase.Refresh();
}
if (GUILayout.Button("CreateTexture", "ButtonLeft", GUILayout.Width(100f), GUILayout.Height(16f)))
{
string assetPath = AssetDatabase.GetAssetPath(at).Replace(".prefab",".png") ;
Texture2D texture = AssetDatabase.LoadAssetAtPath(assetPath,typeof(Texture2D)) as Texture2D;
SeperateRGBAandlphaChannel(at.texture as Texture2D);
AssetDatabase.Refresh();
}
}
GUILayout.EndHorizontal();
}
protected void Search(System.Type mType)
{
_mSearched = true;
List<string> pathList = new List<string>();
_GetAllAtlasPath(pathList);
bool isComponent = mType.IsSubclassOf(typeof(Component));
List<Object> list = new List<Object>();
if (mObjects != null)
{
for (int i = 0; i < mObjects.Length; ++i)
if (mObjects[i] != null)
list.Add(mObjects[i]);
}
string path = "";
string[] paths = pathList.ToArray();
for (int i = 0; i < paths.Length; ++i)
{
path = paths[i];
EditorUtility.DisplayProgressBar("Loading", "Searching assets, please wait...", (float)i / paths.Length);
Object obj = AssetDatabase.LoadMainAssetAtPath(path);
if (obj == null || list.Contains(obj)) continue;
if (!isComponent)
{
System.Type t = obj.GetType();
if (t == mType || t.IsSubclassOf(mType) && !list.Contains(obj))
list.Add(obj);
}
else if (PrefabUtility.GetPrefabType(obj) == PrefabType.Prefab)
{
Object t = (obj as GameObject).GetComponent(mType);
if (t != null && !list.Contains(t)) list.Add(t);
}
}
list.Sort(delegate (Object a, Object b) { return a.name.CompareTo(b.name); });
mObjects = list.ToArray();
EditorUtility.ClearProgressBar();
}
private void _GetAllAtlasPath(List<string> pathList)
{
for (int i = 0; i < atlasPaths.Length; i++)
{
FileUtils.GetAllFiles(Application.dataPath + atlasPaths[i], pathList);
}
for (int i = 0; i < pathList.Count; i++)
{
pathList[i] = pathList[i].Replace("\\", "/").Replace(Application.dataPath, "Assets");
}
}
static string c_flg = "_C";
static string a_flg = "_A";
static string exp_flg = ".png";
static private void _ChangeShaderToNormal(UIAtlas obj)
{
if (obj.spriteMaterial.shader.name != defShader)
{
obj.spriteMaterial.shader = Shader.Find(defShader);
string path = AssetDatabase.GetAssetPath(obj.spriteMaterial.mainTexture);
string p = path.Substring(0, path.IndexOf(c_flg + "."))+ ".png";
obj.spriteMaterial.mainTexture = AssetDatabase.LoadAssetAtPath(p , typeof(Texture2D)) as Texture2D;
AssetDatabase.SaveAssets();
}
}
static private void _ChangeShaderToEtc(UIAtlas obj)
{
if (obj.spriteMaterial.shader.name != newShader)
{
obj.spriteMaterial.shader = Shader.Find(newShader);
string path = AssetDatabase.GetAssetPath(obj.spriteMaterial.mainTexture);
string p = path.Substring(0, path.IndexOf("."));
string cpath = p + c_flg + exp_flg;
string apath = p + a_flg + exp_flg;
obj.spriteMaterial.mainTexture = AssetDatabase.LoadAssetAtPath(cpath, typeof(Texture2D)) as Texture2D;
obj.spriteMaterial.SetTexture("_MaskTex", AssetDatabase.LoadAssetAtPath(apath, typeof(Texture2D)) as Texture2D);
Debug.Log("path = " + cpath + " " + apath);
AssetDatabase.SaveAssets();
}
}
static void SeperateRGBAandlphaChannel(Texture2D sourcetex)
{
string path = AssetDatabase.GetAssetPath(sourcetex);
AssetDatabase.ImportAsset(path);
string p = path.Substring(0, path.IndexOf("."));
string cpath = p + c_flg + exp_flg;
string apath = p + a_flg + exp_flg;
AssetDatabase.DeleteAsset(cpath);
AssetDatabase.DeleteAsset(apath);
TextureImporter ti = AssetImporter.GetAtPath(path) as TextureImporter;
MakeTextureReadable(ti, path, false);
Color[] sc = sourcetex.GetPixels();
int sw = sourcetex.width;
int sh = sourcetex.height;
Texture2D rgbTex = new Texture2D(sw, sh, TextureFormat.RGB24, false);
Texture2D alphaTex = new Texture2D(sw, sh, TextureFormat.RGB24, false);
Color[] ac = new Color[sw * sh];
for (int i = 0; i <sw; i++)
{
for (int j = 0; j < sh; j++)
{
int ind = j * sw + i;
Color color = sc[ind];
color.r = color.a;
color.g = color.a;
color.b = color.a;
ac[ind] = color;
}
}
rgbTex.SetPixels(sc);
rgbTex.Apply();
alphaTex.SetPixels(ac);
alphaTex.Apply();
byte[] bytes = rgbTex.EncodeToPNG();
File.WriteAllBytes(cpath, bytes);
bytes = alphaTex.EncodeToPNG();
File.WriteAllBytes(apath, bytes);
AssetDatabase.ImportAsset(cpath);
AssetDatabase.ImportAsset(apath);
TextureImporter tic = AssetImporter.GetAtPath(cpath) as TextureImporter;
TextureImporter tia = AssetImporter.GetAtPath(apath) as TextureImporter;
SetTextureFormat(tic, tia);
MakeTextureAnAtlas(ti, path, false, true);
MakeTextureAnAtlas(tic, cpath, false, false);
MakeTextureAnAtlas(tia, apath, false, true);
}
static public bool MakeTextureReadable(TextureImporter ti, string path, bool force)
{
TextureImporterSettings settings = new TextureImporterSettings();
ti.ReadTextureSettings(settings);
if (force || !settings.readable || settings.npotScale != TextureImporterNPOTScale.None || settings.alphaIsTransparency)
{
settings.readable = true;
if (NGUISettings.trueColorAtlas) settings.textureFormat = TextureImporterFormat.AutomaticTruecolor;
settings.npotScale = TextureImporterNPOTScale.None;
settings.alphaIsTransparency = false;
ti.SetTextureSettings(settings);
AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate | ImportAssetOptions.ForceSynchronousImport);
}
return true;
}
static bool MakeTextureAnAtlas(TextureImporter ti, string path, bool force, bool alphaTransparency)
{
TextureImporterSettings settings = new TextureImporterSettings();
ti.ReadTextureSettings(settings);
if (force ||
settings.readable ||
settings.maxTextureSize < 4096 ||
settings.wrapMode != TextureWrapMode.Clamp ||
settings.npotScale != TextureImporterNPOTScale.ToNearest)
{
settings.readable = false;
settings.maxTextureSize = 4096;
settings.wrapMode = TextureWrapMode.Clamp;
settings.npotScale = TextureImporterNPOTScale.ToNearest;
if (NGUISettings.trueColorAtlas)
{
settings.textureFormat = TextureImporterFormat.ARGB32;
settings.filterMode = FilterMode.Trilinear;
}
settings.aniso = 4;
settings.alphaIsTransparency = alphaTransparency;
ti.SetTextureSettings(settings);
AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate | ImportAssetOptions.ForceSynchronousImport);
}
return true;
}
static public void SetTextureFormat(TextureImporter tic, TextureImporter tia)
{
tic.alphaIsTransparency = false;
tic.isReadable = tia.isReadable = false;
tic.mipmapEnabled = tia.mipmapEnabled = false;
// TextureImporterPlatformSettings textureImporterPlatformSettings = tic.
// UnityEditor.TextureImporter.SetPlatformTextureSettings();
TextureImporterPlatformSettings tips = new TextureImporterPlatformSettings();
tips.name = "Android";
tips.overridden = true;
tips.maxTextureSize = 2048;
tips.format = TextureImporterFormat.ETC_RGB4;
tips.textureCompression = (int)UnityEditor.TextureCompressionQuality.Fast;
tic.SetPlatformTextureSettings(tips);
tia.SetPlatformTextureSettings(tips);
tips.overridden = true;
tips.name = "iPhone";
tips.format = TextureImporterFormat.PVRTC_RGB4;
tic.SetPlatformTextureSettings(tips);
tia.SetPlatformTextureSettings(tips);
#if UNITY_ANDROID || UNITY_STANDALONE
tips.name = "Standalone";
tips.format = TextureImporterFormat.ETC_RGB4;
tips.overridden = true;
tic.SetPlatformTextureSettings(tips);
tia.SetPlatformTextureSettings(tips);
// tic.SetPlatformTextureSettings("Standalone", 1024, TextureImporterFormat.ETC_RGB4, (int)TextureCompressionQuality.Fast);
// tia.SetPlatformTextureSettings("Standalone", 1024, TextureImporterFormat.ETC_RGB4, (int)TextureCompressionQuality.Fast);
// tic.textureFormat = TextureImporterFormat.ETC_RGB4;
// tia.textureFormat = TextureImporterFormat.ETC_RGB4;
#else
tips.name = "Standalone";
tips.format = TextureImporterFormat.PVRTC_RGB4;
tips.overridden = true;
tic.SetPlatformTextureSettings(tips);
tia.SetPlatformTextureSettings(tips);
// tic.SetPlatformTextureSettings("Standalone", 1024, TextureImporterFormat.PVRTC_RGB4, (int)TextureCompressionQuality.Fast);
// tia.SetPlatformTextureSettings("Standalone", 1024, TextureImporterFormat.PVRTC_RGB4, (int)TextureCompressionQuality.Fast);
// tic.textureFormat = TextureImporterFormat.PVRTC_RGB4;
// tia.textureFormat = TextureImporterFormat.PVRTC_RGB4;
#endif
}
public static bool flg = false;
static public void OnInspectorGUI(UIAtlas mLastAtlas)
{
GUILayout.BeginHorizontal();
UIAtlasChangeShaderTool.flg = EditorGUILayout.Toggle("spaceAlpha", UIAtlasChangeShaderTool.flg, GUILayout.Width(100f));
GUILayout.Label("分隔color,Alpha兩通道");
GUILayout.EndHorizontal();
}
static public void updateAtlas(UIAtlas mLastAtlas)
{
updateAtlas(mLastAtlas, UIAtlasChangeShaderTool.flg);
}
static public void updateAtlas(UIAtlas mLastAtlas, bool flg)
{
if (flg)
{
SeperateRGBAandlphaChannel(mLastAtlas.texture as Texture2D);
_ChangeShaderToEtc(mLastAtlas);
}
else
{
_ChangeShaderToNormal(mLastAtlas);
}
AssetDatabase.Refresh();
}
}
注意,我這用的是Unity2018.3,比較早期的版本在圖集格式設(shè)置那塊需要修改 。就是SetPlatformTextureSettings這個(gè)方法。
NGUI中打圖集的工具寫在UIAtlasMaker.cs里面。我們需要在系統(tǒng)打圖集的選項(xiàng)里面增加一個(gè)選擇打分離通道的。如下:

UIAtlasMaker.cs需要增加代碼位置為:
void OnEnable () { instance = this;
//默認(rèn)勾選上
UIAtlasChangeShaderTool.flg = true;
}
void OnGUI ()
{
//省略之前代碼。在布局的最后添加這個(gè)。
UIAtlasChangeShaderTool.OnInspectorGUI(NGUISettings.atlas);
NGUIEditorTools.EndContents();
if (delete)
{
List<SpriteEntry> sprites = new List<SpriteEntry>();
ExtractSprites(NGUISettings.atlas, sprites);
for (int i = sprites.Count; i > 0; )
{
SpriteEntry ent = sprites[--i];
if (mDelNames.Contains(ent.name))
sprites.RemoveAt(i);
}
UpdateAtlas(NGUISettings.atlas, sprites);
mDelNames.Clear();
NGUIEditorTools.RepaintSprites();
}
else if (update){
//更新的位置還原默認(rèn)圖集,用來生成RGBA32
UIAtlasChangeShaderTool.updateAtlas(NGUISettings.atlas, false);
UpdateAtlas(textures, true);
}
else if (replace) UpdateAtlas(textures, false);
if (NGUISettings.atlas != null && !string.IsNullOrEmpty(selection))
{
NGUIEditorTools.SelectSprite(selection);
}
else if (NGUISettings.autoUpgradeSprites && (update || replace))
{
NGUIEditorTools.UpgradeTexturesToSprites(NGUISettings.atlas);
NGUIEditorTools.RepaintSprites();
//這里生成兩個(gè)圖集
UIAtlasChangeShaderTool.updateAtlas(NGUISettings.atlas);
}
}