private static SecondarySpriteTexture?CreateSecondarySpriteTexture(string dataAssetPath, SpritesheetMaterialData materialData) { if (materialData == null) { return(null); } string textureName; // Only a couple of roles are supported for secondary textures switch (materialData.MaterialRole) { case MaterialRole.Mask: textureName = "_MaskTex"; break; case MaterialRole.Normal: textureName = "_NormalMap"; break; default: Log($"Material role {materialData.MaterialRole} is unrecognized and the associated image won't be processed", LogLevel.Warning); return(null); } Log($"Material \"{materialData.name}\" with role {materialData.role} will have the secondary texture name \"{textureName}\"", LogLevel.Verbose); string secondaryTexturePath = Path.Combine(Path.GetDirectoryName(dataAssetPath), materialData.file); Texture2D secondaryTexture = AssetDatabase.LoadAssetAtPath <Texture2D>(secondaryTexturePath); if (secondaryTexture == null) { throw new InvalidOperationException($"Expected to find a secondary texture at asset path \"{secondaryTexturePath}\" based on .ssdata file at \"{dataAssetPath}\""); } return(new SecondarySpriteTexture { name = textureName, texture = secondaryTexture }); }
static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { List <string> processedSpritesheets = new List <string>(); var assets = importedAssets.Concat(movedAssets).ToList(); foreach (string inputAssetPath in assets) { string assetPath = inputAssetPath.Replace('/', '\\'); SpritesheetData spritesheetData; #region Find and load spritesheet data file // Regardless of whether an .ssdata file or an image file is being imported, we try to load the // spritesheet data and process everything it references, to keep all of the files in sync if (assetPath.EndsWith(".ssdata")) { spritesheetData = LoadSpritesheetDataFile(assetPath); } else { // Check if this is a texture which may have a .ssdata associated if (AssetDatabase.LoadAssetAtPath <Texture2D>(assetPath) == null) { continue; } spritesheetData = FindSpritesheetData(assetPath); } #endregion if (spritesheetData == null) { continue; } // When loading in several images that represent a texture and its secondary textures, we want to avoid // repeating work, so we only process each spritesheet data file once per import if (processedSpritesheets.Contains(spritesheetData.dataFilePath)) { Log($"Spritesheet at path \"{spritesheetData.dataFilePath} has already been processed in this import operation; skipping", LogLevel.Verbose); continue; } processedSpritesheets.Add(spritesheetData.dataFilePath); var spritesheetImporter = AssetImporter.GetAtPath(spritesheetData.dataFilePath) as SpritesheetDataImporter; if (spritesheetImporter == null) { Log($"SpritesheetDataImporter for asset \"{inputAssetPath}\" unavailable; it's probably running later in this import operation", LogLevel.Verbose); continue; } string successMessage = $"Import of assets referenced in \"{spritesheetData.dataFilePath}\" completed successfully. "; SliceSpritesheets(spritesheetData, spritesheetImporter); #region Set up secondary textures #if SECONDARY_TEXTURES_AVAILABLE if (spritesheetData.materialData.Count > 0) { SpritesheetMaterialData[] albedoMaterials = spritesheetData.materialData.Where(mat => mat.MaterialRole == MaterialRole.Albedo).ToArray(); SpritesheetMaterialData[] maskMaterials = spritesheetData.materialData.Where(mat => mat.MaterialRole == MaterialRole.Mask).ToArray(); SpritesheetMaterialData[] normalMaterials = spritesheetData.materialData.Where(mat => mat.MaterialRole == MaterialRole.Normal).ToArray(); SpritesheetMaterialData[] otherMaterials = spritesheetData.materialData.Where(mat => mat.MaterialRole == MaterialRole.UnassignedOrUnrecognized).ToArray(); #region Validate material data if (albedoMaterials.Length != 1) { throw new InvalidOperationException($"There should be exactly 1 albedo material; found {albedoMaterials.Length} for data file {spritesheetData.dataFilePath}"); } if (maskMaterials.Length > 1) { throw new InvalidOperationException($"There should be at most 1 mask material; found {maskMaterials.Length} for data file {spritesheetData.dataFilePath}"); } if (normalMaterials.Length > 1) { throw new InvalidOperationException($"There should be at most 1 normal material; found {normalMaterials.Length} for data file {spritesheetData.dataFilePath}"); } if (otherMaterials.Length > 0) { Log($"Data file {spritesheetData.dataFilePath} references {otherMaterials.Length} materials of unknown purpose. These will need to be configured manually.", LogLevel.Warning); } #endregion SpritesheetMaterialData albedo = albedoMaterials[0]; SpritesheetMaterialData mask = maskMaterials.Length > 0 ? maskMaterials[0] : null; SpritesheetMaterialData normal = normalMaterials.Length > 0 ? normalMaterials[0] : null; Log($"Asset has mask material texture: {mask != null}", LogLevel.Verbose); Log($"Asset has normal material texture: {normal != null}", LogLevel.Verbose); #region Create secondary textures List <SecondarySpriteTexture> secondarySpriteTextures = new List <SecondarySpriteTexture>(); if (mask != null) { secondarySpriteTextures.Add(CreateSecondarySpriteTexture(spritesheetData.dataFilePath, mask).Value); } if (normal != null) { secondarySpriteTextures.Add(CreateSecondarySpriteTexture(spritesheetData.dataFilePath, normal).Value); } #endregion // Secondary textures are handled through the TextureImporter, and making changes to the importer during OnPostprocessAllAssets // results in them being applied the next time the asset is imported. We therefore have to trigger a reimport ourselves, but // being careful only to do so if something has changed, or else we'll get stuck in an infinite import loop. string albedoTextureAssetPath = Path.Combine(Path.GetDirectoryName(spritesheetData.dataFilePath), albedo.file); TextureImporter albedoTextureImporter = AssetImporter.GetAtPath(albedoTextureAssetPath) as TextureImporter; bool importSettingsChanged = false; #region Check for changes in import settings if (secondarySpriteTextures.Count != albedoTextureImporter.secondarySpriteTextures.Length) { importSettingsChanged = true; } else { // Compare each element between the two sets pairwise. We always import in a consistent // order so we don't need to worry about that. for (int i = 0; i < secondarySpriteTextures.Count; i++) { var newSecondaryTexture = secondarySpriteTextures[i]; var oldSecondaryTexture = albedoTextureImporter.secondarySpriteTextures[i]; string newAssetPath = AssetDatabase.GetAssetPath(newSecondaryTexture.texture); string oldAssetPath = AssetDatabase.GetAssetPath(oldSecondaryTexture.texture); importSettingsChanged = importSettingsChanged || (newSecondaryTexture.name != oldSecondaryTexture.name) || (newAssetPath != oldAssetPath); } } #endregion if (importSettingsChanged) { Log("A change has occurred in the secondary textures; triggering a reimport", LogLevel.Verbose); albedoTextureImporter.secondarySpriteTextures = secondarySpriteTextures.ToArray(); albedoTextureImporter.SaveAndReimport(); } successMessage += $"Configured {secondarySpriteTextures.Count} secondary textures. "; } #endif #endregion #region Create/update animations if (spritesheetImporter.createAnimations && spritesheetData.animations.Count > 0) { Log($"Going to create or replace {spritesheetData.animations.Count} animation clips for data file at \"{spritesheetData.dataFilePath}\"", LogLevel.Verbose); string mainImageAssetPath = GetMainImagePath(spritesheetData); string assetDirectory = Path.GetDirectoryName(mainImageAssetPath); UnityEngine.Object[] sprites = AssetDatabase.LoadAllAssetRepresentationsAtPath(mainImageAssetPath); Log($"Loaded {sprites.Length} sprites from main image asset path \"{mainImageAssetPath}\"", LogLevel.Verbose); foreach (var animationData in spritesheetData.animations) { bool multipleAnimationsFromSameSource = spritesheetData.animations.Where(anim => anim.name == animationData.name).Count() > 1; // Only include rotation in name if needed to disambiguate string rotation = multipleAnimationsFromSameSource ? "_rot" + animationData.rotation.ToString().PadLeft(3, '0') : ""; string clipName = FormatAssetName(spritesheetData.baseObjectName + "_" + animationData.name + rotation + ".anim"); string clipPath = assetDirectory; if (multipleAnimationsFromSameSource && spritesheetImporter.placeAnimationsInSubfolders) { string subfolderName = SpritesheetImporterSettings.animationSubfolderNameFormat.value .Replace("{anim}", FormatAssetName(animationData.name)) .Replace("{obj}", FormatAssetName(spritesheetData.baseObjectName)); clipPath = Path.Combine(clipPath, subfolderName); Log($"Creating subfolder at \"{clipPath}\" to contain animations named \"{animationData.name}\"", LogLevel.Verbose); Directory.CreateDirectory(clipPath); } clipPath = Path.Combine(clipPath, clipName); AnimationClip clip = CreateAnimationClip(spritesheetData, animationData, sprites); Log($"Animation clip saving at path \"{clipPath}\"", LogLevel.Verbose); AssetDatabase.CreateAsset(clip, clipPath); } Log("Done creating/replacing animation assets; now saving asset database", LogLevel.Verbose); AssetDatabase.SaveAssets(); successMessage += $"Created or updated {spritesheetData.animations.Count} animation clips. "; } #endregion Log(successMessage, LogLevel.Info); } }