├── Psd ├── Hook │ ├── DotNetHook.cs │ ├── Extensions.cs │ └── Manager.cs ├── IPSDLayer.cs ├── PSD2UGUIWindow.cs ├── PSDInfo.cs ├── PSDLayerGroup.cs ├── PSDLayerImage.cs ├── PSDLayerText.cs ├── PSDParse.cs ├── PSDPreviewWindow.cs ├── PSDSetting.cs ├── ReadMe.md └── ReadMe │ ├── 1.png │ ├── 2.png │ ├── 3.png │ └── ReadMe.md └── README.md /Psd/Hook/DotNetHook.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace Aspose.Hook.Share 7 | { 8 | public class DotNetHook : IDisposable 9 | { 10 | private bool _disposedValue; 11 | 12 | protected virtual void Dispose(bool disposing) 13 | { 14 | if (!_disposedValue) 15 | { 16 | Remove(); 17 | _disposedValue = true; 18 | } 19 | } 20 | 21 | public void Dispose() 22 | { 23 | Dispose(true); 24 | GC.SuppressFinalize(this); 25 | } 26 | 27 | ~DotNetHook() 28 | { 29 | Dispose(false); 30 | } 31 | 32 | public bool IsEnabled { get; protected set; } 33 | 34 | public byte[] FromPtrData { get; private set; } 35 | 36 | public MethodBase FromMethod { get; } 37 | 38 | public MethodBase ToMethod { get; } 39 | 40 | private byte[] _existingPtrData; 41 | private byte[] _originalPtrData; 42 | 43 | private IntPtr _toPtr; 44 | private IntPtr _fromPtr; 45 | 46 | public DotNetHook(MethodBase from, MethodBase to) 47 | { 48 | FromMethod = from; 49 | ToMethod = to; 50 | } 51 | 52 | public void Apply() 53 | { 54 | Redirect(FromMethod.MethodHandle, ToMethod.MethodHandle); 55 | IsEnabled = true; 56 | } 57 | 58 | public void ReApply() 59 | { 60 | if (_existingPtrData == null) 61 | { 62 | throw new NullReferenceException("ExistingPtrData was null. Call DotNetHook.Remove() to populate the data."); 63 | } 64 | 65 | VirtualProtect(_fromPtr, (IntPtr)5, 0x40, out uint x); 66 | 67 | for (var i = 0; i < _existingPtrData.Length; i++) 68 | { 69 | Marshal.WriteByte(_fromPtr, i, _existingPtrData[i]); 70 | } 71 | 72 | VirtualProtect(_fromPtr, (IntPtr)5, x, out x); 73 | IsEnabled = true; 74 | } 75 | 76 | public void Remove() 77 | { 78 | if (_originalPtrData == null) 79 | { 80 | throw new NullReferenceException("OriginalPtrData was null. Call DotNetHook.Apply() to populate the data."); 81 | } 82 | 83 | VirtualProtect(_fromPtr, (IntPtr)5, 0x40, out uint x); 84 | 85 | _existingPtrData = new byte[_originalPtrData.Length]; 86 | 87 | for (var i = 0; i < _originalPtrData.Length; i++) 88 | { 89 | _existingPtrData[i] = Marshal.ReadByte(_fromPtr, i); 90 | Marshal.WriteByte(_fromPtr, i, _originalPtrData[i]); 91 | } 92 | 93 | VirtualProtect(_fromPtr, (IntPtr)5, x, out x); 94 | IsEnabled = false; 95 | } 96 | 97 | public T Call(object instance, params object[] args) 98 | { 99 | Remove(); 100 | try 101 | { 102 | var ret = FromMethod.Invoke(instance, args); 103 | ReApply(); 104 | return (T)Convert.ChangeType(ret, typeof(T)); 105 | } 106 | catch (Exception) 107 | { 108 | // TODO: On Hook failure 109 | } 110 | 111 | ReApply(); 112 | return default(T); 113 | 114 | } 115 | 116 | private void Redirect(RuntimeMethodHandle from, RuntimeMethodHandle to) 117 | { 118 | RuntimeHelpers.PrepareMethod(from); 119 | RuntimeHelpers.PrepareMethod(to); 120 | 121 | if (_fromPtr == default(IntPtr)) _fromPtr = from.GetFunctionPointer(); 122 | if (_toPtr == default(IntPtr)) _toPtr = to.GetFunctionPointer(); 123 | 124 | FromPtrData = new byte[32]; 125 | Marshal.Copy(_fromPtr, FromPtrData, 0, 32); 126 | 127 | VirtualProtect(_fromPtr, (IntPtr)5, 0x40, out uint x); 128 | 129 | if (IntPtr.Size == 8) 130 | { 131 | // x64 132 | _originalPtrData = new byte[13]; 133 | 134 | // 13 135 | Marshal.Copy(_fromPtr, _originalPtrData, 0, 13); 136 | 137 | Marshal.WriteByte(_fromPtr, 0, 0x49); 138 | Marshal.WriteByte(_fromPtr, 1, 0xbb); 139 | 140 | Marshal.WriteInt64(_fromPtr, 2, _toPtr.ToInt64()); 141 | 142 | Marshal.WriteByte(_fromPtr, 10, 0x41); 143 | Marshal.WriteByte(_fromPtr, 11, 0xff); 144 | Marshal.WriteByte(_fromPtr, 12, 0xe3); 145 | 146 | } 147 | else if (IntPtr.Size == 4) 148 | { 149 | // x86 150 | _originalPtrData = new byte[6]; 151 | 152 | // 6 153 | Marshal.Copy(_fromPtr, _originalPtrData, 0, 6); 154 | 155 | Marshal.WriteByte(_fromPtr, 0, 0xe9); 156 | Marshal.WriteInt32(_fromPtr, 1, _toPtr.ToInt32() - _fromPtr.ToInt32() - 5); 157 | Marshal.WriteByte(_fromPtr, 5, 0xc3); 158 | } 159 | 160 | VirtualProtect(_fromPtr, (IntPtr)5, x, out x); 161 | } 162 | 163 | [DllImport("kernel32.dll")] 164 | private static extern bool VirtualProtect(IntPtr lpAddress, IntPtr dwSize, uint flNewProtect,out uint lpflOldProtect); 165 | } 166 | } -------------------------------------------------------------------------------- /Psd/Hook/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace Aspose.Hook.Share 5 | { 6 | public static class Extensions 7 | { 8 | public static MethodInfo GetMethod(this string type, string method, BindingFlags flags = BindingFlags.Default,bool breakOnFind = true) 9 | { 10 | MethodInfo found = null; 11 | foreach (var mInfo in Type.GetType(type).GetMethods(flags)) 12 | { 13 | if (mInfo.Name != method) continue; 14 | 15 | found = mInfo; 16 | if (breakOnFind) 17 | break; 18 | } 19 | 20 | return found; 21 | } 22 | public static MethodInfo GetMethod(this string type, string method,Type[] parameterTypes, BindingFlags flags = BindingFlags.Default) 23 | { 24 | return Type.GetType(type).GetMethod(method, flags, null, parameterTypes, null); 25 | } 26 | 27 | } 28 | } -------------------------------------------------------------------------------- /Psd/Hook/Manager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | using Aspose.Hook.Share; 5 | 6 | namespace Aspose.Hook 7 | { 8 | public static class Manager 9 | { 10 | private static DotNetHook mCompareHook; 11 | private static DotNetHook mGreaterThanHook; 12 | 13 | private const string DATE_COMPARE_METHOD = "op_GreaterThan"; 14 | private const string STRING_COMPARE_METHOD = "Compare"; 15 | 16 | /// 17 | /// 启用hook 18 | /// 19 | public static void StartHook() 20 | { 21 | //string compare hook 22 | if (mCompareHook == null) 23 | { 24 | MethodBase stringCompareMethod = typeof(string).FullName.GetMethod(STRING_COMPARE_METHOD, new[] { typeof(string), typeof(string) }, BindingFlags.Static | BindingFlags.Public); 25 | MethodBase stringCompareMethodNew = typeof(Manager).FullName.GetMethod(nameof(NewCompare), BindingFlags.Static | BindingFlags.NonPublic); 26 | mCompareHook = new DotNetHook(stringCompareMethod, stringCompareMethodNew); 27 | } 28 | mCompareHook.Apply(); 29 | 30 | //date compare hook 31 | if (mGreaterThanHook == null) 32 | { 33 | MethodBase dateCompareMethod = typeof(DateTime).FullName.GetMethod(DATE_COMPARE_METHOD, new[] { typeof(DateTime), typeof(DateTime) }, BindingFlags.Static | BindingFlags.Public); 34 | MethodBase dateCompareMethodNew = typeof(Manager).FullName.GetMethod(nameof(NewGreaterThan), BindingFlags.Static | BindingFlags.NonPublic); 35 | mGreaterThanHook = new DotNetHook(dateCompareMethod, dateCompareMethodNew); 36 | } 37 | mGreaterThanHook.Apply(); 38 | 39 | //search all referenced Aspose assemblies. 40 | var assemblies = Assembly.GetCallingAssembly().GetReferencedAssemblies(); 41 | foreach (var assembly in assemblies) 42 | { 43 | if (assembly.Name.StartsWith("Aspose.")) 44 | { 45 | var type = Assembly.Load(assembly).GetType(assembly.Name + ".License"); 46 | if (type == null) 47 | { 48 | type = Assembly.Load(assembly).GetType(System.Threading.Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(assembly.Name.ToLower()) + ".License"); 49 | } 50 | if (type != null) 51 | { 52 | var instance = Activator.CreateInstance(type); 53 | type.GetMethod("SetLicense", new[] { typeof(Stream) }).Invoke(instance, new[] { new MemoryStream(Convert.FromBase64String("PExpY2Vuc2U+CiAgPERhdGE+CiAgICA8TGljZW5zZWRUbz5TdXpob3UgQXVuYm94IFNvZnR3YXJlIENvLiwgTHRkLjwvTGljZW5zZWRUbz4KICAgIDxFbWFpbFRvPnNhbGVzQGF1bnRlYy5jb208L0VtYWlsVG8+CiAgICA8TGljZW5zZVR5cGU+RGV2ZWxvcGVyIE9FTTwvTGljZW5zZVR5cGU+CiAgICA8TGljZW5zZU5vdGU+TGltaXRlZCB0byAxIGRldmVsb3BlciwgdW5saW1pdGVkIHBoeXNpY2FsIGxvY2F0aW9uczwvTGljZW5zZU5vdGU+CiAgICA8T3JkZXJJRD4xOTA4MjYwODA3NTM8L09yZGVySUQ+CiAgICA8VXNlcklEPjEzNDk3NjAwNjwvVXNlcklEPgogICAgPE9FTT5UaGlzIGlzIGEgcmVkaXN0cmlidXRhYmxlIGxpY2Vuc2U8L09FTT4KICAgIDxQcm9kdWN0cz4KICAgICAgPFByb2R1Y3Q+QXNwb3NlLlRvdGFsIGZvciAuTkVUPC9Qcm9kdWN0PgogICAgPC9Qcm9kdWN0cz4KICAgIDxFZGl0aW9uVHlwZT5FbnRlcnByaXNlPC9FZGl0aW9uVHlwZT4KICAgIDxTZXJpYWxOdW1iZXI+M2U0NGRlMzAtZmNkMi00MTA2LWIzNWQtNDZjNmEzNzE1ZmMyPC9TZXJpYWxOdW1iZXI+CiAgICA8U3Vic2NyaXB0aW9uRXhwaXJ5PjIwMjAwODI3PC9TdWJzY3JpcHRpb25FeHBpcnk+CiAgICA8TGljZW5zZVZlcnNpb24+My4wPC9MaWNlbnNlVmVyc2lvbj4KICAgIDxMaWNlbnNlSW5zdHJ1Y3Rpb25zPmh0dHBzOi8vcHVyY2hhc2UuYXNwb3NlLmNvbS9wb2xpY2llcy91c2UtbGljZW5zZTwvTGljZW5zZUluc3RydWN0aW9ucz4KICA8L0RhdGE+CiAgPFNpZ25hdHVyZT53UGJtNUt3ZTYvRFZXWFNIY1o4d2FiVEFQQXlSR0pEOGI3L00zVkV4YWZpQnd5U2h3YWtrNGI5N2c2eGtnTjhtbUFGY3J0c0cwd1ZDcnp6MytVYk9iQjRYUndTZWxsTFdXeXNDL0haTDNpN01SMC9jZUFxaVZFOU0rWndOQkR4RnlRbE9uYTFQajhQMzhzR1grQ3ZsemJLZFZPZXk1S3A2dDN5c0dqYWtaL1E9PC9TaWduYXR1cmU+CjwvTGljZW5zZT4=")) }); 54 | } 55 | } 56 | } 57 | // assemblies = Assembly.GetEntryAssembly().GetReferencedAssemblies(); 58 | // foreach (var assembly in assemblies) 59 | // { 60 | // if (assembly.Name.StartsWith("Aspose.")) 61 | // { 62 | // var type = Assembly.Load(assembly).GetType(assembly.Name + ".License"); 63 | // if (type == null) 64 | // { 65 | // type = Assembly.Load(assembly).GetType(System.Threading.Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(assembly.Name.ToLower()) + ".License"); 66 | // } 67 | // if (type != null) 68 | // { 69 | // var instance = Activator.CreateInstance(type); 70 | // type.GetMethod("SetLicense", new[] { typeof(Stream) }).Invoke(instance, new[] { new MemoryStream(Convert.FromBase64String("PExpY2Vuc2U+CiAgPERhdGE+CiAgICA8TGljZW5zZWRUbz5TdXpob3UgQXVuYm94IFNvZnR3YXJlIENvLiwgTHRkLjwvTGljZW5zZWRUbz4KICAgIDxFbWFpbFRvPnNhbGVzQGF1bnRlYy5jb208L0VtYWlsVG8+CiAgICA8TGljZW5zZVR5cGU+RGV2ZWxvcGVyIE9FTTwvTGljZW5zZVR5cGU+CiAgICA8TGljZW5zZU5vdGU+TGltaXRlZCB0byAxIGRldmVsb3BlciwgdW5saW1pdGVkIHBoeXNpY2FsIGxvY2F0aW9uczwvTGljZW5zZU5vdGU+CiAgICA8T3JkZXJJRD4xOTA4MjYwODA3NTM8L09yZGVySUQ+CiAgICA8VXNlcklEPjEzNDk3NjAwNjwvVXNlcklEPgogICAgPE9FTT5UaGlzIGlzIGEgcmVkaXN0cmlidXRhYmxlIGxpY2Vuc2U8L09FTT4KICAgIDxQcm9kdWN0cz4KICAgICAgPFByb2R1Y3Q+QXNwb3NlLlRvdGFsIGZvciAuTkVUPC9Qcm9kdWN0PgogICAgPC9Qcm9kdWN0cz4KICAgIDxFZGl0aW9uVHlwZT5FbnRlcnByaXNlPC9FZGl0aW9uVHlwZT4KICAgIDxTZXJpYWxOdW1iZXI+M2U0NGRlMzAtZmNkMi00MTA2LWIzNWQtNDZjNmEzNzE1ZmMyPC9TZXJpYWxOdW1iZXI+CiAgICA8U3Vic2NyaXB0aW9uRXhwaXJ5PjIwMjAwODI3PC9TdWJzY3JpcHRpb25FeHBpcnk+CiAgICA8TGljZW5zZVZlcnNpb24+My4wPC9MaWNlbnNlVmVyc2lvbj4KICAgIDxMaWNlbnNlSW5zdHJ1Y3Rpb25zPmh0dHBzOi8vcHVyY2hhc2UuYXNwb3NlLmNvbS9wb2xpY2llcy91c2UtbGljZW5zZTwvTGljZW5zZUluc3RydWN0aW9ucz4KICA8L0RhdGE+CiAgPFNpZ25hdHVyZT53UGJtNUt3ZTYvRFZXWFNIY1o4d2FiVEFQQXlSR0pEOGI3L00zVkV4YWZpQnd5U2h3YWtrNGI5N2c2eGtnTjhtbUFGY3J0c0cwd1ZDcnp6MytVYk9iQjRYUndTZWxsTFdXeXNDL0haTDNpN01SMC9jZUFxaVZFOU0rWndOQkR4RnlRbE9uYTFQajhQMzhzR1grQ3ZsemJLZFZPZXk1S3A2dDN5c0dqYWtaL1E9PC9TaWduYXR1cmU+CjwvTGljZW5zZT4=")) }); 71 | // } 72 | // } 73 | // } 74 | } 75 | 76 | /// 77 | /// 停用Hook 78 | /// 79 | public static void StopHook() 80 | { 81 | if(mCompareHook != null && mCompareHook.IsEnabled) 82 | { 83 | mCompareHook.Remove(); 84 | } 85 | if (mGreaterThanHook != null && mGreaterThanHook.IsEnabled) 86 | { 87 | mGreaterThanHook.Remove(); 88 | } 89 | } 90 | 91 | private static bool NewGreaterThan(DateTime t1, DateTime t2) 92 | { 93 | if (Assembly.GetCallingAssembly().FullName.StartsWith("Aspose.") && t2.ToString("yyyyMMdd") == "20200827") 94 | { 95 | return false; 96 | } 97 | else 98 | { 99 | return mGreaterThanHook.Call(null, t1, t2); 100 | } 101 | } 102 | 103 | private static int NewCompare(string s1, string s2) 104 | { 105 | 106 | if (Assembly.GetCallingAssembly().FullName.StartsWith("Aspose.") && s2 == "20200827") 107 | { 108 | return -1; 109 | } 110 | else 111 | { 112 | return mCompareHook.Call(null, s1, s2); 113 | } 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /Psd/IPSDLayer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using UnityEngine; 4 | 5 | namespace XGame 6 | { 7 | public abstract class IPSDLayer 8 | { 9 | public PSDLayerGroup Parent { set; get; } 10 | public string RealName { get; set; } 11 | public string Name { get; set; } 12 | public string UName { get; set; } 13 | public Vector2 Size { set; get; } 14 | public Vector2 CenterPosition { set; get; } 15 | public bool Ignore 16 | { 17 | get 18 | { 19 | if (IsRoot) 20 | return false; 21 | var res = Tags.Contains("ignore") || (Parent?.Ignore ?? false); 22 | return res; 23 | } 24 | } 25 | 26 | public bool Visible = true; 27 | public bool Reference => Tags.Contains("ref"); 28 | public bool IsRoot; 29 | public int LayerIndex; 30 | public string[] Tags; 31 | 32 | public void SetTransform(RectTransform transform, Vector2 screenSize) 33 | { 34 | transform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, Size.x); 35 | transform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, Size.y); 36 | transform.position = CenterPosition + new Vector2(screenSize.x / 2, screenSize.y / 2); 37 | transform.gameObject.SetActive(Visible); 38 | } 39 | 40 | public abstract void SetVariableValue(RectTransform rect); 41 | public abstract void SetDefaultValue(RectTransform obj); 42 | 43 | public virtual bool ValidTag(string tag) 44 | { 45 | if (tag == "ignore" || tag == "ref") 46 | return true; 47 | return false; 48 | } 49 | 50 | public void ReloadTags() 51 | { 52 | var nameMatches = RealName.Split('@'); 53 | Tags = new string[nameMatches.Length - 1]; 54 | if (Tags.Length > 0) 55 | Array.Copy(nameMatches, 1, Tags, 0, Tags.Length); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /Psd/PSD2UGUIWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using Model; 7 | using UnityEditor; 8 | using UnityEditor.SceneManagement; 9 | using UnityEditorInternal; 10 | using UnityEngine; 11 | using UnityEngine.SceneManagement; 12 | 13 | namespace XGame 14 | { 15 | public class PSD2UGUIWindow : SearchableEditorWindow 16 | { 17 | [MenuItem("UGUI/PSD2UGUI")] 18 | static void ShowWin() 19 | { 20 | var win = GetWindow(); 21 | win.Show(); 22 | } 23 | 24 | private string _dirPath = "./Psd"; 25 | private string _toFolder = "Assets/UI"; 26 | private string _imgPath = "Assets/Images/UI"; 27 | private string _settingAssets => PathUtil.PSDSetting; 28 | private AutocompleteSearchField _searchField; 29 | private Vector2 _scrollPos; 30 | private Vector2 _rightScrollPos; 31 | private Dictionary _folderShow = new Dictionary(); 32 | private List _latestOpened = new List(); 33 | private string _selectedDir; 34 | private PSDSetting _setting; 35 | private ReorderableList _componentList; 36 | private SerializedObject _settingObj; 37 | private string _operatingFile; 38 | 39 | public override void OnEnable() 40 | { 41 | _searchField = new AutocompleteSearchField(); 42 | if (File.Exists(_settingAssets.Replace("Assets", Application.dataPath))) 43 | { 44 | _setting = AssetDatabase.LoadAssetAtPath(_settingAssets); 45 | } 46 | else 47 | { 48 | _setting = CreateInstance(); 49 | AssetDatabase.CreateAsset(_setting, _settingAssets); 50 | } 51 | 52 | _dirPath = _setting.PsdFolder; 53 | _toFolder = _setting.UIFolder; 54 | _latestOpened = _setting.LatesdOpened; 55 | _settingObj = new SerializedObject(_setting); 56 | var prop = _settingObj.FindProperty("ComponentTypes"); 57 | _componentList = new ReorderableList(_settingObj, prop, true, true, true, true); 58 | _componentList.drawElementCallback += (rect, index, active, focused) => 59 | { 60 | rect.height = EditorGUIUtility.singleLineHeight; 61 | rect.y += 2; 62 | EditorGUI.PropertyField(rect,prop.GetArrayElementAtIndex(index), new GUIContent($"{index}")); 63 | }; 64 | _componentList.drawHeaderCallback += rect => 65 | { 66 | GUI.Label(rect, "组件优先级"); 67 | }; 68 | } 69 | 70 | private void OnGUI() 71 | { 72 | var top = new Rect(0, 0, position.width - 305, 100); 73 | var right = new Rect(position.width - 300, 0, 300, position.height); 74 | var center = new Rect(0, top.height + 5, position.width - 305, position.height - top.height - 10); 75 | GUILayout.BeginArea(top, "", "box"); 76 | OnTopGUI(); 77 | GUILayout.EndArea(); 78 | 79 | GUILayout.BeginArea(center, ""); 80 | OnCenterGUI(); 81 | GUILayout.EndArea(); 82 | 83 | GUILayout.BeginArea(right, "", "box"); 84 | OnRightGUI(); 85 | GUILayout.EndArea(); 86 | } 87 | 88 | private void OnTopGUI() 89 | { 90 | EditorGUILayout.BeginHorizontal(); 91 | _dirPath = EditorGUILayout.TextField("文件夹路径", _dirPath, GUILayout.Width(position.width / 2)); 92 | if (GUILayout.Button("打开文件夹", GUILayout.Width(100))) 93 | { 94 | var folderInfo = new DirectoryInfo(_dirPath); 95 | System.Diagnostics.Process.Start("explorer.exe", folderInfo.FullName); 96 | } 97 | EditorGUILayout.EndHorizontal(); 98 | EditorGUILayout.BeginHorizontal(); 99 | _toFolder = EditorGUILayout.TextField("目标文件夹", _toFolder, GUILayout.Width(position.width / 2)); 100 | if (GUILayout.Button("打开文件夹", GUILayout.Width(100))) 101 | { 102 | var folderInfo = new DirectoryInfo(_toFolder); 103 | System.Diagnostics.Process.Start("explorer.exe", folderInfo.FullName); 104 | } 105 | EditorGUILayout.EndHorizontal(); 106 | _searchField.OnGUI(); 107 | 108 | if (_latestOpened.Count > 0) 109 | { 110 | EditorGUILayout.BeginHorizontal(); 111 | EditorGUILayout.LabelField("最近打开", GUILayout.Width(100)); 112 | for (int i = 0; i < _latestOpened.Count; i++) 113 | { 114 | EditorGUILayout.BeginHorizontal(GUILayout.MaxWidth(200)); 115 | var dir = new DirectoryInfo(_latestOpened[i]); 116 | if (!dir.Exists) 117 | { 118 | _latestOpened.RemoveAt(i); 119 | return; 120 | } 121 | 122 | var btnName = dir.FullName.Replace("\\", "/") 123 | .Replace(_dirPath.Replace("\\", "/"), ""); 124 | GUI.backgroundColor = _selectedDir == dir.FullName ? Color.green : Color.white; 125 | if (GUILayout.Button(btnName)) 126 | { 127 | if (_selectedDir == dir.FullName) 128 | _selectedDir = ""; 129 | else 130 | _selectedDir = dir.FullName; 131 | } 132 | GUI.backgroundColor = Color.white; 133 | 134 | if (GUILayout.Button("X", GUILayout.Width(20))) 135 | { 136 | if (_selectedDir == _latestOpened[i]) 137 | _selectedDir = ""; 138 | _latestOpened.RemoveAt(i); 139 | return; 140 | } 141 | EditorGUILayout.EndHorizontal(); 142 | } 143 | EditorGUILayout.EndHorizontal(); 144 | } 145 | EditorUtility.SetDirty(_setting); 146 | } 147 | 148 | private void OnCenterGUI() 149 | { 150 | if (!Directory.Exists(_dirPath)) 151 | return; 152 | _setting.PsdFolder = _dirPath; 153 | _setting.UIFolder = _toFolder; 154 | _scrollPos = EditorGUILayout.BeginScrollView(_scrollPos, "box"); 155 | var dir = string.IsNullOrEmpty(_selectedDir) ? _dirPath : _selectedDir; 156 | var folderInfo = new DirectoryInfo(dir); 157 | if (string.IsNullOrEmpty(_searchField.searchString)) 158 | { 159 | var files = folderInfo.GetFiles("*.psd"); 160 | if (files.Length > 0) 161 | { 162 | for (int i = 0; i < files.Length; i++) 163 | { 164 | GUI.backgroundColor = i % 2 == 0 ? new Color(220 / 255f, 220 / 255f, 220 / 255f) : Color.white; 165 | DrawFile(files[i], 0); 166 | } 167 | GUI.backgroundColor = Color.white; 168 | } 169 | foreach (var d in folderInfo.GetDirectories()) 170 | { 171 | DrawFolder(d, 0); 172 | } 173 | } 174 | else 175 | { 176 | var files = folderInfo.GetFiles("*.psd", SearchOption.AllDirectories); 177 | if (files.Length > 0) 178 | { 179 | for (int i = 0; i < files.Length; i++) 180 | { 181 | if (files[i].Name.Contains(_searchField.searchString)) 182 | { 183 | GUI.backgroundColor = i % 2 == 0 ? new Color(220 / 255f, 220 / 255f, 220 / 255f) : Color.white; 184 | DrawFile(files[i], 0); 185 | } 186 | } 187 | GUI.backgroundColor = Color.white; 188 | } 189 | } 190 | 191 | EditorGUILayout.EndScrollView(); 192 | } 193 | 194 | private void DrawFolder(DirectoryInfo dir, int tuck) 195 | { 196 | _folderShow.TryGetValue(dir.FullName, out var flag); 197 | EditorGUILayout.BeginHorizontal("dockareaStandalone", GUILayout.Height(40)); 198 | EditorGUILayout.LabelField("", GUILayout.Width(20 * tuck)); 199 | flag = EditorGUILayout.Foldout(flag, dir.Name); 200 | if (GUILayout.Button("批量生成", GUILayout.Width(100))) 201 | { 202 | if (!OpenNewScene()) 203 | return; 204 | PushInLatestOpened(dir.FullName); 205 | foreach (var fileInfo in dir.GetFiles("*.psd", SearchOption.AllDirectories)) 206 | { 207 | GenFile(fileInfo, false); 208 | } 209 | } 210 | // if (GUILayout.Button("批量生成并导出图片", GUILayout.Width(200))) 211 | // { 212 | // foreach (var fileInfo in dir.GetFiles("*.psd", SearchOption.AllDirectories)) 213 | // { 214 | // GenFile(fileInfo, true); 215 | // } 216 | // } 217 | EditorGUILayout.EndHorizontal(); 218 | 219 | 220 | _folderShow[dir.FullName] = flag; 221 | if (flag) 222 | { 223 | var files = dir.GetFiles("*.psd"); 224 | if (files.Length > 0) 225 | { 226 | EditorGUILayout.BeginVertical("box"); 227 | for (int i = 0; i < files.Length; i++) 228 | { 229 | GUI.backgroundColor = i % 2 == 0 ? new Color(220 / 255f, 220 / 255f, 220 / 255f) : Color.white; 230 | DrawFile(files[i], tuck + 1); 231 | } 232 | EditorGUILayout.EndVertical(); 233 | } 234 | GUI.backgroundColor = Color.white; 235 | foreach (var d in dir.GetDirectories()) 236 | { 237 | DrawFolder(d, tuck + 1); 238 | } 239 | } 240 | } 241 | 242 | private void OnRightGUI() 243 | { 244 | EditorGUILayout.BeginVertical(); 245 | _setting.DefaultFont = (Font) EditorGUILayout.ObjectField("默认字体", _setting.DefaultFont, typeof(Font), false); 246 | _setting.ScreenSize = EditorGUILayout.Vector2Field("屏幕尺寸", _setting.ScreenSize); 247 | EditorGUILayout.EndVertical(); 248 | 249 | _rightScrollPos = EditorGUILayout.BeginScrollView(_rightScrollPos, "box", GUILayout.MaxHeight(400)); 250 | _componentList.DoLayoutList(); 251 | EditorGUILayout.EndScrollView(); 252 | if (_settingObj.hasModifiedProperties) 253 | _settingObj.ApplyModifiedProperties(); 254 | } 255 | 256 | private void DrawFile(FileInfo file, int tuck) 257 | { 258 | EditorGUILayout.BeginHorizontal("box"); 259 | GUI.backgroundColor = _operatingFile == file.FullName ? Color.green : Color.white; 260 | EditorGUILayout.LabelField("", GUILayout.Width(30 * tuck)); 261 | EditorGUILayout.LabelField(file.Name); 262 | 263 | if (GUILayout.Button("打开PSD", GUILayout.Width(100))) 264 | { 265 | _operatingFile = file.FullName; 266 | CMDHelper.RunCmd($"explorer file:///{file.FullName}"); 267 | } 268 | 269 | if (GUILayout.Button("预览", GUILayout.Width(100))) 270 | { 271 | _operatingFile = file.FullName; 272 | PSDPreviewWindow.ShowWin(GetParseInfo(file)); 273 | } 274 | if (GUILayout.Button("生成", GUILayout.Width(100))) 275 | { 276 | if (!OpenNewScene()) 277 | return; 278 | _operatingFile = file.FullName; 279 | GenFile(file, false); 280 | PushInLatestOpened(file.DirectoryName); 281 | } 282 | 283 | EditorGUILayout.EndHorizontal(); 284 | } 285 | 286 | public PSDInfo GetParseInfo(FileInfo file) 287 | { 288 | var info = new PSDParseInfo(); 289 | info.Path = file.FullName; 290 | var fileName = Path.GetFileNameWithoutExtension(file.FullName); 291 | info.Name = fileName.Substring(fileName.IndexOf('@') + 1); 292 | var dir = new DirectoryInfo(_dirPath); 293 | info.PublicPath = file.FullName.Replace(dir.FullName, "") 294 | .Replace(file.Name, "").Replace('\\', '/').Trim('/'); 295 | info.ToFolder = _toFolder; 296 | info.ImageSavePath = $"{_imgPath}/{info.PublicPath}"; 297 | info.Setting = _setting; 298 | 299 | return new PSDInfo(info.Path, info); 300 | } 301 | 302 | private void GenFile(FileInfo file, bool exportImage) 303 | { 304 | PSDParse.Parse(GetParseInfo(file).ParseInfo, _setting.ScreenSize); 305 | } 306 | 307 | private void PushInLatestOpened(string dir) 308 | { 309 | _latestOpened.Remove(dir); 310 | _latestOpened.Insert(0, dir); 311 | if (_latestOpened.Count > 5) 312 | _latestOpened.RemoveAt(5); 313 | } 314 | 315 | private bool OpenNewScene() 316 | { 317 | var activeScene = SceneManager.GetActiveScene(); 318 | if (activeScene.name != "") 319 | { 320 | var res = EditorUtility.DisplayDialog("提示", "生成UI需要在空场景中进行,即将创建新场景,是否继续?", "继续", "取消"); 321 | if (res) 322 | { 323 | if (activeScene.isDirty) 324 | { 325 | var save = EditorUtility.DisplayDialog("提示", $"场景{activeScene.name}有更改,是否保存", "是", "否"); 326 | if (save) 327 | EditorSceneManager.SaveScene(activeScene); 328 | } 329 | EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects, NewSceneMode.Single); 330 | } 331 | return res; 332 | } 333 | 334 | return true; 335 | } 336 | } 337 | } -------------------------------------------------------------------------------- /Psd/PSDInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using Aspose.PSD; 6 | using Aspose.PSD.FileFormats.Psd; 7 | using Aspose.PSD.FileFormats.Psd.Layers; 8 | using Aspose.PSD.ImageOptions; 9 | using Model; 10 | using UnityEngine; 11 | 12 | namespace XGame 13 | { 14 | public class PSDInfo 15 | { 16 | public PSDLayerGroup Root; 17 | public PSDParseInfo ParseInfo; 18 | 19 | private HashSet _layerNames; 20 | private string _path; 21 | private Dictionary _layersMap; 22 | public PSDInfo(string path, PSDParseInfo parseInfo) 23 | { 24 | ParseInfo = parseInfo; 25 | _path = path; 26 | _layerNames = new HashSet(); 27 | _layersMap = new Dictionary(); 28 | var psdImage = (PsdImage) Image.Load(path); 29 | Root = new PSDLayerGroup(); 30 | Root.Size = new Vector2(psdImage.Width, psdImage.Height); 31 | var fileName = Path.GetFileNameWithoutExtension(path); 32 | Root.RealName = fileName; 33 | Root.Name = fileName.Substring(fileName.IndexOf('@') + 1); 34 | Root.UName = Root.Name; 35 | Root.CenterPosition = Vector2.zero; 36 | Root.IsRoot = true; 37 | _layersMap[Root.UName.GetHashCode()] = Root; 38 | var stack = new Stack(); 39 | stack.Push(Root); 40 | try 41 | { 42 | for (var index = 0; index < psdImage.Layers.Length; index++) 43 | { 44 | var layer = psdImage.Layers[index]; 45 | InLayerGroup(layer, index, stack); 46 | } 47 | } 48 | finally 49 | { 50 | psdImage.Dispose(); 51 | } 52 | FinalVerify(Root); 53 | } 54 | 55 | public IPSDLayer GetLayer(int uid) 56 | { 57 | _layersMap.TryGetValue(uid, out var layer); 58 | return layer; 59 | } 60 | 61 | public void SaveImage(string dirPath) 62 | { 63 | var pngOption = new PngOptions(); 64 | var psdImage = (PsdImage) Image.Load(_path); 65 | foreach (var layer in psdImage.Layers) 66 | { 67 | if (!(layer is SectionDividerLayer || layer is TextLayer)) 68 | { 69 | if (layer is LayerGroup) 70 | { 71 | if (layer.Name.Contains("@img=")) 72 | { 73 | var idx = layer.Name.IndexOf("@img=", StringComparison.Ordinal); 74 | var imageName = layer.Name.Substring(0, idx); 75 | if (!Directory.Exists(dirPath)) 76 | Directory.CreateDirectory(dirPath); 77 | layer.Save($"{dirPath}/{imageName}.png", pngOption); 78 | } 79 | } 80 | else 81 | { 82 | var imageName = layer.Name; 83 | if (layer.Name.Contains("@name=")) 84 | { 85 | var idx = layer.Name.IndexOf("@name=", StringComparison.Ordinal); 86 | imageName = layer.Name.Substring(0, idx); 87 | } 88 | 89 | if (!Directory.Exists(dirPath)) 90 | Directory.CreateDirectory(dirPath); 91 | layer.Save($"{dirPath}/{imageName}.png", 92 | pngOption); 93 | } 94 | 95 | } 96 | } 97 | } 98 | 99 | private void InLayerGroup(Layer l, int index, Stack stack) 100 | { 101 | if (l is SectionDividerLayer || l.DisplayName == "") 102 | { 103 | var psdGroup = new PSDLayerGroup(); 104 | psdGroup.Parent = stack.Peek(); 105 | psdGroup.Parent.PsdLayers.Add(psdGroup); 106 | psdGroup.LayerDividerIndex = index; 107 | stack.Push(psdGroup); 108 | return; 109 | } 110 | 111 | 112 | IPSDLayer psdLayer = null; 113 | var group = stack.Peek(); 114 | switch (l) 115 | { 116 | case LayerGroup p1: 117 | psdLayer = group; 118 | break; 119 | case TextLayer p2: 120 | psdLayer = new PSDLayerText(); 121 | psdLayer.Parent = @group; 122 | break; 123 | default: 124 | psdLayer = new PSDLayerImage(); 125 | psdLayer.Parent = @group; 126 | break; 127 | } 128 | 129 | 130 | psdLayer.RealName = l.DisplayName.Replace("Layer group: ", ""); 131 | var nameMatches = psdLayer.RealName.Split('@'); 132 | psdLayer.Name = nameMatches.TryMatchOne(a => a.StartsWith("name="), out var res) 133 | ? res.Replace("name=", "") 134 | : nameMatches[0]; 135 | psdLayer.LayerIndex = index; 136 | psdLayer.Tags = new string[nameMatches.Length - 1]; 137 | if (psdLayer.Tags.Length > 0) 138 | Array.Copy(nameMatches, 1, psdLayer.Tags, 0, psdLayer.Tags.Length); 139 | 140 | if (l is LayerGroup layerGroup) 141 | { 142 | (group.CenterPosition, group.Size) = GetPosSize(layerGroup); 143 | stack.Pop(); 144 | return; 145 | } 146 | 147 | if (l is TextLayer textLayer) 148 | { 149 | var text = psdLayer as PSDLayerText; 150 | var font = textLayer.Font; 151 | text.Text = textLayer.InnerText; 152 | text.FontSize = font.Size; 153 | text.Bold = font.Bold; 154 | text.Italic = font.Italic; 155 | text.Color = new UnityEngine.Color(textLayer.TextColor.R / 255f, textLayer.TextColor.G / 255f, textLayer.TextColor.B / 255f, textLayer.TextColor.A); 156 | } 157 | else 158 | { 159 | var image = psdLayer as PSDLayerImage; 160 | image.ImageLayer = l; 161 | image.Alpha = l.FillOpacity; 162 | image.ImageName = nameMatches[0]; 163 | } 164 | 165 | psdLayer.Size = new Vector2(l.Width, l.Height); 166 | psdLayer.CenterPosition = GetAnchorPos(l); 167 | psdLayer.Visible = l.IsVisible; 168 | @group.PsdLayers.Add(psdLayer); 169 | } 170 | 171 | private void FindBound(LayerGroup layerGroup, ref int l, ref int r, ref int t, ref int b) 172 | { 173 | foreach (var g in layerGroup.Layers) 174 | { 175 | if (g is LayerGroup lg) 176 | { 177 | FindBound(lg, ref l, ref r, ref t, ref b); 178 | } 179 | else if (! (g is SectionDividerLayer)) 180 | { 181 | l = Mathf.Min(l, g.Left); 182 | r = Mathf.Max(r, g.Right); 183 | t = Mathf.Min(t, g.Top); 184 | b = Mathf.Max(b, g.Bottom); 185 | } 186 | } 187 | } 188 | 189 | private Vector2 GetAnchorPos(Layer layer) 190 | { 191 | var t = layer.Top; 192 | var l = layer.Left; 193 | var r = layer.Right; 194 | var b = layer.Bottom; 195 | var center = new Vector2(Root.Size.x / 2, Root.Size.y / 2); 196 | var pos = new Vector2((l + r) / 2f, (t + b) / 2f); 197 | return new Vector2(pos.x - center.x, center.y - pos.y); 198 | } 199 | 200 | 201 | private (Vector2, Vector2) GetPosSize(LayerGroup layerGroup) 202 | { 203 | var l = 9999; 204 | var r = 0; 205 | var t = 9999; 206 | var b = 0; 207 | FindBound(layerGroup, ref l, ref r, ref t, ref b); 208 | var center = new Vector2(Root.Size.x / 2, Root.Size.y / 2); 209 | var pos = new Vector2((l + r) / 2f, (t + b) / 2f); 210 | return (new Vector2(pos.x - center.x, center.y - pos.y), 211 | new Vector2(r - l, b - t)); 212 | } 213 | 214 | private void VerifyNames(string name) 215 | { 216 | if (string.IsNullOrEmpty(name)) 217 | return; 218 | if (!_layerNames.Contains(name)) 219 | { 220 | _layerNames.Add(name); 221 | } 222 | else 223 | { 224 | throw new Exception($"{Root.Name}存在重复名称{name}, 可能会导致重新生成时数据错乱或丢失"); 225 | } 226 | 227 | } 228 | 229 | 230 | private void FinalVerify(IPSDLayer group) 231 | { 232 | if (group.Parent != null) 233 | { 234 | group.UName = $"{group.Parent.UName}-{group.Name}"; 235 | _layersMap[group.UName.GetHashCode()] = group; 236 | if (!group.Ignore) 237 | { 238 | VerifyNames(group.UName); 239 | } 240 | } 241 | if (group is PSDLayerGroup layerGroup) 242 | { 243 | foreach (var psdLayer in layerGroup.PsdLayers) 244 | { 245 | FinalVerify(psdLayer); 246 | } 247 | } 248 | } 249 | 250 | } 251 | } -------------------------------------------------------------------------------- /Psd/PSDLayerGroup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEngine.UI; 5 | 6 | namespace XGame 7 | { 8 | public class PSDLayerGroup : IPSDLayer 9 | { 10 | public List PsdLayers = new List(); 11 | 12 | public int LayerDividerIndex; 13 | 14 | public override void SetVariableValue(RectTransform transform) 15 | { 16 | if (IsRoot) 17 | { 18 | return; 19 | } 20 | foreach (var tag in Tags) 21 | { 22 | 23 | } 24 | 25 | } 26 | 27 | public override void SetDefaultValue(RectTransform obj) 28 | { 29 | 30 | } 31 | 32 | public override bool ValidTag(string tag) 33 | { 34 | if (tag.StartsWith("grid") || tag.StartsWith("ver") || tag.StartsWith("hor") 35 | || tag.StartsWith("list=")) 36 | return true; 37 | return base.ValidTag(tag); 38 | } 39 | 40 | 41 | } 42 | 43 | public enum PSDGroupFlag 44 | { 45 | None, 46 | GridLayout, 47 | HorizontalLayout, 48 | VerticalLayout, 49 | } 50 | } -------------------------------------------------------------------------------- /Psd/PSDLayerImage.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using Aspose.PSD.FileFormats.Psd.Layers; 4 | using Model; 5 | using UnityEditor; 6 | using UnityEngine; 7 | using UnityEngine.UI; 8 | 9 | namespace XGame 10 | { 11 | public class PSDLayerImage : IPSDLayer 12 | { 13 | public int Alpha; 14 | public string ImageName; 15 | public Layer ImageLayer; 16 | //仅用于创建RectTransform,不带Image组件 17 | public bool Empty => Tags?.Contains("empty") ?? false; 18 | 19 | public override void SetVariableValue(RectTransform rect) 20 | { 21 | foreach (var tag in Tags) 22 | { 23 | if (tag.StartsWith("name=")) 24 | { 25 | Name = tag.Replace("name=", "").Trim(); 26 | rect.gameObject.name = Name; 27 | }else if (tag.StartsWith("ys=")) 28 | { 29 | var prefab = tag.Replace("ys=", "").Trim(); 30 | if (!string.IsNullOrEmpty(prefab)) 31 | { 32 | var sub = rect.GetComponent(); 33 | if (sub == null) 34 | sub = rect.gameObject.AddComponent(); 35 | sub.PrefabAsset = prefab; 36 | } 37 | } 38 | } 39 | 40 | if (!Empty) 41 | { 42 | var image = rect.GetComponent(); 43 | if (image == null) 44 | { 45 | Debug.LogError($"{rect.gameObject.name}不存在组件Image,请检查是否需要被标记为Empty"); 46 | return; 47 | } 48 | var c = image.color; 49 | c.a = Alpha / 100f; 50 | image.color = c; 51 | var sprites = AssetDatabase.FindAssets($"t:Sprite {ImageName}"); 52 | foreach (var s in sprites) 53 | { 54 | var p = AssetDatabase.GUIDToAssetPath(s); 55 | if (Path.GetFileNameWithoutExtension(p) == ImageName) 56 | { 57 | image.sprite = AssetDatabase.LoadAssetAtPath(p); 58 | if (image.sprite.border != Vector4.zero) 59 | { 60 | image.type = Image.Type.Sliced; 61 | } 62 | break; 63 | } 64 | } 65 | 66 | } 67 | 68 | 69 | } 70 | 71 | public override void SetDefaultValue(RectTransform obj) 72 | { 73 | var img = obj.GetComponent(); 74 | if (img == null && !Empty) 75 | img = obj.gameObject.AddComponent(); 76 | } 77 | 78 | public override bool ValidTag(string tag) 79 | { 80 | if (tag.StartsWith("ys=") || tag == "empty" || tag.StartsWith("name=")) 81 | return true; 82 | return base.ValidTag(tag); 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /Psd/PSDLayerText.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Model; 4 | using UnityEditor; 5 | using UnityEngine; 6 | using UnityEngine.UI; 7 | 8 | namespace XGame 9 | { 10 | public class PSDLayerText : IPSDLayer 11 | { 12 | public string Text; 13 | public float FontSize; 14 | public Color Color; 15 | public string Font; 16 | public bool Bold; 17 | public bool Italic; 18 | 19 | public override void SetVariableValue(RectTransform rect) 20 | { 21 | foreach (var tag in Tags) 22 | { 23 | if (tag.Contains("font=")) 24 | { 25 | Font = tag.Replace("font=", "").Trim(); 26 | } 27 | else if (tag.Contains("size=")) 28 | { 29 | FontSize = Convert.ToInt32(tag.Replace("size=", "")); 30 | } 31 | 32 | } 33 | 34 | var text = rect.GetComponent(); 35 | text.color = Color; 36 | text.fontSize = (int) FontSize; 37 | text.text = Text; 38 | if (!string.IsNullOrEmpty(Font)) 39 | { 40 | var fonts = AssetDatabase.FindAssets($"t:Font {Font}"); 41 | foreach (var f in fonts) 42 | { 43 | var p = AssetDatabase.GUIDToAssetPath(f); 44 | if (Path.GetFileNameWithoutExtension(p) == Font) 45 | { 46 | text.font = AssetDatabase.LoadAssetAtPath(p); 47 | break; 48 | } 49 | } 50 | } 51 | else 52 | { 53 | var setting = AssetDatabase.LoadAssetAtPath(PathUtil.PSDSetting); 54 | text.font = setting.DefaultFont; 55 | } 56 | 57 | if (Bold && Italic) 58 | { 59 | text.fontStyle = FontStyle.BoldAndItalic; 60 | } 61 | else if (Bold) 62 | { 63 | text.fontStyle = FontStyle.Bold; 64 | }else if (Italic) 65 | { 66 | text.fontStyle = FontStyle.Italic; 67 | } 68 | else 69 | { 70 | text.fontStyle = FontStyle.Normal; 71 | } 72 | } 73 | 74 | public override void SetDefaultValue(RectTransform obj) 75 | { 76 | var t = obj.GetComponent(); 77 | if (t == null) 78 | t = obj.gameObject.AddComponent(); 79 | t.horizontalOverflow = HorizontalWrapMode.Overflow; 80 | t.verticalOverflow = VerticalWrapMode.Overflow; 81 | t.alignment = TextAnchor.MiddleCenter; 82 | t.raycastTarget = false; 83 | } 84 | 85 | public override bool ValidTag(string tag) 86 | { 87 | if (tag.StartsWith("font=") || tag.StartsWith("size=")) 88 | return true; 89 | return base.ValidTag(tag); 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /Psd/PSDParse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using Model; 5 | using UnityEditor; 6 | using UnityEditorInternal; 7 | using UnityEngine; 8 | 9 | namespace XGame 10 | { 11 | public struct PSDParseInfo 12 | { 13 | public string Path; 14 | //导出的Prefab名 15 | public string Name; 16 | public string PublicPath; 17 | public string ToFolder; 18 | public string ImageSavePath; 19 | public bool ExportImage; 20 | public PSDSetting Setting; 21 | 22 | public string TargetPath => $"{ToFolder}/{PublicPath}/{Name}.prefab"; 23 | } 24 | 25 | public static class PSDParse 26 | { 27 | private static PSDParseInfo _info; 28 | private static Vector2 _screenSize; 29 | public static void Parse(PSDParseInfo info, Vector2 screenSize) 30 | { 31 | _info = info; 32 | _screenSize = screenSize; 33 | var file = new PSDInfo(info.Path, info); 34 | if (info.ExportImage) 35 | { 36 | file.SaveImage(info.ImageSavePath); 37 | AssetDatabase.Refresh(); 38 | } 39 | 40 | var canvas = UnityEngine.Object.FindObjectOfType(); 41 | if (canvas == null) 42 | { 43 | EditorApplication.ExecuteMenuItem("GameObject/UI/Canvas"); 44 | canvas = UnityEngine.Object.FindObjectOfType(); 45 | } 46 | 47 | var target = info.TargetPath; 48 | if (!Directory.Exists(Path.GetDirectoryName(target))) 49 | { 50 | Directory.CreateDirectory(Path.GetDirectoryName(target)); 51 | } 52 | if (File.Exists(target)) 53 | { 54 | var prefab = AssetDatabase.LoadAssetAtPath(target); 55 | var obj = (GameObject) PrefabUtility.InstantiatePrefab(prefab, canvas.transform); 56 | PrefabUtility.UnpackPrefabInstance(obj, PrefabUnpackMode.OutermostRoot, InteractionMode.AutomatedAction); 57 | var objs = new List(); 58 | UpdatePrefab(file.Root, obj, objs); 59 | PrefabUtility.SaveAsPrefabAssetAndConnect(obj.gameObject, target, InteractionMode.AutomatedAction); 60 | EditorGUIUtility.PingObject(obj.gameObject); 61 | } 62 | else 63 | { 64 | RectTransform obj = new GameObject(file.Root.Name).AddComponent(); 65 | obj.SetParent(canvas.transform, false); 66 | file.Root.SetTransform(obj, screenSize); 67 | var objs = new List(); 68 | Group2UGUI(obj, file.Root, objs); 69 | if (!Directory.Exists(Path.GetDirectoryName(target))) 70 | { 71 | Directory.CreateDirectory(Path.GetDirectoryName(target)); 72 | } 73 | EditorGUIUtility.PingObject(obj.gameObject); 74 | PrefabUtility.SaveAsPrefabAssetAndConnect(obj.gameObject, target, InteractionMode.AutomatedAction); 75 | } 76 | } 77 | 78 | private static void UpdatePrefab(PSDLayerGroup layer, GameObject node, List objs) 79 | { 80 | var nodeRect = node.GetComponent(); 81 | var allChild = node.transform.Children().ToList(); 82 | layer.SetTransform(nodeRect, _screenSize); 83 | layer.SetVariableValue(nodeRect); 84 | var index = 0; 85 | foreach (var psdLayer in layer.PsdLayers) 86 | { 87 | if (psdLayer.Ignore) 88 | continue; 89 | var nodeTrans = node.transform.Find(psdLayer.Name); 90 | if (nodeTrans == null) 91 | { 92 | nodeTrans = new GameObject(psdLayer.Name).AddComponent(); 93 | nodeTrans.SetParent(nodeRect); 94 | psdLayer.SetDefaultValue((RectTransform) nodeTrans); 95 | } 96 | else 97 | { 98 | allChild.Remove(nodeTrans); 99 | } 100 | 101 | if (psdLayer is PSDLayerGroup group) 102 | { 103 | UpdatePrefab(group, nodeTrans.gameObject, objs); 104 | } 105 | else 106 | { 107 | var rect = nodeTrans.GetComponent(); 108 | psdLayer.SetTransform(rect, _screenSize); 109 | psdLayer.SetVariableValue(rect); 110 | } 111 | nodeTrans.SetSiblingIndex(index); 112 | index++; 113 | 114 | if (psdLayer.Reference) 115 | { 116 | objs.Add(nodeTrans.gameObject); 117 | } 118 | } 119 | 120 | foreach (var transform in allChild) 121 | { 122 | Object.DestroyImmediate(transform.gameObject); 123 | } 124 | } 125 | 126 | private static void Group2UGUI(RectTransform node, PSDLayerGroup group, List objs) 127 | { 128 | foreach (var psdLayer in group.PsdLayers) 129 | { 130 | if (psdLayer.Ignore) 131 | continue; 132 | RectTransform lo = null; 133 | lo = CreateRect(psdLayer); 134 | if (lo != null) 135 | { 136 | lo.transform.SetParent(node.transform, false); 137 | psdLayer.SetTransform(lo, _screenSize); 138 | } 139 | if (psdLayer is PSDLayerGroup g) 140 | { 141 | Group2UGUI(lo, g, objs); 142 | g.SetVariableValue(lo); 143 | }else if (psdLayer is PSDLayerImage image) 144 | { 145 | image.SetDefaultValue(lo); 146 | image.SetVariableValue(lo); 147 | }else if (psdLayer is PSDLayerText text) 148 | { 149 | text.SetDefaultValue(lo); 150 | text.SetVariableValue(lo); 151 | } 152 | 153 | if (psdLayer.Reference) 154 | { 155 | objs.Add(lo.gameObject); 156 | } 157 | } 158 | } 159 | 160 | private static void CopyGameObject(GameObject from, GameObject to) 161 | { 162 | var components = from.GetComponents(); 163 | foreach (var c in components) 164 | { 165 | ComponentUtility.CopyComponent(c); 166 | if (to.TryGetComponent(c.GetType(), out var target)) 167 | { 168 | ComponentUtility.PasteComponentValues(target); 169 | } 170 | else 171 | { 172 | ComponentUtility.PasteComponentAsNew(to); 173 | } 174 | } 175 | } 176 | 177 | private static RectTransform CreateRect(IPSDLayer layer) 178 | { 179 | var obj = new GameObject(layer.Name); 180 | var rect = obj.AddComponent(); 181 | return rect; 182 | } 183 | } 184 | } -------------------------------------------------------------------------------- /Psd/PSDPreviewWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Aspose.PSD.FileFormats.Psd; 5 | using Aspose.PSD.FileFormats.Psd.Layers; 6 | using Model; 7 | using UnityEditor; 8 | using UnityEditor.IMGUI.Controls; 9 | using UnityEngine; 10 | using UnityEngine.UI; 11 | using Image = Aspose.PSD.Image; 12 | 13 | namespace XGame 14 | { 15 | public class PSDPreviewWindow : EditorWindow 16 | { 17 | public static void ShowWin(PSDInfo psdInfo) 18 | { 19 | var win = GetWindow(); 20 | win.Show(); 21 | win.ReadFile(psdInfo); 22 | } 23 | 24 | private PSDInfo _psdInfo; 25 | private EditorTreeView _uiNewTree; 26 | private EditorTreeView _uiOldTree; 27 | 28 | private List _newTreeData; 29 | private List _oldTreeData; 30 | 31 | private TreeViewState _newtreeViewState; 32 | private TreeViewState _oldtreeViewState; 33 | 34 | private bool _changed; 35 | private bool _changed2; 36 | private int _controlId; 37 | private Vector2 _inspectorScroll; 38 | private TreeLayer _selectLayer; 39 | private GameObject _selectObj; 40 | private GameObject _prefabObj; 41 | private Vector2 _attrScroll; 42 | 43 | private HashSet _uiNewUNames = new HashSet(); 44 | private HashSet _uiOldUNames = new HashSet(); 45 | private List _cachedEditors = new List(); 46 | 47 | private void OnEnable() 48 | { 49 | _newtreeViewState = new TreeViewState(); 50 | _oldtreeViewState = new TreeViewState(); 51 | 52 | _uiOldTree = new EditorTreeView(_oldtreeViewState); 53 | _uiOldTree.OnDrawRowCallback += UiOldTreeOnOnDrawRowCallback; 54 | _uiOldTree.OnMoveElementsResult += UiOldTreeOnOnMoveElementsResult; 55 | _uiOldTree.BeforeDroppingDraggedItems += UiOldTreeOnBeforeDroppingDraggedItems; 56 | _uiOldTree.OnSingleClickedItem += UiOldTreeOnSingleClickedItem; 57 | _uiOldTree.OnDoubleClickItem += UiOldTreeOnOnDoubleClickItem; 58 | _uiOldTree.OnDropVerify += UiOldTreeOnOnDropVerify; 59 | _uiOldTree.OnContextClickedItem+= UiOldTreeOnOnContextClickedItem; 60 | 61 | _uiNewTree = new EditorTreeView(_newtreeViewState); 62 | _uiNewTree.OnDrawRowCallback += UiNewTreeOnOnDrawRowCallback; 63 | _uiNewTree.OnSingleClickedItem += UiNewTreeOnOnSingleClickedItem; 64 | _uiNewTree.OnDoubleClickItem += UiNewTreeOnOnSingleClickedItem; 65 | _uiNewTree.OnContextClickedItem += UiNewTreeOnOnContextClickedItem; 66 | _uiNewTree.BeforeDroppingDraggedItems += OnBeforeDroppingDraggedItems; 67 | _uiNewTree.OnDropVerify += UiNewTreeOnOnDropVerify; 68 | _uiNewTree.OnMoveElementsResult += UiNewTreeOnOnMoveElementsResult; 69 | } 70 | 71 | private void UiOldTreeOnOnContextClickedItem(int obj) 72 | { 73 | var layer = _uiOldTree.Data.Find((int) obj); 74 | var target = (GameObject) EditorUtility.InstanceIDToObject(layer.UID); 75 | if (target == null) 76 | return; 77 | var menu = new GenericMenu(); 78 | } 79 | 80 | private void UiOldTreeOnOnDoubleClickItem(int obj) 81 | { 82 | var layer = _uiOldTree.Data.Find((int) obj); 83 | var target = (GameObject) EditorUtility.InstanceIDToObject(layer.UID); 84 | if (target == null) 85 | return; 86 | if (target.name == target.transform.root.name) 87 | { 88 | EditorGUIUtility.PingObject(target.transform.root.gameObject); 89 | } 90 | 91 | _uiNewTree.SetSelection(new List(){obj}); 92 | _uiNewTree.FrameItem(obj); 93 | _selectLayer = layer; 94 | _selectObj = (GameObject) EditorUtility.InstanceIDToObject(_selectLayer.UID); 95 | _cachedEditors.Clear(); 96 | var coms = _selectObj.GetComponents(); 97 | foreach (var com in coms) 98 | { 99 | _cachedEditors.Add(Editor.CreateEditor(com)); 100 | } 101 | } 102 | 103 | private bool UiOldTreeOnOnDropVerify(TreeViewItem arg1, List arg2) 104 | { 105 | return false; 106 | } 107 | 108 | private void UiOldTreeOnSingleClickedItem(int obj) 109 | { 110 | _uiNewTree.SetSelection(new List(){obj}); 111 | _uiNewTree.FrameItem(obj); 112 | var layer = _uiOldTree.Data.Find((int) obj); 113 | _selectLayer = layer; 114 | _selectObj = (GameObject) EditorUtility.InstanceIDToObject(_selectLayer.UID); 115 | _cachedEditors.Clear(); 116 | var coms = _selectObj.GetComponents(); 117 | foreach (var com in coms) 118 | { 119 | _cachedEditors.Add(Editor.CreateEditor(com)); 120 | } 121 | } 122 | 123 | private void UiOldTreeOnBeforeDroppingDraggedItems(IList obj) 124 | { 125 | foreach (TreeViewItem item in obj) 126 | { 127 | DepthRemove(item.Data, _uiOldUNames); 128 | } 129 | _uiNewTree.Reload(); 130 | _uiOldTree.Reload(); 131 | } 132 | 133 | private void UiNewTreeOnOnMoveElementsResult(TreeLayer parent, List arg2) 134 | { 135 | foreach (TreeViewItem item in arg2) 136 | { 137 | DepthAdd(item.Data, _uiNewUNames); 138 | } 139 | _uiNewTree.Reload(); 140 | _uiOldTree.Reload(); 141 | } 142 | 143 | private void OnBeforeDroppingDraggedItems(IList obj) 144 | { 145 | foreach (TreeViewItem item in obj) 146 | { 147 | DepthRemove(item.Data, _uiNewUNames); 148 | } 149 | _uiNewTree.Reload(); 150 | _uiOldTree.Reload(); 151 | _changed = true; 152 | } 153 | 154 | private void UiOldTreeOnOnMoveElementsResult(TreeLayer parent, List arg2) 155 | { 156 | foreach (TreeViewItem treeViewItem in arg2) 157 | { 158 | DepthAdd(treeViewItem.Data, _uiOldUNames); 159 | } 160 | 161 | _changed2 = true; 162 | _uiNewTree.Reload(); 163 | _uiOldTree.Reload(); 164 | } 165 | 166 | private void DepthRemove(TreeLayer item, HashSet names) 167 | { 168 | names.Remove(item.uName); 169 | if (item.HasChildren) 170 | { 171 | foreach (TreeLayer layer in item.Children) 172 | { 173 | DepthRemove(layer, names); 174 | } 175 | } 176 | } 177 | 178 | private void DepthAdd(TreeLayer item, HashSet names) 179 | { 180 | names.Add(item.uName); 181 | if (item.HasChildren) 182 | { 183 | foreach (TreeLayer layer in item.Children) 184 | { 185 | DepthAdd(layer, names); 186 | } 187 | } 188 | } 189 | 190 | private bool UiNewTreeOnOnDropVerify(TreeViewItem arg1, List arg2) 191 | { 192 | var parent = (TreeViewItem) arg1; 193 | if (parent == null) 194 | return false; 195 | if (parent.Data.LayerType == LayerType.Group) 196 | return true; 197 | return false; 198 | } 199 | 200 | private void DataOnModelChanged() 201 | { 202 | _changed = true; 203 | _uiNewTree.Reload(); 204 | } 205 | 206 | 207 | 208 | private void UiNewTreeOnOnContextClickedItem(int id) 209 | { 210 | var layer = _uiNewTree.Data.Find(id); 211 | if (layer.PsdLayer?.IsRoot ?? false) 212 | return; 213 | var menu = new GenericMenu (); 214 | menu.AddItem(new GUIContent("删除层级"), false, RemoveLayer, new MenuItemArgs(id)); 215 | if (layer.LayerType == LayerType.Group) 216 | { 217 | menu.AddItem (new GUIContent ("Add/Folder"), false, AddFolder, new MenuItemArgs(id)); 218 | } 219 | menu.AddItem(new GUIContent("添加标签/引用(@ref)"), false, AddTag, 220 | new MenuItemArgs(id) 221 | { 222 | StrParam = "@ref" 223 | }); 224 | menu.AddItem(new GUIContent("添加标签/忽略(@ignore)"), false, AddTag, 225 | new MenuItemArgs(id) 226 | { 227 | StrParam = "@ignore" 228 | }); 229 | if (layer.LayerType == LayerType.Image) 230 | { 231 | menu.AddItem(new GUIContent("添加标签/空(@empty)"), false, AddTag, 232 | new MenuItemArgs(id) 233 | { 234 | StrParam = "@empty" 235 | }); 236 | } 237 | menu.ShowAsContext (); 238 | } 239 | 240 | private void RemoveLayer(object obj) 241 | { 242 | var p = (MenuItemArgs) obj; 243 | var layer = _uiNewTree.Data.Find(p.Id); 244 | _uiNewTree.Data.RemoveElements(new List(){p.Id}); 245 | _uiNewTree.Reload(); 246 | } 247 | 248 | 249 | private void AddFolder(object obj) 250 | { 251 | var p = (MenuItemArgs) obj; 252 | _changed = true; 253 | var layer = _uiNewTree.Data.Find(p.Id); 254 | var childrenCount = layer.Children?.Count ?? 0; 255 | var newLayer = new TreeLayer(){Name = $"NewLayer{childrenCount}", 256 | RealName = "NewLayer", LayerType = LayerType.Group}; 257 | _uiNewTree.Data.AddElement(newLayer, layer, childrenCount); 258 | _uiNewTree.SetSelection(new List(){newLayer.Id}); 259 | _uiNewTree.FrameItem(newLayer.Id); 260 | _selectLayer = newLayer; 261 | _uiNewUNames.Add(newLayer.uName); 262 | } 263 | 264 | private void AddTag(object obj) 265 | { 266 | var p = (MenuItemArgs) obj; 267 | _changed = true; 268 | var layer = _uiNewTree.Data.Find(p.Id); 269 | if (!layer.RealName.Contains(p.StrParam)) 270 | { 271 | layer.RealName = layer.RealName + p.StrParam; 272 | if (layer.PsdLayer != null) 273 | { 274 | layer.PsdLayer.RealName = layer.RealName; 275 | layer.PsdLayer.ReloadTags(); 276 | } 277 | } 278 | } 279 | 280 | private void UiNewTreeOnOnSingleClickedItem(int obj) 281 | { 282 | var layer = _uiNewTree.Data.Find((int) obj); 283 | _selectLayer = layer; 284 | if (!_uiOldTree.Inited) return; 285 | _uiOldTree.SetSelection(new List(){obj}); 286 | _uiOldTree.FrameItem(obj); 287 | } 288 | 289 | private void UiOldTreeOnOnDrawRowCallback(TreeViewItemRow obj) 290 | { 291 | var indent = obj.GetContentIndent.Invoke(obj.item); 292 | var cellRect = obj.rowRect; 293 | cellRect.x += indent; 294 | 295 | if (!_uiNewUNames.Contains(obj.item.Data.uName)) 296 | { 297 | obj.item.Data.Tag = UpdateTag.Remove; 298 | } 299 | else 300 | { 301 | obj.item.Data.Tag = UpdateTag.None; 302 | } 303 | 304 | var iconName = GetIcon(obj.item.Data.Tag); 305 | if (!string.IsNullOrEmpty(iconName)) 306 | { 307 | var icon = EditorGUIUtility.IconContent(iconName); 308 | var iconRect = obj.rowRect; 309 | iconRect.y -= 5; 310 | EditorGUI.LabelField(iconRect, icon); 311 | } 312 | 313 | var flagRect = cellRect; 314 | flagRect.y -= 5; 315 | EditorGUI.LabelField(flagRect, EditorGUIUtility.IconContent(GetIcon(obj.item.Data.LayerType))); 316 | 317 | cellRect.x += 20; 318 | EditorGUI.LabelField(cellRect, $"{obj.item.Data.Name}", new GUIStyle(){richText = true}); 319 | 320 | } 321 | 322 | private string GetIcon(UpdateTag tag) 323 | { 324 | switch (tag) 325 | { 326 | case UpdateTag.Remove: 327 | return "d_winbtn_mac_close_h"; 328 | case UpdateTag.Add: 329 | return "d_winbtn_mac_max_h"; 330 | case UpdateTag.Update: 331 | return "d_winbtn_mac_min"; 332 | case UpdateTag.Ignore: 333 | return "winbtn_mac_min_h@2x"; 334 | } 335 | 336 | return null; 337 | } 338 | 339 | private string GetIcon(LayerType type) 340 | { 341 | switch (type) 342 | { 343 | case LayerType.Group: 344 | return "Folder Icon"; 345 | case LayerType.Image: 346 | return "RawImage Icon"; 347 | case LayerType.Text: 348 | return "Text Icon"; 349 | case LayerType.GameObject: 350 | return "PreMatCube@2x"; 351 | } 352 | 353 | return null; 354 | } 355 | 356 | private void UiNewTreeOnOnDrawRowCallback(TreeViewItemRow obj) 357 | { 358 | var indent = obj.GetContentIndent.Invoke(obj.item); 359 | var cellRect = obj.rowRect; 360 | cellRect.x += indent; 361 | if (!_uiOldUNames.Contains(obj.item.Data.uName)) 362 | { 363 | if (obj.item.Data.PsdLayer?.Ignore ?? false) 364 | obj.item.Data.Tag = UpdateTag.Ignore; 365 | else 366 | obj.item.Data.Tag = UpdateTag.Add; 367 | } 368 | else 369 | { 370 | obj.item.Data.Tag = UpdateTag.None; 371 | } 372 | 373 | var iconName = GetIcon(obj.item.Data.Tag); 374 | if (!string.IsNullOrEmpty(iconName)) 375 | { 376 | var icon = EditorGUIUtility.IconContent(iconName); 377 | var rowRect = obj.rowRect; 378 | rowRect.y -= 5; 379 | EditorGUI.LabelField(rowRect, icon); 380 | } 381 | 382 | var iconRect = cellRect; 383 | iconRect.y -= 5; 384 | EditorGUI.LabelField(iconRect, EditorGUIUtility.IconContent(GetIcon(obj.item.Data.LayerType))); 385 | cellRect.x += 20; 386 | EditorGUI.LabelField(cellRect, $"{obj.item.Data.Name} {obj.item.Data.RealName}", 387 | new GUIStyle(){richText = true}); 388 | 389 | } 390 | 391 | private void ReadFile(PSDInfo psdInfo) 392 | { 393 | _psdInfo = psdInfo; 394 | var treeNew = new TreeLayer(); 395 | _newTreeData = new List(); 396 | _oldTreeData = new List(); 397 | 398 | _newTreeData.Add(new TreeLayer(){Depth = -1, Name = ""}); 399 | BuildUINewTree(treeNew, psdInfo.Root, 0); 400 | _uiNewTree.Build(new TreeModel(_newTreeData)); 401 | _uiNewTree.Data.ModelChanged += DataOnModelChanged; 402 | 403 | var prefab = AssetDatabase.LoadAssetAtPath(psdInfo.ParseInfo.TargetPath); 404 | if (prefab != null) 405 | { 406 | _prefabObj = prefab; 407 | var treeOld = new TreeLayer(); 408 | _oldTreeData.Add(new TreeLayer(){Depth = -1, Name = ""}); 409 | BuildUINewTree(treeOld, prefab.transform, 0); 410 | _uiOldTree.Build(new TreeModel(_oldTreeData)); 411 | 412 | _uiOldTree.ExpandAll(); 413 | } 414 | _uiNewTree.ExpandAll(); 415 | } 416 | 417 | private void BuildUINewTree(TreeLayer tree, IPSDLayer layer, int depth) 418 | { 419 | tree.TargetType = TargetType.PSD; 420 | tree.Depth = depth; 421 | tree.RealName = layer.RealName; 422 | tree.Name = layer.Name; 423 | tree.UID = layer.UName.GetHashCode(); 424 | tree.PsdLayer = layer; 425 | _uiNewUNames.Add(tree.uName); 426 | if (layer is PSDLayerGroup) 427 | { 428 | tree.LayerType = LayerType.Group; 429 | }else if (layer is PSDLayerText layerText) 430 | { 431 | tree.LayerType = LayerType.Text; 432 | tree.TextVal = layerText.Text; 433 | } 434 | else 435 | { 436 | tree.LayerType = LayerType.Image; 437 | var textures = AssetDatabase.FindAssets($"{((PSDLayerImage) layer).ImageName} t:Texture"); 438 | foreach (var s in textures) 439 | { 440 | var p = AssetDatabase.GUIDToAssetPath(s); 441 | if (Path.GetFileNameWithoutExtension(p) == ((PSDLayerImage) layer).ImageName) 442 | { 443 | tree.Img = AssetDatabase.LoadAssetAtPath(p); 444 | break; 445 | } 446 | } 447 | } 448 | 449 | _newTreeData.Add(tree); 450 | if (layer is PSDLayerGroup layerGroup) 451 | { 452 | tree.Children = new List(); 453 | for (int i = 0; i < layerGroup.PsdLayers.Count; i++) 454 | { 455 | var item = new TreeLayer(); 456 | item.Parent = tree; 457 | tree.Children.Add(item); 458 | BuildUINewTree(item, layerGroup.PsdLayers[i], depth + 1); 459 | } 460 | } 461 | } 462 | 463 | private void BuildUINewTree(TreeLayer tree, Transform layer, int depth) 464 | { 465 | tree.TargetType = TargetType.Prefab; 466 | tree.Depth = depth; 467 | tree.Name = layer.name; 468 | tree.UID = layer.gameObject.GetInstanceID(); 469 | if (layer.gameObject.GetComponent() != null) 470 | tree.LayerType = LayerType.Text; 471 | else if (layer.gameObject.GetComponent() != null) 472 | tree.LayerType = LayerType.Image; 473 | else 474 | tree.LayerType = LayerType.GameObject; 475 | _oldTreeData.Add(tree); 476 | _uiOldUNames.Add(tree.uName); 477 | if (layer.childCount > 0) 478 | { 479 | tree.Children = new List(); 480 | for (int i = 0; i < layer.childCount; i++) 481 | { 482 | var item = new TreeLayer(); 483 | item.Parent = tree; 484 | tree.Children.Add(item); 485 | BuildUINewTree(item, layer.GetChild(i), depth + 1); 486 | } 487 | } 488 | } 489 | 490 | private void OnAttrWinGUI(Vector2 size) 491 | { 492 | if (_selectLayer == null) 493 | return; 494 | if (_selectLayer.TargetType == TargetType.PSD) 495 | { 496 | EditorGUILayout.LabelField("名称", _selectLayer.Name); 497 | EditorGUILayout.LabelField("PSD层级的名称"); 498 | EditorGUI.BeginChangeCheck(); 499 | _selectLayer.RealName = EditorGUILayout.DelayedTextField(_selectLayer.RealName, EditorStyles.textArea, GUILayout.Height(100)); 500 | if (EditorGUI.EndChangeCheck()) 501 | { 502 | DepthRemove(_selectLayer, _uiNewUNames); 503 | var ss = _selectLayer.RealName.Split('@'); 504 | if (ss.TryMatchOne(a => a.StartsWith("name="), out var str)) 505 | { 506 | _selectLayer.Name = str.Replace("name=", ""); 507 | } 508 | else 509 | { 510 | _selectLayer.Name = ss[0]; 511 | } 512 | DepthAdd(_selectLayer, _uiNewUNames); 513 | if (_selectLayer.PsdLayer != null) 514 | { 515 | _selectLayer.PsdLayer.RealName = _selectLayer.RealName; 516 | _selectLayer.PsdLayer.ReloadTags(); 517 | } 518 | _uiOldTree.Reload(); 519 | _uiNewTree.Reload(); 520 | _changed = true; 521 | } 522 | 523 | if (_selectLayer.PsdLayer?.Tags != null) 524 | { 525 | foreach (var tag in _selectLayer.PsdLayer.Tags) 526 | { 527 | var color = _selectLayer.PsdLayer.ValidTag(tag) ? Color.white : Color.red; 528 | var style = new GUIStyle("AppToolbarButtonLeft"); 529 | GUI.backgroundColor = color; 530 | EditorGUILayout.LabelField(tag, style); 531 | GUI.backgroundColor = Color.white; 532 | } 533 | } 534 | 535 | EditorGUILayout.LabelField("层级类型", _selectLayer.LayerType.ToString()); 536 | switch (_selectLayer.LayerType) 537 | { 538 | case LayerType.Group: 539 | break; 540 | case LayerType.Image: 541 | var width = position.width * 0.2f - 10; 542 | if (_selectLayer.Img != null) 543 | { 544 | var rate = width / _selectLayer.Img.width; 545 | var h = _selectLayer.Img.height * rate; 546 | GUILayout.Box(_selectLayer.Img, GUILayout.Width(width), GUILayout.Height(h)); 547 | } 548 | break; 549 | case LayerType.Text: 550 | EditorGUILayout.LabelField("Text", _selectLayer.TextVal); 551 | break; 552 | } 553 | } 554 | else 555 | { 556 | _selectObj.name = EditorGUILayout.TextField("Name", _selectObj.name); 557 | _inspectorScroll = EditorGUILayout.BeginScrollView(_inspectorScroll); 558 | foreach (var e in _cachedEditors) 559 | { 560 | e.DrawHeader(); 561 | e.DrawDefaultInspector(); 562 | } 563 | EditorGUILayout.Space(); 564 | EditorGUILayout.Space(); 565 | 566 | EditorGUILayout.EndScrollView(); 567 | if (GUI.changed) 568 | { 569 | _changed2 = true; 570 | EditorUtility.SetDirty(_prefabObj); 571 | _controlId = GUIUtility.hotControl; 572 | } 573 | } 574 | 575 | if (EditorUtility.IsDirty(_prefabObj)) 576 | { 577 | if (_controlId != GUIUtility.hotControl) 578 | PrefabUtility.SavePrefabAsset(_prefabObj); 579 | } 580 | } 581 | 582 | private void OnGUI() 583 | { 584 | GUILayout.BeginArea(new Rect(10, 35, position.width * 0.4f, 30)); 585 | EditorGUILayout.BeginHorizontal(); 586 | if (_changed) 587 | { 588 | if (GUILayout.Button("保存", GUILayout.Width(60))) 589 | { 590 | Save(); 591 | var genInfo = GetWindow().GetParseInfo(new FileInfo(_psdInfo.ParseInfo.Path)); 592 | ReadFile(genInfo); 593 | } 594 | 595 | } 596 | if (GUILayout.Button("删除错误标签", GUILayout.Width(100))) 597 | { 598 | RemoveInvalidTag(); 599 | _changed = true; 600 | } 601 | EditorGUILayout.EndHorizontal(); 602 | GUILayout.EndArea(); 603 | 604 | GUILayout.BeginArea(new Rect(position.width *0.4f, 35, position.width * 0.4f, 30)); 605 | if (_changed2) 606 | { 607 | EditorGUILayout.BeginHorizontal(); 608 | if (GUILayout.Button("保存", GUILayout.Width(60))) 609 | { 610 | SavePrefab(); 611 | } 612 | EditorGUILayout.EndHorizontal(); 613 | } 614 | GUILayout.EndArea(); 615 | 616 | EditorGUI.LabelField(new Rect(position.width * 0.2f - 50, 0, 100, 30), "生成预览"); 617 | EditorGUI.LabelField(new Rect(position.width * 0.6f - 50, 0, 100, 30), "现有预制体"); 618 | 619 | 620 | _uiNewTree.OnGUI(new Rect(0, 70, position.width * 0.4f, position.height - 60)); 621 | _uiOldTree.OnGUI(new Rect(position.width * 0.4f, 70, position.width *0.4f, position.height - 60)); 622 | 623 | var attrRect = new Rect(position.width * 0.8f, 5, position.width * 0.2f, position.height); 624 | GUILayout.BeginArea(attrRect); 625 | _attrScroll = EditorGUILayout.BeginScrollView(_attrScroll); 626 | OnAttrWinGUI(attrRect.size); 627 | EditorGUILayout.EndScrollView(); 628 | GUILayout.EndArea(); 629 | 630 | } 631 | 632 | private void RemoveInvalidTag() 633 | { 634 | var layers = _uiNewTree.Data.Data; 635 | for (var index = 2; index < layers.Length; index++) 636 | { 637 | var treeLayer = layers[index]; 638 | if (treeLayer.PsdLayer?.Tags != null) 639 | { 640 | foreach (var tag in treeLayer.PsdLayer.Tags) 641 | { 642 | if (!treeLayer.PsdLayer.ValidTag(tag)) 643 | { 644 | treeLayer.PsdLayer.RealName = treeLayer.PsdLayer.RealName.Replace($"@{tag}", ""); 645 | treeLayer.RealName = treeLayer.PsdLayer.RealName; 646 | } 647 | } 648 | 649 | treeLayer.PsdLayer.ReloadTags(); 650 | } 651 | } 652 | _uiNewTree.Reload(); 653 | } 654 | 655 | private void Save() 656 | { 657 | _changed = false; 658 | var layers = _uiNewTree.Data.Data; 659 | Aspose.Hook.Manager.StartHook(); 660 | var psdImg = (PsdImage) Image.Load(_psdInfo.ParseInfo.Path); 661 | try 662 | { 663 | var psdLayers = new List(); 664 | var dividerIndex = new Stack>(); 665 | for (var index = 1; index < layers.Length; index++) 666 | { 667 | var treeLayer = layers[index]; 668 | var layer = _psdInfo.GetLayer(treeLayer.UID); 669 | if (layer == null) //是一个新的Group 670 | { 671 | while (dividerIndex.Count > 0) 672 | { 673 | if (!treeLayer.Parent.Equals(dividerIndex.Peek().Key)) 674 | { 675 | var ele = dividerIndex.Pop(); 676 | var group = psdImg.Layers[ele.Value]; 677 | group.DisplayName = $"Layer group: {ele.Key.RealName}"; 678 | psdLayers.Add(group); 679 | } 680 | else 681 | break; 682 | } 683 | 684 | var idx = psdImg.Layers.Length; 685 | psdImg.AddLayerGroup(treeLayer.Name, idx, false); 686 | psdLayers.Add(psdImg.Layers[idx]); 687 | if (treeLayer.HasChildren) 688 | { 689 | dividerIndex.Push(new KeyValuePair(treeLayer, idx + 1)); 690 | } 691 | else 692 | { 693 | var group = (LayerGroup) psdImg.Layers[idx + 1]; 694 | group.DisplayName = $"Layer group: {treeLayer.RealName}"; 695 | psdLayers.Add(group); 696 | } 697 | } 698 | else if (layer.IsRoot) 699 | { 700 | 701 | } 702 | else 703 | { 704 | if (layer is PSDLayerGroup layerGroup) // 705 | { 706 | while (dividerIndex.Count > 0) 707 | { 708 | if (!treeLayer.Parent.Equals(dividerIndex.Peek().Key)) 709 | { 710 | var ele = dividerIndex.Pop(); 711 | var group = psdImg.Layers[ele.Value]; 712 | group.DisplayName = $"Layer group: {ele.Key.RealName}"; 713 | psdLayers.Add(group); 714 | }else 715 | break; 716 | } 717 | psdLayers.Add(psdImg.Layers[layerGroup.LayerDividerIndex]); 718 | 719 | dividerIndex.Push(new KeyValuePair(treeLayer, layerGroup.LayerIndex)); 720 | if (!treeLayer.HasChildren) 721 | { 722 | var ele = dividerIndex.Pop(); 723 | var group = psdImg.Layers[ele.Value]; 724 | group.DisplayName = $"Layer group: {ele.Key.RealName}"; 725 | psdLayers.Add(group); 726 | } 727 | } 728 | else 729 | { 730 | while (dividerIndex.Count > 0) 731 | { 732 | if (!treeLayer.Parent.Equals(dividerIndex.Peek().Key)) 733 | { 734 | var ele = dividerIndex.Pop(); 735 | var group = psdImg.Layers[ele.Value]; 736 | group.DisplayName = $"Layer group: {ele.Key.RealName}"; 737 | psdLayers.Add(group); 738 | } 739 | else 740 | break; 741 | } 742 | 743 | var cur = psdImg.Layers[layer.LayerIndex]; 744 | cur.DisplayName = treeLayer.RealName; 745 | 746 | psdLayers.Add(cur); 747 | } 748 | } 749 | } 750 | 751 | while (dividerIndex.Count > 0) 752 | { 753 | var ele = dividerIndex.Pop(); 754 | var group = psdImg.Layers[ele.Value]; 755 | group.DisplayName = $"Layer group: {ele.Key.RealName}"; 756 | psdLayers.Add(group); 757 | } 758 | 759 | var arr = psdLayers.ToArray(); 760 | psdImg.Layers = arr; 761 | psdImg.Save(); 762 | } 763 | catch (Exception e) 764 | { 765 | throw; 766 | } 767 | finally 768 | { 769 | psdImg.Dispose(); 770 | 771 | } 772 | 773 | } 774 | 775 | 776 | 777 | private void SavePrefab() 778 | { 779 | _changed2 = false; 780 | var layers = _uiNewTree.Data.Data; 781 | for (int i = 1; i < layers.Length; i++) 782 | { 783 | var la = layers[i]; 784 | if (la.Parent != null) 785 | { 786 | var pObj = (GameObject) EditorUtility.InstanceIDToObject(((TreeLayer) la.Parent).UID); 787 | if (pObj != null) 788 | { 789 | var layerObj = (GameObject) EditorUtility.InstanceIDToObject(la.UID); 790 | if (layerObj != null) 791 | { 792 | layerObj.transform.SetParent(pObj.transform, true); 793 | } 794 | } 795 | } 796 | } 797 | AssetDatabase.SaveAssets(); 798 | } 799 | private void OnDestroy() 800 | { 801 | if (_changed || _changed2) 802 | { 803 | var tip = EditorUtility.DisplayDialog("提示", "更改尚未保存,是否保存?", "保存", "关闭"); 804 | if (tip) 805 | { 806 | Save(); 807 | } 808 | } 809 | } 810 | 811 | private class TreeLayer : TreeElement 812 | { 813 | public override int Id 814 | { 815 | get 816 | { 817 | if (uName == null) 818 | { 819 | return 0; 820 | } 821 | return uName.GetHashCode(); 822 | } 823 | } 824 | 825 | public string RealName; 826 | 827 | public string uName 828 | { 829 | get 830 | { 831 | var p = (TreeLayer) Parent; 832 | return $"{p?.uName}-{Name}".Trim('-'); 833 | } 834 | } 835 | 836 | public TargetType TargetType; 837 | public UpdateTag Tag; 838 | public LayerType LayerType; 839 | public int UID; 840 | public IPSDLayer PsdLayer; 841 | public Texture Img; 842 | public string TextVal; 843 | } 844 | 845 | private enum UpdateTag 846 | { 847 | None, 848 | Add, 849 | Update, 850 | Remove, 851 | Ignore, 852 | } 853 | 854 | private enum LayerType 855 | { 856 | Group, 857 | Image, 858 | Text, 859 | GameObject 860 | } 861 | 862 | private enum TargetType 863 | { 864 | PSD, 865 | Prefab 866 | } 867 | 868 | private class MenuItemArgs 869 | { 870 | public MenuItemArgs(int id) 871 | { 872 | Id = id; 873 | } 874 | public int Id; 875 | public string StrParam; 876 | } 877 | 878 | } 879 | } -------------------------------------------------------------------------------- /Psd/PSDSetting.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | using UnityEngine.UI; 4 | 5 | namespace XGame 6 | { 7 | public class PSDSetting : ScriptableObject 8 | { 9 | public string PsdFolder; 10 | public string UIFolder; 11 | public Vector2 ScreenSize = new Vector2(1920, 1080); 12 | public List LatesdOpened = new List(); 13 | public Font DefaultFont; 14 | public List ComponentTypes = new List(); 15 | } 16 | } -------------------------------------------------------------------------------- /Psd/ReadMe.md: -------------------------------------------------------------------------------- 1 | 使用步骤: 2 | 1.检查PSD命名,同级节点不允许重名 3 | 2.对不同类型层级定义关键词 4 | ---通用 5 | --@ignore 忽略该层级,如果是文件夹则忽略该文件夹及以下所有节点 6 | ---图片层级 7 | --@name=xxx 为当前层级取别名 8 | --@empty 只取当前层级的大小位置信息 9 | --@ys=xxx 为当前层添加PrefabStub组件 10 | ---文件夹层级 11 | --@grid=0x3 为当前层级添加GridLayoutGroup组件并且Space=0x3 12 | --@ver=3 为当前层级添加VerticalLayoutGroup组件并且Space=3 13 | --@hor=3 为当前层级添加HorizontalLayoutGroup组件并且Space=3 14 | --@list=xxx 为当前层级添加UIList组件 15 | ---Text层级 16 | --@font=zy 设置Text的font=zy 17 | -------------------------------------------------------------------------------- /Psd/ReadMe/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SagaciousG/UnityTools/b09acbae4613f893d26720437c6659dc9e8975c4/Psd/ReadMe/1.png -------------------------------------------------------------------------------- /Psd/ReadMe/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SagaciousG/UnityTools/b09acbae4613f893d26720437c6659dc9e8975c4/Psd/ReadMe/2.png -------------------------------------------------------------------------------- /Psd/ReadMe/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SagaciousG/UnityTools/b09acbae4613f893d26720437c6659dc9e8975c4/Psd/ReadMe/3.png -------------------------------------------------------------------------------- /Psd/ReadMe/ReadMe.md: -------------------------------------------------------------------------------- 1 |  2 | ###说明: 3 | 该工具旨在减少psd的学习负担,保留极少数psd关键词来创建组件并赋值, 4 | 绝大数组件都在UGUI上手动添加。 5 | ###注意: 6 | 该工具与之前Psd2UGUI不能混用,两者制作流程不一样。 7 | 8 | 9 | ###使用步骤: 10 | 1. 检查PSD命名,同级节点不允许重名 11 | 2. 对不同类型层级定义关键词 12 | 13 | |层级|关键词|说明| 14 | |-------|:-----|:---| 15 | |通用|@ignore|忽略该层级,如果是文件夹则忽略该文件夹及以下所有节点| 16 | |图片层级|@name=xxx|为当前层级取别名| 17 | | |@empty|只取当前层级的大小位置信息| 18 | | |@ys=xxx|为当前层添加PrefabStub组件,并赋值预设名| 19 | |文件夹层级|@grid=0x3|为当前层级添加GridLayoutGroup组件并且Space=0x3| 20 | | |@ver=3|为当前层级添加VerticalLayoutGroup组件并且Space=3| 21 | | |@hor=3|为当前层级添加HorizontalLayoutGroup组件并且Space=3| 22 | | |@list=xxx|为当前层级添加UIList组件| 23 | |Text层级|@font=zy|设置Text的font=zy| 24 | ###流程: 25 | 1. 检查PSD命名,调整层级以匹配自己即将要使用的组件,如:制作list 26 | - 创建Viewport文件夹,将.mask图片放至子节点 27 | ![](1.png "") 28 | - 合并Content节点下的图片,并添加@ignore 29 | 2. 使用Psd工具生成预制体,因为**Scene1**下第一个Canvas是**BloodRoot**节点,所以 30 | 最好新建一个场景,在***空场景***里生成psd 31 | ![](2.png) 32 | 3. 如果已存在目标预制体,则会保留**同一层级**下**名称相同**的对象的组件和数据。重新生成时 33 | 只会改变部分组件的属性。 34 | - 在`PSDLayerGroup.SetVariableValue`,`PSDLayerImage.SetVariableValue` 35 | `PSDLayerText.SetVariableValue`定义不同层级的可变属性。 36 | 4. 如果不存在目标预制体,则会生成一个与Psd层级相同的预制体。为预制体添加**LuaBehaviour** 37 | 点击**FillName**获取(创建)lua文件路径。 38 | ![](3.png) 39 | 5. 为层级添加组件。将需要引用的层级拖入luaBehaviour选区添加引用 40 | 41 | ###写在最后 42 | 1. 再次生成预制体时会保留数据(前提是找得到对应关系) 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |  2 | ###说明: 3 | 该工具旨在减少psd的学习负担,保留极少数psd关键词来创建组件并赋值, 4 | 绝大数组件都在UGUI上手动添加。 5 | ###注意: 6 | 该工具与之前Psd2UGUI不能混用,两者制作流程不一样。 7 | 8 | 9 | ###使用步骤: 10 | 1. 检查PSD命名,同级节点不允许重名 11 | 2. 对不同类型层级定义关键词 12 | 13 | |层级|关键词|说明| 14 | |-------|:-----|:---| 15 | |通用|@ignore|忽略该层级,如果是文件夹则忽略该文件夹及以下所有节点| 16 | |图片层级|@name=xxx|为当前层级取别名| 17 | | |@empty|只取当前层级的大小位置信息| 18 | | |@ys=xxx|为当前层添加PrefabStub组件,并赋值预设名| 19 | |文件夹层级|@grid=0x3|为当前层级添加GridLayoutGroup组件并且Space=0x3| 20 | | |@ver=3|为当前层级添加VerticalLayoutGroup组件并且Space=3| 21 | | |@hor=3|为当前层级添加HorizontalLayoutGroup组件并且Space=3| 22 | | |@list=xxx|为当前层级添加UIList组件| 23 | |Text层级|@font=zy|设置Text的font=zy| 24 | ###流程: 25 | 1. 检查PSD命名,调整层级以匹配自己即将要使用的组件,如:制作list 26 | - 创建Viewport文件夹,将.mask图片放至子节点 27 | ![1](https://user-images.githubusercontent.com/49907344/167817107-b1079ad6-8b89-4cf0-b32f-52a6bb62837e.png) 28 | 29 | - 合并Content节点下的图片,并添加@ignore 30 | 2. 使用Psd工具生成预制体,因为**Scene1**下第一个Canvas是**BloodRoot**节点,所以 31 | 最好新建一个场景,在***空场景***里生成psd 32 | ![2](https://user-images.githubusercontent.com/49907344/167817183-7caeee05-e684-45d2-bfb3-551b648e991f.png) 33 | 34 | 3. 如果已存在目标预制体,则会保留**同一层级**下**名称相同**的对象的组件和数据。重新生成时 35 | 只会改变部分组件的属性。 36 | - 在`PSDLayerGroup.SetVariableValue`,`PSDLayerImage.SetVariableValue` 37 | `PSDLayerText.SetVariableValue`定义不同层级的可变属性。 38 | 4. 如果不存在目标预制体,则会生成一个与Psd层级相同的预制体。为预制体添加**LuaBehaviour** 39 | 点击**FillName**获取(创建)lua文件路径。 40 | ![3](https://user-images.githubusercontent.com/49907344/167817196-80cbfeb4-af86-43bd-b6a7-d3261649f299.png) 41 | 42 | 5. 为层级添加组件。将需要引用的层级拖入luaBehaviour选区添加引用 43 | 44 | ###写在最后 45 | 1. 再次生成预制体时会保留数据(前提是找得到对应关系) 46 | 47 | --------------------------------------------------------------------------------