963 lines
38 KiB
C#
963 lines
38 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Unity.Collections;
|
|
using Unity.Mathematics;
|
|
using UnityEngine;
|
|
using UnityEditor.AssetImporters;
|
|
using UnityEditor.U2D.Aseprite.Common;
|
|
using UnityEditor.U2D.Sprites;
|
|
using UnityEngine.Serialization;
|
|
|
|
namespace UnityEditor.U2D.Aseprite
|
|
{
|
|
/// <summary>
|
|
/// ScriptedImporter to import Aseprite files
|
|
/// </summary>
|
|
// Version using unity release + 5 digit padding for future upgrade. Eg 2021.2 -> 21200000
|
|
[ScriptedImporter(21300003, new string[] { "aseprite", "ase" }, AllowCaching = true)]
|
|
[HelpURL("https://docs.unity3d.com/Packages/com.unity.2d.aseprite@latest")]
|
|
public partial class AsepriteImporter : ScriptedImporter, ISpriteEditorDataProvider
|
|
{
|
|
[SerializeField]
|
|
TextureImporterSettings m_TextureImporterSettings = new TextureImporterSettings()
|
|
{
|
|
mipmapEnabled = false,
|
|
mipmapFilter = TextureImporterMipFilter.BoxFilter,
|
|
sRGBTexture = true,
|
|
borderMipmap = false,
|
|
mipMapsPreserveCoverage = false,
|
|
alphaTestReferenceValue = 0.5f,
|
|
readable = false,
|
|
|
|
#if ENABLE_TEXTURE_STREAMING
|
|
streamingMipmaps = false,
|
|
streamingMipmapsPriority = 0,
|
|
#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.Point,
|
|
aniso = 1,
|
|
mipmapBias = 0.0f,
|
|
wrapModeU = TextureWrapMode.Clamp,
|
|
wrapModeV = TextureWrapMode.Clamp,
|
|
wrapModeW = TextureWrapMode.Clamp,
|
|
};
|
|
|
|
|
|
[SerializeField] AsepriteImporterSettings m_PreviousAsepriteImporterSettings;
|
|
[SerializeField]
|
|
AsepriteImporterSettings m_AsepriteImporterSettings = new AsepriteImporterSettings()
|
|
{
|
|
fileImportMode = FileImportModes.AnimatedSprite,
|
|
importHiddenLayers = false,
|
|
layerImportMode = LayerImportModes.MergeFrame,
|
|
defaultPivotAlignment = SpriteAlignment.BottomCenter,
|
|
defaultPivotSpace = PivotSpaces.Canvas,
|
|
customPivotPosition = new Vector2(0.5f, 0.5f),
|
|
mosaicPadding = 4,
|
|
spritePadding = 0,
|
|
generateAnimationClips = true,
|
|
generateModelPrefab = true,
|
|
addSortingGroup = true,
|
|
addShadowCasters = false
|
|
};
|
|
|
|
// 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] string m_TextureAssetName = null;
|
|
|
|
[SerializeField] List<SpriteMetaData> m_SingleSpriteImportData = new List<SpriteMetaData>(1) { new SpriteMetaData() };
|
|
[FormerlySerializedAs("m_MultiSpriteImportData")]
|
|
[SerializeField] List<SpriteMetaData> m_AnimatedSpriteImportData = new List<SpriteMetaData>();
|
|
[SerializeField] List<SpriteMetaData> m_SpriteSheetImportData = new List<SpriteMetaData>();
|
|
|
|
[SerializeField] List<Layer> m_AsepriteLayers = new List<Layer>();
|
|
|
|
[SerializeField] List<TextureImporterPlatformSettings> m_PlatformSettings = new()
|
|
{
|
|
TextureImporterPlatformUtilities.defaultPlatformSettings.Clone()
|
|
};
|
|
|
|
[SerializeField] bool m_GeneratePhysicsShape = false;
|
|
[SerializeField] SecondarySpriteTexture[] m_SecondarySpriteTextures;
|
|
[SerializeField] string m_SpritePackingTag = "";
|
|
|
|
SpriteImportMode spriteImportModeToUse => m_TextureImporterSettings.textureType != TextureImporterType.Sprite ?
|
|
SpriteImportMode.None : (SpriteImportMode)m_TextureImporterSettings.spriteMode;
|
|
|
|
AsepriteImportData m_ImportData;
|
|
AsepriteFile m_AsepriteFile;
|
|
List<Tag> m_Tags = new List<Tag>();
|
|
List<Frame> m_Frames = new List<Frame>();
|
|
|
|
[SerializeField] Vector2Int m_CanvasSize;
|
|
|
|
GameObject m_RootGameObject;
|
|
readonly Dictionary<int, GameObject> m_LayerIdToGameObject = new Dictionary<int, GameObject>();
|
|
|
|
AsepriteImportData importData
|
|
{
|
|
get
|
|
{
|
|
var returnValue = m_ImportData;
|
|
if (returnValue == null)
|
|
{
|
|
// Using LoadAllAssetsAtPath because AsepriteImportData is hidden
|
|
var assets = AssetDatabase.LoadAllAssetsAtPath(assetPath);
|
|
foreach (var asset in assets)
|
|
{
|
|
if (asset is AsepriteImportData data)
|
|
returnValue = data;
|
|
}
|
|
}
|
|
|
|
|
|
if (returnValue == null)
|
|
returnValue = ScriptableObject.CreateInstance<AsepriteImportData>();
|
|
|
|
m_ImportData = returnValue;
|
|
return returnValue;
|
|
}
|
|
}
|
|
|
|
internal bool isNPOT => Mathf.IsPowerOfTwo(importData.textureActualWidth) && Mathf.IsPowerOfTwo(importData.textureActualHeight);
|
|
|
|
internal int textureActualWidth
|
|
{
|
|
get => importData.textureActualWidth;
|
|
private set => importData.textureActualWidth = value;
|
|
}
|
|
|
|
internal int textureActualHeight
|
|
{
|
|
get => importData.textureActualHeight;
|
|
private set => importData.textureActualHeight = value;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void OnImportAsset(AssetImportContext ctx)
|
|
{
|
|
if (m_ImportData == null)
|
|
m_ImportData = ScriptableObject.CreateInstance<AsepriteImportData>();
|
|
m_ImportData.hideFlags = HideFlags.HideInHierarchy;
|
|
|
|
try
|
|
{
|
|
var isSuccessful = ParseAsepriteFile(ctx.assetPath);
|
|
if (!isSuccessful)
|
|
return;
|
|
|
|
var layersFromFile = FetchLayersFromFile(asepriteFile, m_CanvasSize, includeHiddenLayers, layerImportMode == LayerImportModes.MergeFrame);
|
|
|
|
FetchImageDataFromLayers(ref layersFromFile, out var imageBuffers, out var imageSizes);
|
|
|
|
var mosaicPad = m_AsepriteImporterSettings.mosaicPadding;
|
|
var spritePad = m_AsepriteImporterSettings.fileImportMode == FileImportModes.AnimatedSprite ? m_AsepriteImporterSettings.spritePadding : 0;
|
|
var requireSquarePotTexture = IsRequiringSquarePotTexture(ctx);
|
|
ImagePacker.Pack(imageBuffers.ToArray(), imageSizes.ToArray(), (int)mosaicPad, spritePad, requireSquarePotTexture, out var outputImageBuffer, out var packedTextureWidth, out var packedTextureHeight, out var spriteRects, out var uvTransforms);
|
|
|
|
var packOffsets = new Vector2Int[spriteRects.Length];
|
|
for (var i = 0; i < packOffsets.Length; ++i)
|
|
{
|
|
packOffsets[i] = new Vector2Int(uvTransforms[i].x - spriteRects[i].position.x, uvTransforms[i].y - spriteRects[i].position.y);
|
|
packOffsets[i] *= -1;
|
|
}
|
|
|
|
SpriteMetaData[] spriteImportData;
|
|
if (m_AsepriteImporterSettings.fileImportMode == FileImportModes.SpriteSheet)
|
|
spriteImportData = GetSpriteImportData().ToArray();
|
|
else
|
|
{
|
|
CellTasks.GetCellsFromLayers(m_AsepriteLayers, out var cells);
|
|
|
|
var newSpriteMeta = new List<SpriteMetaData>(cells.Count);
|
|
|
|
// Create SpriteMetaData for each cell
|
|
var importedRectsHaveChanged = false;
|
|
for (var i = 0; i < cells.Count; ++i)
|
|
{
|
|
var cell = cells[i];
|
|
var dataIndex = newSpriteMeta.Count;
|
|
var spriteData = CreateNewSpriteMetaData(
|
|
cell.name,
|
|
cell.spriteId,
|
|
cell.cellRect.position,
|
|
in spriteRects[dataIndex],
|
|
in packOffsets[dataIndex],
|
|
in uvTransforms[dataIndex]);
|
|
newSpriteMeta.Add(spriteData);
|
|
|
|
if (cell.updatedCellRect)
|
|
importedRectsHaveChanged = true;
|
|
}
|
|
|
|
spriteImportData = UpdateSpriteImportData(newSpriteMeta, spriteRects, uvTransforms, importedRectsHaveChanged);
|
|
}
|
|
|
|
var output = TextureGeneration.Generate(
|
|
ctx,
|
|
outputImageBuffer,
|
|
packedTextureWidth,
|
|
packedTextureHeight,
|
|
spriteImportData,
|
|
m_PlatformSettings,
|
|
in m_TextureImporterSettings,
|
|
m_SpritePackingTag,
|
|
secondaryTextures);
|
|
|
|
textureActualHeight = packedTextureHeight;
|
|
textureActualWidth = packedTextureWidth;
|
|
|
|
if (output.texture)
|
|
{
|
|
importData.importedTextureHeight = output.texture.height;
|
|
importData.importedTextureWidth = output.texture.width;
|
|
}
|
|
else
|
|
{
|
|
importData.importedTextureHeight = textureActualHeight;
|
|
importData.importedTextureWidth = textureActualWidth;
|
|
}
|
|
|
|
if (output.texture != null && output.sprites != null)
|
|
SetPhysicsOutline(GetDataProvider<ISpritePhysicsOutlineDataProvider>(), output.sprites, definitionScale, pixelsPerUnit, m_GeneratePhysicsShape);
|
|
|
|
RegisterAssets(ctx, output);
|
|
OnPostAsepriteImport?.Invoke(new ImportEventArgs(this, ctx));
|
|
|
|
outputImageBuffer.DisposeIfCreated();
|
|
foreach (var cellBuffer in imageBuffers)
|
|
cellBuffer.DisposeIfCreated();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError($"Failed to import file {assetPath}. Error: {e.Message} \n{e.StackTrace}");
|
|
}
|
|
finally
|
|
{
|
|
m_PreviousAsepriteImporterSettings = m_AsepriteImporterSettings;
|
|
EditorUtility.SetDirty(this);
|
|
m_AsepriteFile?.Dispose();
|
|
}
|
|
}
|
|
|
|
bool ParseAsepriteFile(string path)
|
|
{
|
|
m_AsepriteFile = AsepriteReader.ReadFile(path);
|
|
if (m_AsepriteFile == null)
|
|
return false;
|
|
|
|
m_CanvasSize = new Vector2Int(m_AsepriteFile.width, m_AsepriteFile.height);
|
|
|
|
m_Frames = ExtractFrameData(in m_AsepriteFile);
|
|
m_Tags = ExtractTagsData(in m_AsepriteFile);
|
|
|
|
return true;
|
|
}
|
|
|
|
static List<Layer> FetchLayersFromFile(in AsepriteFile asepriteFile, Vector2Int canvasSize, bool includeHiddenLayers, bool isMerged)
|
|
{
|
|
var newLayers = RestructureLayerAndCellData(in asepriteFile, canvasSize);
|
|
FilterOutLayers(newLayers, includeHiddenLayers);
|
|
UpdateCellNames(newLayers, isMerged);
|
|
return newLayers;
|
|
}
|
|
|
|
static List<Layer> RestructureLayerAndCellData(in AsepriteFile file, Vector2Int canvasSize)
|
|
{
|
|
var frameData = file.frameData;
|
|
|
|
var nameGenerator = new UniqueNameGenerator();
|
|
var layers = new List<Layer>();
|
|
var parentTable = new Dictionary<int, Layer>();
|
|
for (var i = 0; i < frameData.Count; ++i)
|
|
{
|
|
var chunks = frameData[i].chunks;
|
|
for (var m = 0; m < chunks.Count; ++m)
|
|
{
|
|
if (chunks[m].chunkType == ChunkTypes.Layer)
|
|
{
|
|
var layerChunk = chunks[m] as LayerChunk;
|
|
|
|
var layer = new Layer();
|
|
var childLevel = layerChunk.childLevel;
|
|
parentTable[childLevel] = layer;
|
|
|
|
layer.parentIndex = childLevel == 0 ? -1 : parentTable[childLevel - 1].index;
|
|
|
|
layer.name = nameGenerator.GetUniqueName(layerChunk.name, layer.parentIndex);
|
|
layer.layerFlags = layerChunk.flags;
|
|
layer.layerType = layerChunk.layerType;
|
|
layer.blendMode = layerChunk.blendMode;
|
|
layer.opacity = layerChunk.opacity / 255f;
|
|
layer.index = layers.Count;
|
|
layer.guid = Layer.GenerateGuid(layer);
|
|
|
|
layers.Add(layer);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (var i = 0; i < frameData.Count; ++i)
|
|
{
|
|
var chunks = frameData[i].chunks;
|
|
for (var m = 0; m < chunks.Count; ++m)
|
|
{
|
|
if (chunks[m].chunkType == ChunkTypes.Cell)
|
|
{
|
|
var cellChunk = chunks[m] as CellChunk;
|
|
var layer = layers.Find(x => x.index == cellChunk.layerIndex);
|
|
if (layer == null)
|
|
{
|
|
Debug.LogWarning($"Could not find the layer for one of the cells. Frame Index={i}, Chunk Index={m}.");
|
|
continue;
|
|
}
|
|
|
|
var cellType = cellChunk.cellType;
|
|
if (cellType == CellTypes.LinkedCell)
|
|
{
|
|
var cell = new LinkedCell();
|
|
cell.frameIndex = i;
|
|
cell.linkedToFrame = cellChunk.linkedToFrame;
|
|
layer.linkedCells.Add(cell);
|
|
}
|
|
else
|
|
{
|
|
var cell = new Cell();
|
|
cell.frameIndex = i;
|
|
cell.updatedCellRect = false;
|
|
|
|
// Flip Y. Aseprite 0,0 is at Top Left. Unity 0,0 is at Bottom Left.
|
|
var cellY = (canvasSize.y - cellChunk.posY) - cellChunk.height;
|
|
cell.cellRect = new RectInt(cellChunk.posX, cellY, cellChunk.width, cellChunk.height);
|
|
cell.opacity = cellChunk.opacity / 255f;
|
|
cell.blendMode = layer.blendMode;
|
|
cell.image = cellChunk.image;
|
|
cell.additiveSortOrder = cellChunk.zIndex;
|
|
cell.name = layer.name;
|
|
cell.spriteId = GUID.Generate();
|
|
|
|
var opacity = cell.opacity * layer.opacity;
|
|
if ((1f - opacity) > Mathf.Epsilon)
|
|
TextureTasks.AddOpacity(ref cell.image, opacity);
|
|
|
|
layer.cells.Add(cell);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return layers;
|
|
}
|
|
|
|
static void FilterOutLayers(List<Layer> layers, bool includeHiddenLayers)
|
|
{
|
|
for (var i = layers.Count - 1; i >= 0; --i)
|
|
{
|
|
var layer = layers[i];
|
|
if (!includeHiddenLayers && !ImportUtilities.IsLayerVisible(layer.index, layers))
|
|
{
|
|
DisposeCellsInLayer(layer);
|
|
layers.RemoveAt(i);
|
|
continue;
|
|
}
|
|
|
|
var cells = layer.cells;
|
|
for (var m = cells.Count - 1; m >= 0; --m)
|
|
{
|
|
var width = cells[m].cellRect.width;
|
|
var height = cells[m].cellRect.width;
|
|
if (width == 0 || height == 0)
|
|
cells.RemoveAt(m);
|
|
else if (cells[m].image == default || !cells[m].image.IsCreated)
|
|
cells.RemoveAt(m);
|
|
else if (ImportUtilities.IsEmptyImage(cells[m].image))
|
|
cells.RemoveAt(m);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void DisposeCellsInLayer(Layer layer)
|
|
{
|
|
foreach (var cell in layer.cells)
|
|
{
|
|
var image = cell.image;
|
|
image.DisposeIfCreated();
|
|
}
|
|
}
|
|
|
|
static void UpdateCellNames(List<Layer> layers, bool isMerged)
|
|
{
|
|
for (var i = 0; i < layers.Count; ++i)
|
|
{
|
|
var cells = layers[i].cells;
|
|
for (var m = 0; m < cells.Count; ++m)
|
|
{
|
|
var cell = cells[m];
|
|
cell.name = ImportUtilities.GetCellName(cell.name, cell.frameIndex, cells.Count, isMerged);
|
|
cells[m] = cell;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FetchImageDataFromLayers(ref List<Layer> newLayers, out List<NativeArray<Color32>> imageBuffers, out List<int2> imageSizes)
|
|
{
|
|
if (layerImportMode == LayerImportModes.IndividualLayers)
|
|
{
|
|
m_AsepriteLayers = UpdateLayers(newLayers, m_AsepriteLayers, true);
|
|
|
|
CellTasks.GetCellsFromLayers(m_AsepriteLayers, out var cells);
|
|
CellTasks.CollectDataFromCells(cells, out imageBuffers, out imageSizes);
|
|
CellTasks.FlipCellBuffers(ref imageBuffers, imageSizes);
|
|
}
|
|
else
|
|
{
|
|
var assetName = System.IO.Path.GetFileNameWithoutExtension(assetPath);
|
|
ImportMergedLayers.Import(assetName, ref newLayers, out imageBuffers, out imageSizes);
|
|
|
|
// Update layers after merged, since merged import creates new layers.
|
|
// The new layers should be compared and merged together with the ones existing in the meta file.
|
|
m_AsepriteLayers = UpdateLayers(newLayers, m_AsepriteLayers, false);
|
|
}
|
|
}
|
|
|
|
static List<Layer> UpdateLayers(List<Layer> newLayers, List<Layer> oldLayers, bool isIndividual)
|
|
{
|
|
if (oldLayers.Count == 0)
|
|
return new List<Layer>(newLayers);
|
|
|
|
var finalLayers = new List<Layer>(oldLayers);
|
|
|
|
if (isIndividual)
|
|
{
|
|
// Remove old layers
|
|
for (var i = 0; i < oldLayers.Count; ++i)
|
|
{
|
|
var oldLayer = oldLayers[i];
|
|
if (newLayers.FindIndex(x => x.guid == oldLayer.guid) == -1)
|
|
finalLayers.Remove(oldLayer);
|
|
}
|
|
|
|
// Add new layers
|
|
for (var i = 0; i < newLayers.Count; ++i)
|
|
{
|
|
var newLayer = newLayers[i];
|
|
var layerIndex = finalLayers.FindIndex(x => x.guid == newLayer.guid);
|
|
if (layerIndex == -1)
|
|
finalLayers.Add(newLayer);
|
|
}
|
|
}
|
|
|
|
// Update layer data
|
|
for (var i = 0; i < finalLayers.Count; ++i)
|
|
{
|
|
var finalLayer = finalLayers[i];
|
|
var layerIndex = isIndividual ? newLayers.FindIndex(x => x.guid == finalLayer.guid) : 0;
|
|
if (layerIndex != -1)
|
|
{
|
|
var oldCells = finalLayer.cells;
|
|
var newCells = newLayers[layerIndex].cells;
|
|
for (var m = 0; m < newCells.Count; ++m)
|
|
{
|
|
if (m < oldCells.Count)
|
|
{
|
|
var oldCell = oldCells[m];
|
|
var newCell = newCells[m];
|
|
newCell.spriteId = oldCell.spriteId;
|
|
#if UNITY_2023_1_OR_NEWER
|
|
newCell.updatedCellRect = newCell.cellRect != oldCell.cellRect;
|
|
#else
|
|
newCell.updatedCellRect = !newCell.cellRect.IsEqual(oldCell.cellRect);
|
|
#endif
|
|
newCells[m] = newCell;
|
|
}
|
|
}
|
|
finalLayer.cells = new List<Cell>(newCells);
|
|
finalLayer.linkedCells = new List<LinkedCell>(newLayers[layerIndex].linkedCells);
|
|
finalLayer.index = newLayers[layerIndex].index;
|
|
finalLayer.opacity = newLayers[layerIndex].opacity;
|
|
finalLayer.parentIndex = newLayers[layerIndex].parentIndex;
|
|
}
|
|
}
|
|
|
|
return finalLayers;
|
|
}
|
|
|
|
bool IsRequiringSquarePotTexture(AssetImportContext ctx)
|
|
{
|
|
var platformSettings = TextureImporterPlatformUtilities.GetPlatformTextureSettings(ctx.selectedBuildTarget, m_PlatformSettings);
|
|
return (TextureImporterFormat.PVRTC_RGB2 <= platformSettings.format && platformSettings.format <= TextureImporterFormat.PVRTC_RGBA4);
|
|
}
|
|
|
|
static List<Frame> ExtractFrameData(in AsepriteFile file)
|
|
{
|
|
var noOfFrames = file.noOfFrames;
|
|
var frames = new List<Frame>(noOfFrames);
|
|
for (var i = 0; i < noOfFrames; ++i)
|
|
{
|
|
var frameData = file.frameData[i];
|
|
var eventStrings = ExtractEventStringFromCells(frameData);
|
|
|
|
var frame = new Frame()
|
|
{
|
|
duration = frameData.frameDuration,
|
|
eventStrings = eventStrings
|
|
};
|
|
frames.Add(frame);
|
|
}
|
|
|
|
return frames;
|
|
}
|
|
|
|
static string[] ExtractEventStringFromCells(FrameData frameData)
|
|
{
|
|
var chunks = frameData.chunks;
|
|
var eventStrings = new HashSet<string>();
|
|
for (var i = 0; i < chunks.Count; ++i)
|
|
{
|
|
if (chunks[i].chunkType != ChunkTypes.Cell)
|
|
continue;
|
|
var cellChunk = (CellChunk)chunks[i];
|
|
if (cellChunk.dataChunk == null)
|
|
continue;
|
|
var dataText = cellChunk.dataChunk.text;
|
|
if (string.IsNullOrEmpty(dataText) || !dataText.StartsWith("event:"))
|
|
continue;
|
|
var eventString = dataText.Remove(0, "event:".Length);
|
|
eventString = eventString.Trim(' ');
|
|
eventStrings.Add(eventString);
|
|
}
|
|
|
|
var stringArr = new string[eventStrings.Count];
|
|
eventStrings.CopyTo(stringArr);
|
|
return stringArr;
|
|
}
|
|
|
|
static List<Tag> ExtractTagsData(in AsepriteFile file)
|
|
{
|
|
var tags = new List<Tag>();
|
|
|
|
var noOfFrames = file.noOfFrames;
|
|
for (var i = 0; i < noOfFrames; ++i)
|
|
{
|
|
var frame = file.frameData[i];
|
|
var noOfChunks = frame.chunkCount;
|
|
for (var m = 0; m < noOfChunks; ++m)
|
|
{
|
|
var chunk = frame.chunks[m];
|
|
if (chunk.chunkType != ChunkTypes.Tags)
|
|
continue;
|
|
|
|
var tagChunk = chunk as TagsChunk;
|
|
var noOfTags = tagChunk.noOfTags;
|
|
for (var n = 0; n < noOfTags; ++n)
|
|
{
|
|
var data = tagChunk.tagData[n];
|
|
var tag = new Tag();
|
|
tag.name = data.name;
|
|
tag.noOfRepeats = data.noOfRepeats;
|
|
tag.fromFrame = data.fromFrame;
|
|
// Adding one more frame as Aseprite's tags seems to always be 1 short.
|
|
tag.toFrame = data.toFrame + 1;
|
|
|
|
tags.Add(tag);
|
|
}
|
|
}
|
|
}
|
|
|
|
return tags;
|
|
}
|
|
|
|
SpriteMetaData[] UpdateSpriteImportData(IReadOnlyList<SpriteMetaData> newSpriteMeta, IReadOnlyList<RectInt> spriteRects, IReadOnlyList<Vector2Int> uvTransforms, bool importedRectsHaveChanged)
|
|
{
|
|
var finalSpriteMeta = GetSpriteImportData();
|
|
if (finalSpriteMeta.Count <= 0)
|
|
{
|
|
finalSpriteMeta.Clear();
|
|
finalSpriteMeta.AddRange(newSpriteMeta);
|
|
}
|
|
else
|
|
{
|
|
// Remove old SpriteMeta.
|
|
for (var i = finalSpriteMeta.Count - 1; i >= 0; --i)
|
|
{
|
|
var spriteData = finalSpriteMeta[i];
|
|
if (newSpriteMeta.FindIndex(x => x.spriteID == spriteData.spriteID) == -1)
|
|
finalSpriteMeta.Remove(spriteData);
|
|
}
|
|
|
|
// Add new SpriteMeta.
|
|
for (var i = 0; i < newSpriteMeta.Count; ++i)
|
|
{
|
|
var newMeta = newSpriteMeta[i];
|
|
if (finalSpriteMeta.FindIndex(x => x.spriteID == newMeta.spriteID) == -1)
|
|
finalSpriteMeta.Add(newMeta);
|
|
}
|
|
|
|
// Update with new pack data
|
|
for (var i = 0; i < newSpriteMeta.Count; ++i)
|
|
{
|
|
var newMeta = newSpriteMeta[i];
|
|
var finalMeta = finalSpriteMeta.Find(x => x.spriteID == newMeta.spriteID);
|
|
if (finalMeta != null)
|
|
{
|
|
if (AreSettingsUpdated() || importedRectsHaveChanged)
|
|
{
|
|
finalMeta.alignment = newMeta.alignment;
|
|
finalMeta.pivot = newMeta.pivot;
|
|
}
|
|
|
|
finalMeta.rect = new Rect(spriteRects[i].x, spriteRects[i].y, spriteRects[i].width, spriteRects[i].height);
|
|
finalMeta.uvTransform = uvTransforms[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
return finalSpriteMeta.ToArray();
|
|
}
|
|
|
|
bool AreSettingsUpdated()
|
|
{
|
|
return !m_PreviousAsepriteImporterSettings.IsDefault() &&
|
|
(pivotAlignment != m_PreviousAsepriteImporterSettings.defaultPivotAlignment ||
|
|
pivotSpace != m_PreviousAsepriteImporterSettings.defaultPivotSpace ||
|
|
customPivotPosition != m_PreviousAsepriteImporterSettings.customPivotPosition ||
|
|
spritePadding != m_PreviousAsepriteImporterSettings.spritePadding);
|
|
}
|
|
|
|
SpriteMetaData CreateNewSpriteMetaData(
|
|
in string spriteName,
|
|
in GUID spriteID,
|
|
in Vector2Int position,
|
|
in RectInt spriteRect,
|
|
in Vector2Int packOffset,
|
|
in Vector2Int uvTransform)
|
|
{
|
|
var spriteData = new SpriteMetaData();
|
|
spriteData.border = Vector4.zero;
|
|
|
|
if (pivotSpace == PivotSpaces.Canvas)
|
|
{
|
|
spriteData.alignment = SpriteAlignment.Custom;
|
|
|
|
var cellRect = new RectInt(position.x, position.y, spriteRect.width, spriteRect.height);
|
|
cellRect.x += packOffset.x;
|
|
cellRect.y += packOffset.y;
|
|
|
|
spriteData.pivot = ImportUtilities.CalculateCellPivot(cellRect, spritePadding, m_CanvasSize, pivotAlignment, customPivotPosition);
|
|
}
|
|
else
|
|
{
|
|
spriteData.alignment = pivotAlignment;
|
|
spriteData.pivot = customPivotPosition;
|
|
}
|
|
|
|
spriteData.rect = new Rect(spriteRect.x, spriteRect.y, spriteRect.width, spriteRect.height);
|
|
spriteData.spriteID = spriteID;
|
|
spriteData.name = spriteName;
|
|
spriteData.uvTransform = uvTransform;
|
|
return spriteData;
|
|
}
|
|
|
|
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 generated = false;
|
|
if ((outline == null || outline.Count == 0) && generatePhysicsShape)
|
|
{
|
|
InternalEditorBridge.GenerateOutlineFromSprite(sprite, 0.25f, 200, true, out var defaultOutline);
|
|
outline = new List<Vector2[]>(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;
|
|
|
|
var outlineOffset = Vector2.zero;
|
|
outlineOffset.x = sprite.rect.width * 0.5f;
|
|
outlineOffset.y = sprite.rect.height * 0.5f;
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RegisterAssets(AssetImportContext ctx, TextureGenerationOutput output)
|
|
{
|
|
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), -1, true, this);
|
|
UnityEngine.Object mainAsset = null;
|
|
|
|
RegisterTextureAsset(ctx, output, assetName, ref mainAsset);
|
|
RegisterSprites(ctx, output, assetNameGenerator);
|
|
RegisterGameObjects(ctx, output, ref mainAsset);
|
|
RegisterAnimationClip(ctx, assetName, output);
|
|
RegisterAnimatorController(ctx, assetName);
|
|
|
|
ctx.AddObjectToAsset("AsepriteImportData", m_ImportData);
|
|
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;
|
|
}
|
|
|
|
static void RegisterSprites(AssetImportContext ctx, TextureGenerationOutput output, UniqueNameGenerator assetNameGenerator)
|
|
{
|
|
if (output.sprites == null)
|
|
return;
|
|
|
|
foreach (var sprite in output.sprites)
|
|
{
|
|
var spriteGuid = sprite.GetSpriteID().ToString();
|
|
var spriteAssetName = assetNameGenerator.GetUniqueName(spriteGuid, -1, false, sprite);
|
|
ctx.AddObjectToAsset(spriteAssetName, sprite);
|
|
}
|
|
}
|
|
|
|
void RegisterGameObjects(AssetImportContext ctx, TextureGenerationOutput output, ref UnityEngine.Object mainAsset)
|
|
{
|
|
if (output.sprites.Length == 0)
|
|
return;
|
|
if (m_AsepriteImporterSettings.fileImportMode != FileImportModes.AnimatedSprite)
|
|
return;
|
|
|
|
PrefabGeneration.Generate(
|
|
ctx,
|
|
output,
|
|
m_AsepriteLayers,
|
|
m_LayerIdToGameObject,
|
|
m_CanvasSize,
|
|
m_AsepriteImporterSettings,
|
|
ref mainAsset,
|
|
out m_RootGameObject);
|
|
}
|
|
|
|
void RegisterAnimationClip(AssetImportContext ctx, string assetName, TextureGenerationOutput output)
|
|
{
|
|
if (output.sprites.Length == 0)
|
|
return;
|
|
if (m_AsepriteImporterSettings.fileImportMode != FileImportModes.AnimatedSprite)
|
|
return;
|
|
if (!generateAnimationClips)
|
|
return;
|
|
var noOfFrames = m_AsepriteFile.noOfFrames;
|
|
if (noOfFrames == 1)
|
|
return;
|
|
|
|
var sprites = output.sprites;
|
|
var clips = AnimationClipGeneration.Generate(
|
|
assetName,
|
|
sprites,
|
|
m_AsepriteFile,
|
|
m_AsepriteLayers,
|
|
m_Frames,
|
|
m_Tags,
|
|
m_LayerIdToGameObject);
|
|
|
|
for (var i = 0; i < clips.Length; ++i)
|
|
ctx.AddObjectToAsset(clips[i].name, clips[i]);
|
|
}
|
|
|
|
void RegisterAnimatorController(AssetImportContext ctx, string assetName)
|
|
{
|
|
if (m_AsepriteImporterSettings.fileImportMode != FileImportModes.AnimatedSprite)
|
|
return;
|
|
|
|
AnimatorControllerGeneration.Generate(ctx, assetName, m_RootGameObject, generateModelPrefab);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override bool SupportsRemappedAssetType(Type type)
|
|
{
|
|
if (type == typeof(AnimationClip))
|
|
return true;
|
|
return base.SupportsRemappedAssetType(type);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void SetDirty()
|
|
{
|
|
EditorUtility.SetDirty(this);
|
|
}
|
|
|
|
List<SpriteMetaData> GetSpriteImportData()
|
|
{
|
|
if (spriteImportModeToUse == SpriteImportMode.Multiple)
|
|
{
|
|
switch (m_AsepriteImporterSettings.fileImportMode)
|
|
{
|
|
case FileImportModes.SpriteSheet:
|
|
return m_SpriteSheetImportData;
|
|
case FileImportModes.AnimatedSprite:
|
|
default:
|
|
return m_AnimatedSpriteImportData;
|
|
}
|
|
}
|
|
return m_SingleSpriteImportData;
|
|
}
|
|
|
|
internal SpriteRect GetSpriteData(GUID guid)
|
|
{
|
|
if (spriteImportModeToUse != SpriteImportMode.Multiple)
|
|
return m_SingleSpriteImportData[0];
|
|
|
|
switch (m_AsepriteImporterSettings.fileImportMode)
|
|
{
|
|
case FileImportModes.SpriteSheet:
|
|
{
|
|
foreach (var metaData in m_SpriteSheetImportData)
|
|
{
|
|
if (metaData.spriteID == guid)
|
|
return metaData;
|
|
}
|
|
return default;
|
|
}
|
|
case FileImportModes.AnimatedSprite:
|
|
default:
|
|
{
|
|
foreach (var metaData in m_AnimatedSpriteImportData)
|
|
{
|
|
if (metaData.spriteID == guid)
|
|
return metaData;
|
|
}
|
|
return default;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal TextureImporterPlatformSettings[] GetAllPlatformSettings()
|
|
{
|
|
return m_PlatformSettings.ToArray();
|
|
}
|
|
|
|
internal void ReadTextureSettings(TextureImporterSettings dest)
|
|
{
|
|
m_TextureImporterSettings.CopyTo(dest);
|
|
}
|
|
}
|
|
}
|