using System; using System.Collections.Generic; using System.IO; using PDNWrapper; using UnityEngine; using Unity.Collections; using System.Linq; using System.Reflection; using UnityEditor.AssetImporters; using UnityEditor.U2D.Animation; using UnityEditor.U2D.Common; using UnityEditor.U2D.Sprites; using UnityEngine.U2D; using UnityEngine.U2D.Animation; using UnityEngine.Scripting.APIUpdating; namespace UnityEditor.U2D.PSD { /// /// ScriptedImporter to import Photoshop files /// // Version using unity release + 5 digit padding for future upgrade. Eg 2021.2 -> 21200000 [ScriptedImporter(22200003, new string[]{"psb"}, new []{"psd"}, AllowCaching = true)] [HelpURL("https://docs.unity3d.com/Packages/com.unity.2d.psdimporter@latest")] [MovedFrom("UnityEditor.Experimental.AssetImporters")] public partial class PSDImporter : ScriptedImporter, ISpriteEditorDataProvider { internal enum ELayerMappingOption { UseLayerName, UseLayerNameCaseSensitive, UseLayerId } IPSDLayerMappingStrategy[] m_MappingCompare = { new LayerMappingUseLayerName(), new LayerMappingUseLayerNameCaseSensitive(), new LayerMappingUserLayerID(), }; [SerializeField] TextureImporterSettings m_TextureImporterSettings = new TextureImporterSettings() { mipmapEnabled = true, mipmapFilter = TextureImporterMipFilter.BoxFilter, sRGBTexture = true, borderMipmap = false, mipMapsPreserveCoverage = false, alphaTestReferenceValue = 0.5f, readable = false, #if ENABLE_TEXTURE_STREAMING streamingMipmaps = true, #endif fadeOut = false, mipmapFadeDistanceStart = 1, mipmapFadeDistanceEnd = 3, convertToNormalMap = false, heightmapScale = 0.25F, normalMapFilter = 0, generateCubemap = TextureImporterGenerateCubemap.AutoCubemap, cubemapConvolution = 0, seamlessCubemap = false, npotScale = TextureImporterNPOTScale.ToNearest, spriteMode = (int)SpriteImportMode.Multiple, spriteExtrude = 1, spriteMeshType = SpriteMeshType.Tight, spriteAlignment = (int)SpriteAlignment.Center, spritePivot = new Vector2(0.5f, 0.5f), spritePixelsPerUnit = 100.0f, spriteBorder = new Vector4(0.0f, 0.0f, 0.0f, 0.0f), alphaSource = TextureImporterAlphaSource.FromInput, alphaIsTransparency = true, spriteTessellationDetail = -1.0f, textureType = TextureImporterType.Sprite, textureShape = TextureImporterShape.Texture2D, filterMode = FilterMode.Bilinear, aniso = 1, mipmapBias = 0.0f, wrapModeU = TextureWrapMode.Repeat, wrapModeV = TextureWrapMode.Repeat, wrapModeW = TextureWrapMode.Repeat, swizzleR = TextureImporterSwizzle.R, swizzleG = TextureImporterSwizzle.G, swizzleB = TextureImporterSwizzle.B, swizzleA = TextureImporterSwizzle.A, }; [SerializeField] // SpriteData for both single and multiple mode List m_SpriteImportData = new List(); // we use index 0 for single sprite and the rest for multiple sprites [SerializeField] // SpriteData for Mosaic mode List m_MosaicSpriteImportData = new List(); [SerializeField] // SpriteData for Rig mode List m_RigSpriteImportData = new List(); [SerializeField] // CharacterData for Rig mode CharacterData m_CharacterData = new CharacterData(); [SerializeField] // SpriteData for shared rig mode List m_SharedRigSpriteImportData = new List(); [SerializeField] // CharacterData for shared rig mode CharacterData m_SharedRigCharacterData = new CharacterData(); [SerializeField] List m_PlatformSettings = new List(); [SerializeField] bool m_MosaicLayers = true; [SerializeField] bool m_CharacterMode = true; [SerializeField] Vector2 m_DocumentPivot = Vector2.zero; [SerializeField] SpriteAlignment m_DocumentAlignment = SpriteAlignment.BottomCenter; [SerializeField] bool m_ImportHiddenLayers = false; [SerializeField] ELayerMappingOption m_LayerMappingOption = ELayerMappingOption.UseLayerId; [SerializeField] bool m_GeneratePhysicsShape = false; [SerializeField] bool m_PaperDollMode = false; [SerializeField] bool m_KeepDupilcateSpriteName = true; [SerializeField] string m_SkeletonAssetReferenceID = null; [SerializeField] ScriptableObject m_Pipeline; [SerializeField] string m_PipelineVersion; [SerializeField] int m_Padding = 4; [SerializeField] ushort m_SpriteSizeExpand = 0; [SerializeField] SpriteCategoryList m_SpriteCategoryList = new SpriteCategoryList() {categories = new List()}; GameObjectCreationFactory m_GameObjectFactory = new GameObjectCreationFactory(null); PSDImportData m_ImportData; internal PSDImportData importData { get { var returnValue = m_ImportData; if (returnValue == null && !PSDImporterAssetPostProcessor.ContainsImporter(this)) // Using LoadAllAssetsAtPath because PSDImportData is hidden returnValue = AssetDatabase.LoadAllAssetsAtPath(assetPath).FirstOrDefault(x => x is PSDImportData) as PSDImportData; if (returnValue == null) returnValue = ScriptableObject.CreateInstance(); m_ImportData = returnValue; return returnValue; } } internal int textureActualWidth { get => importData.textureActualWidth; private set =>importData.textureActualWidth = value; } internal int textureActualHeight { get => importData.textureActualHeight; private set =>importData.textureActualHeight = value; } [SerializeField] string m_SpritePackingTag = ""; [SerializeField] bool m_ResliceFromLayer = false; [SerializeField] List m_MosaicPSDLayers = new List(); [SerializeField] List m_RigPSDLayers = new List(); [SerializeField] List m_SharedRigPSDLayers = new List(); [SerializeField] PSDLayerImportSetting[] m_PSDLayerImportSetting; // Use for inspector to check if the file node is checked [SerializeField] #pragma warning disable 169, 414 bool m_ImportFileNodeState = true; // Used by platform settings to mark it dirty so that it will trigger a reimport [SerializeField] #pragma warning disable 169, 414 long m_PlatformSettingsDirtyTick; [SerializeField] bool m_SpriteSizeExpandChanged = false; [SerializeField] bool m_GenerateGOHierarchy = false; [SerializeField] string m_TextureAssetName = null; [SerializeField] string m_PrefabAssetName = null; [SerializeField] string m_SpriteLibAssetName = null; [SerializeField] string m_SkeletonAssetName = null; [SerializeField] SecondarySpriteTexture[] m_SecondarySpriteTextures; PSDExtractLayerData[] m_ExtractData; internal bool isNPOT => Mathf.IsPowerOfTwo(importData.textureActualWidth) && Mathf.IsPowerOfTwo(importData.textureActualHeight); bool shouldProduceGameObject => m_CharacterMode && m_MosaicLayers && spriteImportModeToUse == SpriteImportMode.Multiple; bool shouldResliceFromLayer => m_ResliceFromLayer && m_MosaicLayers && spriteImportModeToUse == SpriteImportMode.Multiple; bool inCharacterMode => inMosaicMode && m_CharacterMode; float definitionScale { get { var definitionScaleW = importData.importedTextureWidth / (float)textureActualWidth; var definitionScaleH = importData.importedTextureHeight / (float)textureActualHeight; return Mathf.Min(definitionScaleW, definitionScaleH); } } internal SecondarySpriteTexture[] secondaryTextures { get => m_SecondarySpriteTextures; set => m_SecondarySpriteTextures = value; } internal SpriteBone[] mainSkeletonBones { get { var skeleton = skeletonAsset; return skeleton != null ? skeleton.GetSpriteBones() : null; } } /// /// PSDImporter constructor. /// public PSDImporter() { m_TextureImporterSettings.swizzleA = TextureImporterSwizzle.A; m_TextureImporterSettings.swizzleR = TextureImporterSwizzle.R; m_TextureImporterSettings.swizzleG = TextureImporterSwizzle.G; m_TextureImporterSettings.swizzleB = TextureImporterSwizzle.B; } void PackImage(NativeArray[] buffers, int[] width, int[] height, int padding, uint spriteSizeExpand, out NativeArray outPackedBuffer, out int outPackedBufferWidth, out int outPackedBufferHeight, out RectInt[] outPackedRect, out Vector2Int[] outUVTransform, bool requireSquarePOT = false) { try { ScriptableObject pipeline = m_Pipeline; if(pipeline == null) pipeline = AssetDatabase.LoadAssetAtPath("Packages/com.unity.2d.psdimporter/Editor/Pipeline.asset"); var args = new object[] { buffers, width, height, padding, spriteSizeExpand, null, 0, 0, null, null, requireSquarePOT }; pipeline.GetType().InvokeMember("PackImage", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Static, null, pipeline, args); outPackedBuffer = (NativeArray)args[5]; outPackedBufferWidth = (int)args[6]; outPackedBufferHeight = (int)args[7]; outPackedRect = (RectInt[])args[8]; outUVTransform = (Vector2Int[])args[9]; } catch (Exception e) { Debug.LogError("Unable to pack image. ex:"+e.ToString(), this); ImagePacker.Pack(buffers, width, height, padding, spriteSizeExpand, out outPackedBuffer, out outPackedBufferWidth, out outPackedBufferHeight, out outPackedRect, out outUVTransform, requireSquarePOT); } } /// /// Implementation of ScriptedImporter.OnImportAsset /// /// /// This argument contains all the contextual information needed to process the import /// event and is also used by the custom importer to store the resulting Unity Asset. /// public override void OnImportAsset(AssetImportContext ctx) { var fileStream = new FileStream(ctx.assetPath, FileMode.Open, FileAccess.Read); Document doc = null; if(m_ImportData == null) m_ImportData = ScriptableObject.CreateInstance(); m_ImportData.hideFlags = HideFlags.HideInHierarchy; try { UnityEngine.Profiling.Profiler.BeginSample("OnImportAsset"); UnityEngine.Profiling.Profiler.BeginSample("PsdLoad"); doc = PaintDotNet.Data.PhotoshopFileType.PsdLoad.Load(fileStream); UnityEngine.Profiling.Profiler.EndSample(); m_ImportData.CreatePSDLayerData(doc.Layers); ValidatePSDLayerId(doc, m_LayerMappingOption); SetDocumentImportData(doc); importData.documentSize = new Vector2Int(doc.width, doc.height); var singleSpriteMode = m_TextureImporterSettings.textureType == TextureImporterType.Sprite && m_TextureImporterSettings.spriteMode != (int)SpriteImportMode.Multiple; EnsureSingleSpriteExist(); TextureGenerationOutput output; if (m_TextureImporterSettings.textureType != TextureImporterType.Sprite || m_MosaicLayers == false || singleSpriteMode) { output = ImportFlattenImage(doc, ctx, singleSpriteMode); } else { output = ImportFromLayers(ctx); } if (output.texture != null && output.sprites != null) SetPhysicsOutline(GetDataProvider(), output.sprites, definitionScale, pixelsPerUnit, m_GeneratePhysicsShape); RegisterAssets(ctx, output); } catch (Exception e) { Debug.LogError($"Failed to import file {assetPath}. Ex:{e.Message}"); } finally { fileStream.Close(); if (doc != null) doc.Dispose(); UnityEngine.Profiling.Profiler.EndSample(); EditorUtility.SetDirty(this); } } void ValidatePSDLayerId(Document doc, ELayerMappingOption layerMappingOption) { if (layerMappingOption == ELayerMappingOption.UseLayerId) { var uniqueNameGenerator = new UniqueNameGenerator(); ImportUtilities.ValidatePSDLayerId(GetPSDLayers(), doc.Layers, uniqueNameGenerator); } } TextureGenerationOutput ImportTexture(AssetImportContext ctx, NativeArray imageData, int textureWidth, int textureHeight, SpriteMetaData[] sprites) { if (!imageData.IsCreated || imageData.Length == 0) return new TextureGenerationOutput(); TextureGenerationOutput output = new TextureGenerationOutput(); UnityEngine.Profiling.Profiler.BeginSample("ImportTexture"); try { var platformSettings = TextureImporterUtilities.GetPlatformTextureSettings(ctx.selectedBuildTarget, in m_PlatformSettings); var textureSettings = m_TextureImporterSettings.ExtractTextureSettings(); textureSettings.assetPath = ctx.assetPath; textureSettings.enablePostProcessor = true; textureSettings.containsAlpha = true; textureSettings.hdr = false; var textureAlphaSettings = m_TextureImporterSettings.ExtractTextureAlphaSettings(); var textureMipmapSettings = m_TextureImporterSettings.ExtractTextureMipmapSettings(); var textureCubemapSettings = m_TextureImporterSettings.ExtractTextureCubemapSettings(); var textureWrapSettings = m_TextureImporterSettings.ExtractTextureWrapSettings(); switch (m_TextureImporterSettings.textureType) { case TextureImporterType.Default: output = TextureGeneratorHelper.GenerateTextureDefault(imageData, textureWidth, textureHeight, textureSettings, platformSettings, textureAlphaSettings, textureMipmapSettings, textureCubemapSettings, textureWrapSettings); break; case TextureImporterType.NormalMap: var textureNormalSettings = m_TextureImporterSettings.ExtractTextureNormalSettings(); output = TextureGeneratorHelper.GenerateNormalMap(imageData, textureWidth, textureHeight, textureSettings, platformSettings, textureNormalSettings, textureMipmapSettings, textureCubemapSettings, textureWrapSettings); break; case TextureImporterType.GUI: output = TextureGeneratorHelper.GenerateTextureGUI(imageData, textureWidth, textureHeight, textureSettings, platformSettings, textureAlphaSettings, textureMipmapSettings, textureWrapSettings); break; case TextureImporterType.Sprite: var textureSpriteSettings = m_TextureImporterSettings.ExtractTextureSpriteSettings(); textureSpriteSettings.packingTag = m_SpritePackingTag; textureSpriteSettings.qualifyForPacking = !string.IsNullOrEmpty(m_SpritePackingTag); textureSpriteSettings.spriteSheetData = new SpriteImportData[sprites.Length]; textureSettings.npotScale = TextureImporterNPOTScale.None; textureSettings.secondaryTextures = secondaryTextures; for (int i = 0; i < sprites.Length; ++i) textureSpriteSettings.spriteSheetData[i] = sprites[i]; output = TextureGeneratorHelper.GenerateTextureSprite(imageData, textureWidth, textureHeight, textureSettings, platformSettings, textureSpriteSettings, textureAlphaSettings, textureMipmapSettings, textureWrapSettings); break; case TextureImporterType.Cursor: output = TextureGeneratorHelper.GenerateTextureCursor(imageData, textureWidth, textureHeight, textureSettings, platformSettings, textureAlphaSettings, textureMipmapSettings, textureWrapSettings); break; case TextureImporterType.Cookie: output = TextureGeneratorHelper.GenerateCookie(imageData, textureWidth, textureHeight, textureSettings, platformSettings, textureAlphaSettings, textureMipmapSettings, textureCubemapSettings, textureWrapSettings); break; case TextureImporterType.Lightmap: output = TextureGeneratorHelper.GenerateLightmap(imageData, textureWidth, textureHeight, textureSettings, platformSettings, textureMipmapSettings, textureWrapSettings); break; case TextureImporterType.SingleChannel: output = TextureGeneratorHelper.GenerateTextureSingleChannel(imageData, textureWidth, textureHeight, textureSettings, platformSettings, textureAlphaSettings, textureMipmapSettings, textureCubemapSettings, textureWrapSettings); break; default: Debug.LogAssertion("Unknown texture type for import"); output = default(TextureGenerationOutput); break; } } catch (Exception e) { Debug.LogError("Unable to generate Texture2D. Possibly texture size is too big to be generated. ex:"+e.ToString(), this); } finally { UnityEngine.Profiling.Profiler.EndSample(); } return output; } void SetDocumentImportData(IEnumerable layers, PSDExtractLayerData[] extractData, IPSDLayerMappingStrategy mappingStrategy, List psdLayers, PSDExtractLayerData parent = null) { for (var i = 0; i < layers.Count(); ++i) { var layer = layers.ElementAt(i); PSDLayerImportSetting importSetting = null; if (m_PSDLayerImportSetting != null && m_PSDLayerImportSetting.Length > 0) { importSetting = m_PSDLayerImportSetting.FirstOrDefault(x => mappingStrategy.Compare(x, layer)); } var c = psdLayers?.FirstOrDefault(x => mappingStrategy.Compare(x, layer)); if (c != null) { if(c.spriteID.Empty()) c.spriteID = importSetting != null ? importSetting.spriteId : GUID.Generate(); if (importSetting == null) { importSetting = new PSDLayerImportSetting() { flatten = c.flatten, importLayer = layer.Visible || m_ImportHiddenLayers, }; } importSetting.spriteId = c.spriteID; } if (importSetting == null) { importSetting = new PSDLayerImportSetting() { flatten = false, importLayer = layer.Visible || m_ImportHiddenLayers, }; } extractData[i] = new PSDExtractLayerData() { bitmapLayer = layer, importSetting = importSetting, }; PSDExtractLayerData[] childrenExtractData = null; if (layer.ChildLayer != null) { childrenExtractData = new PSDExtractLayerData[layer.ChildLayer.Count()]; SetDocumentImportData(layer.ChildLayer, childrenExtractData, mappingStrategy, psdLayers, extractData[i]); } extractData[i].children = childrenExtractData; } } void SetDocumentImportData(Document doc) { var oldPsdLayers = GetPSDLayers(); var mappingStrategy = GetLayerMappingStrategy(); m_ExtractData = new PSDExtractLayerData[doc.Layers.Count]; SetDocumentImportData(doc.Layers, m_ExtractData, mappingStrategy, oldPsdLayers); } TextureGenerationOutput ImportFlattenImage(Document doc, AssetImportContext ctx, bool singleSpriteMode) { TextureGenerationOutput output; var outputImageBuffer = new NativeArray(doc.width * doc.height, Allocator.Persistent); try { FlattenImageTask.Execute(m_ExtractData, ref outputImageBuffer, m_ImportHiddenLayers, canvasSize); var spriteImportData = GetSpriteImportData(); if (spriteImportData.Count <= 0 || spriteImportData[0] == null) { spriteImportData.Add(new SpriteMetaData()); } spriteImportData[0].name = System.IO.Path.GetFileNameWithoutExtension(ctx.assetPath) + "_1"; spriteImportData[0].alignment = (SpriteAlignment)m_TextureImporterSettings.spriteAlignment; spriteImportData[0].border = m_TextureImporterSettings.spriteBorder; spriteImportData[0].pivot = m_TextureImporterSettings.spritePivot; spriteImportData[0].rect = new Rect(0, 0, doc.width, doc.height); importData.importedTextureWidth = textureActualWidth = doc.width; importData.importedTextureHeight = textureActualHeight = doc.height; var spriteRects = new SpriteMetaData[0]; if (m_TextureImporterSettings.textureType == TextureImporterType.Sprite) { if (singleSpriteMode) spriteRects = new[] { spriteImportData[0] }; else if (spriteImportData.Count > 1) spriteRects = spriteImportData.GetRange(1, spriteDataCount).ToArray(); } output = ImportTexture(ctx, outputImageBuffer, doc.width, doc.height, spriteRects); importData.importedTextureWidth = output.texture.width; importData.importedTextureHeight = output.texture.height; } finally { outputImageBuffer.Dispose(); } return output; } TextureGenerationOutput ImportFromLayers(AssetImportContext ctx) { TextureGenerationOutput output; var outputImageBuffer = default(NativeArray); var layerIndex = new List(); var spriteNameHash = new UniqueNameGenerator(); var platformSettings = TextureImporterUtilities.GetPlatformTextureSettings(ctx.selectedBuildTarget, in m_PlatformSettings); bool requireSquarePOT = (TextureImporterFormat.PVRTC_RGB2 <= platformSettings.format && platformSettings.format <= TextureImporterFormat.PVRTC_RGBA4); var oldPsdLayers = GetPSDLayers(); List psdLayers = null; try { ExtractLayerTask.Execute(in m_ExtractData, out psdLayers, m_ImportHiddenLayers, canvasSize); var mappingStrategy = GetLayerMappingStrategy(); var layerUnique = mappingStrategy.LayersUnique(psdLayers.ConvertAll(x => (IPSDLayerMappingStrategyComparable)x)); if (!string.IsNullOrEmpty(layerUnique)) { Debug.LogWarning(layerUnique,this); } var removedLayersSprite = oldPsdLayers.Where(x => psdLayers.FirstOrDefault(y => mappingStrategy.Compare(y, x)) == null).Select(z => z.spriteID).ToArray(); var hasNewLayer = false; for (var i = 0; i < psdLayers.Count; ++i) { var j = 0; var psdLayer = psdLayers[i]; for (; j < oldPsdLayers.Count; ++j) { if (mappingStrategy.Compare(psdLayer, oldPsdLayers[j])) { psdLayer.spriteName = oldPsdLayers[j].spriteName; psdLayer.mosaicPosition = oldPsdLayers[j].mosaicPosition; if (psdLayer.isImported != oldPsdLayers[j].isImported) hasNewLayer = true; break; } } if(j >= oldPsdLayers.Count) hasNewLayer = true; } var layerBuffers = new List>(); var layerWidth = new List(); var layerHeight = new List(); for (var i = 0; i < psdLayers.Count; ++i) { var l = psdLayers[i]; var expectedBufferLength = l.width * l.height; if (l.texture.IsCreated && l.texture.Length == expectedBufferLength && l.isImported) { layerBuffers.Add(l.texture); layerIndex.Add(i); layerWidth.Add(l.width); layerHeight.Add(l.height); } } PackImage(layerBuffers.ToArray(), layerWidth.ToArray(), layerHeight.ToArray(), m_Padding, m_SpriteSizeExpand, out outputImageBuffer, out int width, out int height, out RectInt[] spriteData, out Vector2Int[] uvTransform, requireSquarePOT); var packOffsets = new Vector2[spriteData.Length]; for (var i = 0; i < packOffsets.Length; ++i) packOffsets[i] = new Vector2((uvTransform[i].x - spriteData[i].position.x) / -1f, (uvTransform[i].y - spriteData[i].position.y) / -1f); var spriteImportData = GetSpriteImportData(); if (spriteImportData.Count <= 0 || shouldResliceFromLayer || hasNewLayer) { var newSpriteMeta = new List(); for (int i = 0; i < spriteData.Length && i < layerIndex.Count; ++i) { var psdLayer = psdLayers[layerIndex[i]]; var spriteSheet = spriteImportData.FirstOrDefault(x => x.spriteID == psdLayer.spriteID); if (spriteSheet == null) { spriteSheet = new SpriteMetaData(); spriteSheet.border = Vector4.zero; spriteSheet.alignment = (SpriteAlignment)m_TextureImporterSettings.spriteAlignment; spriteSheet.pivot = m_TextureImporterSettings.spritePivot; spriteSheet.rect = new Rect(spriteData[i].x, spriteData[i].y, spriteData[i].width, spriteData[i].height); spriteSheet.spriteID = psdLayer.spriteID; } else { var r = spriteSheet.rect; r.position = r.position - psdLayer.mosaicPosition + spriteData[i].position; spriteSheet.rect = r; } psdLayer.spriteName = ImportUtilities.GetUniqueSpriteName(psdLayer.name, spriteNameHash, m_KeepDupilcateSpriteName); spriteSheet.name = psdLayer.spriteName; spriteSheet.spritePosition = psdLayer.layerPosition + packOffsets[i]; if(shouldResliceFromLayer) spriteSheet.rect = new Rect(spriteData[i].x, spriteData[i].y, spriteData[i].width, spriteData[i].height); spriteSheet.uvTransform = uvTransform[i]; psdLayer.spriteID = spriteSheet.spriteID; psdLayer.mosaicPosition = spriteData[i].position; newSpriteMeta.Add(spriteSheet); } spriteImportData.Clear(); spriteImportData.AddRange(newSpriteMeta); } else { spriteImportData.RemoveAll(x => removedLayersSprite.Contains(x.spriteID)); // First look for any user created SpriteRect and add those into the name hash foreach (var importData in spriteImportData) { var psdLayer = psdLayers.FirstOrDefault(x => x.spriteID == importData.spriteID); if (psdLayer == null) spriteNameHash.AddHash(importData.name); } foreach (var importData in spriteImportData) { var psdLayer = psdLayers.FirstOrDefault(x => x.spriteID == importData.spriteID); if (psdLayer == null) importData.uvTransform = new Vector2Int((int)importData.rect.position.x, (int)importData.rect.position.y); // If it is user created rect or the name has been changed before // add it into the spriteNameHash and we don't copy it over from the layer if (psdLayer == null || psdLayer.spriteName != importData.name) spriteNameHash.AddHash(importData.name); // If the sprite name has not been changed, we ensure the new // layer name is still unique and use it as the sprite name if (psdLayer != null && psdLayer.spriteName == importData.name) { psdLayer.spriteName = ImportUtilities.GetUniqueSpriteName(psdLayer.name, spriteNameHash, m_KeepDupilcateSpriteName); importData.name = psdLayer.spriteName; } } //Update names for those user has not changed and add new sprite rect based on PSD file. for (var k = 0; k < layerIndex.Count; ++k) { var i = layerIndex[k]; var spriteSheet = spriteImportData.FirstOrDefault(x => x.spriteID == psdLayers[i].spriteID); var inOldLayer = oldPsdLayers.FindIndex(x => mappingStrategy.Compare(x,psdLayers[i])) != -1; if (spriteSheet == null && !inOldLayer) { spriteSheet = new SpriteMetaData(); spriteImportData.Add(spriteSheet); spriteSheet.rect = new Rect(spriteData[k].x, spriteData[k].y, spriteData[k].width, spriteData[k].height); spriteSheet.border = Vector4.zero; spriteSheet.alignment = (SpriteAlignment)m_TextureImporterSettings.spriteAlignment; spriteSheet.pivot = m_TextureImporterSettings.spritePivot; spriteSheet.spritePosition = psdLayers[i].layerPosition; psdLayers[i].spriteName = ImportUtilities.GetUniqueSpriteName(psdLayers[i].name, spriteNameHash, m_KeepDupilcateSpriteName); spriteSheet.name = psdLayers[i].spriteName; } else if (spriteSheet != null) { var r = spriteSheet.rect; r.position = spriteSheet.rect.position - psdLayers[i].mosaicPosition + spriteData[k].position; if (inOldLayer && (m_SpriteSizeExpand > 0 || m_SpriteSizeExpandChanged)) { r.width = spriteData[k].width; r.height = spriteData[k].height; } spriteSheet.rect = r; spriteSheet.spritePosition = psdLayers[i].layerPosition + packOffsets[k]; } if (spriteSheet != null) { spriteSheet.uvTransform = uvTransform[k]; psdLayers[i].spriteID = spriteSheet.spriteID; psdLayers[i].mosaicPosition = spriteData[k].position; } } } foreach (var l in oldPsdLayers) l.Dispose(); oldPsdLayers.Clear(); oldPsdLayers.AddRange(psdLayers); importData.importedTextureHeight = textureActualHeight = height; importData.importedTextureWidth = textureActualWidth = width; output = ImportTexture(ctx, outputImageBuffer, width, height, spriteImportData.ToArray()); if (output.texture) { importData.importedTextureHeight = output.texture.height; importData.importedTextureWidth = output.texture.width; } } finally { if (outputImageBuffer.IsCreated) outputImageBuffer.Dispose(); foreach (var l in psdLayers) l.Dispose(); } return output; } void EnsureSingleSpriteExist() { if (m_SpriteImportData.Count <= 0) m_SpriteImportData.Add(new SpriteMetaData()); // insert default for single sprite mode } void RegisterAssets(AssetImportContext ctx, TextureGenerationOutput output) { ctx.AddObjectToAsset("PSDImportData", m_ImportData); if ((output.sprites == null || output.sprites.Length == 0) && output.texture == null) { Debug.LogWarning(TextContent.noSpriteOrTextureImportWarning, this); return; } var assetNameGenerator = new UniqueNameGenerator(); if (!string.IsNullOrEmpty(output.importInspectorWarnings)) { Debug.LogWarning(output.importInspectorWarnings); } if (output.importWarnings != null && output.importWarnings.Length != 0) { foreach (var warning in output.importWarnings) Debug.LogWarning(warning); } if (output.thumbNail == null) Debug.LogWarning("Thumbnail generation fail"); if (output.texture == null) { throw new Exception("Texture import fail"); } var assetName = assetNameGenerator.GetUniqueName(System.IO.Path.GetFileNameWithoutExtension(ctx.assetPath), true, this); UnityEngine.Object mainAsset = null; RegisterTextureAsset(ctx, output, assetName, ref mainAsset); RegisterSpriteLibraryAsset(ctx, output, assetName); RegisterGameObjects(ctx, output, ref mainAsset); RegisterSprites(ctx, output, assetNameGenerator); RegisterSkeletonAsset(ctx, output, assetName); ctx.SetMainObject(mainAsset); } void RegisterTextureAsset(AssetImportContext ctx, TextureGenerationOutput output, string assetName, ref UnityEngine.Object mainAsset) { var registerTextureNameId = string.IsNullOrEmpty(m_TextureAssetName) ? "Texture" : m_TextureAssetName; output.texture.name = assetName; ctx.AddObjectToAsset(registerTextureNameId, output.texture, output.thumbNail); mainAsset = output.texture; } void RegisterSpriteLibraryAsset(AssetImportContext ctx, TextureGenerationOutput output, string assetName) { if (output.sprites == null) return; var slAsset = ProduceSpriteLibAsset(output.sprites); if (slAsset == null) return; slAsset.name = assetName; var spriteLibAssetNameId = string.IsNullOrEmpty(m_SpriteLibAssetName) ? "SpriteLibAsset" : m_SpriteLibAssetName; ctx.AddObjectToAsset(spriteLibAssetNameId, slAsset); } void RegisterGameObjects(AssetImportContext ctx, TextureGenerationOutput output, ref UnityEngine.Object mainAsset) { if (output.sprites == null) return; if (!shouldProduceGameObject) return; var contextObjects = new List(); ctx.GetObjects(contextObjects); var slAsset = contextObjects.Find(x => x.GetType() == typeof(SpriteLibraryAsset)) as SpriteLibraryAsset; var prefabRootNameId = string.IsNullOrEmpty(m_TextureAssetName) ? "root" : m_TextureAssetName; var registerPrefabNameId = string.IsNullOrEmpty(m_PrefabAssetName) ? "Prefab" : m_PrefabAssetName; GameObject prefab = null; if (m_PaperDollMode) prefab = OnProducePaperDollPrefab(prefabRootNameId, output.sprites, slAsset); else prefab = OnProducePrefab(prefabRootNameId, output.sprites, slAsset); if (prefab != null) { ctx.AddObjectToAsset(registerPrefabNameId, prefab); mainAsset = prefab; } } void RegisterSprites(AssetImportContext ctx, TextureGenerationOutput output, UniqueNameGenerator assetNameGenerator) { if (output.sprites == null) return; foreach (var s in output.sprites) { var spriteAssetName = assetNameGenerator.GetUniqueName(s.GetSpriteID().ToString(), false, s); ctx.AddObjectToAsset(spriteAssetName, s); } } void RegisterSkeletonAsset(AssetImportContext ctx, TextureGenerationOutput output, string assetName) { var skeletonAssetNameId = string.IsNullOrEmpty(m_SkeletonAssetName) ? "SkeletonAsset" : m_SkeletonAssetName; if (output.sprites != null) { if (inCharacterMode && skeletonAsset == null) { var characterRig = ScriptableObject.CreateInstance(); characterRig.name = assetName + " Skeleton"; var bones = GetDataProvider().GetCharacterData().bones; characterRig.SetSpriteBones(bones); ctx.AddObjectToAsset(skeletonAssetNameId, characterRig); } } if (!string.IsNullOrEmpty(m_SkeletonAssetReferenceID)) { var primaryAssetPath = AssetDatabase.GUIDToAssetPath(m_SkeletonAssetReferenceID); if (!string.IsNullOrEmpty(primaryAssetPath) && primaryAssetPath != assetPath) { ctx.DependsOnArtifact(primaryAssetPath); } } } void BuildGroupGameObject(List psdGroup, int index, Transform root) { var psdData = psdGroup[index]; if (psdData.gameObject == null) { var spriteImported = !psdGroup[index].spriteID.Empty() && psdGroup[index].isImported; var isVisibleGroup = psdData.isGroup && (ImportUtilities.VisibleInHierarchy(psdGroup, index) || m_ImportHiddenLayers) && m_GenerateGOHierarchy; if (spriteImported || isVisibleGroup) { var spriteData = GetSpriteImportData().FirstOrDefault(x => x.spriteID == psdData.spriteID); // Determine if need to create GameObject i.e. if the sprite is not in a SpriteLib or if it is the first one var b = ImportUtilities.SpriteIsMainFromSpriteLib(m_SpriteCategoryList.categories, psdData.spriteID.ToString(), out var categoryName); var goName = string.IsNullOrEmpty(categoryName) ? spriteData != null ? spriteData.name : psdData.name : categoryName; if (b) psdData.gameObject = m_GameObjectFactory.CreateGameObject(goName); } if (psdData.parentIndex >= 0 && m_GenerateGOHierarchy && psdData.gameObject != null) { BuildGroupGameObject(psdGroup, psdData.parentIndex, root); root = psdGroup[psdData.parentIndex].gameObject.transform; } if (psdData.gameObject != null) { psdData.gameObject.transform.SetParent(root); psdData.gameObject.transform.SetSiblingIndex(root.childCount-1); } } } void CreateBoneGO(int index, SpriteBone[] bones, BoneGO[] bonesGO, Transform defaultRoot) { if (bonesGO[index].go != null) return; var bone = bones[index]; if (bone.parentId != -1 && bonesGO[bone.parentId].go == null) CreateBoneGO(bone.parentId, bones, bonesGO, defaultRoot); var go = m_GameObjectFactory.CreateGameObject(bone.name); if (bone.parentId == -1) go.transform.SetParent(defaultRoot); else go.transform.SetParent(bonesGO[bone.parentId].go.transform); go.transform.localPosition = bone.position * 1 / pixelsPerUnit; go.transform.localRotation = bone.rotation; bonesGO[index] = new BoneGO() { go = go, index = index }; } BoneGO[] CreateBonesGO(Transform root) { if (inCharacterMode) { var characterSkeleton = GetDataProvider().GetCharacterData(); var bones = characterSkeleton.bones; if (bones != null) { var boneGOs = new BoneGO[bones.Length]; for (int i = 0; i < bones.Length; ++i) { CreateBoneGO(i, bones, boneGOs, root); } return boneGOs; } } return new BoneGO[0]; } void GetSpriteLibLabel(string spriteId, out string category, out string label) { category = ""; label = ""; foreach (var cat in m_SpriteCategoryList.categories) { var index = cat.labels.FindIndex(x => x.spriteId == spriteId); if (index != -1) { category = cat.name; label = cat.labels[index].name; break; } } } GameObject OnProducePaperDollPrefab(string assetName, Sprite[] sprites, SpriteLibraryAsset spriteLib) { GameObject root = null; if (sprites != null && sprites.Length > 0) { root = new GameObject(); root.name = assetName + "_GO"; var spriteImportData = GetSpriteImportData(); var psdLayers = GetPSDLayers(); var boneGOs = CreateBonesGO(root.transform); if (spriteLib != null) root.AddComponent().spriteLibraryAsset = spriteLib; var currentCharacterData = characterData; for (var i = 0; i < sprites.Length; ++i) { if (ImportUtilities.SpriteIsMainFromSpriteLib(m_SpriteCategoryList.categories, sprites[i].GetSpriteID().ToString(), out var categoryName)) { var spriteBones = currentCharacterData.parts.FirstOrDefault(x => new GUID(x.spriteId) == sprites[i].GetSpriteID()).bones; var rootBone = root; if (spriteBones != null && spriteBones.Any()) { var b = spriteBones.Where(x => x >= 0 && x < boneGOs.Length).Select(x => boneGOs[x]).OrderBy(x => x.index); if (b.Any()) rootBone = b.First().go; } var srGameObject = m_GameObjectFactory.CreateGameObject(string.IsNullOrEmpty(categoryName) ? sprites[i].name : categoryName); var sr = srGameObject.AddComponent(); sr.sprite = sprites[i]; sr.sortingOrder = psdLayers.Count - psdLayers.FindIndex(x => x.spriteID == sprites[i].GetSpriteID()); srGameObject.transform.parent = rootBone.transform; var spriteMetaData = spriteImportData.FirstOrDefault(x => x.spriteID == sprites[i].GetSpriteID()); if (spriteMetaData != null) { var uvTransform = spriteMetaData.uvTransform; var outlineOffset = new Vector2(spriteMetaData.rect.x - uvTransform.x + (spriteMetaData.pivot.x * spriteMetaData.rect.width), spriteMetaData.rect.y - uvTransform.y + (spriteMetaData.pivot.y * spriteMetaData.rect.height)) * definitionScale / sprites[i].pixelsPerUnit; srGameObject.transform.position = new Vector3(outlineOffset.x, outlineOffset.y, 0); } GetSpriteLibLabel(sprites[i].GetSpriteID().ToString(), out var category, out var labelName); if (!string.IsNullOrEmpty(category) && !string.IsNullOrEmpty(labelName)) { var resolver = srGameObject.AddComponent(); resolver.SetCategoryAndLabel(category, labelName); resolver.ResolveSpriteToSpriteRenderer(); } } } } return root; } internal void SetPlatformTextureSettings(TextureImporterPlatformSettings platformSettings) { var index = m_PlatformSettings.FindIndex(x => x.name == platformSettings.name); if(index < 0) m_PlatformSettings.Add(platformSettings); else m_PlatformSettings[index] = platformSettings; } internal TextureImporterPlatformSettings[] GetAllPlatformSettings() { return m_PlatformSettings.ToArray(); } GameObject OnProducePrefab(string assetname, Sprite[] sprites, SpriteLibraryAsset spriteLib) { GameObject root = null; CharacterData? characterSkeleton = inCharacterMode ? new CharacterData ? (GetDataProvider().GetCharacterData()) : null; if (sprites != null && sprites.Length > 0) { var currentCharacterData = characterData; var spriteImportData = GetSpriteImportData(); root = new GameObject(); root.transform.SetSiblingIndex(0); root.name = assetname + "_GO"; if (spriteLib != null) root.AddComponent().spriteLibraryAsset = spriteLib; var psdLayers = GetPSDLayers(); for (var i = 0; i < psdLayers.Count; ++i) { BuildGroupGameObject(psdLayers, i, root.transform); } var boneGOs = CreateBonesGO(root.transform); for (var i = 0; i < psdLayers.Count; ++i) { var l = psdLayers[i]; var layerSpriteID = l.spriteID; var sprite = sprites.FirstOrDefault(x => x.GetSpriteID() == layerSpriteID); var spriteMetaData = spriteImportData.FirstOrDefault(x => x.spriteID == layerSpriteID); if (sprite != null && spriteMetaData != null && l.gameObject != null) { var spriteRenderer = l.gameObject.AddComponent(); spriteRenderer.sprite = sprite; spriteRenderer.sortingOrder = psdLayers.Count - i; var pivot = spriteMetaData.pivot; pivot.x *= spriteMetaData.rect.width; pivot.y *= spriteMetaData.rect.height; var spritePosition = spriteMetaData.spritePosition; spritePosition.x += pivot.x; spritePosition.y += pivot.y; spritePosition *= (definitionScale / sprite.pixelsPerUnit); l.gameObject.transform.position = new Vector3(spritePosition.x, spritePosition.y, 0f); if (characterSkeleton != null) { var part = characterSkeleton.Value.parts.FirstOrDefault(x => x.spriteId == spriteMetaData.spriteID.ToString()); if (part.bones != null && part.bones.Length > 0) { var spriteSkin = l.gameObject.AddComponent(); if (spriteRenderer.sprite != null && spriteRenderer.sprite.GetBindPoses().Length > 0) { var spriteBones = currentCharacterData.parts.FirstOrDefault(x => new GUID(x.spriteId) == spriteRenderer.sprite.GetSpriteID()).bones.Where(x => x >= 0 && x < boneGOs.Length).Select(x => boneGOs[x]); if (spriteBones.Any()) { spriteSkin.rootBone = root.transform; spriteSkin.boneTransforms = spriteBones.Select(x => x.go.transform).ToArray(); if (spriteSkin.isValid) spriteSkin.CalculateBounds(); } } } } GetSpriteLibLabel(layerSpriteID.ToString(), out var category, out var labelName); if (!string.IsNullOrEmpty(category) && !string.IsNullOrEmpty(labelName)) { var resolver = l.gameObject.AddComponent(); resolver.SetCategoryAndLabel(category, labelName); resolver.ResolveSpriteToSpriteRenderer(); } } } var prefabBounds = new Rect(0 , 0, importData.documentSize.x / pixelsPerUnit, importData.documentSize.y / pixelsPerUnit); var documentPivot = (Vector3)ImportUtilities.GetPivotPoint(prefabBounds, m_DocumentAlignment, m_DocumentPivot); for (var i = 0; i < psdLayers.Count; ++i) { var l = psdLayers[i]; if (l.gameObject == null || l.gameObject.GetComponent() == null) continue; var p = l.gameObject.transform.localPosition; p -= documentPivot; l.gameObject.transform.localPosition = p; } for (int i = 0; i < boneGOs.Length; ++i) { if (boneGOs[i].go.transform.parent != root.transform) continue; var p = boneGOs[i].go.transform.position; p -= documentPivot; boneGOs[i].go.transform.position = p; } } return root; } int spriteDataCount { get { var spriteImportData = GetSpriteImportData(); if (inMosaicMode) return spriteImportData.Count; if (spriteImportModeToUse != SpriteImportMode.Multiple) return 1; return spriteImportData.Count - 1; } } internal void Apply() { // Do this so that asset change save dialog will not show var originalValue = EditorPrefs.GetBool("VerifySavingAssets", false); EditorPrefs.SetBool("VerifySavingAssets", false); AssetDatabase.ForceReserializeAssets(new string[] { assetPath }, ForceReserializeAssetsOptions.ReserializeMetadata); EditorPrefs.SetBool("VerifySavingAssets", originalValue); } List GetSpriteImportData() { if (inMosaicMode) { if (inCharacterMode) { if (skeletonAsset != null) { return m_SharedRigSpriteImportData; } return m_RigSpriteImportData; } return m_MosaicSpriteImportData; } return m_SpriteImportData; } SkeletonAsset skeletonAsset => AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(m_SkeletonAssetReferenceID)); internal List GetPSDLayers() { if (inMosaicMode) { if (inCharacterMode) { if (skeletonAsset != null) return m_SharedRigPSDLayers; else return m_RigPSDLayers; } return m_MosaicPSDLayers; } return null; } internal SpriteMetaData[] GetSpriteMetaData() { var spriteImportData = GetSpriteImportData(); var skip = inMosaicMode ? 0 : 1; return spriteImportModeToUse == SpriteImportMode.Multiple ? spriteImportData.Skip(skip).ToArray() : new[] { new SpriteMetaData(spriteImportData[0]) }; } internal SpriteRect GetSpriteDataFromAllMode(GUID guid) { var spriteMetaData = m_RigSpriteImportData.FirstOrDefault(x => x.spriteID == guid); if (spriteMetaData == null) spriteMetaData = m_SharedRigSpriteImportData.FirstOrDefault(x => x.spriteID == guid); if (spriteMetaData == null) spriteMetaData = m_MosaicSpriteImportData.FirstOrDefault(x => x.spriteID == guid); if (spriteMetaData == null) spriteMetaData = m_SpriteImportData.FirstOrDefault(x => x.spriteID == guid); return spriteMetaData; } internal SpriteRect GetSpriteData(GUID guid) { var spriteImportData = GetSpriteImportData(); var skip = inMosaicMode ? 0 : 1; return spriteImportModeToUse == SpriteImportMode.Multiple ? spriteImportData.Skip(skip).FirstOrDefault(x => x.spriteID == guid) : spriteImportData[0]; } internal Vector2 GetDocumentPivot() { return ImportUtilities.GetPivotPoint(new Rect(0, 0, 1, 1), m_DocumentAlignment, m_DocumentPivot); } internal void SetDocumentPivot(Vector2 pivot) { ImportUtilities.TranslatePivotPoint(pivot, new Rect(0, 0, 1, 1), out m_DocumentAlignment, out m_DocumentPivot); } bool inMosaicMode => spriteImportModeToUse == SpriteImportMode.Multiple && m_MosaicLayers; SpriteImportMode spriteImportModeToUse => m_TextureImporterSettings.textureType != TextureImporterType.Sprite ? SpriteImportMode.None : (SpriteImportMode)m_TextureImporterSettings.spriteMode; internal CharacterData characterData { get { if (skeletonAsset != null) return m_SharedRigCharacterData; return m_CharacterData; } set { if (skeletonAsset != null) m_SharedRigCharacterData = value; else m_CharacterData = value; } } internal Vector2Int canvasSize => importData.documentSize; SpriteLibraryAsset ProduceSpriteLibAsset(Sprite[] sprites) { if (!inCharacterMode || m_SpriteCategoryList.categories == null) return null; var categories = m_SpriteCategoryList.categories.Select(x => new SpriteLibCategory() { name = x.name, categoryList = x.labels.Select(y => { var sprite = sprites.FirstOrDefault(z => z.GetSpriteID().ToString() == y.spriteId); return new SpriteCategoryEntry() { name = y.name, sprite = sprite }; }).ToList() }).ToList(); categories.RemoveAll(x => x.categoryList.Count == 0); if (categories.Count > 0) { // Always set version to 0 since we will never be updating this return SpriteLibraryAsset.CreateAsset(categories, "Sprite Lib", 0); } return null; } internal void ReadTextureSettings(TextureImporterSettings dest) { m_TextureImporterSettings.CopyTo(dest); } internal IPSDLayerMappingStrategy GetLayerMappingStrategy() { return m_MappingCompare[(int)m_LayerMappingOption]; } static void SetPhysicsOutline(ISpritePhysicsOutlineDataProvider physicsOutlineDataProvider, Sprite[] sprites, float definitionScale, float pixelsPerUnit, bool generatePhysicsShape) { foreach (var sprite in sprites) { var guid = sprite.GetSpriteID(); var outline = physicsOutlineDataProvider.GetOutlines(guid); var outlineOffset = sprite.rect.size / 2; var generated = false; if ((outline == null || outline.Count == 0) && generatePhysicsShape) { InternalEditorBridge.GenerateOutlineFromSprite(sprite, 0.25f, 200, true, out var defaultOutline); outline = new List(defaultOutline.Length); for (var i = 0; i < defaultOutline.Length; ++i) { outline.Add(defaultOutline[i]); } generated = true; } if (outline != null && outline.Count > 0) { // Ensure that outlines are all valid. var validOutlineCount = 0; for (var i = 0; i < outline.Count; ++i) validOutlineCount += ( (outline[i].Length > 2) ? 1 : 0 ); var index = 0; var convertedOutline = new Vector2[validOutlineCount][]; var useScale = generated ? pixelsPerUnit * definitionScale : definitionScale; for (var i = 0; i < outline.Count; ++i) { if (outline[i].Length > 2) { convertedOutline[index] = new Vector2[outline[i].Length]; for (var j = 0; j < outline[i].Length; ++j) { convertedOutline[index][j] = outline[i][j] * useScale + outlineOffset; } index++; } } sprite.OverridePhysicsShape(convertedOutline); } } } } }