using System; using System.Collections.Generic; using System.IO; using PDNWrapper; using UnityEngine; using Unity.Collections; using System.Linq; using UnityEditor.Experimental.AssetImporters; using UnityEditor.U2D.Animation; using UnityEditor.U2D.Common; using UnityEditor.U2D.Sprites; using UnityEngine.Assertions; using UnityEngine.Experimental.U2D.Animation; using UnityEngine.U2D; using UnityEngine.U2D.Animation; using UnityEditor.MemoryProfiler; using UnityEngine.Profiling.Memory.Experimental; namespace UnityEditor.U2D.PSD { /// /// ScriptedImporter to import Photoshop files /// [ScriptedImporter(4, "psb")] [HelpURL("https://docs.unity3d.com/Packages/com.unity.2d.psdimporter@3.1/manual/index.html")] public class PSDImporter : ScriptedImporter, ISpriteEditorDataProvider { class UniqueNameGenerator { List m_NameHash = new List(); public bool ContainHash(int i) { return m_NameHash.Contains(i); } public void AddHash(int i) { m_NameHash.Add(i); } public string GetUniqueName(string name) { return PSDImporter.GetUniqueName(name, m_NameHash); } } class GameObjectCreationFactory : UniqueNameGenerator { public GameObject CreateGameObject(string name, params System.Type[] components) { var newName = GetUniqueName(name); return new GameObject(newName, components); } } struct BoneGO { public GameObject go; public int index; } [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, }; [SerializeField] List m_SpriteImportData = new List(); // we use index 0 for single sprite and the rest for multiple sprites [SerializeField] List m_MosaicSpriteImportData = new List(); [SerializeField] List m_RigSpriteImportData = new List(); [SerializeField] List m_PlatformSettings = new List(); [SerializeField] bool m_MosaicLayers = true; [SerializeField] Vector2 m_DocumentPivot = Vector2.zero; [SerializeField] SpriteAlignment m_DocumentAlignment = SpriteAlignment.BottomCenter; [SerializeField] bool m_ImportHiddenLayers = false; [SerializeField] int m_ImportedTextureWidth; [SerializeField] int m_ImportedTextureHeight; [SerializeField] Vector2Int m_DocumentSize; [SerializeField] bool m_PaperDollMode = false; [SerializeField] bool m_KeepDupilcateSpriteName = false; [SerializeField] SpriteCategoryList m_SpriteCategoryList = new SpriteCategoryList() {categories = new List()}; GameObjectCreationFactory m_GameObjectFactory = new GameObjectCreationFactory(); internal SpriteCategoryList spriteCategoryList { get { return m_SpriteCategoryList; } set { m_SpriteCategoryList = value; } } [SerializeField] int m_TextureActualWidth; internal int textureActualWidth { get { return m_TextureActualWidth; } private set { m_TextureActualWidth = value; } } [SerializeField] int m_TextureActualHeight; internal int textureActualHeight { get { return m_TextureActualHeight; } private set { m_TextureActualHeight = value; } } [SerializeField] string m_SpritePackingTag = ""; [SerializeField] bool m_ResliceFromLayer = false; [SerializeField] bool m_CharacterMode = true; [SerializeField] List m_MosaicPSDLayers = new List(); [SerializeField] List m_RigPSDLayers = new List(); [SerializeField] CharacterData m_CharacterData = new CharacterData(); [SerializeField] bool m_GenerateGOHierarchy = false; [SerializeField] string m_TextureAssetName = null; [SerializeField] string m_PrefabAssetName = null; [SerializeField] string m_SpriteLibAssetName = null; [SerializeField] SecondarySpriteTexture[] m_SecondarySpriteTextures; /// /// 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) { FileStream fileStream = new FileStream(ctx.assetPath, FileMode.Open, FileAccess.Read); Document doc = null; try { UnityEngine.Profiling.Profiler.BeginSample("OnImportAsset"); UnityEngine.Profiling.Profiler.BeginSample("PsdLoad"); doc = PaintDotNet.Data.PhotoshopFileType.PsdLoad.Load(fileStream); UnityEngine.Profiling.Profiler.EndSample(); ValidatePSDLayerId(doc); m_DocumentSize = new Vector2Int(doc.width, doc.height); bool singleSpriteMode = m_TextureImporterSettings.textureType == TextureImporterType.Sprite && m_TextureImporterSettings.spriteMode != (int)SpriteImportMode.Multiple; EnsureSingleSpriteExist(); if (m_TextureImporterSettings.textureType != TextureImporterType.Sprite || m_MosaicLayers == false || singleSpriteMode) { var image = new NativeArray(doc.width * doc.height, Allocator.Persistent); try { var spriteImportData = GetSpriteImportData(); FlattenImageTask.Execute(doc.Layers, m_ImportHiddenLayers, doc.width, doc.height, image); int spriteCount = spriteDataCount; int spriteIndexStart = 1; 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); if (singleSpriteMode) { spriteCount = 1; spriteIndexStart = 0; } textureActualWidth = doc.width; textureActualHeight = doc.height; var output = ImportTexture(ctx, image, doc.width, doc.height, spriteIndexStart, spriteCount); RegisterAssets(ctx, output); } finally { image.Dispose(); } } else { ImportFromLayers(ctx, doc); } } finally { fileStream.Close(); if (doc != null) doc.Dispose(); UnityEngine.Profiling.Profiler.EndSample(); EditorUtility.SetDirty(this); } // Debug Profiler. // UnityEngine.Profiling.Memory.Experimental.MemoryProfiler.TakeSnapshot("snapshot.snap", MemorySnapshotFinish, CaptureFlags.ManagedObjects | CaptureFlags.NativeObjects | CaptureFlags.NativeAllocations | CaptureFlags.NativeStackTraces); } static void ValidatePSDLayerId(List layers, UniqueNameGenerator uniqueNameGenerator) { for (int i = 0; i < layers.Count; ++i) { if (uniqueNameGenerator.ContainHash(layers[i].LayerID)) { var importWarning = string.Format("Layer {0}: LayerId is not unique. Mapping will be done by Layer's name.", layers[i].Name); var layerName = uniqueNameGenerator.GetUniqueName(layers[i].Name); if (layerName != layers[i].Name) importWarning += "\nLayer names are not unique. Please ensure they are unique to for SpriteRect to be mapped back correctly."; layers[i].LayerID = layerName.GetHashCode(); Debug.LogWarning(importWarning); } else uniqueNameGenerator.AddHash(layers[i].LayerID); if (layers[i].ChildLayer != null) { ValidatePSDLayerId(layers[i].ChildLayer, uniqueNameGenerator); } } } void ValidatePSDLayerId(Document doc) { UniqueNameGenerator uniqueNameGenerator = new UniqueNameGenerator(); ValidatePSDLayerId(doc.Layers, uniqueNameGenerator); } TextureGenerationOutput ImportTexture(AssetImportContext ctx, NativeArray imageData, int textureWidth, int textureHeight, int spriteStart, int spriteCount) { if (!imageData.IsCreated || imageData.Length == 0) return new TextureGenerationOutput(); UnityEngine.Profiling.Profiler.BeginSample("ImportTexture"); var platformSettings = GetPlatformTextureSettings(ctx.selectedBuildTarget); 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(); TextureGenerationOutput output; 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 UnityEditor.Experimental.AssetImporters.SpriteImportData[spriteCount]; textureSettings.npotScale = TextureImporterNPOTScale.None; textureSettings.secondaryTextures = secondaryTextures; var spriteImportData = GetSpriteImportData(); for (int i = 0; i < spriteCount; ++i) { //AutoGenerateSpriteSkinData(m_SpriteImportData[spriteStart + i]); textureSpriteSettings.spriteSheetData[i] = spriteImportData[spriteStart + 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; } UnityEngine.Profiling.Profiler.EndSample(); return output; } void AutoGenerateSpriteSkinData(SpriteMetaData metaData) { //If bone data exist but skinning data doesn't exist, auto generate them if (metaData.spriteBone != null && metaData.spriteBone.Count > 0 && (metaData.vertices == null || metaData.vertices.Count == 0)) { var spriteMeshDataController = new SpriteMeshDataController(); var smd = new SpriteMeshData(); smd.spriteID = metaData.spriteID; smd.frame = metaData.rect; smd.pivot = metaData.pivot; smd.bones = ModuleUtility.CreateSpriteBoneData(metaData.spriteBone.ToArray(), Matrix4x4.TRS(metaData.rect.position, Quaternion.identity, Vector3.one)); spriteMeshDataController.spriteMeshData = smd; spriteMeshDataController.OutlineFromAlpha(new OutlineGenerator(), GetDataProvider(), 0.05f, 200); spriteMeshDataController.Triangulate(new Triangulator()); spriteMeshDataController.Subdivide(new Triangulator(), 0.25f); spriteMeshDataController.CalculateWeights(new BoundedBiharmonicWeightsGenerator(), null, 0.01f); spriteMeshDataController.SortTrianglesByDepth(); List vmd = new List(smd.vertices.Count); foreach (var v in smd.vertices) vmd.Add(new Vertex2DMetaData() { position = v.position - smd.frame.position, boneWeight = v.editableBoneWeight.ToBoneWeight(true) }); List emd = new List(smd.edges.Count); foreach (var e in smd.edges) emd.Add(new Vector2Int(e.index1, e.index2)); metaData.vertices = vmd; metaData.indices = smd.indices.ToArray(); metaData.edges = emd.ToArray(); } } string GetUniqueSpriteName(string name, List namehash) { if (m_KeepDupilcateSpriteName) return name; return GetUniqueName(name, namehash); } void ImportFromLayers(AssetImportContext ctx, Document doc) { NativeArray output = default(NativeArray); List layerIndex = new List(); List spriteNameHash = new List(); var oldPsdLayers = GetPSDLayers(); try { var psdLayers = new List(); ExtractLayerTask.Execute(psdLayers, doc.Layers, m_ImportHiddenLayers); var removedLayersSprite = oldPsdLayers.Where(x => psdLayers.FirstOrDefault(y => y.layerID == x.layerID) == null).Select(z => z.spriteID).ToArray(); for (int i = 0; i < psdLayers.Count; ++i) { int j = 0; var psdLayer = psdLayers[i]; for (; j < oldPsdLayers.Count; ++j) { if (psdLayer.layerID == oldPsdLayers[j].layerID) { psdLayer.spriteID = oldPsdLayers[j].spriteID; psdLayer.spriteName = oldPsdLayers[j].spriteName; psdLayer.mosaicPosition = oldPsdLayers[j].mosaicPosition; break; } } } int expectedBufferLength = doc.width * doc.height; var layerBuffers = new List>(); for (int i = 0; i < psdLayers.Count; ++i) { var l = psdLayers[i]; if (l.texture.IsCreated && l.texture.Length == expectedBufferLength) { layerBuffers.Add(l.texture); layerIndex.Add(i); } } RectInt[] spritedata; int width, height; int padding = 4; Vector2Int[] uvTransform; ImagePacker.Pack(layerBuffers.ToArray(), doc.width, doc.height, padding, out output, out width, out height, out spritedata, out uvTransform); var spriteImportData = GetSpriteImportData(); if (spriteImportData.Count <= 0 || shouldResliceFromLayer) { var newSpriteMeta = new List(); for (int i = 0; i < spritedata.Length && i < layerIndex.Count; ++i) { var spriteSheet = spriteImportData.FirstOrDefault(x => x.spriteID == psdLayers[layerIndex[i]].spriteID); if (spriteSheet == null) { spriteSheet = new SpriteMetaData(); spriteSheet.border = Vector4.zero; spriteSheet.alignment = (SpriteAlignment)m_TextureImporterSettings.spriteAlignment; spriteSheet.pivot = m_TextureImporterSettings.spritePivot; } psdLayers[layerIndex[i]].spriteName = GetUniqueSpriteName(psdLayers[layerIndex[i]].name, spriteNameHash); spriteSheet.name = psdLayers[layerIndex[i]].spriteName; spriteSheet.rect = new Rect(spritedata[i].x, spritedata[i].y, spritedata[i].width, spritedata[i].height); spriteSheet.uvTransform = uvTransform[i]; psdLayers[layerIndex[i]].spriteID = spriteSheet.spriteID; psdLayers[layerIndex[i]].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 spriteData in spriteImportData) { var psdLayer = psdLayers.FirstOrDefault(x => x.spriteID == spriteData.spriteID); if (psdLayer == null) spriteNameHash.Add(spriteData.name.GetHashCode()); } foreach (var spriteData in spriteImportData) { var psdLayer = psdLayers.FirstOrDefault(x => x.spriteID == spriteData.spriteID); if (psdLayer == null) spriteData.uvTransform = new Vector2Int((int)spriteData.rect.position.x, (int)spriteData.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 != spriteData.name) spriteNameHash.Add(spriteData.name.GetHashCode()); // 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 == spriteData.name) { psdLayer.spriteName = GetUniqueSpriteName(psdLayer.name, spriteNameHash); spriteData.name = psdLayer.spriteName; } } //Update names for those user has not changed and add new sprite rect based on PSD file. for (int k = 0; k < layerIndex.Count; ++k) { int i = layerIndex[k]; var spriteSheet = spriteImportData.FirstOrDefault(x => x.spriteID == psdLayers[i].spriteID); var inOldLayer = oldPsdLayers.FindIndex(x => x.layerID == psdLayers[i].layerID) != -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; psdLayers[i].spriteName = GetUniqueSpriteName(psdLayers[i].name, spriteNameHash); spriteSheet.name = psdLayers[i].spriteName; } else if (spriteSheet != null) { var r = spriteSheet.rect; r.position = spriteSheet.rect.position - psdLayers[i].mosaicPosition + spritedata[k].position; spriteSheet.rect = r; } 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); m_ImportedTextureHeight = textureActualHeight = height; m_ImportedTextureWidth = textureActualWidth = width; var generatedTexture = ImportTexture(ctx, output, width, height, 0, spriteImportData.Count); if (generatedTexture.texture) { m_ImportedTextureHeight = generatedTexture.texture.height; m_ImportedTextureWidth = generatedTexture.texture.width; } RegisterAssets(ctx, generatedTexture); } finally { if (output.IsCreated) output.Dispose(); foreach (var l in oldPsdLayers) l.Dispose(); } } void MemorySnapshotFinish(string path, bool done) { } void EnsureSingleSpriteExist() { if (m_SpriteImportData.Count <= 0) m_SpriteImportData.Add(new SpriteMetaData()); // insert default for single sprite mode } internal TextureImporterPlatformSettings GetPlatformTextureSettings(BuildTarget buildTarget) { var buildTargetName = TexturePlatformSettingsModal.kValidBuildPlatform.FirstOrDefault(x => x.buildTarget.Contains(buildTarget)); var defaultTargetName = TexturePlatformSettingsModal.kValidBuildPlatform.FirstOrDefault(x => x.buildTarget.Contains(BuildTarget.NoTarget)); TextureImporterPlatformSettings platformSettings = null; platformSettings = m_PlatformSettings.SingleOrDefault(x => x.name == buildTargetName.buildTargetName); platformSettings = platformSettings ?? m_PlatformSettings.SingleOrDefault(x => x.name == defaultTargetName.buildTargetName); if (platformSettings == null) { platformSettings = new TextureImporterPlatformSettings(); platformSettings.name = name; platformSettings.overridden = false; } return platformSettings; } void RegisterAssets(AssetImportContext ctx, TextureGenerationOutput output) { List assetNameHash = new List(); 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 = GetUniqueName(System.IO.Path.GetFileNameWithoutExtension(ctx.assetPath), assetNameHash, true); // Setup all fixed name on the hash table if (string.IsNullOrEmpty(m_TextureAssetName)) m_TextureAssetName = GetUniqueName(string.Format("{0} Texture",assetName), assetNameHash, true); if (string.IsNullOrEmpty(m_PrefabAssetName)) m_PrefabAssetName = GetUniqueName(string.Format("{0} Prefab", assetName), assetNameHash, true); if (string.IsNullOrEmpty(m_SpriteLibAssetName)) m_SpriteLibAssetName = GetUniqueName(string.Format("{0} Sprite Lib", assetName), assetNameHash, true); output.texture.name = assetName; ctx.AddObjectToAsset(m_TextureAssetName, output.texture, output.thumbNail); UnityEngine.Object mainAsset = output.texture; if (output.sprites != null) { var slAsset = ProduceSpriteLibAsset(output.sprites); if (shouldProduceGameObject) { GameObject prefab = null; if (m_PaperDollMode) prefab = OnProducePaperDollPrefab(m_TextureAssetName, output.sprites, slAsset); else prefab = OnProducePrefab(m_TextureAssetName, output.sprites, slAsset); if (prefab != null) { ctx.AddObjectToAsset(m_PrefabAssetName, prefab); mainAsset = prefab; } } foreach (var s in output.sprites) { var spriteAssetName = GetUniqueName(s.GetSpriteID().ToString(), assetNameHash, false, s); ctx.AddObjectToAsset(spriteAssetName, s); } if (slAsset != null) { slAsset.name = assetName; ctx.AddObjectToAsset(m_SpriteLibAssetName, slAsset); } } ctx.SetMainObject(mainAsset); } bool SpriteIsMainFromSpriteLib(string spriteId, out string categoryName) { categoryName = ""; if (m_SpriteCategoryList.categories != null) { foreach (var category in m_SpriteCategoryList.categories) { var index = category.labels.FindIndex(x => x.spriteId == spriteId); if (index == 0) { categoryName = category.name; return true; } if (index > 0) return false; } } return true; } void BuildGroupGameObject(List psdGroup, int index, Transform root) { var spriteData = GetSpriteImportData().FirstOrDefault(x => x.spriteID == psdGroup[index].spriteID); if (psdGroup[index].gameObject == null) { if (m_GenerateGOHierarchy || (!psdGroup[index].spriteID.Empty() && psdGroup[index].isGroup == false)) { // Determine if need to create GameObject i.e. if the sprite is not in a SpriteLib or if it is the first one string categoryName; var b = SpriteIsMainFromSpriteLib(psdGroup[index].spriteID.ToString(), out categoryName); string goName = string.IsNullOrEmpty(categoryName) ? spriteData != null ? spriteData.name : psdGroup[index].name : categoryName; if (b) psdGroup[index].gameObject = m_GameObjectFactory.CreateGameObject(goName); } if (psdGroup[index].parentIndex >= 0 && m_GenerateGOHierarchy) { BuildGroupGameObject(psdGroup, psdGroup[index].parentIndex, root); root = psdGroup[psdGroup[index].parentIndex].gameObject.transform; } if (psdGroup[index].gameObject != null) psdGroup[index].gameObject.transform.SetParent(root); } } bool shouldProduceGameObject { get { return m_CharacterMode && m_MosaicLayers && spriteImportMode == SpriteImportMode.Multiple; } } bool shouldResliceFromLayer { get { return m_ResliceFromLayer && m_MosaicLayers && spriteImportMode == SpriteImportMode.Multiple; } } bool characterMode { get { return mosaicMode && m_CharacterMode == true; } } float definitionScale { get { float definitionScaleW = m_ImportedTextureWidth / (float)textureActualWidth; float definitionScaleH = m_ImportedTextureHeight / (float)textureActualHeight; return Mathf.Min(definitionScaleW, definitionScaleH); } } private Vector2 GetPivotPoint(Rect rect, SpriteAlignment alignment) { switch (alignment) { case SpriteAlignment.TopLeft: return new Vector2(rect.xMin, rect.yMax); case SpriteAlignment.TopCenter: return new Vector2(rect.center.x, rect.yMax); case SpriteAlignment.TopRight: return new Vector2(rect.xMax, rect.yMax); case SpriteAlignment.LeftCenter: return new Vector2(rect.xMin, rect.center.y); case SpriteAlignment.Center: return new Vector2(rect.center.x, rect.center.y); case SpriteAlignment.RightCenter: return new Vector2(rect.xMax, rect.center.y); case SpriteAlignment.BottomLeft: return new Vector2(rect.xMin, rect.yMin); case SpriteAlignment.BottomCenter: return new Vector2(rect.center.x, rect.yMin); case SpriteAlignment.BottomRight: return new Vector2(rect.xMax, rect.yMin); case SpriteAlignment.Custom: return new Vector2(m_DocumentPivot.x * rect.width, m_DocumentPivot.y * rect.height); } return Vector2.zero; } 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 (characterMode) { 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 = ""; var index = -1; foreach (var cat in m_SpriteCategoryList.categories) { 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; CharacterData? characterSkeleton = characterMode ? new CharacterData ? (GetDataProvider().GetCharacterData()) : 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; for (int i = 0; i < sprites.Length; ++i) { string categoryName; if (SpriteIsMainFromSpriteLib(sprites[i].GetSpriteID().ToString(), out categoryName)) { var spriteBones = m_CharacterData.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); } var category = ""; var labelName = ""; GetSpriteLiblabel(sprites[i].GetSpriteID().ToString(), out category, out labelName); if (!string.IsNullOrEmpty(category) && !string.IsNullOrEmpty(labelName)) { var sresolver = srGameObject.AddComponent(); sresolver.SetCategoryAndLabel(category, labelName); sresolver.ResolveSpriteToSpriteRenderer(); } } } } return root; } GameObject OnProducePrefab(string assetname, Sprite[] sprites, SpriteLibraryAsset spriteLib) { GameObject root = null; CharacterData? characterSkeleton = characterMode ? new CharacterData ? (GetDataProvider().GetCharacterData()) : null; if (sprites != null && sprites.Length > 0) { var spriteImportData = GetSpriteImportData(); root = new GameObject(); root.name = assetname + "_GO"; if (spriteLib != null) root.AddComponent().spriteLibraryAsset = spriteLib; var psdLayers = GetPSDLayers(); for (int i = 0; i < psdLayers.Count; ++i) { BuildGroupGameObject(psdLayers, i, root.transform); } var boneGOs = CreateBonesGO(root.transform); for (int i = 0; i < psdLayers.Count; ++i) { var l = psdLayers[i]; GUID 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 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 / sprite.pixelsPerUnit; l.gameObject.transform.position = new Vector3(outlineOffset.x, outlineOffset.y, 0); 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 = m_CharacterData.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 = spriteBones.OrderBy(x => x.index).First().go.transform; spriteSkin.boneTransforms = spriteBones.Select(x => x.go.transform).ToArray(); if (spriteSkin.isValid) spriteSkin.CalculateBounds(); } } } } var category = ""; var labelName = ""; GetSpriteLiblabel(layerSpriteID.ToString(), out category, out labelName); if (!string.IsNullOrEmpty(category) && !string.IsNullOrEmpty(labelName)) { var sresolver = l.gameObject.AddComponent(); sresolver.SetCategoryAndLabel(category, labelName); sresolver.ResolveSpriteToSpriteRenderer(); } } } var prefabBounds = new Rect(0 , 0, m_DocumentSize.x / pixelsPerUnit, m_DocumentSize.y / pixelsPerUnit); var documentPivot = (Vector3)GetPivotPoint(prefabBounds, m_DocumentAlignment); for (int 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; } Bounds? GetBoundingBox(GameObject root) { Bounds? prefabBounds1 = null; var sr = root.GetComponent(); if (sr != null) { prefabBounds1 = sr.bounds; } for (int i = 0; i < root.transform.childCount; ++i) { var b = GetBoundingBox(root.transform.GetChild(i).gameObject); if (prefabBounds1 == null) prefabBounds1 = b; else { if (b.HasValue) { var bb = prefabBounds1.Value; bb.Encapsulate(b.Value); prefabBounds1 = bb; } } } return prefabBounds1; } bool CleanUpGameobjectsWithOutRig(GameObject root) { var sr = root.GetComponent(); var canDelete = true; if (sr != null && sr.sprite != null) { try { var bones = GetDataProvider().GetBones(sr.sprite.GetSpriteID()); canDelete = bones == null || bones.Count == 0; } catch (Exception e) { Debug.LogError(e); } } List cleanup = new List(); for (int i = 0; i < root.transform.childCount; ++i) { var go = root.transform.GetChild(i); if (CleanUpGameobjectsWithOutRig(go.gameObject)) cleanup.Add(go.gameObject); } for (int i = 0; i < cleanup.Count; ++i) GameObject.DestroyImmediate(cleanup[i]); cleanup.Clear(); if (root.transform.childCount == 0 && canDelete) return true; return false; } static string SanitizeName(string name) { string newName = null; // We can't create asset name with these name. if ((name.Length == 2 && name[0] == '.' && name[1] == '.') || (name.Length == 1 && name[0] == '.') || (name.Length == 1 && name[0] == '/')) newName += name + "_"; if (!string.IsNullOrEmpty(newName)) { Debug.LogWarning(string.Format("File contains layer with invalid name for generating asset. {0} is renamed to {1}", name, newName)); return newName; } return name; } static string GetUniqueName(string name, List stringHash, bool logNewNameGenerated = false, UnityEngine.Object context = null) { string uniqueName = string.Copy(SanitizeName(name)); int index = 1; while (true) { int hash = uniqueName.GetHashCode(); var p = stringHash.Where(x => x == hash); if (!p.Any()) { stringHash.Add(hash); if (logNewNameGenerated && name != uniqueName) Debug.Log(string.Format("Asset name {0} is changed to {1} to ensure uniqueness", name, uniqueName), context); return uniqueName; } uniqueName = string.Format("{0}_{1}", name, index); ++index; } } // ISpriteEditorDataProvider interface internal SpriteImportMode spriteImportMode { get { return m_TextureImporterSettings.textureType != TextureImporterType.Sprite ? SpriteImportMode.None : (SpriteImportMode)m_TextureImporterSettings.spriteMode; } } SpriteImportMode ISpriteEditorDataProvider.spriteImportMode => spriteImportMode; internal int spriteDataCount { get { var spriteImportData = GetSpriteImportData(); if (mosaicMode) return spriteImportData.Count; if (spriteImportMode != SpriteImportMode.Multiple) return 1; return spriteImportData.Count - 1; } } internal UnityEngine.Object targetObject { get { return this; } } UnityEngine.Object ISpriteEditorDataProvider.targetObject => targetObject; internal float pixelsPerUnit { get { return m_TextureImporterSettings.spritePixelsPerUnit; } } float ISpriteEditorDataProvider.pixelsPerUnit =>pixelsPerUnit; internal T GetDataProvider() where T : class { if (typeof(T) == typeof(ISpriteBoneDataProvider)) { return new SpriteBoneDataProvider { dataProvider = this } as T; } if (typeof(T) == typeof(ISpriteMeshDataProvider)) { return new SpriteMeshDataProvider { dataProvider = this } as T; } if (typeof(T) == typeof(ISpriteOutlineDataProvider)) { return new SpriteOutlineDataProvider { dataProvider = this } as T; } if (typeof(T) == typeof(ISpritePhysicsOutlineDataProvider)) { return new SpritePhysicsOutlineProvider { dataProvider = this } as T; } if (typeof(T) == typeof(ITextureDataProvider)) { return new TextureDataProvider { dataProvider = this } as T; } if (typeof(T) == typeof(ICharacterDataProvider)) { return characterMode ? new CharacterDataProvider { dataProvider = this } as T : null; } if (typeof(T) == typeof(ISpriteLibDataProvider)) { return new SpriteLibraryDataProvider() { dataProvider = this } as T; } if (typeof(T) == typeof(ISecondaryTextureDataProvider)) { return new SecondaryTextureDataProvider() { dataProvider = this } as T; } else return this as T; } T ISpriteEditorDataProvider.GetDataProvider() { return GetDataProvider(); } internal bool HasDataProvider(Type type) { if (characterMode && type == typeof(ICharacterDataProvider)) return true; if (type == typeof(ISpriteBoneDataProvider) || type == typeof(ISpriteMeshDataProvider) || type == typeof(ISpriteOutlineDataProvider) || type == typeof(ISpritePhysicsOutlineDataProvider) || type == typeof(ITextureDataProvider) || type == typeof(ISpriteLibDataProvider) || type == typeof(ISecondaryTextureDataProvider)) { return true; } else return type.IsAssignableFrom(GetType()); } bool ISpriteEditorDataProvider.HasDataProvider(Type type) { return HasDataProvider(type); } internal void AddSpriteData(SpriteRect spriteRect) { if (spriteImportMode != SpriteImportMode.Multiple) Debug.LogWarning("Can only add sprite data when import mode is multiple"); else { GetSpriteImportData().Add(new SpriteMetaData(spriteRect)); } } internal void DeleteSpriteData(SpriteRect spriteRect) { if (spriteImportMode != SpriteImportMode.Multiple) Debug.LogWarning("Can only add sprite data when import mode is multiple"); else { var spriteImportData = GetSpriteImportData(); int index = spriteImportData.FindIndex(x => x.spriteID == spriteRect.spriteID); Assert.AreEqual(0, index, "Cannot delete Sprite from single sprite mode"); spriteImportData.RemoveAt(index); } } internal int GetSpriteDataIndex(GUID guid) { switch (spriteImportMode) { case SpriteImportMode.Single: case SpriteImportMode.Polygon: return 0; case SpriteImportMode.Multiple: { var spriteImportData = GetSpriteImportData(); return spriteImportData.FindIndex(x => x.spriteID == guid); } default: throw new InvalidOperationException("GUID not found"); } } 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); } void ISpriteEditorDataProvider.Apply() { Apply(); } internal void InitSpriteEditorDataProvider() {} void ISpriteEditorDataProvider.InitSpriteEditorDataProvider() { InitSpriteEditorDataProvider(); } internal SpriteRect[] GetSpriteRects() { var spriteImportData = GetSpriteImportData(); var skip = mosaicMode ? 0 : 1; return spriteImportMode == SpriteImportMode.Multiple ? spriteImportData.Skip(skip).Select(x => new SpriteMetaData(x) as SpriteRect).ToArray() : new[] {new SpriteMetaData(spriteImportData[0]) }; } SpriteRect[] ISpriteEditorDataProvider.GetSpriteRects() { return GetSpriteRects(); } List GetSpriteImportData() { return mosaicMode ? (characterMode ? m_RigSpriteImportData : m_MosaicSpriteImportData) : m_SpriteImportData; } internal List GetPSDLayers() { return mosaicMode ? (characterMode ? m_RigPSDLayers : m_MosaicPSDLayers) : null; } internal SpriteMetaData[] GetSpriteMetaData() { var spriteImportData = GetSpriteImportData(); var skip = mosaicMode ? 0 : 1; return spriteImportMode == SpriteImportMode.Multiple ? spriteImportData.Skip(skip).ToArray() : new[] { new SpriteMetaData(spriteImportData[0]) }; } internal SpriteRect GetSpriteData(GUID guid) { var spriteImportData = GetSpriteImportData(); var skip = mosaicMode ? 0 : 1; return spriteImportMode == SpriteImportMode.Multiple ? spriteImportData.Skip(skip).FirstOrDefault(x => x.spriteID == guid) : spriteImportData[0]; } internal void SetSpriteRects(SpriteRect[] spriteRects) { var spriteImportData = GetSpriteImportData(); if (spriteImportMode == SpriteImportMode.Multiple) { var singleSpriteID = mosaicMode ? new GUID() : spriteImportData[0].spriteID; spriteImportData.RemoveAll(data => data.spriteID != singleSpriteID && spriteRects.FirstOrDefault(x => x.spriteID == data.spriteID) == null); foreach (var sr in spriteRects) { var importData = spriteImportData.FirstOrDefault(x => x.spriteID == sr.spriteID); if (importData == null) spriteImportData.Add(new SpriteMetaData(sr)); else { importData.name = sr.name; importData.alignment = sr.alignment; importData.border = sr.border; importData.pivot = sr.pivot; importData.rect = sr.rect; } } } else if (spriteRects.Length == 1 && (spriteImportMode == SpriteImportMode.Single || spriteImportMode == SpriteImportMode.Polygon)) { if (spriteImportData[0].spriteID == spriteRects[0].spriteID) { spriteImportData[0].name = spriteRects[0].name; spriteImportData[0].alignment = spriteRects[0].alignment; m_TextureImporterSettings.spriteAlignment = (int)spriteRects[0].alignment; m_TextureImporterSettings.spriteBorder = spriteImportData[0].border = spriteRects[0].border; m_TextureImporterSettings.spritePivot = spriteImportData[0].pivot = spriteRects[0].pivot; spriteImportData[0].rect = spriteRects[0].rect; } else { spriteImportData[0] = new SpriteMetaData(spriteRects[0]); } } } void ISpriteEditorDataProvider.SetSpriteRects(SpriteRect[] spriteRects) { SetSpriteRects(spriteRects); } bool mosaicMode { get { return spriteImportMode == SpriteImportMode.Multiple && m_MosaicLayers; } } internal CharacterData characterData { get { return m_CharacterData; } set { m_CharacterData = value; } } internal Vector2Int documentSize { get { return m_DocumentSize; } } SpriteLibraryAsset ProduceSpriteLibAsset(Sprite[] sprites) { if (!characterMode || m_SpriteCategoryList.categories == null) return null; var sla = ScriptableObject.CreateInstance(); sla.name = "Sprite Lib"; sla.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 Categorylabel() { name = y.name, sprite = sprite }; }).ToList() }).ToList(); sla.categories.RemoveAll(x => x.categoryList.Count == 0); if (sla.categories.Count > 0) { sla.UpdateHashes(); return sla; } return null; } internal SecondarySpriteTexture[] secondaryTextures { get { return m_SecondarySpriteTextures; } set { m_SecondarySpriteTextures = value; } } } }