日常开发中,UI制作一般是美术在PS中制作效果图,然后编写文档告诉客户端UI使用了哪些图片,图片的像素大小,字体的大小等等,客户端程序再根据这些文档制作UI布局。耗时耗力!客户端程序员难以通过肉眼确定UI图片的具体位置,图片的大小也经常没有在文档中指定出来,制作非常麻烦。如果能直接将美术制作的PSD文件解析并自动在Unity中生成UI,可以节省很多时间精力。利用PS的脚本功能,我们可以编写PS脚本,将PS中的UI图层信息导出到XML文件中,这样Unity就能读取解析XML文件,并自动生成NGUI界面。
一:导出PS图层信息到XML文件。PS支持JS脚本,还自带了编辑器ExcendScript来调试,非常良心。相关API可以在此查询:http://www.adobe.com/cn/devnet/photoshop/scripting.html。以下就是导出XML的JS代码,保存为.JSX文件即可:
var doc = app.activeDocument
var out ="" //创建存储遍历结果的变量
var depth = 0;
var root = new XML("<root/>");
getLayers(doc.layers, 0)//遍历图层
var xml = root.toXMLString();
var file = new File();
var fileNew = file.saveDlg("Save new file", "*.xml");
if (fileNew)
{
fileNew.open("w");
fileNew.write(xml);
fileNew.close();
}
function getLayers(layers, count)
{
for (var i =0; i<layers.length; i++)
{
if(!layers[i].visible)
{
continue;
}
if(layers[i].typename == "LayerSet")//判断是否是图层组
{
//out = out + "\n " + repeat(" ",count) +"[" + layers[i].name +"]";
var group = new XML("<group/>");
group.@name = layers[i].name;
group.@parnet = layers[i].parent.name;
group.@opacity = layers[i].opacity.toFixed(0);
root.appendChild(group);
getLayers(layers[i].layers, count+1);//递归
}
else
{
//out = out + "\n " + repeat(" ",count) + layers[i].name + "," + layers[i].bounds;
var layer = new XML("<layer/>");
layer.@name = layers[i].name;
layer.@index = depth;
layer.@kind = layers[i].kind;
layer.@parnet = layers[i].parent.name;
layer.@bounds = layers[i].bounds;
layer.@opacity = layers[i].opacity.toFixed(0);
if(layers[i].kind == LayerKind.TEXT)
{
layer.@r = layers[i].textItem.color.rgb.red.toFixed(0);
layer.@g = layers[i].textItem.color.rgb.green.toFixed(0);
layer.@b = layers[i].textItem.color.rgb.blue.toFixed(0);
layer.@txt = layers[i].textItem.contents;
layer.@font = layers[i].textItem.font;
}
root.appendChild(layer);
depth++;
}
}
}
执行后(PS中文件->脚本->浏览载入运行或直接在ExcendScript中运行),生成的XML文件如下:
<root>
<layer name="comm_1_icon_coppercup" index="0" kind="LayerKind.NORMAL" parnet="比赛奖励.psd" bounds="277 px,277 px,332 px,341 px" opacity="100"/>
<layer name="comm_1_icon_silvercup" index="1" kind="LayerKind.SMARTOBJECT" parnet="比赛奖励.psd" bounds="277 px,193 px,332 px,257 px" opacity="100"/>
<layer name="comm_1_icon_goldcup" index="2" kind="LayerKind.SMARTOBJECT" parnet="比赛奖励.psd" bounds="276 px,103 px,331 px,167 px" opacity="100"/>
<group name="组 462" parnet="比赛奖励.psd" opacity="100"/>
</root>
layer节点是一个图层的信息,group节点是图层组。有了这些信息后,我们就能在Unity中解析并构建UI布局。
二:Unity中读取并构建UI布局。首先是读取文件:
string path = EditorUtility.OpenFilePanel("XML For UI", "", "xml");
if (!string.IsNullOrEmpty(path))
{
FileStream aFile = new FileStream(path, FileMode.Open);
if (aFile != null)
{
StreamReader sr = new StreamReader(aFile, Encoding.Default);
string strXML = sr.ReadToEnd();
}}
然后解析XML并构建UI:
XmlDocument doc = new XmlDocument();
doc.LoadXml(strXML);
XmlNode nodeRoot = doc.SelectSingleNode("root");
int length = nodeRoot.ChildNodes.Count;
foreach (XmlNode node in nodeRoot)
{
XmlAttributeCollection attrColl = node.Attributes;
XmlAttribute attrName = (XmlAttribute)attrColl.GetNamedItem("name");
XmlAttribute attrIndex = (XmlAttribute)attrColl.GetNamedItem("index");
XmlAttribute attrKind = (XmlAttribute)attrColl.GetNamedItem("kind");
XmlAttribute attrParent = (XmlAttribute)attrColl.GetNamedItem("parnet");
XmlAttribute attrBounds = (XmlAttribute)attrColl.GetNamedItem("bounds");
XmlAttribute attrOpacity = (XmlAttribute)attrColl.GetNamedItem("opacity");
XmlAttribute attrR = (XmlAttribute)attrColl.GetNamedItem("r");
XmlAttribute attrG = (XmlAttribute)attrColl.GetNamedItem("g");
XmlAttribute attrB = (XmlAttribute)attrColl.GetNamedItem("b");
XmlAttribute attrTxt = (XmlAttribute)attrColl.GetNamedItem("txt");
XmlAttribute attrFont = (XmlAttribute)attrColl.GetNamedItem("font");
string name = attrName.Value;
string parent = attrParent.Value;
int opac = int.Parse(attrOpacity.Value);
if (node.Name == "group")
{
GameObject gobjParent = GetParent(parent);
GameObject gobjGroup = new GameObject(name);
gobjGroup.transform.parent = gobjParent.transform;
gobjGroup.transform.localPosition = Vector3.zero;
gobjGroup.transform.localScale = Vector3.one;
}
else if(node.Name == "layer")
{
int index = int.Parse(attrIndex.Value);
string kind = attrKind.Value;
string strBounds = attrBounds.Value;
Bounds bounds = GetBounds(strBounds);
if (kind.Contains(".TEXT"))
{
//文本
byte R = byte.Parse(attrR.Value);
byte G = byte.Parse(attrG.Value);
byte B = byte.Parse(attrB.Value);
string txt = attrTxt.Value;
string font = attrFont.Value;
GameObject gobjParent = GetParent(parent);
UILabel uiLab = NGUISettings.AddLabel(gobjParent);
uiLab.text = txt;
uiLab.color = new Color32(R, G, B,(byte)(opac / 100f * 255));
// 层级
uiLab.depth = length - index;
//大小
uiLab.transform.localPosition = new Vector3(bounds.center.x, bounds.center.y, 0);
uiLab.overflowMethod = UILabel.Overflow.ResizeFreely;
uiLab.fontSize = (int)bounds.size.y;
uiLab.applyGradient = false;
//uiLab.SetDimensions((int)bounds.size.x, (int)bounds.size.y);
}
else
{
//图片
GameObject gobjParent = GetParent(parent);
UIAtlas atlas = GetAtlas(name);
UISprite txu = NGUITools.AddSprite(gobjParent, atlas, name);
//层级
txu.depth = length - index;
//大小
txu.transform.localPosition = new Vector3(bounds.center.x, bounds.center.y, 0);
txu.SetDimensions((int)bounds.size.x, (int)bounds.size.y);
//alpha
txu.alpha = opac / 100f;
}
}
}
要根据parent属性确定父物体,
private static GameObject GetParent(string parent)
{
GameObject gobjParent = null;
if (parent.Contains(".psd"))
{
gobjParent = Selection.activeGameObject;
}
else
{
gobjParent = GameObject.Find(parent);
}
return gobjParent;
}
根据name属性确定并载入图集:
public static UIAtlas GetAtlas(string name)
{
string path = "Assets/ResourcesHot/Atlas/pcomm/pcomm.prefab";
if (name.StartsWith("comm"))
{
path = "Assets/ResourcesHot/Atlas/pcomm/pcomm.prefab";
}
else if (name.StartsWith("battle"))
{
path = "Assets/ResourcesHot/Atlas/battle/battle.prefab";
}
else if (name.StartsWith("icon"))
{
path = "Assets/ResourcesHot/Atlas/icon/icon.prefab";
}
GameObject gobjAtlas = AssetDatabase.LoadMainAssetAtPath(path) as GameObject;
UIAtlas atlas = gobjAtlas.GetComponent<UIAtlas>();
return atlas;
}
根据bounds属性确定位置及大小:
private static Bounds GetBounds(string bounds)
{
string[] vals = bounds.Split(',');
string valLeft = vals[0].Replace(" px", "");
string valUp = vals[1].Replace(" px", "");
string valRight = vals[2].Replace(" px", "");
string valBottom = vals[3].Replace(" px", "");
int left = int.Parse(valLeft);
int right = int.Parse(valRight);
int up = int.Parse(valUp);
int bottom = int.Parse(valBottom);
int width = right - left;
int height = bottom - up;
int x = Mathf.RoundToInt(left + width / 2f - mSceneWidth / 2f);
int y = Mathf.RoundToInt(mSceneHeight / 2f - up - height / 2f);
return new Bounds(new Vector3(x, y), new Vector3(width, height));
}
PS中的bounds属性是这样的:bounds="277 px,277 px,332 px,341 px",分别为左上及右下点坐标,而且原点在画布左上角。我们需要处理成NGUI的坐标系。