赞
踩
PS图层 -> Unity UGUI
pic_ 前缀:图层会被切图包含里面的特效。
txt_ 文本:切图成文字,导出相关信息。Unity中会加载PSD中文字切图,然后,对比变更文字样式,删除图片即可。
pre_ prefab:在PS侧,对应的是智能对象,是另外一个PSD文件,这样PSD文件可以复用。当然美术那边要有一定的规范。
脚本和psd文件放置在同一个文件夹内即可。PSD和UGUI中的Prefab的一一对应,使用的pre_前缀来实现,pre_前缀的对象在PS中一定要是一个智能对象。
# !/usr/bin/env python3 import os from psd_tools import PSDImage import json # 将PSD解析成Json文件,PS中的智能对象对应于Unity中的Prefab # pre_,txt_,pic_分别对应图片。 # Unity 中使用C# 解析 json,还原 PSD 中的结构。 # 运行时,从编辑器中拖拽到文件夹,实现 PSD 转换 UGUI。 # 写文件 def writeFileWithStr(filePath_, str_): if not os.path.exists(os.path.dirname(filePath_)): os.makedirs(os.path.dirname(filePath_)) try: _file = open(filePath_, 'w') try: _file.write(str_) finally: _file.close() except Exception as e: print(filePath_, e) class PSDAnalyse(object): def __init__(self, psdFolderPath_: str, targetFolderPath_: str): self.psdFolderPath = psdFolderPath_ self.targetFolderPath = targetFolderPath_ def analysePsdFile(self, uiName_: str): _psdName = uiName_ print('analyseFile = ' + str(_psdName)) _psd = PSDImage.open( self.psdFolderPath + _psdName + ".psd" ) _psdDict = self.psdToJson( _psd, self.targetFolderPath, _psdName ) _unityDict = {} _unityDict["name"] = _psdName _unityDict["type"] = "Root" _unityDict["bbox"] = {} _unityDict["bbox"]["left"] = _psdDict["bbox"]["left"] _unityDict["bbox"]["top"] = _psdDict["bbox"]["top"] _unityDict["bbox"]["right"] = _psdDict["bbox"]["right"] _unityDict["bbox"]["bottom"] = _psdDict["bbox"]["bottom"] self.convertToUnityUIJson(_psdDict, _unityDict) writeFileWithStr( self.targetFolderPath + "jsons/" + _psdName + ".json", str(json.dumps(_unityDict, indent=4, sort_keys=False, ensure_ascii=False)) ) def psdToJson(self, psd_: PSDImage, targetFolderPath_: str, psdName_: str): _psdDict = {} _psdDict["name"] = psdName_ _psdDict["realName"] = _psdDict["name"] _psdDict["paths"] = [_psdDict["name"]] _psdDict["isVisible"] = True _psdDict["opacity"] = 255 _psdDict["viewbox"] = {} _psdDict["viewbox"]["left"] = psd_.viewbox[0] _psdDict["viewbox"]["top"] = psd_.viewbox[1] _psdDict["viewbox"]["right"] = psd_.viewbox[2] _psdDict["viewbox"]["bottom"] = psd_.viewbox[3] _psdDict["bbox"] = {} _psdDict["bbox"]["left"] = psd_.bbox[0] _psdDict["bbox"]["top"] = psd_.bbox[1] _psdDict["bbox"]["right"] = psd_.bbox[2] _psdDict["bbox"]["bottom"] = psd_.bbox[3] _psdDict["layerList"] = [] _layerPathAndImageDict = {} for _layer in psd_: self.layerToJson(_layer, _psdDict, _layerPathAndImageDict, False) # 图片保存出来写入路径 for _layerPathKey in _layerPathAndImageDict: _psdImage: PSDImage = _layerPathAndImageDict[_layerPathKey] print(' png : ' + str(_layerPathKey)+".png") _psdImage.save(targetFolderPath_ + "pngs/" + _layerPathKey + '.png') return _psdDict def layerToJson(self, layer_, parentLayerDict_: dict, layerPathAndImageDict_: dict, isClipLayer_=False): _layerDict = {} _layerDict["name"] = layer_.name _layerDict["paths"] = self.copyList(parentLayerDict_["paths"]) _layerDict["paths"].append(_layerDict["name"]) _layerDict["realName"] = self.layerPathsToName(_layerDict["paths"]) _layerDict["blendMode"] = str(layer_.blend_mode).split("BlendMode.")[1] _layerDict["isVisible"] = layer_.is_visible() _layerDict["opacity"] = layer_.opacity # 【0-255】 _layerDict["bbox"] = {} _layerDict["bbox"]["left"] = layer_.bbox[0] _layerDict["bbox"]["top"] = layer_.bbox[1] _layerDict["bbox"]["right"] = layer_.bbox[2] _layerDict["bbox"]["bottom"] = layer_.bbox[3] _layerDict["hasMask"] = layer_.has_mask() _layerDict["hasOrigination"] = layer_.has_origination() _layerDict["hasStroke"] = layer_.has_stroke() _layerDict["clipLayerList"] = [] for _clipLayer in layer_.clip_layers: self.layerToJson(_clipLayer, _layerDict, layerPathAndImageDict_, True) if not len(_layerDict["clipLayerList"]) > 0: del _layerDict["clipLayerList"] _layerDict["effects"] = [] if layer_.has_effects(): for _effect in layer_.effects: _effectDict = {} _effectDict["type"] = str(_effect) _effectDict["enabled"] = _effect.enabled _layerDict["effects"].append(_effectDict) if not len(_layerDict["effects"]) > 0: del _layerDict["effects"] _layerDict["offsetX"] = layer_.offset[0] _layerDict["offsetY"] = layer_.offset[1] if layer_.is_group(): _layerDict["layerList"] = [] for _childLayer in layer_: self.layerToJson(_childLayer, _layerDict, layerPathAndImageDict_, False) if not len(_layerDict["layerList"]) > 0: del _layerDict["layerList"] else: _layerDict["kind"] = layer_.kind if layer_.kind == "type": _layerDict["text"] = layer_.text _layerDict["fontSize"] = layer_.height + 2 # 实际的图片资源 if _layerDict["name"].startswith("pic_") or \ _layerDict["name"].startswith("pre_") or \ _layerDict["name"].startswith("txt_"): layerPathAndImageDict_[_layerDict["realName"]] = layer_.composite() if _layerDict["name"].startswith("pic_"): layerPathAndImageDict_[_layerDict["name"]] = layer_.composite() if isClipLayer_: parentLayerDict_["clipLayerList"].append(_layerDict) else: parentLayerDict_["layerList"].append(_layerDict) def copyList(self, list_: list): _newList = [] for _element in list_: _newList.append(_element) return _newList def layerPathsToName(self, paths_: list): _realName = "" _count = 0 for _layerName in paths_: _layerName = self.convertLayerName(_layerName) if _count == 0: _realName = _layerName else: _realName = _realName + "&" + _layerName _count = _count + 1 return _realName def convertLayerName(self, layerName_: str): _layerName = layerName_ if _layerName.find(' ') > 0 or _layerName.find('"') > 0 or _layerName.find('\'') > 0 or _layerName.find( '.') > 0 or _layerName.find('/') > 0: print('ERROR : ' + str("层名错误 : " + _layerName)) return _layerName def convertToUnityUIJson(self, layerDict_: dict, unityGameObject_: dict): if not layerDict_["isVisible"]: return if not "layerList" in layerDict_: print('ERROR : ' + str( "不是层 : " + layerDict_["name"] + " - " + layerDict_["realName"] + " - " + str(layerDict_["isVisible"]) )) _currentIdx = 0 unityGameObject_["children"] = [] _layerDictList = layerDict_["layerList"] if _layerDictList: for _layerDict in _layerDictList: if not _layerDict["isVisible"]: continue _unityLayerGameObject = {} _unityLayerGameObject["name"] = _layerDict["name"] _unityLayerGameObject["realName"] = _layerDict["realName"] _unityLayerGameObject["x"] = _layerDict["offsetX"] _unityLayerGameObject["y"] = _layerDict["offsetY"] _unityLayerGameObject["opacity"] = _layerDict["opacity"] _unityLayerGameObject["bbox"] = {} _unityLayerGameObject["bbox"]["left"] = _layerDict["bbox"]["left"] _unityLayerGameObject["bbox"]["top"] = _layerDict["bbox"]["top"] _unityLayerGameObject["bbox"]["right"] = _layerDict["bbox"]["right"] _unityLayerGameObject["bbox"]["bottom"] = _layerDict["bbox"]["bottom"] # 不同类型 if str(_layerDict["name"]).startswith("pre_"): _unityLayerGameObject["type"] = "prefab" _subUIName = str(_layerDict["name"]).split("_")[1] # 切出子ui名称 self.analysePsdFile(_subUIName) elif str(_layerDict["name"]).startswith("pic_"): _unityLayerGameObject["type"] = "sprite" elif str(_layerDict["name"]).startswith("txt_"): _unityLayerGameObject["type"] = "text" _unityLayerGameObject["text"] = _layerDict["text"] _unityLayerGameObject["fontSize"] = _layerDict["fontSize"] else: _unityLayerGameObject["type"] = "gameObject" self.convertToUnityUIJson(_layerDict, _unityLayerGameObject) # 获取层级 _unityLayerGameObject["index"] = _currentIdx _currentIdx += 1 # 记录节点 unityGameObject_["children"].append(_unityLayerGameObject) if __name__ == "__main__": _psdAnalyse = PSDAnalyse( "PSD目录", "Unity工程/Assets/PSD/Resources/psd/" ) # 写主文件即可,关联的智能对象,通过pre_前缀自动识别转换 _psdAnalyse.analysePsdFile("PSD主文件名")
//PsdCreatePrefab using System.IO; using UnityEngine; using UnityEditor; public class PsdCreatePrefab : MonoBehaviour { void Awake () { //缓存复制对象 PsdUtils.cacheCopyInstances(gameObject); //缓存png PsdUtils.cacheSpriteInFolder("psd/pngs"); //缓存jsons PsdUtils.cachePsdJsonInFolder("psd/jsons"); //循环缓存的jsonData foreach (var _item in PsdUtils.psdJsonCacheDict){ string _uiName = _item.Key; string _prefabPath = "Assets/PSD/Resources/prefabs/" + _uiName+ ".prefab";//拼接路径 if(!File.Exists(_prefabPath)){ GameObject _uiGameObject = PsdUtils.createUIByName(_uiName);//创建一个UI,放置到场景上 _uiGameObject.transform.parent = gameObject.transform; PrefabUtility.CreatePrefab(_prefabPath, _uiGameObject);//创建Prefabs } } } }
//PsdLinkPrefab using System; using UnityEngine; public class PsdLinkPrefab : MonoBehaviour { public String uiName; public Transform parentTransform = null; public PSDWarp psdWarp = null; void Start () { GameObject _uiPrefabGameObject = PsdUtils.linkUIPrefabByName(uiName); _uiPrefabGameObject.transform.SetParent(gameObject.transform); _uiPrefabGameObject.transform.localPosition = new Vector3(0, 0, 0f); _uiPrefabGameObject.transform.localScale = new Vector3(1f,1f,1f); if (parentTransform != null){//如果有父容器,继续放置到父容器上 gameObject.transform.SetParent(parentTransform); } if (psdWarp != null){//如果是封装的话,开始遍历 psdWarp.recursiveChildren(gameObject); } parentTransform = null; psdWarp = null; } void OnDestroy(){ uiName = null; parentTransform = null; psdWarp = null; } }
//PsdUtils using System; using System.Collections; using System.Collections.Generic; using LitJson; using UnityEngine; using UnityEngine.UI; using Object = UnityEngine.Object; public class PsdUtils{ public static Shader spriteRendererShader = Shader.Find("Sprites/Default"); private static Dictionary<string, Sprite> _spriteCacheDict = new Dictionary<string, Sprite>(); public static Dictionary<string, JsonData> psdJsonCacheDict = new Dictionary<string, JsonData>(); private static GameObject _imageCopyInstance; private static GameObject _textCopyInstance; private static GameObject _gameObjectCopyInstance; //Create object for 'GameObject.Instantiate()' public static void cacheCopyInstances(GameObject gameObject_){ Transform _copyInstanceContainer = gameObject_.transform.Find("CopyInstance"); _imageCopyInstance = _copyInstanceContainer.Find("ImageInstance").gameObject; //_textCopyInstance = _copyInstanceContainer.Find("TextMeshInstance").gameObject; _textCopyInstance = _copyInstanceContainer.Find("TextInstance").gameObject; _gameObjectCopyInstance = _copyInstanceContainer.Find("GameObjectInstance").gameObject; } //Png file in 'Assets/PSD/pngs'. public static void cacheSpriteInFolder(string pngFolderPath_){ Object[] _sprites = Resources.LoadAll(pngFolderPath_, typeof(UnityEngine.Sprite)); for (int _idx = 0; _idx < _sprites.Length; _idx++){ var _sp = _sprites[_idx]; _spriteCacheDict[_sp.name] = _sp as UnityEngine.Sprite; } } //Text file in 'Assets/PSD/jsons'. public static void cachePsdJsonInFolder(string jsonFolderPath_){ Object[] _textFiles = Resources.LoadAll(jsonFolderPath_, typeof(TextAsset)); for (int _idx = 0; _idx < _textFiles.Length; _idx++){ JsonData _jsonRoot = JsonMapper.ToObject((_textFiles[_idx] as TextAsset).ToString()); string _name = (_jsonRoot["name"] as IJsonWrapper).GetString(); psdJsonCacheDict[_name] = _jsonRoot; } } public static GameObject createUIByName(string uiName_){ GameObject _gameObject = getUIByName(uiName_); adjustPos(_gameObject, null); //先矫正所有节点位置 adjustTxtChild(_gameObject); //再矫正节点中的字符串位置 _gameObject.transform.localPosition = new Vector3(0f, 0f, 0f); _gameObject.transform.localScale = new Vector3(1f, 1f, 1f); return _gameObject; } public static GameObject getUIByName(string uiName_){ JsonData _jsonData = psdJsonCacheDict[uiName_]; GameObject _gameObject = createGameObjectByJsonData(_jsonData); return _gameObject; } //调整位置 public static void adjustPos(GameObject gameObject_, GameObject gameObjectParent_ = null){ int _children = gameObject_.transform.childCount; for (int _idx = 0; _idx < _children; _idx++){ //先向下调整位置,再调整自己的位置。 Transform _trans = gameObject_.transform.GetChild(_idx); adjustPos(_trans.gameObject, gameObject_); } if (gameObjectParent_){ gameObject_.transform.localPosition -= gameObjectParent_.transform.localPosition; } } public static void adjustTxtChild(GameObject gameObject_){ int _children = gameObject_.transform.childCount; for (int _idx = 0; _idx < _children; _idx++){ //先向下调整位置,再调整自己的位置。 Transform _trans = gameObject_.transform.GetChild(_idx); if (_trans.gameObject.name.StartsWith("txt_")){ Transform _txtTrans = _trans.gameObject.transform.GetChild(0); (_txtTrans as RectTransform).anchoredPosition3D = new Vector3(0f, 0f, 0f); _txtTrans.localPosition = new Vector3(0f, 0f, 0f); _txtTrans.localScale = new Vector3(1f, 1f, 1f); } else{ adjustTxtChild(_trans.gameObject); } } } //通过名称获取Sprite public static Sprite getSpriteByName(string spriteName_){ Sprite _sprite = null; if (!_spriteCacheDict.TryGetValue(spriteName_, out _sprite)){ Debug.LogError("ERROR " + System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.FullName + " -> " + new System.Diagnostics.StackTrace().GetFrame(0).GetMethod().Name + " : " + spriteName_ + " : there is no sprite with this name." ); } return _sprite; } //get float value from LitJson. public static float getFloat(JsonData valueData_){ if (valueData_.IsLong){ return (float) Convert.ToDouble((valueData_ as IJsonWrapper).GetLong()); } else if (valueData_.IsInt){ return (float) Convert.ToDouble((valueData_ as IJsonWrapper).GetInt()); } else{ return (float) (valueData_ as IJsonWrapper).GetDouble(); } } public static GameObject linkUIPrefabByName(string uiName_){ GameObject _gameObject = getGameObjectByPrefabName(uiName_); return _gameObject; } public static GameObject getGameObjectByPrefabName(string uiName_){ GameObject _uiPrefabGameObject = Resources.Load<GameObject>("prefabs/" + uiName_); if (_uiPrefabGameObject == null){ Debug.LogError("ERROR " + System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.FullName + " -> " + new System.Diagnostics.StackTrace().GetFrame(0).GetMethod().Name + " : " + uiName_ + " : 没有找到这个界面." ); } GameObject _gameObject = GameObject.Instantiate(_uiPrefabGameObject) as GameObject; linkChildrenPrefab(_gameObject); return _gameObject; } public static void linkChildrenPrefab(GameObject gameObject_){ int _children = gameObject_.transform.childCount; for (int _idx = 0; _idx < _children; _idx++){ //先向下调整位置,再调整自己的位置。 Transform _trans = gameObject_.transform.GetChild(_idx); GameObject _currentGameObject = _trans.gameObject; if (_currentGameObject.name.StartsWith("pre_")){ GameObject.Destroy(_currentGameObject.GetComponent<Image>()); GameObject.Destroy(_currentGameObject.GetComponent<CanvasRenderer>()); ArrayList _nameArr = new ArrayList(_currentGameObject.name.Split('_')); string _prefabName = (string) _nameArr[1]; GameObject _subGameObject = getGameObjectByPrefabName(_prefabName); _subGameObject.transform.SetParent(_currentGameObject.transform); (_subGameObject.transform as RectTransform).anchoredPosition3D = new Vector3(0f, 0f, 0f); _subGameObject.transform.localScale = new Vector3(1f, 1f, 1f); } else if (_currentGameObject.name.StartsWith("txt_")){ } else if (_currentGameObject.name.StartsWith("pic_")){ } else{ linkChildrenPrefab(_currentGameObject); } } } //创建 public static GameObject createGameObjectByJsonData(JsonData value_){ string _name = (value_["name"] as IJsonWrapper).GetString(); JsonData _bbox = value_["bbox"]; float _left = getFloat(_bbox["left"]); float _top = getFloat(_bbox["top"]); float _right = getFloat(_bbox["right"]); float _bottom = getFloat(_bbox["bottom"]); float _width = _right - _left; float _height = _bottom - _top; string _type = (value_["type"] as IJsonWrapper).GetString(); string _realName = null; int _opacity = 255; float _x = 0; float _y = 0; int _index = 0; if (_type != "Root"){ _realName = (value_["realName"] as IJsonWrapper).GetString(); _opacity = (value_["opacity"] as IJsonWrapper).GetInt(); _x = getFloat(value_["x"]); _y = getFloat(value_["y"]); _index = (value_["index"] as IJsonWrapper).GetInt(); } float _xPos = _x + _width * 0.5f; float _yPos = -_y - _height * 0.5f; GameObject _gameObject = null; if (_type == "text"){ _gameObject = GameObject.Instantiate(_imageCopyInstance); _gameObject.GetComponent<Image>().sprite = getSpriteByName(_realName.ToString()); RectTransform _rectTransform = (RectTransform) _gameObject.transform; _rectTransform.sizeDelta = new Vector2(_width, _height); //创建一个文字放置到那个位置,创建的时候就添加,这样可以方便编辑。 string _text = (value_["text"] as IJsonWrapper).GetString(); int _fontSize = (value_["fontSize"] as IJsonWrapper).GetInt(); GameObject _textGameObject = GameObject.Instantiate(_textCopyInstance); Text _textComponent = _textGameObject.GetComponent<Text>(); (_textGameObject.transform as RectTransform).sizeDelta = new Vector2(_fontSize * 10, _text.Length * _fontSize); _textComponent.fontSize = _fontSize; _textComponent.alignment = TextAnchor.MiddleCenter; _textComponent.text = _text; _textGameObject.transform.SetParent(_gameObject.transform); } else if (_type == "sprite"){ string _spriteName = (string) new ArrayList(_name.Split('_'))[1]; _gameObject = GameObject.Instantiate(_imageCopyInstance); _gameObject.GetComponent<Image>().sprite = getSpriteByName(_name.ToString()); RectTransform _rectTransform = (RectTransform) _gameObject.transform; _rectTransform.sizeDelta = new Vector2(_width, _height); } else if (_type == "prefab"){ _gameObject = GameObject.Instantiate(_imageCopyInstance); _gameObject.GetComponent<Image>().sprite = getSpriteByName(_realName.ToString()); RectTransform _rectTransform = (RectTransform) _gameObject.transform; _rectTransform.sizeDelta = new Vector2(_width, _height); //运行时实际去加载一个prefab,这里不会加载 } else if (_type == "gameObject" || _type == "Root"){ _gameObject = GameObject.Instantiate(_gameObjectCopyInstance); JsonData _children = value_["children"]; int _numChildren = _children.Count; for (int _disIndex = 0; _disIndex < _numChildren; _disIndex++){ JsonData _childInfo = _children[_disIndex]; GameObject _gameObjectChild = createGameObjectByJsonData(_childInfo); _gameObjectChild.transform.SetParent(_gameObject.transform); _gameObjectChild.transform.SetSiblingIndex(_disIndex); } } _gameObject.name = _name; _gameObject.transform.localPosition = new Vector3(_xPos, _yPos, 0f); _gameObject.transform.localScale = new Vector3(1f, 1f, 1f); return _gameObject; } }
PsdCreatePrefab : Python执行过后,文件生成到json和png文件中,这个时候,先执行这个场景,会生成 PSD 对应的 UGUI Prefab,并保存到prefab文件夹内。
场景内容如下,图片文字和GameObject节点,在运行时,都是通过CopyInstance下的节点复制生成的。
PsdLinkPrefab : 将 PSD 导出的 json 文件名,填写在 uiName 处。运行即可。
由于项目换成FGUI了,所以生成UGUI的舍弃,给大家抛砖引玉。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。