工具设计目的
NGUI中图集默认使用"Unlit/Transparent Colored" Shader来创建材质。这样的话需要一张RGBA32的带透明通道的贴图,一张515x512的图占用空间1M,加载到内存后变成2M。在手机内存还是比较宝贵的时代这个是不大能接受的。一般项目的做法是分隔成两张图一张color图包含RGB通道,一张alpha图包含A通道。通过shader来组合长RGBA的图,来实现接近于RGBA的效果。通常Android使用ETC格式,IOS使用PVRTC格式。一张ETC格式的512x512的图占用128kb,两张一起256kb。这样相对于RGBA32格式的只占用其四分之一的空间。
NGUI自带的图集工具并不支持打ETC通道分离。通常一般做法采用第三方工具TexturePacker来做这个,但是使用第三方工具来做这个也会比较麻烦。所以通过修改NGUI打图集工具来实现这个通道分离。
图集增加通道分离
通道分离的做法是生成一张完整RGBA32的图片,然后读取RGBA分别生成两张和RGBA32一样大小的RGB图片和Alpha图片。最后替换shader,生成两张图片合成的材质 。代码如下:
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,比较早期的版本在图集格式设置那块需要修改 。就是SetPlatformTextureSettings这个方法。
NGUI中打图集的工具写在UIAtlasMaker.cs里面。我们需要在系统打图集的选项里面增加一个选择打分离通道的。如下:
image.png
UIAtlasMaker.cs需要增加代码位置为:
void OnEnable () { instance = this;
//默认勾选上
UIAtlasChangeShaderTool.flg = true;
}
void OnGUI ()
{
//省略之前代码。在布局的最后添加这个。
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){
//更新的位置还原默认图集,用来生成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();
//这里生成两个图集
UIAtlasChangeShaderTool.updateAtlas(NGUISettings.atlas);
}
}
网友评论